Merge branch 'dev' into master
1
.github/CONTRIBUTING.md
vendored
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
84
README.md
|
@ -1,34 +1,32 @@
|
|||
<p align="center"><a href="https://newpipe.schabi.org"><img src="assets/new_pipe_icon_5.png" width="150"/></a></p>
|
||||
<h2 align="center"><b>NewPipe</b></h2>
|
||||
<h4 align="center">A free lightweight YouTube frontend for Android.</h4>
|
||||
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"/></a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/TeamNewPipe/NewPipe" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" /></a>
|
||||
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPL v3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg" /></a>
|
||||
<a href="https://travis-ci.org/TeamNewPipe/NewPipe" alt="Build Status"><img src="https://travis-ci.org/TeamNewPipe/NewPipe.svg" /></a>
|
||||
<a href="https://hosted.weblate.org/engage/NewPipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/NewPipe/-/svg-badge.svg" /></a>
|
||||
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg" /></a>
|
||||
</p>
|
||||
<hr />
|
||||
<p align="center"><a href="#screenshots">Screenshots</a> • <a href="#description">Description</a> • <a href="#features">Features</a> • <a href="#contribution">Contribution</a> • <a href="#donate">Donate</a> • <a href="#license">License</a></p>
|
||||
<hr />
|
||||
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
|
||||
|
||||
[<img src="screenshots/screenshot_1.png" width=160>](screenshots/screenshot_1.png)
|
||||
[<img src="screenshots/screenshot_2.png" width=160>](screenshots/screenshot_2.png)
|
||||
[<img src="screenshots/screenshot_3.png" width=160>](screenshots/screenshot_3.png)
|
||||
[<img src="screenshots/screenshot_4.png" width=160>](screenshots/screenshot_4.png)
|
||||
[<img src="screenshots/screenshot_5.png" width=160>](screenshots/screenshot_5.png)
|
||||
[<img src="screenshots/screenshot_6.png" width=160>](screenshots/screenshot_6.png)
|
||||
[<img src="screenshots/screenshot_7.png" width=160>](screenshots/screenshot_7.png)
|
||||
[<img src="screenshots/screenshot_8.png" width=160>](screenshots/screenshot_8.png)
|
||||
[<img src="screenshots/screenshot_9.png" width=160>](screenshots/screenshot_9.png)
|
||||
|
||||
[<img src="screenshots/shot_1.png" width=160>](screenshots/shot_1.png)
|
||||
[<img src="screenshots/shot_2.png" width=160>](screenshots/shot_2.png)
|
||||
[<img src="screenshots/shot_3.png" width=160>](screenshots/shot_3.png)
|
||||
[<img src="screenshots/shot_4.png" width=160>](screenshots/shot_4.png)
|
||||
[<img src="screenshots/shot_5.png" width=160>](screenshots/shot_5.png)
|
||||
[<img src="screenshots/shot_6.png" width=160>](screenshots/shot_6.png)
|
||||
[<img src="screenshots/shot_7.png" width=160>](screenshots/shot_7.png)
|
||||
[<img src="screenshots/shot_8.png" width=160>](screenshots/shot_8.png)
|
||||
[<img src="screenshots/shot_9.png" width=160>](screenshots/shot_9.png)
|
||||
[<img src="screenshots/shot_10.png" width=160>](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/).
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://bitcoin.org/img/icons/logotop.svg" alt="Bitcoin" /></td>
|
||||
<td><img src="assets/bitcoin_qr_code.png" alt="Bitcoin QR Code" width="100px"/></td>
|
||||
<td><samp>16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh</samp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Bountysource.png/320px-Bountysource.png" alz="Bountysource" width="190px" /></a></td>
|
||||
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="assets/bountysource_qr_code.png" alt="Visit NewPipe at bountysource.com" width="100px"/></a></td>
|
||||
<td><a href="https://www.bountysource.com/teams/newpipe/issues"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f" height="30px" alt="Check out how many bounties you can earn." /></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## License
|
||||
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
10
app/proguard-rules.pro
vendored
|
@ -25,3 +25,13 @@
|
|||
-dontwarn org.mozilla.javascript.tools.**
|
||||
-dontwarn android.arch.util.paging.CountedDataSource
|
||||
-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.* <fields>;
|
||||
}
|
||||
-keepnames class * { @icepick.State *;}
|
||||
|
|
10
app/src/beta/AndroidManifest.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application
|
||||
android:label="NewPipe Beta"
|
||||
tools:replace="android:label">
|
||||
</application>
|
||||
|
||||
</manifest>
|
BIN
app/src/beta/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
app/src/beta/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
app/src/beta/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
app/src/beta/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
app/src/beta/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 19 KiB |
|
@ -88,10 +88,14 @@
|
|||
<service android:name="us.shandian.giga.service.DownloadManagerService"/>
|
||||
|
||||
<activity
|
||||
android:name="com.nononsenseapps.filepicker.FilePickerActivity"
|
||||
android:name=".util.FilePickerActivityHelper"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/FilePickerTheme"/>
|
||||
android:theme="@style/FilePickerThemeDark">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.GET_CONTENT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ReCaptchaActivity"
|
||||
|
|
|
@ -12,7 +12,6 @@ import java.io.InterruptedIOException;
|
|||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
@ -135,11 +134,8 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
|||
}
|
||||
|
||||
in = new BufferedReader(new InputStreamReader(con.getInputStream()));
|
||||
for (Map.Entry<String, List<String>> entry : con.getHeaderFields().entrySet()) {
|
||||
System.err.println(entry.getKey() + ": " + entry.getValue());
|
||||
}
|
||||
String inputLine;
|
||||
|
||||
String inputLine;
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
response.append(inputLine);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<SearchHistoryEntry> {
|
|||
@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<List<SearchHistoryEntry>> getAll();
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME + " GROUP BY " + SEARCH + ORDER_BY_CREATION_DATE + " LIMIT :limit")
|
||||
Flowable<List<SearchHistoryEntry>> getUniqueEntries(int limit);
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE)
|
||||
@Override
|
||||
Flowable<List<SearchHistoryEntry>> listByService(int serviceId);
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%' GROUP BY " + SEARCH + " LIMIT :limit")
|
||||
Flowable<List<SearchHistoryEntry>> getSimilarEntries(String query, int limit);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<StreamInfo> implement
|
|||
private boolean wasRelatedStreamsExpanded = false;
|
||||
|
||||
@State
|
||||
protected int serviceId = -1;
|
||||
protected int serviceId = Constants.NO_SERVICE_ID;
|
||||
@State
|
||||
protected String name;
|
||||
@State
|
||||
|
|
|
@ -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<I extends ListInfo> extends BaseListFragment<I, ListExtractor.NextItemsResult> {
|
||||
|
||||
@State
|
||||
protected int serviceId = -1;
|
||||
protected int serviceId = Constants.NO_SERVICE_ID;
|
||||
@State
|
||||
protected String name;
|
||||
@State
|
||||
|
|
|
@ -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,70 +79,79 @@ import io.reactivex.subjects.PublishSubject;
|
|||
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor.NextItemsResult> {
|
||||
public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor.NextItemsResult> implements BackPressable {
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Search
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* 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<String> 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)) {
|
||||
private void setSearchOnResume() {
|
||||
wasLoading.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Fragment's LifeCycle
|
||||
|
@ -135,6 +161,16 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
suggestionListAdapter = new SuggestionListAdapter(activity);
|
||||
searchHistoryDAO = NewPipeDatabase.getInstance().searchHistoryDAO();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
isSuggestionsEnabled = preferences.getBoolean(getString(R.string.show_search_suggestions_key), true);
|
||||
searchLanguage = preferences.getString(getString(R.string.search_language_key), getString(R.string.default_language_value));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -142,14 +178,23 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
return inflater.inflate(R.layout.fragment_search, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View rootView, Bundle savedInstanceState) {
|
||||
super.onViewCreated(rootView, savedInstanceState);
|
||||
showSearchOnStart();
|
||||
initSearchListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
wasSearchFocused = searchEditText.hasFocus();
|
||||
|
||||
if (searchDisposable != null) searchDisposable.dispose();
|
||||
if (suggestionWorkerDisposable != null) suggestionWorkerDisposable.dispose();
|
||||
hideSoftKeyboard(searchEditText);
|
||||
if (suggestionDisposable != null) suggestionDisposable.dispose();
|
||||
if (disposables != null) disposables.clear();
|
||||
hideKeyboardSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -157,10 +202,6 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
if (DEBUG) Log.d(TAG, "onResume() called");
|
||||
super.onResume();
|
||||
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
showSuggestions = preferences.getBoolean(getString(R.string.show_search_suggestions_key), true);
|
||||
searchLanguage = preferences.getString(getString(R.string.search_language_key), getString(R.string.default_language_value));
|
||||
|
||||
if (!TextUtils.isEmpty(searchQuery)) {
|
||||
if (wasLoading.getAndSet(false)) {
|
||||
if (currentNextPage > currentPage) loadMoreItems();
|
||||
|
@ -175,7 +216,16 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
}
|
||||
}
|
||||
|
||||
if (suggestionWorkerDisposable == null || suggestionWorkerDisposable.isDisposed()) initSuggestionObserver();
|
||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) initSuggestionObserver();
|
||||
|
||||
if (TextUtils.isEmpty(searchQuery) || wasSearchFocused) {
|
||||
showKeyboardSearch();
|
||||
showSuggestionsPanel();
|
||||
} else {
|
||||
hideKeyboardSearch();
|
||||
hideSuggestionsPanel();
|
||||
}
|
||||
wasSearchFocused = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -188,17 +238,16 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (!activity.isChangingConfigurations()) StateSaver.onDestroy(savedState);
|
||||
|
||||
if (searchDisposable != null) searchDisposable.dispose();
|
||||
if (suggestionWorkerDisposable != null) suggestionWorkerDisposable.dispose();
|
||||
if (suggestionDisposable != null) suggestionDisposable.dispose();
|
||||
if (disposables != null) disposables.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case ReCaptchaActivity.RECAPTCHA_REQUEST:
|
||||
if (resultCode == Activity.RESULT_OK && searchQuery.length() != 0) {
|
||||
if (resultCode == Activity.RESULT_OK && !TextUtils.isEmpty(searchQuery)) {
|
||||
search(searchQuery);
|
||||
} else Log.e(TAG, "ReCaptcha failed");
|
||||
break;
|
||||
|
@ -209,6 +258,23 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Init
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
||||
super.initViews(rootView, savedInstanceState);
|
||||
suggestionsPanel = rootView.findViewById(R.id.suggestions_panel);
|
||||
suggestionsRecyclerView = rootView.findViewById(R.id.suggestions_list);
|
||||
suggestionsRecyclerView.setAdapter(suggestionListAdapter);
|
||||
suggestionsRecyclerView.setLayoutManager(new LayoutManagerSmoothScroller(activity));
|
||||
|
||||
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
||||
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
||||
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// State Saving
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -229,8 +295,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle bundle) {
|
||||
searchQuery = searchEditText != null && !TextUtils.isEmpty(searchEditText.getText().toString())
|
||||
? searchEditText.getText().toString() : searchQuery;
|
||||
searchQuery = searchEditText != null ? searchEditText.getText().toString() : searchQuery;
|
||||
super.onSaveInstanceState(bundle);
|
||||
}
|
||||
|
||||
|
@ -245,7 +310,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
} else {
|
||||
if (searchEditText != null) {
|
||||
searchEditText.setText("");
|
||||
showSoftKeyboard(searchEditText);
|
||||
showKeyboardSearch();
|
||||
}
|
||||
animateView(errorPanelRoot, false, 200);
|
||||
}
|
||||
|
@ -266,12 +331,6 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
}
|
||||
|
||||
inflater.inflate(R.menu.menu_search, menu);
|
||||
|
||||
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
||||
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
||||
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
|
||||
setupSearchView();
|
||||
|
||||
restoreFilterChecked(menu, filterItemCheckedId);
|
||||
}
|
||||
|
||||
|
@ -301,14 +360,13 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
|
||||
private SearchEngine.Filter getFilterFromMenuId(int itemId) {
|
||||
switch (itemId) {
|
||||
case R.id.menu_filter_all:
|
||||
return SearchEngine.Filter.ANY;
|
||||
case R.id.menu_filter_video:
|
||||
return SearchEngine.Filter.STREAM;
|
||||
case R.id.menu_filter_channel:
|
||||
return SearchEngine.Filter.CHANNEL;
|
||||
case R.id.menu_filter_playlist:
|
||||
return SearchEngine.Filter.PLAYLIST;
|
||||
case R.id.menu_filter_all:
|
||||
default:
|
||||
return SearchEngine.Filter.ANY;
|
||||
}
|
||||
|
@ -320,9 +378,9 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
|
||||
private TextWatcher textWatcher;
|
||||
|
||||
private void setupSearchView() {
|
||||
searchEditText.setText(searchQuery != null ? searchQuery : "");
|
||||
searchEditText.setAdapter(suggestionListAdapter);
|
||||
private void showSearchOnStart() {
|
||||
if (DEBUG) Log.d(TAG, "showSearchOnStart() called, searchQuery → " + searchQuery+", lastSearchedQuery → " + lastSearchedQuery);
|
||||
searchEditText.setText(searchQuery);
|
||||
|
||||
if (TextUtils.isEmpty(searchQuery) || TextUtils.isEmpty(searchEditText.getText())) {
|
||||
searchToolbarContainer.setTranslationX(100);
|
||||
|
@ -334,15 +392,10 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
searchToolbarContainer.setAlpha(1f);
|
||||
searchToolbarContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
initSearchListeners();
|
||||
|
||||
if (TextUtils.isEmpty(searchQuery) || wasSearchFocused) showSoftKeyboard(searchEditText);
|
||||
else hideSoftKeyboard(searchEditText);
|
||||
wasSearchFocused = false;
|
||||
}
|
||||
|
||||
private void initSearchListeners() {
|
||||
if (DEBUG) Log.d(TAG, "initSearchListeners() called");
|
||||
searchClear.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
@ -352,11 +405,9 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
searchEditText.setText("", false);
|
||||
} else searchEditText.setText("");
|
||||
suggestionListAdapter.updateAdapter(new ArrayList<String>());
|
||||
showSoftKeyboard(searchEditText);
|
||||
searchEditText.setText("");
|
||||
suggestionListAdapter.setItems(new ArrayList<SuggestionItem>());
|
||||
showKeyboardSearch();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -366,7 +417,9 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
@Override
|
||||
public void onClick(View v) {
|
||||
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||
searchEditText.showDropDown();
|
||||
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
||||
showSuggestionsPanel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -374,22 +427,24 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (DEBUG) Log.d(TAG, "onFocusChange() called with: v = [" + v + "], hasFocus = [" + hasFocus + "]");
|
||||
if (hasFocus) searchEditText.showDropDown();
|
||||
if (isSuggestionsEnabled && hasFocus && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
||||
showSuggestionsPanel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
searchEditText.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
suggestionListAdapter.setListener(new SuggestionListAdapter.OnSuggestionItemSelected() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onItemClick() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
|
||||
public void onSuggestionItemSelected(SuggestionItem item) {
|
||||
search(item.query);
|
||||
searchEditText.setText(item.query);
|
||||
}
|
||||
String s = suggestionListAdapter.getSuggestion(position);
|
||||
if (DEBUG) Log.d(TAG, "onItemClick text = " + s);
|
||||
submitQuery(s);
|
||||
|
||||
@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<SearchResult, ListExtractor
|
|||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
String newText = searchEditText.getText().toString();
|
||||
if (!TextUtils.isEmpty(newText)) suggestionPublisher.onNext(newText);
|
||||
suggestionPublisher.onNext(newText);
|
||||
}
|
||||
};
|
||||
searchEditText.addTextChangedListener(textWatcher);
|
||||
|
||||
searchEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (DEBUG)
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]");
|
||||
}
|
||||
if (event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER || event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
|
||||
submitQuery(searchEditText.getText().toString());
|
||||
search(searchEditText.getText().toString());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (suggestionWorkerDisposable == null || suggestionWorkerDisposable.isDisposed()) initSuggestionObserver();
|
||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) initSuggestionObserver();
|
||||
}
|
||||
|
||||
private void unsetSearchListeners() {
|
||||
if (DEBUG) Log.d(TAG, "unsetSearchListeners() called");
|
||||
searchClear.setOnClickListener(null);
|
||||
searchClear.setOnLongClickListener(null);
|
||||
searchEditText.setOnClickListener(null);
|
||||
searchEditText.setOnItemClickListener(null);
|
||||
searchEditText.setOnFocusChangeListener(null);
|
||||
searchEditText.setOnEditorActionListener(null);
|
||||
|
||||
|
@ -437,68 +492,166 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
textWatcher = null;
|
||||
}
|
||||
|
||||
private void showSoftKeyboard(View view) {
|
||||
if (DEBUG) Log.d(TAG, "showSoftKeyboard() called with: view = [" + view + "]");
|
||||
if (view == null) return;
|
||||
private void showSuggestionsPanel() {
|
||||
if (DEBUG) Log.d(TAG, "showSuggestionsPanel() called");
|
||||
animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, true, 200);
|
||||
}
|
||||
|
||||
if (view.requestFocus()) {
|
||||
private void hideSuggestionsPanel() {
|
||||
if (DEBUG) Log.d(TAG, "hideSuggestionsPanel() called");
|
||||
animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, false, 200);
|
||||
}
|
||||
|
||||
private void showKeyboardSearch() {
|
||||
if (DEBUG) Log.d(TAG, "showKeyboardSearch() called");
|
||||
if (searchEditText == null) return;
|
||||
|
||||
if (searchEditText.requestFocus()) {
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
|
||||
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideSoftKeyboard(View view) {
|
||||
if (DEBUG) Log.d(TAG, "hideSoftKeyboard() called with: view = [" + view + "]");
|
||||
if (view == null) return;
|
||||
private void hideKeyboardSearch() {
|
||||
if (DEBUG) Log.d(TAG, "hideKeyboardSearch() called");
|
||||
if (searchEditText == null) return;
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
|
||||
view.clearFocus();
|
||||
searchEditText.clearFocus();
|
||||
}
|
||||
|
||||
public void giveSearchEditTextFocus() {
|
||||
showSoftKeyboard(searchEditText);
|
||||
}
|
||||
|
||||
private void initSuggestionObserver() {
|
||||
if (suggestionWorkerDisposable != null) suggestionWorkerDisposable.dispose();
|
||||
final Predicate<String> checkEnabledAndLength = new Predicate<String>() {
|
||||
private void showDeleteSuggestionDialog(final SuggestionItem item) {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(item.query)
|
||||
.setMessage(R.string.delete_item_search_history)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public boolean test(@io.reactivex.annotations.NonNull String s) throws Exception {
|
||||
boolean lengthCheck = s.length() >= THRESHOLD_SUGGESTION;
|
||||
// Clear the suggestions adapter if the length check fails
|
||||
if (!lengthCheck && !suggestionListAdapter.isEmpty()) {
|
||||
suggestionListAdapter.updateAdapter(new ArrayList<String>());
|
||||
}
|
||||
// Only pass through if suggestions is enabled and the query length is equal or greater than THRESHOLD_SUGGESTION
|
||||
return showSuggestions && lengthCheck;
|
||||
}
|
||||
};
|
||||
|
||||
suggestionWorkerDisposable = suggestionPublisher
|
||||
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
|
||||
.startWith(!TextUtils.isEmpty(searchQuery) ? searchQuery : "")
|
||||
.filter(checkEnabledAndLength)
|
||||
.switchMap(new Function<String, Observable<Notification<List<String>>>>() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
disposables.add(Observable
|
||||
.fromCallable(new Callable<Integer>() {
|
||||
@Override
|
||||
public Observable<Notification<List<String>>> apply(@io.reactivex.annotations.NonNull String query) throws Exception {
|
||||
return ExtractorHelper.suggestionsFor(serviceId, query, searchLanguage).toObservable().materialize();
|
||||
public Integer call() throws Exception {
|
||||
return searchHistoryDAO.deleteAllWhereQuery(item.query);
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Consumer<Notification<List<String>>>() {
|
||||
.subscribe(new Consumer<Integer>() {
|
||||
@Override
|
||||
public void accept(@io.reactivex.annotations.NonNull Notification<List<String>> listNotification) throws Exception {
|
||||
public void accept(Integer howManyDeleted) throws Exception {
|
||||
suggestionPublisher.onNext(searchEditText.getText().toString());
|
||||
}
|
||||
}, new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(Throwable throwable) throws Exception {
|
||||
showSnackBarError(throwable, UserAction.SOMETHING_ELSE, "none", "Deleting item failed", R.string.general_error);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed() {
|
||||
if (suggestionsPanel.getVisibility() == View.VISIBLE && infoListAdapter.getItemsList().size() > 0 && !isLoading.get()) {
|
||||
hideSuggestionsPanel();
|
||||
hideKeyboardSearch();
|
||||
searchEditText.setText(lastSearchedQuery);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void giveSearchEditTextFocus() {
|
||||
showKeyboardSearch();
|
||||
}
|
||||
|
||||
private void initSuggestionObserver() {
|
||||
if (DEBUG) Log.d(TAG, "initSuggestionObserver() called");
|
||||
if (suggestionDisposable != null) suggestionDisposable.dispose();
|
||||
|
||||
final Observable<String> observable = suggestionPublisher
|
||||
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
|
||||
.startWith(searchQuery != null ? searchQuery : "")
|
||||
.filter(new Predicate<String>() {
|
||||
@Override
|
||||
public boolean test(@io.reactivex.annotations.NonNull String query) throws Exception {
|
||||
return isSuggestionsEnabled;
|
||||
}
|
||||
});
|
||||
|
||||
suggestionDisposable = observable
|
||||
.switchMap(new Function<String, ObservableSource<Notification<List<SuggestionItem>>>>() {
|
||||
@Override
|
||||
public ObservableSource<Notification<List<SuggestionItem>>> apply(@io.reactivex.annotations.NonNull final String query) throws Exception {
|
||||
final Flowable<List<SearchHistoryEntry>> flowable = query.length() > 0
|
||||
? searchHistoryDAO.getSimilarEntries(query, 3)
|
||||
: searchHistoryDAO.getUniqueEntries(25);
|
||||
final Observable<List<SuggestionItem>> local = flowable.toObservable()
|
||||
.map(new Function<List<SearchHistoryEntry>, List<SuggestionItem>>() {
|
||||
@Override
|
||||
public List<SuggestionItem> apply(@io.reactivex.annotations.NonNull List<SearchHistoryEntry> searchHistoryEntries) throws Exception {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
for (SearchHistoryEntry entry : searchHistoryEntries)
|
||||
result.add(new SuggestionItem(true, entry.getSearch()));
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
if (query.length() < THRESHOLD_NETWORK_SUGGESTION) {
|
||||
// Only pass through if the query length is equal or greater than THRESHOLD_NETWORK_SUGGESTION
|
||||
return local.materialize();
|
||||
}
|
||||
|
||||
final Observable<List<SuggestionItem>> network = ExtractorHelper.suggestionsFor(serviceId, query, searchLanguage).toObservable()
|
||||
.map(new Function<List<String>, List<SuggestionItem>>() {
|
||||
@Override
|
||||
public List<SuggestionItem> apply(@io.reactivex.annotations.NonNull List<String> strings) throws Exception {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
for (String entry : strings) result.add(new SuggestionItem(false, entry));
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
return Observable.zip(local, network, new BiFunction<List<SuggestionItem>, List<SuggestionItem>, List<SuggestionItem>>() {
|
||||
@Override
|
||||
public List<SuggestionItem> apply(@io.reactivex.annotations.NonNull List<SuggestionItem> localResult, @io.reactivex.annotations.NonNull List<SuggestionItem> networkResult) throws Exception {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
if (localResult.size() > 0) result.addAll(localResult);
|
||||
|
||||
// Remove duplicates
|
||||
final Iterator<SuggestionItem> iterator = networkResult.iterator();
|
||||
while (iterator.hasNext() && localResult.size() > 0) {
|
||||
final SuggestionItem next = iterator.next();
|
||||
for (SuggestionItem item : localResult) {
|
||||
if (item.query.equals(next.query)) {
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (networkResult.size() > 0) result.addAll(networkResult);
|
||||
return result;
|
||||
}
|
||||
}).materialize();
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Consumer<Notification<List<SuggestionItem>>>() {
|
||||
@Override
|
||||
public void accept(@io.reactivex.annotations.NonNull Notification<List<SuggestionItem>> 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<SearchResult, ListExtractor
|
|||
|
||||
private void search(final String query) {
|
||||
if (DEBUG) Log.d(TAG, "search() called with: query = [" + query + "]");
|
||||
if (query.isEmpty()) return;
|
||||
|
||||
hideSoftKeyboard(searchEditText);
|
||||
this.searchQuery = query;
|
||||
this.currentPage = 0;
|
||||
try {
|
||||
final StreamingService service = NewPipe.getServiceByUrl(query);
|
||||
if (service != null) {
|
||||
showLoading();
|
||||
disposables.add(Observable
|
||||
.fromCallable(new Callable<Intent>() {
|
||||
@Override
|
||||
public Intent call() throws Exception {
|
||||
return NavigationHelper.getIntentByLink(activity, service, query);
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Consumer<Intent>() {
|
||||
@Override
|
||||
public void accept(Intent intent) throws Exception {
|
||||
getFragmentManager().popBackStackImmediate();
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}, new Consumer<Throwable>() {
|
||||
@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<SearchResult, ListExtractor
|
|||
@Override
|
||||
protected void onItemSelected(InfoItem selectedItem) {
|
||||
super.onItemSelected(selectedItem);
|
||||
hideSoftKeyboard(searchEditText);
|
||||
hideKeyboardSearch();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -595,13 +781,10 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
this.filter = filter;
|
||||
this.filterItemCheckedId = item.getItemId();
|
||||
item.setChecked(true);
|
||||
if (searchQuery != null && !searchQuery.isEmpty()) search(searchQuery);
|
||||
}
|
||||
|
||||
private void submitQuery(String query) {
|
||||
if (DEBUG) Log.d(TAG, "submitQuery() called with: query = [" + query + "]");
|
||||
if (query.isEmpty()) return;
|
||||
search(query);
|
||||
if (!TextUtils.isEmpty(searchQuery)) {
|
||||
search(searchQuery);
|
||||
}
|
||||
}
|
||||
|
||||
private void setQuery(int serviceId, String searchQuery) {
|
||||
|
@ -609,19 +792,23 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
this.searchQuery = searchQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showError(String message, boolean showRetryButton) {
|
||||
super.showError(message, showRetryButton);
|
||||
hideSoftKeyboard(searchEditText);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Suggestion Results
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public void handleSuggestions(@NonNull List<String> suggestions) {
|
||||
public void handleSuggestions(@NonNull final List<SuggestionItem> suggestions) {
|
||||
if (DEBUG) Log.d(TAG, "handleSuggestions() called with: suggestions = [" + suggestions + "]");
|
||||
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<SearchResult, ListExtractor
|
|||
showListFooter(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showError(String message, boolean showRetryButton) {
|
||||
super.showError(message, showRetryButton);
|
||||
hideSuggestionsPanel();
|
||||
hideKeyboardSearch();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Search Results
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -652,6 +846,8 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
|||
showSnackBarError(result.errors, UserAction.SEARCHED, NewPipe.getNameOfService(serviceId), searchQuery, 0);
|
||||
}
|
||||
|
||||
lastSearchedQuery = searchQuery;
|
||||
|
||||
if (infoListAdapter.getItemsList().size() == 0) {
|
||||
if (result.resultList.size() > 0) {
|
||||
infoListAdapter.addInfoItemList(result.resultList);
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package org.schabi.newpipe.fragments.list.search;
|
||||
|
||||
public class SuggestionItem {
|
||||
public final boolean fromHistory;
|
||||
public final String query;
|
||||
|
||||
public SuggestionItem(boolean fromHistory, String query) {
|
||||
this.fromHistory = fromHistory;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + fromHistory + "→" + query + "]";
|
||||
}
|
||||
}
|
|
@ -1,89 +1,108 @@
|
|||
package org.schabi.newpipe.fragments.list.search;
|
||||
|
||||
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 <chris.schabesberger@mailbox.org>
|
||||
* SuggestionListAdapter.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* {@link ResourceCursorAdapter} to display suggestions.
|
||||
*/
|
||||
public class SuggestionListAdapter extends ResourceCursorAdapter {
|
||||
|
||||
private static final String[] columns = new String[]{"_id", "title"};
|
||||
private static final int INDEX_ID = 0;
|
||||
private static final int INDEX_TITLE = 1;
|
||||
public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAdapter.SuggestionItemHolder> {
|
||||
private final ArrayList<SuggestionItem> 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<SuggestionItem> 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<String> suggestions) {
|
||||
MatrixCursor cursor = new MatrixCursor(columns, suggestions.size());
|
||||
int i = 0;
|
||||
for (String suggestion : suggestions) {
|
||||
String[] columnValues = new String[columns.length];
|
||||
columnValues[INDEX_TITLE] = suggestion;
|
||||
columnValues[INDEX_ID] = Integer.toString(i);
|
||||
cursor.addRow(columnValues);
|
||||
i++;
|
||||
}
|
||||
changeCursor(cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the suggestion for a position
|
||||
* @param position the position of the suggestion
|
||||
* @return the suggestion
|
||||
*/
|
||||
public String getSuggestion(int position) {
|
||||
return ((Cursor) getItem(position)).getString(INDEX_TITLE);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<SearchResult> searchFor(final int serviceId, final String query, final int pageNumber, final String searchLanguage, final SearchEngine.Filter filter) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(new Callable<SearchResult>() {
|
||||
@Override
|
||||
public SearchResult call() throws Exception {
|
||||
|
@ -61,6 +68,7 @@ public final class ExtractorHelper {
|
|||
}
|
||||
|
||||
public static Single<NextItemsResult> 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<SearchResult, NextItemsResult>() {
|
||||
@Override
|
||||
|
@ -71,6 +79,7 @@ public final class ExtractorHelper {
|
|||
}
|
||||
|
||||
public static Single<List<String>> suggestionsFor(final int serviceId, final String query, final String searchLanguage) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(new Callable<List<String>>() {
|
||||
@Override
|
||||
public List<String> call() throws Exception {
|
||||
|
@ -80,6 +89,7 @@ public final class ExtractorHelper {
|
|||
}
|
||||
|
||||
public static Single<StreamInfo> getStreamInfo(final int serviceId, final String url, boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable<StreamInfo>() {
|
||||
@Override
|
||||
public StreamInfo call() throws Exception {
|
||||
|
@ -89,6 +99,7 @@ public final class ExtractorHelper {
|
|||
}
|
||||
|
||||
public static Single<ChannelInfo> getChannelInfo(final int serviceId, final String url, boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable<ChannelInfo>() {
|
||||
@Override
|
||||
public ChannelInfo call() throws Exception {
|
||||
|
@ -98,6 +109,7 @@ public final class ExtractorHelper {
|
|||
}
|
||||
|
||||
public static Single<NextItemsResult> getMoreChannelItems(final int serviceId, final String url, final String nextStreamsUrl) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(new Callable<NextItemsResult>() {
|
||||
@Override
|
||||
public NextItemsResult call() throws Exception {
|
||||
|
@ -107,6 +119,7 @@ public final class ExtractorHelper {
|
|||
}
|
||||
|
||||
public static Single<PlaylistInfo> getPlaylistInfo(final int serviceId, final String url, boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable<PlaylistInfo>() {
|
||||
@Override
|
||||
public PlaylistInfo call() throws Exception {
|
||||
|
@ -116,6 +129,7 @@ public final class ExtractorHelper {
|
|||
}
|
||||
|
||||
public static Single<NextItemsResult> getMorePlaylistItems(final int serviceId, final String url, final String nextStreamsUrl) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(new Callable<NextItemsResult>() {
|
||||
@Override
|
||||
public NextItemsResult call() throws Exception {
|
||||
|
@ -133,6 +147,7 @@ public final class ExtractorHelper {
|
|||
* and put the results in the cache.
|
||||
*/
|
||||
private static <I extends Info> Single<I> checkCache(boolean forceLoad, int serviceId, String url, Single<I> loadFromNetwork) {
|
||||
checkServiceId(serviceId);
|
||||
loadFromNetwork = loadFromNetwork.doOnSuccess(new Consumer<I>() {
|
||||
@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 <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url) {
|
||||
checkServiceId(serviceId);
|
||||
return Maybe.defer(new Callable<MaybeSource<? extends I>>() {
|
||||
@Override
|
||||
public MaybeSource<? extends I> call() throws Exception {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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<Object> savedObjects = stateObjectsHolder.remove(savedState.prefixFileSaved);
|
||||
Queue<Object> 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)}.
|
||||
* <p>
|
||||
* It checks if the file already exists and if it does, just return the path, so a good way to save is:
|
||||
* <li> A fixed prefix for the file
|
||||
* <li> A changing suffix
|
||||
* <ul>
|
||||
* <li> A fixed prefix for the file</li>
|
||||
* <li> A changing suffix</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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<Object> savedObjects = new LinkedList<>();
|
||||
LinkedList<Object> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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<DownloadMission> missions) {
|
||||
Collections.sort(missions, new Comparator<DownloadMission>() {
|
||||
@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<DownloadMission>() {
|
||||
@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();
|
||||
|
|
|
@ -51,6 +51,25 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/suggestions_panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:visibility="gone"
|
||||
tools:background="@android:color/transparent"
|
||||
tools:visibility="visible">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/suggestions_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"
|
||||
tools:listitem="@layout/item_search_suggestion"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!--ERROR PANEL-->
|
||||
<include
|
||||
android:id="@+id/error_panel"
|
||||
|
|
36
app/src/main/res/layout/item_search_suggestion.xml
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingTop="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/item_suggestion_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"
|
||||
tools:src="?attr/history"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_suggestion_query"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
android:textSize="14sp"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="Search query"/>
|
||||
</LinearLayout>
|
|
@ -4,16 +4,9 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true">
|
||||
android:background="?attr/colorPrimary">
|
||||
|
||||
<View
|
||||
android:id="@+id/dropdown_anchor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<AutoCompleteTextView
|
||||
<EditText
|
||||
android:id="@+id/toolbar_search_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -24,7 +17,6 @@
|
|||
android:background="?attr/colorPrimary"
|
||||
android:drawableLeft="?attr/search"
|
||||
android:drawablePadding="8dp"
|
||||
android:dropDownAnchor="@+id/dropdown_anchor"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:hint="@string/search"
|
||||
|
|
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 788 B |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
|
@ -2,7 +2,7 @@
|
|||
<resources>
|
||||
<string name="main_bg_subtitle">Calca na gueta pa entamar</string>
|
||||
<string name="view_count_text">%1$s visiones</string>
|
||||
<string name="upload_date_text">Espublizáu\'l %1$s</string>
|
||||
<string name="upload_date_text">Espublizóse\'l %1$s</string>
|
||||
<string name="no_player_found">Nun s\'alcontró un reproductor de fluxos. ¿Quies instalar VLC?</string>
|
||||
<string name="install">Instalar</string>
|
||||
<string name="cancel">Encaboxar</string>
|
||||
|
@ -14,15 +14,15 @@
|
|||
<string name="did_you_mean">¿Quixesti dicir %1$s?</string>
|
||||
<string name="share_dialog_title">Compartir con</string>
|
||||
<string name="choose_browser">Escoyer restolador</string>
|
||||
<string name="screen_rotation">rotación</string>
|
||||
<string name="use_external_video_player_title">Usar reproductor de videu esternu</string>
|
||||
<string name="use_external_audio_player_title">Usar reproductor d\'audiu esternu</string>
|
||||
<string name="screen_rotation">voltéu</string>
|
||||
<string name="use_external_video_player_title">Usar reproductor esternu de videu</string>
|
||||
<string name="use_external_audio_player_title">Usar reproductor esternu d\'audiu</string>
|
||||
|
||||
<string name="download_path_title">Camín de descarga de vídeos</string>
|
||||
<string name="download_path_summary">Camín nel qu\'atroxar los vídeos baxaos.</string>
|
||||
<string name="download_path_summary">Camín nel qu\'atroxar los vídeos baxaos</string>
|
||||
<string name="download_path_dialog_title">Introducir camín de descarga pa vídeos</string>
|
||||
|
||||
<string name="download_path_audio_title">Camín de descarga d\'audiu</string>
|
||||
<string name="download_path_audio_title">Camín de descarga p\'audios</string>
|
||||
<string name="download_path_audio_summary">Camín nel qu\'atroxar los audios baxaos.</string>
|
||||
<string name="download_path_audio_dialog_title">Introducir camín de descarga pa ficheros d\'audiu</string>
|
||||
|
||||
|
@ -30,11 +30,11 @@
|
|||
<string name="play_with_kodi_title">Reproducir con Kodi</string>
|
||||
<string name="kore_not_found">Nun s\'alcontró Kore. ¿Instalalu?</string>
|
||||
<string name="show_play_with_kodi_title">Amosar opción «Reproducir con Kodi»</string>
|
||||
<string name="show_play_with_kodi_summary">Amuesa una opción pa reproducir un videu per Kodi.</string>
|
||||
<string name="show_play_with_kodi_summary">Amuesa una opción pa reproducir un videu per Kodi</string>
|
||||
<string name="play_audio">Audiu</string>
|
||||
<string name="default_audio_format_title">Formatu d\'audiu por defeutu</string>
|
||||
<string name="default_audio_format_title">Formatu por defeutu d\'audiu</string>
|
||||
<string name="webm_description">WebM — formatu llibre</string>
|
||||
<string name="m4a_description">m4a — calidá meyor</string>
|
||||
<string name="m4a_description">M4A — calidá meyor</string>
|
||||
<string name="theme_title">Tema</string>
|
||||
<string name="dark_theme_title">Escuru</string>
|
||||
<string name="light_theme_title">Claru</string>
|
||||
|
@ -43,15 +43,15 @@
|
|||
<string name="next_video_title">Videu siguiente</string>
|
||||
<string name="show_next_and_similar_title">Amosar vídeos siguientes y asemeyaos</string>
|
||||
<string name="url_not_supported_toast">URL non sofitada</string>
|
||||
<string name="search_language_title">Llingua de conteníu preferíu</string>
|
||||
<string name="search_language_title">Llingua por defeutu del conteníu</string>
|
||||
<string name="settings_category_video_audio_title">Videu y audiu</string>
|
||||
<string name="settings_category_appearance_title">Aspeutu</string>
|
||||
<string name="settings_category_other_title">Otru</string>
|
||||
<string name="background_player_playing_toast">Reproduciendo de fondu</string>
|
||||
<string name="play_btn_text">Reproducir</string>
|
||||
<string name="content">Conteníu</string>
|
||||
<string name="show_age_restricted_content_title">Amosar conteníu restrinxíu pola edá</string>
|
||||
<string name="video_is_age_restricted">El videu ta restrinxíu pola edá. Deshabilita esto diendo primero a axustes.</string>
|
||||
<string name="show_age_restricted_content_title">Amosar conteníu torgáu pola edá</string>
|
||||
<string name="video_is_age_restricted">El videu ta torgáu pola edá. Desactiva esto diendo primero a axustes.</string>
|
||||
<string name="duration_live">en direuto</string>
|
||||
<string name="downloads">Descargues</string>
|
||||
<string name="downloads_title">Descargues</string>
|
||||
|
@ -60,16 +60,16 @@
|
|||
<string name="general_error">Fallu</string>
|
||||
<string name="network_error">Fallu de rede</string>
|
||||
<string name="could_not_load_thumbnails">Nun pudieron cargase toles miniatures</string>
|
||||
<string name="youtube_signature_decryption_error">Nun pudo descargase la robla de la url del videu.</string>
|
||||
<string name="parsing_error">Nun pudo analizase\'l sitiu web.</string>
|
||||
<string name="light_parsing_error">Nun pudo analizase dafechu\'l sitiu web.</string>
|
||||
<string name="content_not_available">Conteníu non disponible.</string>
|
||||
<string name="blocked_by_gema">Bloquiáu por GEMA.</string>
|
||||
<string name="could_not_setup_download_menu">Nun pudo configurase\'l menú de descarga.</string>
|
||||
<string name="live_streams_not_supported">Esto ye una tresmisión de direuto pero entá nun ta sofitao.</string>
|
||||
<string name="could_not_get_stream">Nun pudo consiguise tresmisión dala.</string>
|
||||
<string name="youtube_signature_decryption_error">Nun pudo descifrase la robla de la URL</string>
|
||||
<string name="parsing_error">Nun pudo analizase\'l sitiu web</string>
|
||||
<string name="light_parsing_error">Nun pudo analizase dafechu\'l sitiu web</string>
|
||||
<string name="content_not_available">Conteníu non disponible</string>
|
||||
<string name="blocked_by_gema">Bloquiáu por GEMA</string>
|
||||
<string name="could_not_setup_download_menu">Nun pudo configurase\'l menú de descarga</string>
|
||||
<string name="live_streams_not_supported">Esto ye una tresmisión de direuto qu\'entá nun se sofita.</string>
|
||||
<string name="could_not_get_stream">Nun pudo consiguise tresmisión dala</string>
|
||||
<string name="sorry_string">Perdón, eso nun debió asoceder.</string>
|
||||
<string name="error_report_button_text">Fallu d\'informe per corréu</string>
|
||||
<string name="error_report_button_text">Informar per corréu del fallu</string>
|
||||
<string name="error_snackbar_message">Perdón, asocedieron dellos fallos.</string>
|
||||
<string name="error_snackbar_action">INFORMAR</string>
|
||||
<string name="what_device_headline">Información:</string>
|
||||
|
@ -78,11 +78,11 @@
|
|||
<string name="error_details_headline">Detalles:</string>
|
||||
|
||||
|
||||
<string name="list_thumbnail_view_description">Miniatura de previsualización de videu</string>
|
||||
<string name="detail_thumbnail_view_description">Miniatura de previsualización de videu</string>
|
||||
<string name="list_thumbnail_view_description">Miniatura de previsualización del videu</string>
|
||||
<string name="detail_thumbnail_view_description">Miniatura de previsualización del videu</string>
|
||||
<string name="detail_likes_img_view_description">Préstames</string>
|
||||
<string name="use_tor_title">Usar Tor</string>
|
||||
<string name="use_tor_summary">(Esperimental) Forcia\'l tráficu de descargues pente Tor pa más privacidá (la tresmisión de vídeos entá nun ta sofitao).</string>
|
||||
<string name="use_tor_summary">(Esperimental) Forcia\'l tráficu de descargues pente Tor pa más privacidá (la tresmisión de vídeos entá nun se sofita).</string>
|
||||
<string name="report_error">Informa d\'un fallu</string>
|
||||
<string name="user_report">Informe d\'usuariu</string>
|
||||
|
||||
|
@ -92,11 +92,11 @@
|
|||
<string name="video">Videu</string>
|
||||
<string name="audio">Audiu</string>
|
||||
<string name="retry">Retentar</string>
|
||||
<string name="storage_permission_denied">Ñegóse l\'accesu al almacenamientu</string>
|
||||
<string name="storage_permission_denied">Ñegóse\'l permisu d\'accesu al almacenamientu</string>
|
||||
|
||||
<string name="start">Aniciar</string>
|
||||
<string name="pause">Posar</string>
|
||||
<string name="view">Ver</string>
|
||||
<string name="view">Reproducir</string>
|
||||
<string name="delete">Desaniciar</string>
|
||||
<string name="checksum">Suma de comprobación</string>
|
||||
|
||||
|
@ -110,23 +110,23 @@
|
|||
<string name="msg_url_malform">URL malformada o internet non disponible</string>
|
||||
<string name="msg_running_detail">Calca pa detallles</string>
|
||||
<string name="msg_wait">Espera, por favor…</string>
|
||||
<string name="msg_copied">Copióse al cartafueyu.</string>
|
||||
<string name="no_available_dir">Esbilla un direutoriu de descarga disponible, por favor.</string>
|
||||
<string name="msg_copied">Copióse al cartafueyu</string>
|
||||
<string name="no_available_dir">Esbilla una carpeta disponible de descarga, por favor</string>
|
||||
|
||||
<string name="autoplay_by_calling_app_summary">Auto-reproduz un videu al llamar a NewPipe dende otra aplicación.</string>
|
||||
<string name="autoplay_by_calling_app_title">Auto-reproducir al llamar dende otra aplicación</string>
|
||||
<string name="detail_uploader_thumbnail_view_description">Miniatura del xubidor</string>
|
||||
<string name="autoplay_by_calling_app_summary">Auto-reproduz un videu al llamar a NewPipe dende otra aplicación</string>
|
||||
<string name="autoplay_by_calling_app_title">Auto-reproducción</string>
|
||||
<string name="detail_uploader_thumbnail_view_description">Miniatura del avatar del xubidor</string>
|
||||
<string name="detail_dislikes_img_view_description">Despréstames</string>
|
||||
|
||||
|
||||
<string name="msg_running">NewPipe baxando</string>
|
||||
<string name="could_not_load_image">Nun pudo cargase la imaxe</string>
|
||||
<string name="app_ui_crash">Cascó l\'aplicación/IU</string>
|
||||
<string name="info_labels"/>
|
||||
<string name="info_labels">Lo qu\'asocedió:\\nSolicitú:\\nLlingua del conteníu:\\nServiciu:\\nHora GMT:\\nPaquete:\\nVersión:\\nVersión del SO:\\nRangu global d\'IP:</string>
|
||||
<string name="open_in_popup_mode">Abrir en ventanu emerxente</string>
|
||||
<string name="popup_mode_share_menu_title">Mou de ventanu emerxente de NewPipe</string>
|
||||
|
||||
<string name="default_video_format_title">Formatu preferíu de videu</string>
|
||||
<string name="default_video_format_title">Formatu por defeutu de videu</string>
|
||||
<string name="black_theme_title">Prietu</string>
|
||||
|
||||
<string name="popup_playing_toast">Reproduciendo en ventanu emerxente</string>
|
||||
|
@ -134,7 +134,7 @@
|
|||
<string name="channel">Canal</string>
|
||||
<string name="yes">Sí</string>
|
||||
<string name="later">Más sero</string>
|
||||
<string name="disabled">Deshabilitóse</string>
|
||||
<string name="disabled">Desactivóse</string>
|
||||
|
||||
<string name="use_old_player_title">Usar reproductor vieyu</string>
|
||||
|
||||
|
@ -142,26 +142,26 @@
|
|||
<string name="short_thousand">M</string>
|
||||
<string name="short_million">Mill</string>
|
||||
<string name="short_billion">MMill</string>
|
||||
<string name="msg_popup_permission">Precísase esti permisu pa
|
||||
abrir en ventanu emerxente</string>
|
||||
<string name="msg_popup_permission">Precísase esti permisu p\'abrir
|
||||
\nen ventanu emerxente</string>
|
||||
|
||||
<string name="reCaptchaActivity">reCAPTCHA</string>
|
||||
<string name="reCaptcha_title">Prueba reCAPTCHA</string>
|
||||
<string name="recaptcha_request_toast">Prueba reCAPTCHA solicitada</string>
|
||||
<string name="recaptcha_request_toast">Solicitóse la prueba reCAPTCHA</string>
|
||||
|
||||
<string name="controls_background_title">Fondu</string>
|
||||
<string name="controls_popup_title">Ventanu emerxente</string>
|
||||
|
||||
<string name="default_popup_resolution_title">Resolución por defeutu de ventanu emerxente</string>
|
||||
<string name="default_popup_resolution_title">Resolución por defeutu del ventanu emerxente</string>
|
||||
<string name="show_higher_resolutions_title">Amosar resoluciones más altes</string>
|
||||
<string name="show_higher_resolutions_summary">Namái dellos preseos sofiten vídeos en 2k/4k</string>
|
||||
<string name="show_higher_resolutions_summary">Namái dellos preseos sofiten vídeos en 2K/4K</string>
|
||||
<string name="filter">Peñera</string>
|
||||
<string name="refresh">Refrescar</string>
|
||||
<string name="clear">Llimpiar</string>
|
||||
|
||||
<string name="use_external_video_player_summary">Delles resoluciones NUN tendrán audiu al habilitar esta opción</string>
|
||||
<string name="popup_remember_size_pos_title">Tamañu y posición del ventanu emerxente</string>
|
||||
<string name="popup_remember_size_pos_summary">Recuerda la cabera posición y resolución afitada nel ventanu emerxente</string>
|
||||
<string name="use_external_video_player_summary">Delles resoluciones NUN tendrán audiu al activar esta opción</string>
|
||||
<string name="popup_remember_size_pos_title">Recuerdar tamañu y posición del ventanu emerxente</string>
|
||||
<string name="popup_remember_size_pos_summary">Recuerda la cabera posición y resolución afitaes nel ventanu emerxente</string>
|
||||
<string name="player_gesture_controls_title">Controles per xestos del reproductor</string>
|
||||
<string name="player_gesture_controls_summary">Usa xestos pa controlar el brilléu y volume del reproductor</string>
|
||||
<string name="show_search_suggestions_title">Suxerencies de gueta</string>
|
||||
|
@ -170,5 +170,87 @@ abrir en ventanu emerxente</string>
|
|||
<string name="settings_category_popup_title">Ventanu emerxente</string>
|
||||
<string name="popup_resizing_indicator_title">Redimensionáu</string>
|
||||
|
||||
<string name="use_old_player_summary">Compilación vieya del reproductor Mediaframework.</string>
|
||||
<string name="use_old_player_summary">Reproductor vieyu integráu de Mediaframework</string>
|
||||
<string name="subscribe_button_title">Soscribise</string>
|
||||
<string name="subscribed_button_title">Soscribiéstite</string>
|
||||
<string name="channel_unsubscribed">Desoscribiéstite de la canal</string>
|
||||
<string name="subscription_change_failed">Nun pue camudase la resolución</string>
|
||||
<string name="subscription_update_failed">Nun pue anovase la soscripción</string>
|
||||
|
||||
<string name="tab_main">Principal</string>
|
||||
<string name="tab_subscriptions">Soscripciones</string>
|
||||
|
||||
<string name="fragment_whats_new">Qué hai nuevo</string>
|
||||
|
||||
<string name="enable_search_history_title">Historial de gueta</string>
|
||||
<string name="enable_search_history_summary">Atroxa de mou llocal les solicitúes de gueta</string>
|
||||
<string name="enable_watch_history_title">Historial</string>
|
||||
<string name="enable_watch_history_summary">Fai un siguimientu de los vídeos vistos</string>
|
||||
<string name="resume_on_audio_focus_gain_title">Siguir al ganar el focu</string>
|
||||
<string name="resume_on_audio_focus_gain_summary">Sigue reproduciendo tres les interrupciones (por exemplu, llamaes de teléfonu)</string>
|
||||
<string name="settings_category_player_title">Reproductor</string>
|
||||
<string name="settings_category_player_behavior_title">Comportamientu</string>
|
||||
<string name="settings_category_history_title">Historial</string>
|
||||
<string name="playlist">Llistáu de reproducción</string>
|
||||
<string name="best_resolution">La meyor resolución</string>
|
||||
<string name="undo">Desfacer</string>
|
||||
|
||||
<string name="notification_channel_name">Avisu de NewPipe</string>
|
||||
<string name="notification_channel_description">Avisos pa los reproductores de fondu y en ventanu emerxente de NewPipe</string>
|
||||
|
||||
<string name="search_no_results">Ensin resultaos</string>
|
||||
<string name="empty_subscription_feed_subtitle">Equí nun hai más que grillos</string>
|
||||
|
||||
<string name="no_subscribers">Ensin soscriptores</string>
|
||||
<plurals name="subscribers">
|
||||
<item quantity="one">%s soscriptor</item>
|
||||
<item quantity="other">%s soscriptores</item>
|
||||
</plurals>
|
||||
|
||||
<string name="no_views">Ensin visionaos</string>
|
||||
<plurals name="views">
|
||||
<item quantity="one">%s visionáu</item>
|
||||
<item quantity="other">%s visionaos</item>
|
||||
</plurals>
|
||||
|
||||
<string name="no_videos">Nun hai vídeos</string>
|
||||
<plurals name="videos">
|
||||
<item quantity="one">%s videu</item>
|
||||
<item quantity="other">%s vídeos</item>
|
||||
</plurals>
|
||||
|
||||
<string name="settings_category_downloads_title">Descargues</string>
|
||||
<string name="settings_file_charset_title">Caráuteres permitíos nos nomes de ficheros</string>
|
||||
<string name="settings_file_replacement_character_summary">Los caráuteres non válidos tróquense por esti valor</string>
|
||||
<string name="settings_file_replacement_character_title">Troquéu de caráuteres</string>
|
||||
|
||||
<string name="charset_letters_and_digits">Lletres y díxitos</string>
|
||||
<string name="charset_most_special_characters">La mayoría de caráuteres especiales</string>
|
||||
|
||||
<string name="title_activity_about">Tocante a NewPipe</string>
|
||||
<string name="action_settings">Axustes</string>
|
||||
<string name="action_about">Tocante a</string>
|
||||
<string name="title_licenses">Llicencies de terceros</string>
|
||||
<string name="copyright" formatted="true">© %1$s por %2$s so la %3$s</string>
|
||||
<string name="error_unable_to_load_license">Nun pudo cargase la llicencia</string>
|
||||
<string name="action_open_website">Abrir sitiu web</string>
|
||||
<string name="tab_about">Tocante a</string>
|
||||
<string name="tab_contributors">Collaboradores</string>
|
||||
<string name="tab_licenses">Llicencies</string>
|
||||
<string name="app_description">Un frontal llixeru de YouTube p\'Android.</string>
|
||||
<string name="view_on_github">Ver en GitHub</string>
|
||||
<string name="app_license_title">Llicencia de NewPipe</string>
|
||||
<string name="contribution_encouragement">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!</string>
|
||||
<string name="read_full_license">Lleer llicencia</string>
|
||||
<string name="contribution_title">Collaboración</string>
|
||||
|
||||
<string name="title_activity_history">Historial</string>
|
||||
<string name="title_history_search">Guetao</string>
|
||||
<string name="title_history_view">Visto</string>
|
||||
<string name="history_disabled">L\'historial ta desactiváu</string>
|
||||
<string name="action_history">Historial</string>
|
||||
<string name="history_empty">L\'historial ta baleru</string>
|
||||
<string name="history_cleared">Llimpióse l\'historial</string>
|
||||
<string name="item_deleted">Desanicióse l\'elementu</string>
|
||||
<string name="delete_item_search_history">¿Quies desaniciar esti elementu del historial de gueta?</string>
|
||||
</resources>
|
||||
|
|
|
@ -119,7 +119,7 @@
|
|||
|
||||
<string name="start">Starten</string>
|
||||
<string name="pause">Pause</string>
|
||||
<string name="view">Ansehen</string>
|
||||
<string name="view">Abspielen</string>
|
||||
<string name="add">Neue Mission</string>
|
||||
<string name="finish">OK</string>
|
||||
<string name="msg_server_unsupported">Server nicht unterstützt</string>
|
||||
|
@ -254,8 +254,9 @@
|
|||
<string name="charset_most_special_characters">Die meisten Sonderzeichen</string>
|
||||
|
||||
<string name="item_deleted">Element gelöscht</string>
|
||||
<string name="resume_on_audio_focus_gain_title">Fortsetzen beim erneuten fokussieren</string>
|
||||
<string name="resume_on_audio_focus_gain_title">Fortsetzen bei erneutem Fokussieren</string>
|
||||
<string name="settings_category_player_title">Player</string>
|
||||
<string name="empty_subscription_feed_subtitle">Nichts hier außer Grillen</string>
|
||||
|
||||
</resources>
|
||||
<string name="delete_item_search_history">Möchten Sie dieses Element aus dem Suchverlauf löschen?</string>
|
||||
</resources>
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
<string name="show_age_restricted_content_title">Mostrar contenido restringido por edad</string>
|
||||
<string name="video_is_age_restricted">Vídeo restringido por edad. Permitir este tipo de material es posible desde Ajustes.</string>
|
||||
|
||||
<string name="main_bg_subtitle">Toque buscar para empezar</string>
|
||||
<string name="main_bg_subtitle">Toque en buscar para empezar</string>
|
||||
<string name="autoplay_by_calling_app_title">Autoreproducir</string>
|
||||
<string name="autoplay_by_calling_app_summary">Reproducir automáticamente un vídeo cuando NewPipe es llamado desde otra aplicación</string>
|
||||
<string name="duration_live">en vivo</string>
|
||||
|
@ -259,4 +259,5 @@ abrir en modo popup</string>
|
|||
</plurals>
|
||||
|
||||
<string name="item_deleted">Elemento eliminado</string>
|
||||
<string name="delete_item_search_history">¿Desea eliminar este elemento del historial de búsqueda?</string>
|
||||
</resources>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<string name="settings">Paramètres</string>
|
||||
<string name="share">Partager</string>
|
||||
<string name="share_dialog_title">Partager avec</string>
|
||||
<string name="show_play_with_kodi_summary">Afficher une option pour lire la vidéo via la médiathèque Kodi</string>
|
||||
<string name="show_play_with_kodi_summary">Afficher une option pour lire la vidéo via Kodi</string>
|
||||
<string name="show_play_with_kodi_title">Afficher l’option « Lire avec Kodi »</string>
|
||||
<string name="upload_date_text">Ajoutée le %1$s</string>
|
||||
<string name="view_count_text">%1$s vues</string>
|
||||
|
@ -256,4 +256,5 @@
|
|||
<string name="charset_most_special_characters">Caractères spéciaux</string>
|
||||
|
||||
<string name="item_deleted">Objet effacé</string>
|
||||
<string name="delete_item_search_history">Voulez-vous supprimer cet élément de l\'historique de recherche ?</string>
|
||||
</resources>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<string name="download_path_audio_summary">Lokasi untuk menyimpan audio yang diunduh</string>
|
||||
<string name="download_path_audio_dialog_title">Masukkan lokasi unduhan berkas audio</string>
|
||||
|
||||
<string name="autoplay_by_calling_app_title">Putar otomatis ketika dipanggil dari aplikasi lain</string>
|
||||
<string name="autoplay_by_calling_app_title">Putar otomatis</string>
|
||||
<string name="autoplay_by_calling_app_summary">Otomatis memutar video ketika NewPipe dijalankan dari aplikasi lain</string>
|
||||
<string name="default_resolution_title">Resolusi baku</string>
|
||||
<string name="play_with_kodi_title">Putar dengan Kodi</string>
|
||||
|
@ -33,7 +33,7 @@
|
|||
<string name="play_audio">Audio</string>
|
||||
<string name="default_audio_format_title">Format audio baku</string>
|
||||
<string name="webm_description">WebM — format bebas</string>
|
||||
<string name="m4a_description">m4a — kualitas lebih baik</string>
|
||||
<string name="m4a_description">M4A — kualitas lebih baik</string>
|
||||
<string name="theme_title">Tema</string>
|
||||
<string name="dark_theme_title">Gelap</string>
|
||||
<string name="light_theme_title">Terang</string>
|
||||
|
@ -61,15 +61,15 @@
|
|||
<string name="error_report_title">Laporan galat</string>
|
||||
|
||||
<string name="general_error">Galat</string>
|
||||
<string name="parsing_error">Tidak bisa mengurai situs web.</string>
|
||||
<string name="light_parsing_error">Sama sekali tidak bisa mengurai situs web.</string>
|
||||
<string name="content_not_available">Konten tidak tersedia.</string>
|
||||
<string name="blocked_by_gema">Diblokir oleh GEMA.</string>
|
||||
<string name="could_not_setup_download_menu">Tidak bisa menyiapkan menu unduhan.</string>
|
||||
<string name="live_streams_not_supported">Ini adalah SIARAN LANGSUNG. Fitur ini belum didukung.</string>
|
||||
<string name="parsing_error">Tidak dapat mengurai situs web</string>
|
||||
<string name="light_parsing_error">Sama sekali tidak dapat mengurai situs web</string>
|
||||
<string name="content_not_available">Konten tidak tersedia</string>
|
||||
<string name="blocked_by_gema">Diblokir oleh GEMA</string>
|
||||
<string name="could_not_setup_download_menu">Tidak bisa menyiapkan menu unduhan</string>
|
||||
<string name="live_streams_not_supported">Ini adalah SIARAN LANGSUNG, yang mana ini belum didukung.</string>
|
||||
<string name="could_not_load_image">Tidak bisa memuat gambar</string>
|
||||
<string name="sorry_string">Maaf, hal tersebut seharusnya tidak terjadi.</string>
|
||||
<string name="error_report_button_text">Lapor galat via surel</string>
|
||||
<string name="error_report_button_text">Lapor galat via surat elektronik</string>
|
||||
<string name="error_snackbar_message">Maaf, telah terjadi galat.</string>
|
||||
<string name="error_snackbar_action">LAPOR</string>
|
||||
<string name="what_device_headline">Info:</string>
|
||||
|
@ -92,7 +92,7 @@
|
|||
<string name="video">Video</string>
|
||||
<string name="audio">Audio</string>
|
||||
<string name="retry">Ulangi</string>
|
||||
<string name="storage_permission_denied">Izin untuk mengakses penyimpanan ditolak</string>
|
||||
<string name="storage_permission_denied">Izin akses penyimpanan ditolak</string>
|
||||
|
||||
<string name="delete">Hapus</string>
|
||||
<string name="view">Tonton</string>
|
||||
|
@ -117,7 +117,7 @@
|
|||
<string name="no_player_found">Tidak ditemukan pemutar stream. Apakah anda ingin memasang VLC?</string>
|
||||
<string name="youtube_signature_decryption_error">Tidak bisa mendekrip tanda tangan URL video.</string>
|
||||
<string name="app_ui_crash">App/UI rusak</string>
|
||||
<string name="could_not_get_stream">Tidak bisa mendapatkan stream apapun.</string>
|
||||
<string name="could_not_get_stream">Tidak bisa mendapatkan stream apapun</string>
|
||||
<string name="info_labels">Apa:\\nPermintaan:\\nBahasa Konten:\\nLayanan:\\nWaktu GMT:\\nPaket:\\nVersi:\\nVersi OS:\\nIP:</string>
|
||||
<string name="user_report">Laporan pengguna</string>
|
||||
|
||||
|
@ -145,11 +145,11 @@
|
|||
<string name="msg_popup_permission">Izin ini dibutuhkan untuk
|
||||
membuka di mode popup</string>
|
||||
|
||||
<string name="popup_mode_share_menu_title">Mode Popup NewPipe</string>
|
||||
<string name="popup_mode_share_menu_title">Mode popup NewPipe</string>
|
||||
|
||||
<string name="popup_playing_toast">Memutar dalam mode popup</string>
|
||||
<string name="use_old_player_title">Gunakan pemutar lama</string>
|
||||
<string name="use_old_player_summary">Versi lama pemutar Mediaframework.</string>
|
||||
<string name="use_old_player_summary">Versi lama pemutar Mediaframework</string>
|
||||
<string name="disabled">Dinonaktifkan</string>
|
||||
|
||||
<string name="default_video_format_title">Pilihan format video</string>
|
||||
|
@ -166,7 +166,7 @@ membuka di mode popup</string>
|
|||
<string name="filter">Filter</string>
|
||||
<string name="use_external_video_player_summary">Beberapa resolusi TIDAK akan memiliki suara ketika opsi ini diaktifkan</string>
|
||||
<string name="popup_remember_size_pos_title">Ingat ukuran dan posisi sembulan</string>
|
||||
<string name="popup_remember_size_pos_summary">Ingat ukuran terakhir dan pengaturan posisi sembulan</string>
|
||||
<string name="popup_remember_size_pos_summary">Ingat ukuran terakhir dan pengaturan posisi popup</string>
|
||||
|
||||
<string name="settings_category_popup_title">Sembulan</string>
|
||||
<string name="popup_resizing_indicator_title">Ubah ukuran</string>
|
||||
|
@ -212,7 +212,7 @@ membuka di mode popup</string>
|
|||
<string name="tab_main">Utama</string>
|
||||
<string name="enable_search_history_title">Cari riwayat</string>
|
||||
<string name="enable_search_history_summary">Simpan pencarian secara lokal</string>
|
||||
<string name="enable_watch_history_title">Riwayat tontonan</string>
|
||||
<string name="enable_watch_history_title">Riwayat</string>
|
||||
<string name="notification_channel_name">Notifikasi NewPipe</string>
|
||||
<string name="title_activity_history">Riwayat</string>
|
||||
<string name="history_disabled">Riwayat dinonaktifkan</string>
|
||||
|
|
|
@ -12,16 +12,16 @@
|
|||
<string name="settings">Impostazioni</string>
|
||||
<string name="did_you_mean">Intendevi: %1$s ?</string>
|
||||
<string name="share_dialog_title">Condividi con</string>
|
||||
<string name="choose_browser">Scegli browser</string>
|
||||
<string name="choose_browser">Scegli il browser</string>
|
||||
<string name="screen_rotation">rotazione</string>
|
||||
<string name="download_path_title">Cartella dei video scaricati</string>
|
||||
<string name="download_path_summary">Cartella in cui memorizzare i video scaricati.</string>
|
||||
<string name="download_path_title">Percorso dei video scaricati</string>
|
||||
<string name="download_path_summary">Percorso in cui memorizzare i video scaricati</string>
|
||||
<string name="download_path_dialog_title">Inserisci il percorso per i download</string>
|
||||
<string name="default_resolution_title">Risoluzione predefinita</string>
|
||||
<string name="play_with_kodi_title">Riproduci con Kodi</string>
|
||||
<string name="kore_not_found">L\'applicazione Kore non è stata trovata. Vorresti installarla?</string>
|
||||
<string name="kore_not_found">L\'applicazione Kore non è stata trovata. Vuoi installarla?</string>
|
||||
<string name="show_play_with_kodi_title">Mostra l\'opzione \"Riproduci con Kodi\"</string>
|
||||
<string name="show_play_with_kodi_summary">Mostra l\'opzione per riprodurre i video tramite Kodi.</string>
|
||||
<string name="show_play_with_kodi_summary">Mostra l\'opzione per riprodurre i video tramite Kodi</string>
|
||||
<string name="play_audio">Audio</string>
|
||||
<string name="default_audio_format_title">Formato audio predefinito</string>
|
||||
<string name="webm_description">WebM — formato libero</string>
|
||||
|
@ -30,7 +30,7 @@
|
|||
<string name="next_video_title">Prossimo video</string>
|
||||
<string name="show_next_and_similar_title">Mostra video a seguire e video simili</string>
|
||||
<string name="url_not_supported_toast">URL non supportato</string>
|
||||
<string name="search_language_title">Lingua preferita per i contenuti</string>
|
||||
<string name="search_language_title">Lingua predefinita per i contenuti</string>
|
||||
<string name="settings_category_video_audio_title">Video e Audio</string>
|
||||
|
||||
<string name="list_thumbnail_view_description">Anteprima video</string>
|
||||
|
@ -40,12 +40,12 @@
|
|||
<string name="detail_likes_img_view_description">Mi piace</string>
|
||||
<string name="err_dir_create">Impossibile creare la cartella di download \'%1$s\'</string>
|
||||
<string name="info_dir_created">Creata la cartella per i download \'%1$s\'</string>
|
||||
<string name="use_external_video_player_title">Usa un lettore video esterno</string>
|
||||
<string name="use_external_audio_player_title">Usa un lettore audio esterno</string>
|
||||
<string name="use_external_video_player_title">Usa un riproduttore video esterno</string>
|
||||
<string name="use_external_audio_player_title">Usa un riproduttore audio esterno</string>
|
||||
|
||||
<string name="download_path_audio_title">Cartella degli audio scaricati</string>
|
||||
<string name="download_path_audio_summary">Cartella dove salvare gli audio scaricati.</string>
|
||||
<string name="download_path_audio_dialog_title">Inserisci la cartella per i file audio</string>
|
||||
<string name="download_path_audio_title">Percorso degli audio scaricati</string>
|
||||
<string name="download_path_audio_summary">Percorso dove salvare gli audio scaricati</string>
|
||||
<string name="download_path_audio_dialog_title">Inserisci il percorso per i file audio</string>
|
||||
|
||||
<string name="theme_title">Tema</string>
|
||||
<string name="dark_theme_title">Scuro</string>
|
||||
|
@ -63,26 +63,26 @@
|
|||
<string name="content_not_available">Contenuto non disponibile</string>
|
||||
<string name="blocked_by_gema">Bloccato dalla GEMA</string>
|
||||
<string name="use_tor_title">Usa Tor</string>
|
||||
<string name="use_tor_summary">(Sperimentale) Forza il traffico in download tramite Tor per una maggiore privacy (lo streaming dei video non è ancora supportato).</string>
|
||||
<string name="use_tor_summary">(Sperimentale) Forza il traffico in download tramite Tor per una maggiore riservatezza (lo streaming dei video non è ancora supportato).</string>
|
||||
|
||||
<string name="parsing_error">Impossibile analizzare il sito web</string>
|
||||
<string name="could_not_setup_download_menu">Impossibile impostare il menù di download</string>
|
||||
|
||||
|
||||
<string name="live_streams_not_supported">Questo è uno stream dal vivo. Gli stream dal vivo non sono ancora supportati.</string>
|
||||
<string name="live_streams_not_supported">Questo è uno stream in diretta, il quale non è ancora supportato.</string>
|
||||
|
||||
|
||||
<string name="content">Contenuti</string>
|
||||
<string name="show_age_restricted_content_title">Mostra contenuti vincolati all\'età</string>
|
||||
<string name="video_is_age_restricted">Questo video è riservato ad un pubblico maturo. Per accedervi, abilita \"Mostra video vincolati all\'età\" nelle impostazioni.</string>
|
||||
<string name="video_is_age_restricted">Questo video è riservato ad un pubblico maggiorenne. Per accedervi, abilita \"Mostra video vincolati all\'età\" nelle impostazioni.</string>
|
||||
|
||||
<string name="main_bg_subtitle">Tocca \"cerca\" per iniziare</string>
|
||||
<string name="autoplay_by_calling_app_title">Inizia automaticamente la riproduzione se NewPipe viene aperto da un\'altra app</string>
|
||||
<string name="autoplay_by_calling_app_title">Riproduzione automatica</string>
|
||||
<string name="autoplay_by_calling_app_summary">Riproduci i video automaticamente quando NewPipe viene aperto da un\'altra app</string>
|
||||
<string name="duration_live">in diretta</string>
|
||||
|
||||
<string name="light_parsing_error">Impossibile eseguire il parsing completo del sito</string>
|
||||
<string name="could_not_get_stream">Non è stato ottenuto alcuno stream</string>
|
||||
<string name="light_parsing_error">Impossibile analizzare completamente il sito web</string>
|
||||
<string name="could_not_get_stream">Non è stato ottenuto alcun flusso</string>
|
||||
<string name="sorry_string">Ci dispiace, non sarebbe dovuto succedere.</string>
|
||||
<string name="error_report_button_text">Segnala l\'errore via e-mail</string>
|
||||
<string name="error_snackbar_message">Ci dispiace, c\'è stato qualche errore.</string>
|
||||
|
@ -99,18 +99,18 @@
|
|||
<string name="video">Video</string>
|
||||
<string name="audio">Audio</string>
|
||||
<string name="retry">Riprova</string>
|
||||
<string name="storage_permission_denied">È stato negato il permesso di accedere all\'archiviazione di massa</string>
|
||||
<string name="storage_permission_denied">È stato negato il permesso di accesso all\'archiviazione di massa</string>
|
||||
<string name="downloads">Download</string>
|
||||
<string name="downloads_title">Download</string>
|
||||
<string name="error_report_title">Segnalazione errori</string>
|
||||
|
||||
<string name="start">Inizia</string>
|
||||
<string name="pause">Pausa</string>
|
||||
<string name="view">Visualizza</string>
|
||||
<string name="view">Riproduci</string>
|
||||
<string name="delete">Elimina</string>
|
||||
<string name="checksum">Checksum</string>
|
||||
<string name="checksum">Codice di controllo</string>
|
||||
|
||||
<string name="add">Nuova missione</string>
|
||||
<string name="add">Nuovo obiettivo</string>
|
||||
<string name="finish">OK</string>
|
||||
|
||||
<string name="msg_name">Nome del file</string>
|
||||
|
@ -126,7 +126,7 @@
|
|||
<string name="no_available_dir">Seleziona una cartella disponibile in cui salvare i download</string>
|
||||
|
||||
<string name="could_not_load_image">Impossibile caricare l\'immagine</string>
|
||||
<string name="app_ui_crash">L\'app/UI è andata in crash</string>
|
||||
<string name="app_ui_crash">L\'app/UI si è interrotta</string>
|
||||
<string name="info_labels">Cosa:\\nRichiesta:\\nLingua contenuto:\\nServizio:\\nOrario GMT:\\nPacchetto:\\nVersione:\\nVersione SO:\\nRange IP glob.:</string>
|
||||
|
||||
<string name="reCaptchaActivity">reCAPTCHA</string>
|
||||
|
@ -149,26 +149,26 @@
|
|||
|
||||
|
||||
<string name="open_in_popup_mode">Apri in modalità popup</string>
|
||||
<string name="popup_mode_share_menu_title">NewPipe Modo popup</string>
|
||||
<string name="popup_mode_share_menu_title">NewPipe in modalità popup</string>
|
||||
|
||||
<string name="popup_playing_toast">Riproduzione in modalità popup</string>
|
||||
<string name="disabled">Disattivato</string>
|
||||
|
||||
<string name="use_old_player_title">Usa il vecchio riproduttore</string>
|
||||
<string name="use_external_video_player_summary">Alcune risoluzioni non avranno audio se questa opzione viene abilitata.</string>
|
||||
<string name="use_external_video_player_summary">Alcune risoluzioni NON avranno l\'audio se questa opzione viene abilitata</string>
|
||||
<string name="controls_background_title">In sottofondo</string>
|
||||
<string name="controls_popup_title">Popup</string>
|
||||
|
||||
<string name="default_popup_resolution_title">Risoluzione predefinita per il popup</string>
|
||||
<string name="default_popup_resolution_title">Risoluzione predefinita per la modalità popup</string>
|
||||
<string name="show_higher_resolutions_title">Mostra risoluzioni più alte</string>
|
||||
<string name="show_higher_resolutions_summary">Solo alcuni dispositivi supportano la riproduzione di video in 2K e 4K.</string>
|
||||
<string name="default_video_format_title">Formato video preferito</string>
|
||||
<string name="show_higher_resolutions_summary">Solo alcuni dispositivi supportano la riproduzione di video in 2K e 4K</string>
|
||||
<string name="default_video_format_title">Formato video predefinito</string>
|
||||
<string name="popup_remember_size_pos_title">Ricorda grandezza e posizione del popup</string>
|
||||
<string name="popup_remember_size_pos_summary">Ricorda l\'ultima grandezza e posizione del popup</string>
|
||||
<string name="player_gesture_controls_title">Controlli gestuali</string>
|
||||
<string name="player_gesture_controls_summary">Usa i gesti per controllare luminosità e volume.</string>
|
||||
<string name="player_gesture_controls_summary">Usa i gesti per controllare luminosità e volume</string>
|
||||
<string name="show_search_suggestions_title">Suggerimenti di ricerca</string>
|
||||
<string name="show_search_suggestions_summary">Mostra suggerimenti durante la ricerca.</string>
|
||||
<string name="show_search_suggestions_summary">Mostra i suggerimenti durante la ricerca</string>
|
||||
|
||||
<string name="settings_category_popup_title">Popup</string>
|
||||
<string name="filter">Filtra i risultati</string>
|
||||
|
@ -177,10 +177,10 @@
|
|||
<string name="popup_resizing_indicator_title">Ridimensionamento</string>
|
||||
<string name="best_resolution">Risoluzione migliore</string>
|
||||
|
||||
<string name="use_old_player_summary">Precedente riproduttore integrato facente uso di Mediaframework</string>
|
||||
<string name="use_old_player_summary">Precedente riproduttore integrato Mediaframework</string>
|
||||
|
||||
<string name="msg_popup_permission">Questo permesso è necessario
|
||||
\nper la modalità popup</string>
|
||||
\nper aprire la modalità popup</string>
|
||||
|
||||
<string name="action_settings">Impostazioni</string>
|
||||
<string name="action_about">Informazioni</string>
|
||||
|
@ -206,7 +206,7 @@
|
|||
|
||||
<string name="tab_subscriptions">Iscrizioni</string>
|
||||
|
||||
<string name="fragment_whats_new">Nuovo</string>
|
||||
<string name="fragment_whats_new">Novità</string>
|
||||
|
||||
<string name="enable_search_history_title">Cronologia ricerche</string>
|
||||
<string name="enable_search_history_summary">Salva le ricerche localmente</string>
|
||||
|
@ -233,10 +233,10 @@
|
|||
<string name="history_cleared">Cronologia cancellata</string>
|
||||
|
||||
<string name="tab_main">Principale</string>
|
||||
<string name="settings_category_player_title">Player</string>
|
||||
<string name="settings_category_player_title">Riproduttore</string>
|
||||
<string name="settings_category_player_behavior_title">Comportamento</string>
|
||||
<string name="settings_category_history_title">Cronologia</string>
|
||||
<string name="playlist">Playlist</string>
|
||||
<string name="playlist">Scaletta</string>
|
||||
<string name="undo">Annulla</string>
|
||||
|
||||
<string name="notification_channel_name">Notifiche NewPipe</string>
|
||||
|
@ -262,4 +262,7 @@
|
|||
</plurals>
|
||||
|
||||
<string name="item_deleted">Elemento eliminato</string>
|
||||
<string name="empty_subscription_feed_subtitle">Nulla da mostrare</string>
|
||||
|
||||
<string name="delete_item_search_history">Vuoi eliminare questo elemento dalla cronologia?</string>
|
||||
</resources>
|
||||
|
|
|
@ -253,4 +253,5 @@
|
|||
<string name="history_empty">Historikken er tom</string>
|
||||
<string name="history_cleared">Historikk tømt</string>
|
||||
<string name="item_deleted">Element slettet</string>
|
||||
<string name="delete_item_search_history">Ønsker du å slette dette elementet fra søkehistorikken?</string>
|
||||
</resources>
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
<string name="storage_permission_denied">Toegang tot opslag geweigerd</string>
|
||||
<string name="start">Begin</string>
|
||||
<string name="pause">Pauzeren</string>
|
||||
<string name="view">Bekijken</string>
|
||||
<string name="view">Afspelen</string>
|
||||
<string name="delete">Verwijderen</string>
|
||||
<string name="checksum">Controlesom</string>
|
||||
|
||||
|
@ -260,4 +260,5 @@ te openen in pop-upmodus</string>
|
|||
</plurals>
|
||||
|
||||
<string name="item_deleted">Item verwijderd</string>
|
||||
<string name="delete_item_search_history">Wil je dit item uit je geschiedenis verwijderen?</string>
|
||||
</resources>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<string name="what_device_headline">Informações:</string>
|
||||
<string name="webm_description">WebM — formato aberto</string>
|
||||
<string name="view_count_text">%1$s visualizações</string>
|
||||
<string name="view">Ver</string>
|
||||
<string name="view">Reproduzir</string>
|
||||
<string name="video_is_age_restricted">Vídeo com restrição de idade. Permissão para vídeos com essa restrição podem ser feitas no menu configurações.</string>
|
||||
<string name="video">Vídeo</string>
|
||||
<string name="autoplay_by_calling_app_summary">Reproduzir o vídeo automaticamente quando o NewPipe for aberto a partir de outro app</string>
|
||||
|
@ -238,7 +238,8 @@ abrir em modo popup</string>
|
|||
</plurals>
|
||||
|
||||
<string name="item_deleted">Item excluído</string>
|
||||
<string name="settings_category_player_title">Player</string>
|
||||
<string name="settings_category_player_title">Reprodutor</string>
|
||||
<string name="empty_subscription_feed_subtitle">Não há nada aqui</string>
|
||||
|
||||
</resources>
|
||||
<string name="delete_item_search_history">Deseja apagar este item do seu histórico de busca?</string>
|
||||
</resources>
|
||||
|
|
|
@ -21,18 +21,18 @@
|
|||
<string name="download_path_dialog_title">Introduza o caminho para os vídeos</string>
|
||||
<string name="default_resolution_title">Resolução padrão</string>
|
||||
<string name="play_with_kodi_title">Reproduzir no Kodi</string>
|
||||
<string name="kore_not_found">Aplicação não encontrada. Instalar o Kore?</string>
|
||||
<string name="kore_not_found">Aplicação Kore não encontrada. Quer instalá-la?</string>
|
||||
<string name="show_play_with_kodi_title">Mostrar opção \"Reproduzir no Kodi\"</string>
|
||||
<string name="show_play_with_kodi_summary">Mostra uma opção para reproduzir o vídeo no Kodi</string>
|
||||
<string name="play_audio">Áudio</string>
|
||||
<string name="default_audio_format_title">Formato áudio padrão</string>
|
||||
<string name="webm_description">WebM — formato livre</string>
|
||||
<string name="m4a_description">m4a — melhor qualidade</string>
|
||||
<string name="m4a_description">M4A — melhor qualidade</string>
|
||||
<string name="download_dialog_title">Transferir</string>
|
||||
<string name="next_video_title">Vídeo seguinte</string>
|
||||
<string name="show_next_and_similar_title">Mostrar vídeos seguintes e semelhantes</string>
|
||||
<string name="url_not_supported_toast">URL não suportado</string>
|
||||
<string name="search_language_title">Idioma preferido do conteúdo</string>
|
||||
<string name="search_language_title">Idioma predefinido do conteúdo</string>
|
||||
<string name="settings_category_video_audio_title">Vídeo e áudio</string>
|
||||
|
||||
<string name="list_thumbnail_view_description">Miniatura de vídeos</string>
|
||||
|
@ -60,19 +60,19 @@
|
|||
<string name="info_dir_created">Diretório \'%1$s\' criado com sucesso</string>
|
||||
<string name="general_error">Erro</string>
|
||||
<string name="could_not_load_thumbnails">Incapaz de carregar todas as miniaturas</string>
|
||||
<string name="youtube_signature_decryption_error">Incapaz de decodificar a assinatura do vídeo.</string>
|
||||
<string name="parsing_error">Incapaz de processar o sítio web.</string>
|
||||
<string name="content_not_available">Conteúdo não disponível.</string>
|
||||
<string name="blocked_by_gema">Bloqueado pela GEMA.</string>
|
||||
<string name="youtube_signature_decryption_error">Incapaz de decodificar a assinatura do vídeo</string>
|
||||
<string name="parsing_error">Incapaz de processar o site</string>
|
||||
<string name="content_not_available">Conteúdo não disponível</string>
|
||||
<string name="blocked_by_gema">Bloqueado pela GEMA</string>
|
||||
|
||||
<string name="content">Conteúdo</string>
|
||||
<string name="show_age_restricted_content_title">Restringir conteúdo por idade</string>
|
||||
<string name="video_is_age_restricted">O vídeo está restrito por idade. Ative a restrição de vídeos por idade nas definições.</string>
|
||||
<string name="video_is_age_restricted">Vídeo com restrição de idade. É possível permitir este material através das Definições.</string>
|
||||
|
||||
<string name="light_parsing_error">Não foi possível processar o sítio web.</string>
|
||||
<string name="could_not_setup_download_menu">Não foi possível configurar o menu de transferências.</string>
|
||||
<string name="live_streams_not_supported">Esta é uma EMISSÃO EM DIRETO. Estas emissões ainda não são suportadas.</string>
|
||||
<string name="could_not_get_stream">Não foi possível obter a emissão.</string>
|
||||
<string name="light_parsing_error">Não foi possível processar totalmente o site</string>
|
||||
<string name="could_not_setup_download_menu">Não foi possível configurar o menu de transferências</string>
|
||||
<string name="live_streams_not_supported">Esta é uma EMISSÃO EM DIRETO, as quais ainda não são suportadas.</string>
|
||||
<string name="could_not_get_stream">Não foi possível obter a emissão</string>
|
||||
<string name="sorry_string">Desculpe, isto não deveria ter acontecido.</string>
|
||||
<string name="error_report_button_text">Reportar erro por e-mail</string>
|
||||
<string name="error_snackbar_message">Ocorreram alguns erros.</string>
|
||||
|
@ -86,9 +86,9 @@
|
|||
<string name="video">Vídeo</string>
|
||||
<string name="audio">Áudio</string>
|
||||
<string name="retry">Tentar novamente</string>
|
||||
<string name="storage_permission_denied">Não foi concedida permissão para aceder ao armazenamento</string>
|
||||
<string name="storage_permission_denied">Permissão para aceder ao armazenamento foi negada</string>
|
||||
<string name="main_bg_subtitle">Toque para iniciar a pesquisa</string>
|
||||
<string name="autoplay_by_calling_app_title">Reproduzir se invocado por outra aplicação</string>
|
||||
<string name="autoplay_by_calling_app_title">Reprodução automática</string>
|
||||
<string name="autoplay_by_calling_app_summary">Reproduzir vídeo automaticamente se o NewPipe for invocado por outra aplicação</string>
|
||||
<string name="duration_live">direto</string>
|
||||
|
||||
|
@ -101,7 +101,7 @@
|
|||
|
||||
<string name="start">Iniciar</string>
|
||||
<string name="pause">Pausa</string>
|
||||
<string name="view">Ver</string>
|
||||
<string name="view">Reproduzir</string>
|
||||
<string name="delete">Apagar</string>
|
||||
<string name="checksum">Checksum</string>
|
||||
|
||||
|
@ -115,8 +115,8 @@
|
|||
<string name="msg_url_malform">URL inválido ou Internet não disponível</string>
|
||||
<string name="msg_running_detail">Toque para detalhes</string>
|
||||
<string name="msg_wait">Por favor aguarde…</string>
|
||||
<string name="msg_copied">Copiado para a área de transferência.</string>
|
||||
<string name="no_available_dir">Por favor selecione um diretório disponível.</string>
|
||||
<string name="msg_copied">Copiado para a área de transferência</string>
|
||||
<string name="no_available_dir">Por favor selecione a pasta para as descargas</string>
|
||||
|
||||
<string name="finish">OK</string>
|
||||
<string name="msg_threads">Processos</string>
|
||||
|
@ -145,11 +145,11 @@ o modo “popup“</string>
|
|||
<string name="reCaptcha_title">Desafio reCAPTCHA</string>
|
||||
<string name="recaptcha_request_toast">Desafio reCAPTCHA solicitado</string>
|
||||
|
||||
<string name="popup_mode_share_menu_title">Modo popup de NewPipe</string>
|
||||
<string name="popup_mode_share_menu_title">Modo popup do NewPipe</string>
|
||||
|
||||
<string name="popup_playing_toast">Reproduzir em modo de popup</string>
|
||||
<string name="use_old_player_title">Usar reprodutor antigo</string>
|
||||
<string name="use_old_player_summary">Versão antiga no reprodutor Mediaframework.</string>
|
||||
<string name="use_old_player_summary">Versão antiga do reprodutor Mediaframework</string>
|
||||
<string name="default_video_format_title">Formato de vídeo preferido</string>
|
||||
<string name="disabled">Desativado</string>
|
||||
|
||||
|
@ -186,10 +186,74 @@ o modo “popup“</string>
|
|||
<string name="tab_about">Sobre</string>
|
||||
<string name="tab_contributors">Colaboradores</string>
|
||||
<string name="tab_licenses">Licenças</string>
|
||||
<string name="app_description">Aplicação leve, simples e grátis de Youtube para Android.</string>
|
||||
<string name="view_on_github">Ver no Github</string>
|
||||
<string name="app_description">Aplicação leve, simples e grátis de YouTube para Android.</string>
|
||||
<string name="view_on_github">Ver no GitHub</string>
|
||||
<string name="app_license_title">Licença do NewPipe</string>
|
||||
<string name="contribution_encouragement">Se tem ideias, tradução, alterações de design, limpeza de código ou alterações de código pesado, ajuda é sempre bem-vinda. Quanto mais se faz melhor fica!</string>
|
||||
<string name="contribution_encouragement">Se tem ideias de tradução, alterações de design, limpeza de código ou alterações de código pesado—ajuda é sempre bem-vinda. Quanto mais se faz melhor fica!</string>
|
||||
<string name="read_full_license">Ler licença</string>
|
||||
<string name="contribution_title">Contribuição</string>
|
||||
<string name="subscribe_button_title">Subscrever</string>
|
||||
<string name="subscribed_button_title">Subscrito</string>
|
||||
<string name="channel_unsubscribed">Canal não subscrito</string>
|
||||
<string name="subscription_change_failed">Incapaz de alterar a subscrição</string>
|
||||
<string name="subscription_update_failed">Incapaz de atualizar a subscrição</string>
|
||||
|
||||
<string name="tab_main">Principal</string>
|
||||
<string name="tab_subscriptions">Subscrições</string>
|
||||
|
||||
<string name="fragment_whats_new">O que há de novo</string>
|
||||
|
||||
<string name="enable_search_history_title">Histórico de Pesquisa</string>
|
||||
<string name="enable_search_history_summary">Armazenar termos de pesquisa localmente</string>
|
||||
<string name="enable_watch_history_title">Histórico</string>
|
||||
<string name="enable_watch_history_summary">Armazenar histórico de vídeos assistidos</string>
|
||||
<string name="resume_on_audio_focus_gain_title">Retomar ao ganhar foco</string>
|
||||
<string name="resume_on_audio_focus_gain_summary">Continuar reprodução após interrupções (ex. chamadas)</string>
|
||||
<string name="settings_category_player_title">Reprodutor</string>
|
||||
<string name="settings_category_player_behavior_title">Comportamento</string>
|
||||
<string name="settings_category_history_title">Histórico</string>
|
||||
<string name="playlist">Lista de Reprodução</string>
|
||||
<string name="undo">Desfazer</string>
|
||||
|
||||
<string name="notification_channel_name">Notificação do NewPipe</string>
|
||||
<string name="notification_channel_description">Notificações do NewPipe e para reprodutores pop-up</string>
|
||||
|
||||
<string name="search_no_results">Sem resultados</string>
|
||||
<string name="empty_subscription_feed_subtitle">Aqui não há nada para ver</string>
|
||||
|
||||
<string name="no_subscribers">Sem subscritores</string>
|
||||
<plurals name="subscribers">
|
||||
<item quantity="one">%s subscrito</item>
|
||||
<item quantity="other">%s subscritos</item>
|
||||
</plurals>
|
||||
|
||||
<string name="no_views">Sem visualizações</string>
|
||||
<plurals name="views">
|
||||
<item quantity="one">%s visualização</item>
|
||||
<item quantity="other">%s visualizações</item>
|
||||
</plurals>
|
||||
|
||||
<string name="no_videos">Sem vídeos</string>
|
||||
<plurals name="videos">
|
||||
<item quantity="one">%s vídeo</item>
|
||||
<item quantity="other">%s vídeos</item>
|
||||
</plurals>
|
||||
|
||||
<string name="settings_category_downloads_title">Download</string>
|
||||
<string name="settings_file_charset_title">Caracteres permitidos em nomes de ficheiros</string>
|
||||
<string name="settings_file_replacement_character_summary">Caracteres inválidos são substituídos por este valor</string>
|
||||
<string name="settings_file_replacement_character_title">Carácter de substituição</string>
|
||||
|
||||
<string name="charset_letters_and_digits">Letras e dígitos</string>
|
||||
<string name="charset_most_special_characters">Caracteres especiais</string>
|
||||
|
||||
<string name="title_activity_history">Histórico</string>
|
||||
<string name="title_history_search">Pesquisado</string>
|
||||
<string name="title_history_view">Visto</string>
|
||||
<string name="history_disabled">Histórico está desativado</string>
|
||||
<string name="action_history">Histórico</string>
|
||||
<string name="history_empty">O histórico está vazio</string>
|
||||
<string name="history_cleared">Histórico eliminado</string>
|
||||
<string name="item_deleted">Item apagado</string>
|
||||
<string name="delete_item_search_history">Deseja apagar este item do histórico de pesquisa?</string>
|
||||
</resources>
|
||||
|
|
|
@ -96,10 +96,10 @@
|
|||
<string name="live_streams_not_supported">Это прямая трансляция, они пока не поддерживаются.</string>
|
||||
<string name="could_not_load_image">Не удалось загрузить изображение</string>
|
||||
<string name="app_ui_crash">"Падение приложения/пользовательского интерфейса "</string>
|
||||
<string name="sorry_string">Простите, такое не должно было произойти.</string>
|
||||
<string name="sorry_string">Простите, это не должно было произойти.</string>
|
||||
<string name="error_report_button_text">Отправить отчёт об ошибке по электронной почте</string>
|
||||
<string name="error_snackbar_message">Простите, произошли ошибки.</string>
|
||||
<string name="error_snackbar_action">ОТЧЕТ</string>
|
||||
<string name="error_snackbar_action">ОТЧЁТ</string>
|
||||
<string name="what_device_headline">Информация:</string>
|
||||
<string name="what_happened_headline">Что произошло:</string>
|
||||
<string name="error_details_headline">Детали:</string>
|
||||
|
@ -237,7 +237,7 @@
|
|||
<item quantity="one">%s подписчик</item>
|
||||
<item quantity="few">%s подписчика</item>
|
||||
<item quantity="many">%s подписчиков</item>
|
||||
<item quantity="other"></item>
|
||||
<item quantity="other"/>
|
||||
</plurals>
|
||||
|
||||
<string name="no_views">Нет просмотров</string>
|
||||
|
@ -245,7 +245,7 @@
|
|||
<item quantity="one">%s просмотр</item>
|
||||
<item quantity="few">%s просмотра</item>
|
||||
<item quantity="many">%s просмотров</item>
|
||||
<item quantity="other"></item>
|
||||
<item quantity="other"/>
|
||||
</plurals>
|
||||
|
||||
<string name="no_videos">Нет видео</string>
|
||||
|
@ -253,7 +253,9 @@
|
|||
<item quantity="one">%s видео</item>
|
||||
<item quantity="few">%s видео</item>
|
||||
<item quantity="many">%s видео</item>
|
||||
<item quantity="other"></item>
|
||||
<item quantity="other"/>
|
||||
</plurals>
|
||||
|
||||
</resources>
|
||||
<string name="item_deleted">Элемент удалён</string>
|
||||
<string name="delete_item_search_history">Удалить этот элемент из истории поиска?</string>
|
||||
</resources>
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
<string name="main_bg_subtitle">Začnite z iskanjem</string>
|
||||
<string name="start">Začni</string>
|
||||
<string name="pause">Premor</string>
|
||||
<string name="view">Poglej</string>
|
||||
<string name="view">Predvajaj</string>
|
||||
<string name="delete">Izbriši</string>
|
||||
<string name="checksum">Nadzorna vsota</string>
|
||||
|
||||
|
@ -272,4 +272,5 @@ odpiranje v pojavnem načinu</string>
|
|||
</plurals>
|
||||
|
||||
<string name="item_deleted">Predmet je izbrisan</string>
|
||||
<string name="delete_item_search_history">Ali želite izbrisati predmet iz zgodovine iskanja?</string>
|
||||
</resources>
|
||||
|
|
|
@ -100,7 +100,7 @@
|
|||
<string name="autoplay_by_calling_app_summary">Аутоматско пуштање видеа по позиву друге апликације</string>
|
||||
<string name="start">Почни</string>
|
||||
<string name="pause">Паузирај</string>
|
||||
<string name="view">Приказ</string>
|
||||
<string name="view">Пусти</string>
|
||||
<string name="delete">Обриши</string>
|
||||
<string name="checksum">Хеш</string>
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
|
||||
<string name="start">Başlat</string>
|
||||
<string name="pause">Duraklat</string>
|
||||
<string name="view">Görünüm</string>
|
||||
<string name="view">Oynat</string>
|
||||
<string name="delete">Sil</string>
|
||||
<string name="checksum">Sağlama</string>
|
||||
|
||||
|
@ -255,4 +255,5 @@
|
|||
<string name="history_empty">Geçmiş boş</string>
|
||||
<string name="history_cleared">Geçmiş temizlendi</string>
|
||||
<string name="item_deleted">Öge silindi</string>
|
||||
<string name="delete_item_search_history">Bu içeriği arama geçmişinden silmek istiyor musunuz?</string>
|
||||
</resources>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<string name="show_play_with_kodi_title">顯示「用 Kodi 播放」的選項</string>
|
||||
<string name="default_audio_format_title">預設音訊格式</string>
|
||||
<string name="webm_description">WebM — 開放格式</string>
|
||||
<string name="m4a_description">m4a — 更佳畫質</string>
|
||||
<string name="m4a_description">M4A — 更佳畫質</string>
|
||||
<string name="theme_title">主題</string>
|
||||
<string name="dark_theme_title">暗色系</string>
|
||||
<string name="light_theme_title">明亮色系</string>
|
||||
|
@ -36,7 +36,7 @@
|
|||
<string name="next_video_title">下一部影片</string>
|
||||
<string name="show_next_and_similar_title">顯示下一部和相關的影片</string>
|
||||
<string name="url_not_supported_toast">不支援此網址</string>
|
||||
<string name="search_language_title">內容語言</string>
|
||||
<string name="search_language_title">預設內容語言</string>
|
||||
<string name="settings_category_video_audio_title">影片和音訊</string>
|
||||
<string name="settings_category_appearance_title">外觀</string>
|
||||
<string name="settings_category_other_title">其他</string>
|
||||
|
@ -62,7 +62,7 @@
|
|||
<string name="use_external_video_player_summary">勾選後,部分解析度可能不會有音訊</string>
|
||||
<string name="popup_mode_share_menu_title">NewPipe 懸浮視窗模式</string>
|
||||
<string name="controls_background_title">背景</string>
|
||||
<string name="autoplay_by_calling_app_title">從其他應用程式開啟時自動播放</string>
|
||||
<string name="autoplay_by_calling_app_title">自動播放</string>
|
||||
<string name="autoplay_by_calling_app_summary">當 NewPipe 被其他應用程式呼叫時自動播放影片</string>
|
||||
<string name="default_popup_resolution_title">懸浮視窗預設解析度</string>
|
||||
<string name="show_higher_resolutions_title">顯示更高的解析度</string>
|
||||
|
@ -97,14 +97,14 @@
|
|||
<string name="popup_resizing_indicator_title">重新設定大小</string>
|
||||
<string name="general_error">錯誤</string>
|
||||
<string name="could_not_load_thumbnails">無法載入所有縮圖</string>
|
||||
<string name="youtube_signature_decryption_error">無法解析影片 URL 簽名。</string>
|
||||
<string name="parsing_error">無法解析網站。</string>
|
||||
<string name="light_parsing_error">無法完全解析網站。</string>
|
||||
<string name="content_not_available">內容無法使用。</string>
|
||||
<string name="blocked_by_gema">已被 GEMA 阻擋。</string>
|
||||
<string name="could_not_setup_download_menu">無法設定下載選單。</string>
|
||||
<string name="youtube_signature_decryption_error">無法解析影片 URL 簽章</string>
|
||||
<string name="parsing_error">無法解析網站</string>
|
||||
<string name="light_parsing_error">無法完全解析網站</string>
|
||||
<string name="content_not_available">內容無法使用</string>
|
||||
<string name="blocked_by_gema">已被 GEMA 阻擋</string>
|
||||
<string name="could_not_setup_download_menu">無法設定下載選單</string>
|
||||
<string name="live_streams_not_supported">尚未支援現場直播。</string>
|
||||
<string name="could_not_get_stream">無法取得串流。</string>
|
||||
<string name="could_not_get_stream">無法取得串流</string>
|
||||
<string name="could_not_load_image">無法載入圖片</string>
|
||||
<string name="app_ui_crash">應用程式或 UI 已停止運作</string>
|
||||
<string name="sorry_string">抱歉,這不應該發生的。</string>
|
||||
|
@ -126,7 +126,7 @@
|
|||
<string name="retry">重試</string>
|
||||
<string name="storage_permission_denied">無法存取儲存空間</string>
|
||||
<string name="use_old_player_title">使用舊的播放器</string>
|
||||
<string name="use_old_player_summary">舊型內建媒體播放器。</string>
|
||||
<string name="use_old_player_summary">舊型內建媒體播放器</string>
|
||||
|
||||
|
||||
<string name="short_thousand">千</string>
|
||||
|
@ -135,7 +135,7 @@
|
|||
|
||||
<string name="start">開始</string>
|
||||
<string name="pause">暫停</string>
|
||||
<string name="view">檢視</string>
|
||||
<string name="view">播放</string>
|
||||
<string name="delete">刪除</string>
|
||||
<string name="checksum">檢查碼</string>
|
||||
|
||||
|
@ -151,8 +151,8 @@
|
|||
<string name="msg_running">NewPipe 下載中</string>
|
||||
<string name="msg_running_detail">輕觸顯示詳細資訊</string>
|
||||
<string name="msg_wait">請稍候…</string>
|
||||
<string name="msg_copied">已複製至剪貼簿。</string>
|
||||
<string name="no_available_dir">請選擇下載資料夾。</string>
|
||||
<string name="msg_copied">已複製至剪貼簿</string>
|
||||
<string name="no_available_dir">請選擇下載資料夾</string>
|
||||
<string name="msg_popup_permission">使用懸浮視窗模式需要此權限</string>
|
||||
|
||||
<string name="reCaptchaActivity">reCAPTCHA 驗證</string>
|
||||
|
@ -162,4 +162,82 @@
|
|||
<string name="controls_popup_title">懸浮視窗</string>
|
||||
|
||||
<string name="duration_live">現場直播</string>
|
||||
</resources>
|
||||
<string name="subscribe_button_title">訂閱</string>
|
||||
<string name="subscribed_button_title">已訂閱</string>
|
||||
<string name="channel_unsubscribed">已取消訂閱頻道</string>
|
||||
<string name="subscription_change_failed">無法切換訂閱</string>
|
||||
<string name="subscription_update_failed">無法更新訂閱</string>
|
||||
|
||||
<string name="tab_main">主選單</string>
|
||||
<string name="tab_subscriptions">訂閱項目</string>
|
||||
|
||||
<string name="fragment_whats_new">有什麼新鮮事</string>
|
||||
|
||||
<string name="enable_search_history_title">搜尋紀錄</string>
|
||||
<string name="enable_search_history_summary">在本機儲存搜尋紀錄</string>
|
||||
<string name="enable_watch_history_title">歷史紀錄</string>
|
||||
<string name="enable_watch_history_summary">記錄觀看過的影片</string>
|
||||
<string name="resume_on_audio_focus_gain_title">在取得視窗焦點時繼續播放</string>
|
||||
<string name="resume_on_audio_focus_gain_summary">在干擾結束後繼續播放(例如有來電)</string>
|
||||
<string name="settings_category_player_title">播放器</string>
|
||||
<string name="settings_category_player_behavior_title">行為</string>
|
||||
<string name="settings_category_history_title">歷史紀錄</string>
|
||||
<string name="playlist">播放清單</string>
|
||||
<string name="undo">復原</string>
|
||||
|
||||
<string name="notification_channel_name">NewPipe 通知</string>
|
||||
<string name="notification_channel_description">NewPipe 背景播放與懸浮模式播放器的通知</string>
|
||||
|
||||
<string name="search_no_results">沒有結果</string>
|
||||
<string name="empty_subscription_feed_subtitle">空空如也</string>
|
||||
|
||||
<string name="no_subscribers">無訂閱者</string>
|
||||
<plurals name="subscribers">
|
||||
<item quantity="other">%s 位訂閱者</item>
|
||||
</plurals>
|
||||
|
||||
<string name="no_views">無觀看次數</string>
|
||||
<plurals name="views">
|
||||
<item quantity="other">%s 次觀看</item>
|
||||
</plurals>
|
||||
|
||||
<string name="no_videos">沒有影片</string>
|
||||
<plurals name="videos">
|
||||
<item quantity="other">%s 部影片</item>
|
||||
</plurals>
|
||||
|
||||
<string name="settings_category_downloads_title">下載</string>
|
||||
<string name="settings_file_charset_title">檔案名稱中允許的字元</string>
|
||||
<string name="settings_file_replacement_character_summary">不符合設定的字元將會被替換為此字串</string>
|
||||
<string name="settings_file_replacement_character_title">替換為</string>
|
||||
|
||||
<string name="charset_letters_and_digits">字母與數字</string>
|
||||
<string name="charset_most_special_characters">大部分的特殊字元</string>
|
||||
|
||||
<string name="title_activity_about">關於 NewPipe</string>
|
||||
<string name="action_settings">設定</string>
|
||||
<string name="action_about">關於</string>
|
||||
<string name="title_licenses">第三方授權</string>
|
||||
<string name="copyright" formatted="true">© %1$s 由 %2$s 使用 %3$s 授權條款發佈</string>
|
||||
<string name="error_unable_to_load_license">無法載入授權條款</string>
|
||||
<string name="action_open_website">開啟網站</string>
|
||||
<string name="tab_about">關於</string>
|
||||
<string name="tab_contributors">貢獻者</string>
|
||||
<string name="tab_licenses">授權條款</string>
|
||||
<string name="app_description">一款在 Android 上免費輕巧的 YouTube 前端。</string>
|
||||
<string name="view_on_github">在 GitHub 上檢視</string>
|
||||
<string name="app_license_title">NewPipe 使用的授權條款</string>
|
||||
<string name="contribution_encouragement">不管你有什麼點子,翻譯、設計、程式碼整理,或者程式碼撰寫,我們永遠歡迎你來幫忙。完成的越多,NewPipe 也會更好!</string>
|
||||
<string name="read_full_license">閱讀授權條款</string>
|
||||
<string name="contribution_title">貢獻</string>
|
||||
|
||||
<string name="title_activity_history">歷史紀錄</string>
|
||||
<string name="title_history_search">已搜尋</string>
|
||||
<string name="title_history_view">已觀看</string>
|
||||
<string name="history_disabled">歷史紀錄已被停用</string>
|
||||
<string name="action_history">歷史紀錄</string>
|
||||
<string name="history_empty">沒有歷史紀錄</string>
|
||||
<string name="history_cleared">已清除歷史紀錄</string>
|
||||
<string name="item_deleted">項目已刪除</string>
|
||||
<string name="delete_item_search_history">確定要刪除此項搜尋紀錄嗎?</string>
|
||||
</resources>
|
||||
|
|
4
app/src/main/res/values/ic_launcher_background.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#CD201F</color>
|
||||
</resources>
|
|
@ -51,7 +51,7 @@
|
|||
<string-array name="video_format_description_list" translatable="false">
|
||||
<item>MPEG-4</item>
|
||||
<item>WebM</item>
|
||||
<item>3GPP</item>
|
||||
<item>3GP</item>
|
||||
</string-array>
|
||||
<string-array name="video_format_values_list" translatable="false">
|
||||
<item>@string/video_mp4_key</item>
|
||||
|
|
|
@ -194,7 +194,7 @@
|
|||
<!-- Missions -->
|
||||
<string name="start">Start</string>
|
||||
<string name="pause">Pause</string>
|
||||
<string name="view">View</string>
|
||||
<string name="view">Play</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="checksum">Checksum</string>
|
||||
|
||||
|
@ -265,4 +265,5 @@
|
|||
<string name="history_empty">The history is empty</string>
|
||||
<string name="history_cleared">History cleared</string>
|
||||
<string name="item_deleted">Item deleted</string>
|
||||
<string name="delete_item_search_history">Do you want to delete this item from search history?</string>
|
||||
</resources>
|
||||
|
|
|
@ -109,23 +109,47 @@
|
|||
<item name="background">@color/video_overlay_color</item>
|
||||
</style>
|
||||
|
||||
<!-- You can also inherit from NNF_BaseTheme.Light -->
|
||||
<style name="FilePickerTheme" parent="NNF_BaseTheme.Light">
|
||||
<!-- Set these to match your theme -->
|
||||
<style name="FilePickerThemeLight" parent="NNF_BaseTheme.Light">
|
||||
<item name="colorPrimary">@color/light_youtube_primary_color</item>
|
||||
<item name="colorPrimaryDark">@color/light_youtube_dark_color</item>
|
||||
<item name="colorAccent">@color/light_youtube_accent_color</item>
|
||||
<item name="android:background">@color/light_background_color</item>
|
||||
<item name="nnf_separator_color">@color/light_separator_color</item>
|
||||
|
||||
<!-- Need to set this also to style create folder dialog -->
|
||||
<item name="alertDialogTheme">@style/FilePickerAlertDialogTheme</item>
|
||||
|
||||
<!-- If you want to set a specific toolbar theme, do it here -->
|
||||
<!-- <item name="nnf_toolbarTheme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item> -->
|
||||
<item name="alertDialogTheme">@style/FilePickerAlertDialogThemeLight</item>
|
||||
<item name="nnf_toolbarTheme">@style/FilePickerToolbarLight</item>
|
||||
</style>
|
||||
|
||||
<style name="FilePickerAlertDialogTheme" parent="Theme.AppCompat.Dialog.Alert">
|
||||
<style name="FilePickerAlertDialogThemeLight" parent="Theme.AppCompat.Dialog.Alert">
|
||||
<item name="colorPrimary">@color/light_youtube_primary_color</item>
|
||||
<item name="colorPrimaryDark">@color/light_youtube_dark_color</item>
|
||||
<item name="colorAccent">@color/light_youtube_accent_color</item>
|
||||
</style>
|
||||
|
||||
<style name="FilePickerToolbarLight" parent="ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||
<item name="android:background">@color/light_youtube_primary_color</item>
|
||||
</style>
|
||||
|
||||
<style name="FilePickerThemeDark" parent="FilePickerThemeLight">
|
||||
<item name="colorPrimary">@color/dark_youtube_primary_color</item>
|
||||
<item name="colorPrimaryDark">@color/dark_youtube_dark_color</item>
|
||||
<item name="colorAccent">@color/dark_youtube_accent_color</item>
|
||||
<item name="android:background">@color/dark_background_color</item>
|
||||
<item name="android:textColorPrimary">@color/dark_youtube_accent_color</item>
|
||||
<item name="nnf_separator_color">@color/black_separator_color</item>
|
||||
|
||||
<item name="alertDialogTheme">@style/FilePickerAlertDialogThemeDark</item>
|
||||
<item name="nnf_toolbarTheme">@style/FilePickerToolbarDark</item>
|
||||
</style>
|
||||
|
||||
<style name="FilePickerAlertDialogThemeDark" parent="Theme.AppCompat.Dialog.Alert">
|
||||
<item name="colorPrimary">@color/dark_youtube_primary_color</item>
|
||||
<item name="colorPrimaryDark">@color/dark_youtube_dark_color</item>
|
||||
<item name="colorAccent">@color/dark_youtube_accent_color</item>
|
||||
</style>
|
||||
|
||||
<style name="FilePickerToolbarDark" parent="ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||
<item name="android:background">@color/dark_youtube_primary_color</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -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<DownloadMission> 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));
|
||||
}
|
||||
|
||||
}
|
546
assets/BETA_new_pipe_icon_5.svg
Normal file
|
@ -0,0 +1,546 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
viewBox="0 0 192 192"
|
||||
height="204.8"
|
||||
width="204.8"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="BETA_new_pipe_icon_5.svg"
|
||||
inkscape:export-filename="/home/the-scrabi/Projects/NewPipe/assets/new_pipe_icon_5.png"
|
||||
inkscape:export-xdpi="120"
|
||||
inkscape:export-ydpi="120">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="713"
|
||||
id="namedview4149"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.5673112"
|
||||
inkscape:cx="105.05076"
|
||||
inkscape:cy="98.81914"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient4447">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.1"
|
||||
offset="0"
|
||||
id="stop4449" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0"
|
||||
offset="1"
|
||||
id="stop4451" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter4454"
|
||||
width="1.4"
|
||||
height="1.4"
|
||||
x="-0.2"
|
||||
y="-0.2">
|
||||
<feFlood
|
||||
flood-opacity="0.427451"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood4456" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite4458" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="10.9"
|
||||
result="blur"
|
||||
id="feGaussianBlur4460" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="7"
|
||||
result="offset"
|
||||
id="feOffset4462" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite4464" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter4777">
|
||||
<feFlood
|
||||
flood-opacity="0.498039"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood4779" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite4781" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="5.82011"
|
||||
result="blur"
|
||||
id="feGaussianBlur4783" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="5.6"
|
||||
result="offset"
|
||||
id="feOffset4785" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="fbSourceGraphic"
|
||||
id="feComposite4787" />
|
||||
<feColorMatrix
|
||||
result="fbSourceGraphicAlpha"
|
||||
in="fbSourceGraphic"
|
||||
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
|
||||
id="feColorMatrix4789" />
|
||||
<feFlood
|
||||
id="feFlood4791"
|
||||
flood-opacity="0.498039"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
in="fbSourceGraphic" />
|
||||
<feComposite
|
||||
id="feComposite4793"
|
||||
in2="fbSourceGraphic"
|
||||
in="flood"
|
||||
operator="in"
|
||||
result="composite1" />
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur4795"
|
||||
in="composite1"
|
||||
stdDeviation="5.8"
|
||||
result="blur" />
|
||||
<feOffset
|
||||
id="feOffset4797"
|
||||
dx="0"
|
||||
dy="5.6"
|
||||
result="offset" />
|
||||
<feComposite
|
||||
id="feComposite4799"
|
||||
in2="offset"
|
||||
in="fbSourceGraphic"
|
||||
operator="over"
|
||||
result="composite2" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter4885">
|
||||
<feFlood
|
||||
flood-opacity="0.498039"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood4887" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite4889" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="7.9"
|
||||
result="blur"
|
||||
id="feGaussianBlur4891" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="2.54709"
|
||||
result="offset"
|
||||
id="feOffset4893" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="fbSourceGraphic"
|
||||
id="feComposite4895" />
|
||||
<feColorMatrix
|
||||
result="fbSourceGraphicAlpha"
|
||||
in="fbSourceGraphic"
|
||||
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
|
||||
id="feColorMatrix4897" />
|
||||
<feFlood
|
||||
id="feFlood4899"
|
||||
flood-opacity="0.498039"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
in="fbSourceGraphic" />
|
||||
<feComposite
|
||||
id="feComposite4901"
|
||||
in2="fbSourceGraphic"
|
||||
in="flood"
|
||||
operator="in"
|
||||
result="composite1" />
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur4903"
|
||||
in="composite1"
|
||||
stdDeviation="7.9"
|
||||
result="blur" />
|
||||
<feOffset
|
||||
id="feOffset4905"
|
||||
dx="0"
|
||||
dy="2.5"
|
||||
result="offset" />
|
||||
<feComposite
|
||||
id="feComposite4907"
|
||||
in2="offset"
|
||||
in="fbSourceGraphic"
|
||||
operator="over"
|
||||
result="composite2" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter4257">
|
||||
<feFlood
|
||||
flood-opacity="0.498039"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood4259" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite4261" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="7.9"
|
||||
result="blur"
|
||||
id="feGaussianBlur4263" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="5.02645"
|
||||
result="offset"
|
||||
id="feOffset4265" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="fbSourceGraphic"
|
||||
id="feComposite4267" />
|
||||
<feColorMatrix
|
||||
result="fbSourceGraphicAlpha"
|
||||
in="fbSourceGraphic"
|
||||
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
|
||||
id="feColorMatrix4269" />
|
||||
<feFlood
|
||||
id="feFlood4271"
|
||||
flood-opacity="0.498039"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
in="fbSourceGraphic" />
|
||||
<feComposite
|
||||
id="feComposite4273"
|
||||
in2="fbSourceGraphic"
|
||||
in="flood"
|
||||
operator="in"
|
||||
result="composite1" />
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur4275"
|
||||
in="composite1"
|
||||
stdDeviation="7.9"
|
||||
result="blur" />
|
||||
<feOffset
|
||||
id="feOffset4277"
|
||||
dx="0"
|
||||
dy="5"
|
||||
result="offset" />
|
||||
<feComposite
|
||||
id="feComposite4279"
|
||||
in2="offset"
|
||||
in="fbSourceGraphic"
|
||||
operator="over"
|
||||
result="composite2" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter4192">
|
||||
<feFlood
|
||||
flood-opacity="0.498039"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood4194" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite4196" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="7.7"
|
||||
result="blur"
|
||||
id="feGaussianBlur4198" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="5"
|
||||
result="offset"
|
||||
id="feOffset4200" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite4202" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter4349">
|
||||
<feFlood
|
||||
flood-opacity="0.498039"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood4351" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite4353" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="7.2"
|
||||
result="blur"
|
||||
id="feGaussianBlur4355" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="5"
|
||||
result="offset"
|
||||
id="feOffset4357" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite4359" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter4361">
|
||||
<feFlood
|
||||
flood-opacity="0.498039"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood4363" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite4365" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="5.3"
|
||||
result="blur"
|
||||
id="feGaussianBlur4367" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="5"
|
||||
result="offset"
|
||||
id="feOffset4369" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite4371" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter4481">
|
||||
<feFlood
|
||||
flood-opacity="0.498039"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood4483" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite4485" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="5"
|
||||
result="blur"
|
||||
id="feGaussianBlur4487" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="5"
|
||||
result="offset"
|
||||
id="feOffset4489" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite4491" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter4433">
|
||||
<feFlood
|
||||
flood-opacity="0.2"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood4435" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite4437" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="4"
|
||||
result="blur"
|
||||
id="feGaussianBlur4439" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="4"
|
||||
result="offset"
|
||||
id="feOffset4441" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite4443" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4447"
|
||||
id="radialGradient4453"
|
||||
cx="0.56012386"
|
||||
cy="0.35701406"
|
||||
fx="0.56012386"
|
||||
fy="0.35701406"
|
||||
r="88"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.00132321,2.1587518,-2.1815784,0.00133718,1.1131155,-0.39216099)" />
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path
|
||||
style="opacity:1;fill:#ff7575;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path4144-9"
|
||||
r="88"
|
||||
cy="104"
|
||||
cx="88"
|
||||
d=""
|
||||
inkscape:connector-curvature="0" />
|
||||
<circle
|
||||
style="fill:#ff5252;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path4144-6"
|
||||
cx="88"
|
||||
cy="104"
|
||||
r="0" />
|
||||
<circle
|
||||
style="opacity:1;fill:#ff7575;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4433)"
|
||||
id="path4144-67"
|
||||
cx="96"
|
||||
cy="96"
|
||||
r="88" />
|
||||
<path
|
||||
style="opacity:1;fill:#cc4242;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4433)"
|
||||
id="path4144-5"
|
||||
sodipodi:type="arc"
|
||||
sodipodi:cx="96"
|
||||
sodipodi:cy="96"
|
||||
sodipodi:rx="88"
|
||||
sodipodi:ry="88"
|
||||
sodipodi:start="0"
|
||||
sodipodi:end="3.1387981"
|
||||
sodipodi:open="true"
|
||||
d="M 184,96 A 88,88 0 0 1 96.12296,183.99991 88,88 0 0 1 8.0003436,96.24592" />
|
||||
<ellipse
|
||||
style="fill:#cd201f;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4433)"
|
||||
id="path4144"
|
||||
cx="96"
|
||||
cy="96"
|
||||
rx="88"
|
||||
ry="87" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter4433)"
|
||||
d="M 62.03771,154.84774 79.885207,143.47161 80.258503,68.06594 126.18321,95.333 92.950545,114.49592 v 21.92836 L 161.9848,95.333 61.944386,36.538494 Z"
|
||||
id="path4234"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
<path
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path4445-5"
|
||||
sodipodi:type="arc"
|
||||
sodipodi:cx="96.001564"
|
||||
sodipodi:cy="96.021866"
|
||||
sodipodi:rx="88"
|
||||
sodipodi:ry="88"
|
||||
sodipodi:start="0.082013054"
|
||||
sodipodi:end="1.9854818"
|
||||
sodipodi:open="true"
|
||||
d="m 183.70578,103.23093 a 88,88 0 0 1 -42.68319,68.40249 88,88 0 0 1 -80.476409,4.92984" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:37.49999619px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#cd201f;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-4.6590595"
|
||||
y="208.67332"
|
||||
id="text3415"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(0.86357335,-0.50422323,0.50422323,0.86357335,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3417"
|
||||
x="-4.6590595"
|
||||
y="208.67332"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:29.29687309px;font-family:sans-serif;-inkscape-font-specification:sans-serif;fill:#cd201f;fill-opacity:1">BETA</tspan></text>
|
||||
<circle
|
||||
style="opacity:1;fill:url(#radialGradient4453);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path4445"
|
||||
cx="95.978127"
|
||||
cy="96.021866"
|
||||
r="88" />
|
||||
</svg>
|
After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 346 B |
BIN
assets/bountysource_qr_code.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
|
@ -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' }
|
||||
}
|
||||
}
|
||||
|
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -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
|
||||
|
|
110
gradlew
vendored
|
@ -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" "$@"
|
||||
|
|
14
gradlew.bat
vendored
|
@ -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
|
||||
|
|
Before Width: | Height: | Size: 302 KiB |
Before Width: | Height: | Size: 324 KiB |
Before Width: | Height: | Size: 399 KiB |
Before Width: | Height: | Size: 185 KiB |
Before Width: | Height: | Size: 241 KiB |
Before Width: | Height: | Size: 1 MiB |
Before Width: | Height: | Size: 865 KiB |
Before Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 476 KiB |
BIN
screenshots/shot_1.png
Normal file
After Width: | Height: | Size: 112 KiB |
BIN
screenshots/shot_10.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
screenshots/shot_2.png
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
screenshots/shot_3.png
Normal file
After Width: | Height: | Size: 263 KiB |
BIN
screenshots/shot_4.png
Normal file
After Width: | Height: | Size: 101 KiB |
BIN
screenshots/shot_5.png
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
screenshots/shot_6.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
screenshots/shot_7.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
screenshots/shot_8.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
screenshots/shot_9.png
Normal file
After Width: | Height: | Size: 74 KiB |