Merge pull request #7475 from litetex/release/v0.21.14
Release/v0.21.14
This commit is contained in:
commit
3750561b4d
302 changed files with 3799 additions and 1478 deletions
16
.gitignore
vendored
16
.gitignore
vendored
|
@ -1,15 +1,15 @@
|
|||
.gitignore
|
||||
.gradle
|
||||
/local.properties
|
||||
.gradle/
|
||||
local.properties
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
/app/app.iml
|
||||
/.idea
|
||||
/*.iml
|
||||
build/
|
||||
captures/
|
||||
.idea/
|
||||
*.iml
|
||||
*~
|
||||
.weblate
|
||||
*.class
|
||||
**/debug/
|
||||
**/release/
|
||||
|
||||
# vscode / eclipse files
|
||||
*.classpath
|
||||
|
|
3
app/.gitignore
vendored
3
app/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
.gitignore
|
||||
/build
|
||||
*.iml
|
|
@ -17,8 +17,8 @@ android {
|
|||
resValue "string", "app_name", "NewPipe"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 29
|
||||
versionCode 979
|
||||
versionName "0.21.13"
|
||||
versionCode 980
|
||||
versionName "0.21.14"
|
||||
|
||||
multiDexEnabled true
|
||||
|
||||
|
@ -105,9 +105,9 @@ ext {
|
|||
androidxRoomVersion = '2.3.0'
|
||||
|
||||
icepickVersion = '3.2.0'
|
||||
exoPlayerVersion = '2.12.3'
|
||||
exoPlayerVersion = '2.14.2'
|
||||
googleAutoServiceVersion = '1.0'
|
||||
groupieVersion = '2.9.0'
|
||||
groupieVersion = '2.10.0'
|
||||
markwonVersion = '4.6.2'
|
||||
|
||||
leakCanaryVersion = '2.5'
|
||||
|
@ -189,7 +189,7 @@ dependencies {
|
|||
// name and the commit hash with the commit hash of the (pushed) commit you want to test
|
||||
// This works thanks to JitPack: https://jitpack.io/
|
||||
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.21.11'
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.21.12'
|
||||
|
||||
/** Checkstyle **/
|
||||
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
||||
|
@ -208,14 +208,17 @@ dependencies {
|
|||
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
|
||||
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
|
||||
implementation 'androidx.media:media:1.3.1'
|
||||
implementation 'androidx.media:media:1.4.3'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
|
||||
implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
|
||||
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
// Newer version specified to prevent accessibility regressions with RecyclerView, see:
|
||||
// https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||
implementation 'androidx.webkit:webkit:1.4.0'
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
|
||||
|
|
|
@ -256,6 +256,21 @@
|
|||
<data android:pathPrefix="/" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- y2u.be filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="y2u.be" />
|
||||
<data android:pathPrefix="/" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Soundcloud filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
@ -325,8 +340,12 @@
|
|||
<data android:host="skeptikon.fr" />
|
||||
|
||||
<data android:pathPrefix="/videos/" /> <!-- it contains playlists -->
|
||||
<data android:pathPrefix="/w/" /> <!-- short video URLs -->
|
||||
<data android:pathPrefix="/w/p/" /> <!-- short playlist URLs -->
|
||||
<data android:pathPrefix="/accounts/" />
|
||||
<data android:pathPrefix="/a/" /> <!-- short account URLs -->
|
||||
<data android:pathPrefix="/video-channels/" />
|
||||
<data android:pathPrefix="/c/" /> <!-- short video-channels URLs -->
|
||||
</intent-filter>
|
||||
|
||||
<!-- Bandcamp filter for tracks, albums and playlists -->
|
||||
|
|
|
@ -51,8 +51,12 @@ import java.util.ArrayList;
|
|||
* <li>{@link #saveState()}</li>
|
||||
* <li>{@link #restoreState(Parcelable, ClassLoader)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @deprecated Switch to {@link androidx.viewpager2.widget.ViewPager2} and use
|
||||
* {@link androidx.viewpager2.adapter.FragmentStateAdapter} instead.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Deprecated
|
||||
public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapter {
|
||||
private static final String TAG = "FragmentStatePagerAdapt";
|
||||
private static final boolean DEBUG = false;
|
||||
|
@ -86,9 +90,10 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||
private final int mBehavior;
|
||||
private FragmentTransaction mCurTransaction = null;
|
||||
|
||||
private final ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
|
||||
private final ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
|
||||
private final ArrayList<Fragment.SavedState> mSavedState = new ArrayList<>();
|
||||
private final ArrayList<Fragment> mFragments = new ArrayList<>();
|
||||
private Fragment mCurrentPrimaryItem = null;
|
||||
private boolean mExecutingFinishUpdate;
|
||||
|
||||
/**
|
||||
* Constructor for {@link FragmentStatePagerAdapterMenuWorkaround}
|
||||
|
@ -208,7 +213,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||
mFragments.set(position, null);
|
||||
|
||||
mCurTransaction.remove(fragment);
|
||||
if (fragment == mCurrentPrimaryItem) {
|
||||
if (fragment.equals(mCurrentPrimaryItem)) {
|
||||
mCurrentPrimaryItem = null;
|
||||
}
|
||||
}
|
||||
|
@ -247,7 +252,19 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||
@Override
|
||||
public void finishUpdate(@NonNull final ViewGroup container) {
|
||||
if (mCurTransaction != null) {
|
||||
mCurTransaction.commitNowAllowingStateLoss();
|
||||
// We drop any transactions that attempt to be committed
|
||||
// from a re-entrant call to finishUpdate(). We need to
|
||||
// do this as a workaround for Robolectric running measure/layout
|
||||
// calls inline rather than allowing them to be posted
|
||||
// as they would on a real device.
|
||||
if (!mExecutingFinishUpdate) {
|
||||
try {
|
||||
mExecutingFinishUpdate = true;
|
||||
mCurTransaction.commitNowAllowingStateLoss();
|
||||
} finally {
|
||||
mExecutingFinishUpdate = false;
|
||||
}
|
||||
}
|
||||
mCurTransaction = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ public abstract class BaseFragment extends Fragment {
|
|||
//These values are used for controlling fragments when they are part of the frontpage
|
||||
@State
|
||||
protected boolean useAsFrontPage = false;
|
||||
private boolean mIsVisibleToUser = false;
|
||||
|
||||
public void useAsFrontPage(final boolean value) {
|
||||
useAsFrontPage = value;
|
||||
|
@ -85,12 +84,6 @@ public abstract class BaseFragment extends Fragment {
|
|||
AppWatcher.INSTANCE.getObjectWatcher().watch(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
mIsVisibleToUser = isVisibleToUser;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Init
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -109,8 +102,7 @@ public abstract class BaseFragment extends Fragment {
|
|||
if (DEBUG) {
|
||||
Log.d(TAG, "setTitle() called with: title = [" + title + "]");
|
||||
}
|
||||
if ((!useAsFrontPage || mIsVisibleToUser)
|
||||
&& (activity != null && activity.getSupportActionBar() != null)) {
|
||||
if (!useAsFrontPage && activity != null && activity.getSupportActionBar() != null) {
|
||||
activity.getSupportActionBar().setDisplayShowTitleEnabled(true);
|
||||
activity.getSupportActionBar().setTitle(title);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import android.content.Intent;
|
|||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -15,7 +14,6 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.pm.PackageInfoCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
|
@ -48,7 +46,8 @@ public final class CheckForNewAppVersion extends IntentService {
|
|||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
private static final String TAG = CheckForNewAppVersion.class.getSimpleName();
|
||||
|
||||
private static final String GITHUB_APK_SHA1
|
||||
// Public key of the certificate that is used in NewPipe release versions
|
||||
private static final String RELEASE_CERT_PUBLIC_KEY_SHA1
|
||||
= "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
|
||||
private static final String NEWPIPE_API_URL = "https://newpipe.net/api/data.json";
|
||||
|
||||
|
@ -129,44 +128,37 @@ public final class CheckForNewAppVersion extends IntentService {
|
|||
final String versionName,
|
||||
final String apkLocationUrl,
|
||||
final int versionCode) {
|
||||
final int notificationId = 2000;
|
||||
|
||||
if (BuildConfig.VERSION_CODE < versionCode) {
|
||||
// A pending intent to open the apk location url in the browser.
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
final PendingIntent pendingIntent
|
||||
= PendingIntent.getActivity(application, 0, intent, 0);
|
||||
|
||||
final String channelId = application
|
||||
.getString(R.string.app_update_notification_channel_id);
|
||||
final NotificationCompat.Builder notificationBuilder
|
||||
= new NotificationCompat.Builder(application, channelId)
|
||||
.setSmallIcon(R.drawable.ic_newpipe_update)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(application
|
||||
.getString(R.string.app_update_notification_content_title))
|
||||
.setContentText(application
|
||||
.getString(R.string.app_update_notification_content_text)
|
||||
+ " " + versionName);
|
||||
|
||||
final NotificationManagerCompat notificationManager
|
||||
= NotificationManagerCompat.from(application);
|
||||
notificationManager.notify(notificationId, notificationBuilder.build());
|
||||
if (BuildConfig.VERSION_CODE >= versionCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A pending intent to open the apk location url in the browser.
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
final PendingIntent pendingIntent
|
||||
= PendingIntent.getActivity(application, 0, intent, 0);
|
||||
|
||||
final String channelId = application
|
||||
.getString(R.string.app_update_notification_channel_id);
|
||||
final NotificationCompat.Builder notificationBuilder
|
||||
= new NotificationCompat.Builder(application, channelId)
|
||||
.setSmallIcon(R.drawable.ic_newpipe_update)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(application
|
||||
.getString(R.string.app_update_notification_content_title))
|
||||
.setContentText(application
|
||||
.getString(R.string.app_update_notification_content_text)
|
||||
+ " " + versionName);
|
||||
|
||||
final NotificationManagerCompat notificationManager
|
||||
= NotificationManagerCompat.from(application);
|
||||
notificationManager.notify(2000, notificationBuilder.build());
|
||||
}
|
||||
|
||||
private static boolean isConnected(@NonNull final App app) {
|
||||
final ConnectivityManager connectivityManager =
|
||||
ContextCompat.getSystemService(app, ConnectivityManager.class);
|
||||
return connectivityManager != null && connectivityManager.getActiveNetworkInfo() != null
|
||||
&& connectivityManager.getActiveNetworkInfo().isConnected();
|
||||
}
|
||||
|
||||
public static boolean isGithubApk(@NonNull final App app) {
|
||||
return getCertificateSHA1Fingerprint(app).equals(GITHUB_APK_SHA1);
|
||||
public static boolean isReleaseApk(@NonNull final App app) {
|
||||
return getCertificateSHA1Fingerprint(app).equals(RELEASE_CERT_PUBLIC_KEY_SHA1);
|
||||
}
|
||||
|
||||
private void checkNewVersion() throws IOException, ReCaptchaException {
|
||||
|
@ -175,9 +167,8 @@ public final class CheckForNewAppVersion extends IntentService {
|
|||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
|
||||
final NewVersionManager manager = new NewVersionManager();
|
||||
|
||||
// Check if user has enabled/disabled update checking
|
||||
// and if the current apk is a github one or not.
|
||||
if (!prefs.getBoolean(app.getString(R.string.update_app_key), true) || !isGithubApk(app)) {
|
||||
// Check if the current apk is a github one or not.
|
||||
if (!isReleaseApk(app)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -213,6 +204,7 @@ public final class CheckForNewAppVersion extends IntentService {
|
|||
|
||||
// Parse the json from the response.
|
||||
try {
|
||||
|
||||
final JsonObject githubStableObject = JsonParser.object()
|
||||
.from(response.responseBody()).getObject("flavors")
|
||||
.getObject("github").getObject("stable");
|
||||
|
|
|
@ -169,13 +169,54 @@ public class MainActivity extends AppCompatActivity {
|
|||
@Override
|
||||
protected void onPostCreate(final Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
// Start the service which is checking all conditions
|
||||
// and eventually searching for a new version.
|
||||
// The service searching for a new NewPipe version must not be started in background.
|
||||
startNewVersionCheckService();
|
||||
|
||||
final App app = App.getApp();
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
|
||||
|
||||
if (prefs.getBoolean(app.getString(R.string.update_app_key), true)) {
|
||||
// Start the service which is checking all conditions
|
||||
// and eventually searching for a new version.
|
||||
// The service searching for a new NewPipe version must not be started in background.
|
||||
startNewVersionCheckService();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDrawer() throws Exception {
|
||||
private void setupDrawer() throws ExtractionException {
|
||||
addDrawerMenuForCurrentService();
|
||||
|
||||
toggle = new ActionBarDrawerToggle(this, mainBinding.getRoot(),
|
||||
toolbarLayoutBinding.toolbar, R.string.drawer_open, R.string.drawer_close);
|
||||
toggle.syncState();
|
||||
mainBinding.getRoot().addDrawerListener(toggle);
|
||||
mainBinding.getRoot().addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
|
||||
private int lastService;
|
||||
|
||||
@Override
|
||||
public void onDrawerOpened(final View drawerView) {
|
||||
lastService = ServiceHelper.getSelectedServiceId(MainActivity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawerClosed(final View drawerView) {
|
||||
if (servicesShown) {
|
||||
toggleServices();
|
||||
}
|
||||
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
|
||||
ActivityCompat.recreate(MainActivity.this);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
drawerLayoutBinding.navigation.setNavigationItemSelectedListener(this::drawerItemSelected);
|
||||
setupDrawerHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the drawer menu for the current service.
|
||||
*
|
||||
* @throws ExtractionException
|
||||
*/
|
||||
private void addDrawerMenuForCurrentService() throws ExtractionException {
|
||||
//Tabs
|
||||
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
final StreamingService service = NewPipe.getService(currentServiceId);
|
||||
|
@ -214,32 +255,6 @@ public class MainActivity extends AppCompatActivity {
|
|||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
||||
.setIcon(R.drawable.ic_info_outline);
|
||||
|
||||
toggle = new ActionBarDrawerToggle(this, mainBinding.getRoot(),
|
||||
toolbarLayoutBinding.toolbar, R.string.drawer_open, R.string.drawer_close);
|
||||
toggle.syncState();
|
||||
mainBinding.getRoot().addDrawerListener(toggle);
|
||||
mainBinding.getRoot().addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
|
||||
private int lastService;
|
||||
|
||||
@Override
|
||||
public void onDrawerOpened(final View drawerView) {
|
||||
lastService = ServiceHelper.getSelectedServiceId(MainActivity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawerClosed(final View drawerView) {
|
||||
if (servicesShown) {
|
||||
toggleServices();
|
||||
}
|
||||
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
|
||||
ActivityCompat.recreate(MainActivity.this);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
drawerLayoutBinding.navigation.setNavigationItemSelectedListener(this::drawerItemSelected);
|
||||
setupDrawerHeader();
|
||||
}
|
||||
|
||||
private boolean drawerItemSelected(final MenuItem item) {
|
||||
|
@ -347,11 +362,15 @@ public class MainActivity extends AppCompatActivity {
|
|||
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_tabs_group);
|
||||
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_options_about_group);
|
||||
|
||||
// Show up or down arrow
|
||||
drawerHeaderBinding.drawerArrow.setImageResource(
|
||||
servicesShown ? R.drawable.ic_arrow_drop_up : R.drawable.ic_arrow_drop_down);
|
||||
|
||||
if (servicesShown) {
|
||||
showServices();
|
||||
} else {
|
||||
try {
|
||||
showTabs();
|
||||
addDrawerMenuForCurrentService();
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Showing main page tabs", e);
|
||||
}
|
||||
|
@ -359,8 +378,6 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
private void showServices() {
|
||||
drawerHeaderBinding.drawerArrow.setImageResource(R.drawable.ic_arrow_drop_up);
|
||||
|
||||
for (final StreamingService s : NewPipe.getServices()) {
|
||||
final String title = s.getServiceInfo().getName()
|
||||
+ (ServiceHelper.isBeta(s) ? " (beta)" : "");
|
||||
|
@ -424,48 +441,6 @@ public class MainActivity extends AppCompatActivity {
|
|||
menuItem.setActionView(spinner);
|
||||
}
|
||||
|
||||
private void showTabs() throws ExtractionException {
|
||||
drawerHeaderBinding.drawerArrow.setImageResource(R.drawable.ic_arrow_drop_down);
|
||||
|
||||
//Tabs
|
||||
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
final StreamingService service = NewPipe.getService(currentServiceId);
|
||||
|
||||
int kioskId = 0;
|
||||
|
||||
for (final String ks : service.getKioskList().getAvailableKiosks()) {
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, kioskId, ORDER,
|
||||
KioskTranslator.getTranslatedKioskName(ks, this))
|
||||
.setIcon(KioskTranslator.getKioskIcon(ks, this));
|
||||
kioskId++;
|
||||
}
|
||||
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, R.string.tab_subscriptions)
|
||||
.setIcon(R.drawable.ic_tv);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
|
||||
.setIcon(R.drawable.ic_rss_feed);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
|
||||
.setIcon(R.drawable.ic_bookmark);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
|
||||
.setIcon(R.drawable.ic_file_download);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
||||
.setIcon(R.drawable.ic_history);
|
||||
|
||||
//Settings and About
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
||||
.setIcon(R.drawable.ic_settings);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
||||
.setIcon(R.drawable.ic_info_outline);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
|
|
@ -9,8 +9,8 @@ import android.widget.PopupMenu;
|
|||
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
@ -18,6 +18,9 @@ import org.schabi.newpipe.util.NavigationHelper;
|
|||
import java.util.Collections;
|
||||
|
||||
public final class QueueItemMenuUtil {
|
||||
private QueueItemMenuUtil() {
|
||||
}
|
||||
|
||||
public static void openPopupMenu(final PlayQueue playQueue,
|
||||
final PlayQueueItem item,
|
||||
final View view,
|
||||
|
@ -47,13 +50,22 @@ public final class QueueItemMenuUtil {
|
|||
false);
|
||||
return true;
|
||||
case R.id.menu_item_append_playlist:
|
||||
final PlaylistAppendDialog d = PlaylistAppendDialog.fromPlayQueueItems(
|
||||
Collections.singletonList(item)
|
||||
PlaylistDialog.createCorrespondingDialog(
|
||||
context,
|
||||
Collections.singletonList(new StreamEntity(item)),
|
||||
dialog -> dialog.show(
|
||||
fragmentManager,
|
||||
"QueueItemMenuUtil@append_playlist"
|
||||
)
|
||||
);
|
||||
PlaylistAppendDialog.onPlaylistFound(context,
|
||||
() -> d.show(fragmentManager, "QueueItemMenuUtil@append_playlist"),
|
||||
() -> PlaylistCreationDialog.newInstance(d)
|
||||
.show(fragmentManager, "QueueItemMenuUtil@append_playlist"));
|
||||
|
||||
return true;
|
||||
case R.id.menu_item_channel_details:
|
||||
// An intent must be used here.
|
||||
// Opening with FragmentManager transactions is not working,
|
||||
// as PlayQueueActivity doesn't use fragments.
|
||||
NavigationHelper.openChannelFragmentUsingIntent(context, item.getServiceId(),
|
||||
item.getUploaderUrl(), item.getUploader());
|
||||
return true;
|
||||
case R.id.menu_item_share:
|
||||
shareText(context, item.getTitle(), item.getUrl(),
|
||||
|
@ -65,6 +77,4 @@ public final class QueueItemMenuUtil {
|
|||
|
||||
popupMenu.show();
|
||||
}
|
||||
|
||||
private QueueItemMenuUtil() { }
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package org.schabi.newpipe;
|
||||
|
||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO;
|
||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.VIDEO;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
|
@ -30,6 +33,7 @@ import androidx.core.widget.TextViewCompat;
|
|||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
|
||||
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
|
||||
import org.schabi.newpipe.download.DownloadDialog;
|
||||
|
@ -56,6 +60,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
|||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
||||
import org.schabi.newpipe.player.MainPlayer;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||
|
@ -69,14 +74,15 @@ import org.schabi.newpipe.util.ExtractorHelper;
|
|||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
import org.schabi.newpipe.util.urlfinder.UrlFinder;
|
||||
import org.schabi.newpipe.views.FocusOverlayView;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import icepick.Icepick;
|
||||
|
@ -89,9 +95,6 @@ import io.reactivex.rxjava3.disposables.Disposable;
|
|||
import io.reactivex.rxjava3.functions.Consumer;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO;
|
||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.VIDEO;
|
||||
|
||||
/**
|
||||
* Get the url from the intent and open it in the chosen preferred player.
|
||||
*/
|
||||
|
@ -107,6 +110,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||
protected String currentUrl;
|
||||
private StreamingService currentService;
|
||||
private boolean selectionIsDownload = false;
|
||||
private boolean selectionIsAddToPlaylist = false;
|
||||
private AlertDialog alertDialogChoice = null;
|
||||
|
||||
@Override
|
||||
|
@ -350,7 +354,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||
.setNegativeButton(R.string.just_once, dialogButtonsClickListener)
|
||||
.setPositiveButton(R.string.always, dialogButtonsClickListener)
|
||||
.setOnDismissListener((dialog) -> {
|
||||
if (!selectionIsDownload) {
|
||||
if (!selectionIsDownload && !selectionIsAddToPlaylist) {
|
||||
finish();
|
||||
}
|
||||
})
|
||||
|
@ -446,6 +450,10 @@ public class RouterActivity extends AppCompatActivity {
|
|||
final AdapterChoiceItem backgroundPlayer = new AdapterChoiceItem(
|
||||
getString(R.string.background_player_key), getString(R.string.background_player),
|
||||
R.drawable.ic_headset);
|
||||
final AdapterChoiceItem addToPlaylist = new AdapterChoiceItem(
|
||||
getString(R.string.add_to_playlist_key), getString(R.string.add_to_playlist),
|
||||
R.drawable.ic_add);
|
||||
|
||||
|
||||
if (linkType == LinkType.STREAM) {
|
||||
if (isExtVideoEnabled) {
|
||||
|
@ -482,6 +490,10 @@ public class RouterActivity extends AppCompatActivity {
|
|||
getString(R.string.download),
|
||||
R.drawable.ic_file_download));
|
||||
|
||||
// Add to playlist is not necessary for CHANNEL and PLAYLIST linkType since those can
|
||||
// not be added to a playlist
|
||||
returnList.add(addToPlaylist);
|
||||
|
||||
} else {
|
||||
returnList.add(showInfo);
|
||||
if (capabilities.contains(VIDEO) && !isExtVideoEnabled) {
|
||||
|
@ -547,6 +559,12 @@ public class RouterActivity extends AppCompatActivity {
|
|||
return;
|
||||
}
|
||||
|
||||
if (selectedChoiceKey.equals(getString(R.string.add_to_playlist_key))) {
|
||||
selectionIsAddToPlaylist = true;
|
||||
openAddToPlaylistDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
// stop and bypass FetcherService if InfoScreen was selected since
|
||||
// StreamDetailFragment can fetch data itself
|
||||
if (selectedChoiceKey.equals(getString(R.string.show_info_key))) {
|
||||
|
@ -572,6 +590,41 @@ public class RouterActivity extends AppCompatActivity {
|
|||
finish();
|
||||
}
|
||||
|
||||
private void openAddToPlaylistDialog() {
|
||||
// Getting the stream info usually takes a moment
|
||||
// Notifying the user here to ensure that no confusion arises
|
||||
Toast.makeText(
|
||||
getApplicationContext(),
|
||||
getString(R.string.processing_may_take_a_moment),
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
|
||||
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
info -> PlaylistDialog.createCorrespondingDialog(
|
||||
getThemeWrapperContext(),
|
||||
Collections.singletonList(new StreamEntity(info)),
|
||||
playlistDialog -> {
|
||||
playlistDialog.setOnDismissListener(dialog -> finish());
|
||||
|
||||
playlistDialog.show(
|
||||
this.getSupportFragmentManager(),
|
||||
"addToPlaylistDialog"
|
||||
);
|
||||
}
|
||||
),
|
||||
throwable -> handleError(this, new ErrorInfo(
|
||||
throwable,
|
||||
UserAction.REQUESTED_STREAM,
|
||||
"Tried to add " + currentUrl + " to a playlist",
|
||||
currentService.getServiceId())
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private void openDownloadDialog() {
|
||||
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
|
||||
|
|
|
@ -7,6 +7,7 @@ import androidx.room.Query
|
|||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Maybe
|
||||
import org.schabi.newpipe.database.feed.model.FeedEntity
|
||||
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
|
||||
import org.schabi.newpipe.database.stream.StreamWithState
|
||||
|
@ -37,7 +38,7 @@ abstract class FeedDAO {
|
|||
LIMIT 500
|
||||
"""
|
||||
)
|
||||
abstract fun getAllStreams(): Flowable<List<StreamWithState>>
|
||||
abstract fun getAllStreams(): Maybe<List<StreamWithState>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
|
@ -62,7 +63,7 @@ abstract class FeedDAO {
|
|||
LIMIT 500
|
||||
"""
|
||||
)
|
||||
abstract fun getAllStreamsForGroup(groupId: Long): Flowable<List<StreamWithState>>
|
||||
abstract fun getAllStreamsForGroup(groupId: Long): Maybe<List<StreamWithState>>
|
||||
|
||||
/**
|
||||
* @see StreamStateEntity.isFinished()
|
||||
|
@ -97,7 +98,7 @@ abstract class FeedDAO {
|
|||
LIMIT 500
|
||||
"""
|
||||
)
|
||||
abstract fun getLiveOrNotPlayedStreams(): Flowable<List<StreamWithState>>
|
||||
abstract fun getLiveOrNotPlayedStreams(): Maybe<List<StreamWithState>>
|
||||
|
||||
/**
|
||||
* @see StreamStateEntity.isFinished()
|
||||
|
@ -137,7 +138,7 @@ abstract class FeedDAO {
|
|||
LIMIT 500
|
||||
"""
|
||||
)
|
||||
abstract fun getLiveOrNotPlayedStreamsForGroup(groupId: Long): Flowable<List<StreamWithState>>
|
||||
abstract fun getLiveOrNotPlayedStreamsForGroup(groupId: Long): Maybe<List<StreamWithState>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
package org.schabi.newpipe.error;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Ensures that a Exception is serializable.
|
||||
* This is
|
||||
*/
|
||||
public final class EnsureExceptionSerializable {
|
||||
private static final String TAG = "EnsureExSerializable";
|
||||
|
||||
private EnsureExceptionSerializable() {
|
||||
// No instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that an exception is serializable.
|
||||
* <br/>
|
||||
* If that is not the case a {@link WorkaroundNotSerializableException} is created.
|
||||
*
|
||||
* @param exception
|
||||
* @return if an exception is not serializable a new {@link WorkaroundNotSerializableException}
|
||||
* otherwise the exception from the parameter
|
||||
*/
|
||||
public static Exception ensureSerializable(@NonNull final Exception exception) {
|
||||
return checkIfSerializable(exception)
|
||||
? exception
|
||||
: WorkaroundNotSerializableException.create(exception);
|
||||
}
|
||||
|
||||
public static boolean checkIfSerializable(@NonNull final Exception exception) {
|
||||
try {
|
||||
// Check by creating a new ObjectOutputStream which does the serialization
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(bos)
|
||||
) {
|
||||
oos.writeObject(exception);
|
||||
oos.flush();
|
||||
|
||||
bos.toByteArray();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (final IOException ex) {
|
||||
Log.d(TAG, "Exception is not serializable", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class WorkaroundNotSerializableException extends Exception {
|
||||
protected WorkaroundNotSerializableException(
|
||||
final Throwable notSerializableException,
|
||||
final Throwable cause) {
|
||||
super(notSerializableException.toString(), cause);
|
||||
setStackTrace(notSerializableException.getStackTrace());
|
||||
}
|
||||
|
||||
protected WorkaroundNotSerializableException(final Throwable notSerializableException) {
|
||||
super(notSerializableException.toString());
|
||||
setStackTrace(notSerializableException.getStackTrace());
|
||||
}
|
||||
|
||||
public static WorkaroundNotSerializableException create(
|
||||
@NonNull final Exception notSerializableException
|
||||
) {
|
||||
// Build a list of the exception + all causes
|
||||
final List<Throwable> throwableList = new ArrayList<>();
|
||||
|
||||
int pos = 0;
|
||||
Throwable throwableToProcess = notSerializableException;
|
||||
|
||||
while (throwableToProcess != null) {
|
||||
throwableList.add(throwableToProcess);
|
||||
|
||||
pos++;
|
||||
throwableToProcess = throwableToProcess.getCause();
|
||||
}
|
||||
|
||||
// Reverse list so that it starts with the last one
|
||||
Collections.reverse(throwableList);
|
||||
|
||||
// Build exception stack
|
||||
WorkaroundNotSerializableException cause = null;
|
||||
for (final Throwable t : throwableList) {
|
||||
cause = cause == null
|
||||
? new WorkaroundNotSerializableException(t)
|
||||
: new WorkaroundNotSerializableException(t, cause);
|
||||
}
|
||||
|
||||
return cause;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -77,6 +77,16 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
|
||||
private ActivityErrorBinding activityErrorBinding;
|
||||
|
||||
/**
|
||||
* Reports a new error by starting a new activity.
|
||||
* <br/>
|
||||
* Ensure that the data within errorInfo is serializable otherwise
|
||||
* an exception will be thrown!<br/>
|
||||
* {@link EnsureExceptionSerializable} might help.
|
||||
*
|
||||
* @param context
|
||||
* @param errorInfo
|
||||
*/
|
||||
public static void reportError(final Context context, final ErrorInfo errorInfo) {
|
||||
final Intent intent = new Intent(context, ErrorActivity.class);
|
||||
intent.putExtra(ERROR_INFO, errorInfo);
|
||||
|
|
|
@ -20,8 +20,8 @@ public class BlankFragment extends BaseFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setTitle("NewPipe");
|
||||
// leave this inline. Will make it harder for copy cats.
|
||||
// If you are a Copy cat FUCK YOU.
|
||||
|
|
|
@ -52,6 +52,7 @@ import com.squareup.picasso.Callback;
|
|||
|
||||
import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.databinding.FragmentVideoDetailBinding;
|
||||
import org.schabi.newpipe.download.DownloadDialog;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
|
@ -73,8 +74,7 @@ import org.schabi.newpipe.fragments.EmptyFragment;
|
|||
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
|
||||
import org.schabi.newpipe.fragments.list.videos.RelatedItemsFragment;
|
||||
import org.schabi.newpipe.ktx.AnimationType;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.player.MainPlayer;
|
||||
import org.schabi.newpipe.player.MainPlayer.PlayerType;
|
||||
|
@ -99,6 +99,7 @@ import org.schabi.newpipe.util.external_communication.KoreUtils;
|
|||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -444,12 +445,11 @@ public final class VideoDetailFragment
|
|||
break;
|
||||
case R.id.detail_controls_playlist_append:
|
||||
if (getFM() != null && currentInfo != null) {
|
||||
|
||||
final PlaylistAppendDialog d = PlaylistAppendDialog.fromStreamInfo(currentInfo);
|
||||
disposables.add(
|
||||
PlaylistAppendDialog.onPlaylistFound(getContext(),
|
||||
() -> d.show(getFM(), TAG),
|
||||
() -> PlaylistCreationDialog.newInstance(d).show(getFM(), TAG)
|
||||
PlaylistDialog.createCorrespondingDialog(
|
||||
getContext(),
|
||||
Collections.singletonList(new StreamEntity(currentInfo)),
|
||||
dialog -> dialog.show(getFM(), TAG)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -594,6 +594,11 @@ public final class VideoDetailFragment
|
|||
// Init
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
|
||||
super.onViewCreated(rootView, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override // called from onViewCreated in {@link BaseFragment#onViewCreated}
|
||||
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||
super.initViews(rootView, savedInstanceState);
|
||||
|
@ -604,6 +609,18 @@ public final class VideoDetailFragment
|
|||
|
||||
binding.detailThumbnailRootLayout.requestFocus();
|
||||
|
||||
binding.detailControlsPlayWithKodi.setVisibility(
|
||||
KoreUtils.shouldShowPlayWithKodi(requireContext(), serviceId)
|
||||
? View.VISIBLE
|
||||
: View.GONE
|
||||
);
|
||||
binding.detailControlsCrashThePlayer.setVisibility(
|
||||
DEBUG && PreferenceManager.getDefaultSharedPreferences(getContext())
|
||||
.getBoolean(getString(R.string.show_crash_the_player_key), false)
|
||||
? View.VISIBLE
|
||||
: View.GONE
|
||||
);
|
||||
|
||||
if (DeviceUtils.isTv(getContext())) {
|
||||
// remove ripple effects from detail controls
|
||||
final int transparent = ContextCompat.getColor(requireContext(),
|
||||
|
@ -638,8 +655,14 @@ public final class VideoDetailFragment
|
|||
binding.detailControlsShare.setOnClickListener(this);
|
||||
binding.detailControlsOpenInBrowser.setOnClickListener(this);
|
||||
binding.detailControlsPlayWithKodi.setOnClickListener(this);
|
||||
binding.detailControlsPlayWithKodi.setVisibility(KoreUtils.shouldShowPlayWithKodi(
|
||||
requireContext(), serviceId) ? View.VISIBLE : View.GONE);
|
||||
if (DEBUG) {
|
||||
binding.detailControlsCrashThePlayer.setOnClickListener(
|
||||
v -> VideoDetailPlayerCrasher.onCrashThePlayer(
|
||||
this.getContext(),
|
||||
this.player,
|
||||
getLayoutInflater())
|
||||
);
|
||||
}
|
||||
|
||||
binding.overlayThumbnail.setOnClickListener(this);
|
||||
binding.overlayThumbnail.setOnLongClickListener(this);
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
package org.schabi.newpipe.fragments.detail;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
|
||||
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
|
||||
import org.schabi.newpipe.player.Player;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Outsourced logic for crashing the player in the {@link VideoDetailFragment}.
|
||||
*/
|
||||
public final class VideoDetailPlayerCrasher {
|
||||
|
||||
// This has to be <= 23 chars on devices running Android 7 or lower (API <= 25)
|
||||
// or it fails with an IllegalArgumentException
|
||||
// https://stackoverflow.com/a/54744028
|
||||
private static final String TAG = "VideoDetPlayerCrasher";
|
||||
|
||||
private static final Map<String, Supplier<ExoPlaybackException>> AVAILABLE_EXCEPTION_TYPES =
|
||||
getExceptionTypes();
|
||||
|
||||
private VideoDetailPlayerCrasher() {
|
||||
// No impls
|
||||
}
|
||||
|
||||
private static Map<String, Supplier<ExoPlaybackException>> getExceptionTypes() {
|
||||
final String defaultMsg = "Dummy";
|
||||
final Map<String, Supplier<ExoPlaybackException>> exceptionTypes = new LinkedHashMap<>();
|
||||
exceptionTypes.put(
|
||||
"Source",
|
||||
() -> ExoPlaybackException.createForSource(
|
||||
new IOException(defaultMsg)
|
||||
)
|
||||
);
|
||||
exceptionTypes.put(
|
||||
"Renderer",
|
||||
() -> ExoPlaybackException.createForRenderer(
|
||||
new Exception(defaultMsg),
|
||||
"Dummy renderer",
|
||||
0,
|
||||
null,
|
||||
C.FORMAT_HANDLED
|
||||
)
|
||||
);
|
||||
exceptionTypes.put(
|
||||
"Unexpected",
|
||||
() -> ExoPlaybackException.createForUnexpected(
|
||||
new RuntimeException(defaultMsg)
|
||||
)
|
||||
);
|
||||
exceptionTypes.put(
|
||||
"Remote",
|
||||
() -> ExoPlaybackException.createForRemote(defaultMsg)
|
||||
);
|
||||
|
||||
return Collections.unmodifiableMap(exceptionTypes);
|
||||
}
|
||||
|
||||
private static Context getThemeWrapperContext(final Context context) {
|
||||
return new ContextThemeWrapper(
|
||||
context,
|
||||
ThemeHelper.isLightThemeSelected(context)
|
||||
? R.style.LightTheme
|
||||
: R.style.DarkTheme);
|
||||
}
|
||||
|
||||
public static void onCrashThePlayer(
|
||||
@NonNull final Context context,
|
||||
@Nullable final Player player,
|
||||
@NonNull final LayoutInflater layoutInflater
|
||||
) {
|
||||
if (player == null) {
|
||||
Log.d(TAG, "Player is not available");
|
||||
Toast.makeText(context, "Player is not available", Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// -- Build the dialog/UI --
|
||||
|
||||
final Context themeWrapperContext = getThemeWrapperContext(context);
|
||||
|
||||
final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext);
|
||||
final RadioGroup radioGroup = SingleChoiceDialogViewBinding.inflate(layoutInflater)
|
||||
.list;
|
||||
|
||||
final AlertDialog alertDialog = new AlertDialog.Builder(getThemeWrapperContext(context))
|
||||
.setTitle("Choose an exception")
|
||||
.setView(radioGroup)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.create();
|
||||
|
||||
for (final Map.Entry<String, Supplier<ExoPlaybackException>> entry
|
||||
: AVAILABLE_EXCEPTION_TYPES.entrySet()) {
|
||||
final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater).getRoot();
|
||||
radioButton.setText(entry.getKey());
|
||||
radioButton.setChecked(false);
|
||||
radioButton.setLayoutParams(
|
||||
new RadioGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
);
|
||||
radioButton.setOnClickListener(v -> {
|
||||
tryCrashPlayerWith(player, entry.getValue().get());
|
||||
if (alertDialog != null) {
|
||||
alertDialog.cancel();
|
||||
}
|
||||
});
|
||||
radioGroup.addView(radioButton);
|
||||
}
|
||||
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that this method does not crash the underlying exoplayer directly (it's not possible).
|
||||
* It simply supplies a Exception to {@link Player#onPlayerError(ExoPlaybackException)}.
|
||||
* @param player
|
||||
* @param exception
|
||||
*/
|
||||
private static void tryCrashPlayerWith(
|
||||
@NonNull final Player player,
|
||||
@NonNull final ExoPlaybackException exception
|
||||
) {
|
||||
Log.d(TAG, "Crashing the player using player.onPlayerError(ex)");
|
||||
try {
|
||||
player.onPlayerError(exception);
|
||||
} catch (final Exception exPlayer) {
|
||||
Log.e(TAG,
|
||||
"Run into an exception while crashing the player:",
|
||||
exPlayer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -143,7 +143,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||
final View focusedItem = itemsList.getFocusedChild();
|
||||
final RecyclerView.ViewHolder itemHolder =
|
||||
itemsList.findContainingViewHolder(focusedItem);
|
||||
return itemHolder.getAdapterPosition();
|
||||
return itemHolder.getBindingAdapterPosition();
|
||||
} catch (final NullPointerException e) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -378,6 +378,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||
if (KoreUtils.shouldShowPlayWithKodi(context, item.getServiceId())) {
|
||||
entries.add(StreamDialogEntry.play_with_kodi);
|
||||
}
|
||||
|
||||
// show "mark as watched" only when watch history is enabled
|
||||
if (StreamDialogEntry.shouldAddMarkAsWatched(item.getStreamType(), context)) {
|
||||
entries.add(
|
||||
StreamDialogEntry.mark_as_watched
|
||||
);
|
||||
}
|
||||
if (!isNullOrEmpty(item.getUploaderUrl())) {
|
||||
entries.add(StreamDialogEntry.show_channel_details);
|
||||
}
|
||||
|
|
|
@ -98,11 +98,9 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (activity != null
|
||||
&& useAsFrontPage
|
||||
&& isVisibleToUser) {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (activity != null && useAsFrontPage) {
|
||||
setTitle(currentInfo != null ? currentInfo.getName() : name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,9 +99,12 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (useAsFrontPage && isVisibleToUser && activity != null) {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (!Localization.getPreferredContentCountry(requireContext()).equals(contentCountry)) {
|
||||
reloadContent();
|
||||
}
|
||||
if (useAsFrontPage && activity != null) {
|
||||
try {
|
||||
setTitle(kioskTranslatedName);
|
||||
} catch (final Exception e) {
|
||||
|
@ -117,15 +120,6 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||
return inflater.inflate(R.layout.fragment_kiosk, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (!Localization.getPreferredContentCountry(requireContext()).equals(contentCountry)) {
|
||||
reloadContent();
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
|
@ -176,6 +176,12 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
entries.add(StreamDialogEntry.play_with_kodi);
|
||||
}
|
||||
|
||||
// show "mark as watched" only when watch history is enabled
|
||||
if (StreamDialogEntry.shouldAddMarkAsWatched(item.getStreamType(), context)) {
|
||||
entries.add(
|
||||
StreamDialogEntry.mark_as_watched
|
||||
);
|
||||
}
|
||||
if (!isNullOrEmpty(item.getUploaderUrl())) {
|
||||
entries.add(StreamDialogEntry.show_channel_details);
|
||||
}
|
||||
|
|
|
@ -1088,7 +1088,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public int getSuggestionMovementFlags(@NonNull final RecyclerView.ViewHolder viewHolder) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final int position = viewHolder.getBindingAdapterPosition();
|
||||
if (position == RecyclerView.NO_POSITION) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -1099,7 +1099,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
}
|
||||
|
||||
public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final int position = viewHolder.getBindingAdapterPosition();
|
||||
final String query = suggestionListAdapter.getItem(position).query;
|
||||
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
|
|
@ -299,18 +299,36 @@ private fun View.animateLightSlideAndAlpha(enterOrExit: Boolean, duration: Long,
|
|||
}
|
||||
}
|
||||
|
||||
fun View.slideUp(duration: Long, delay: Long, @FloatRange(from = 0.0, to = 1.0) translationPercent: Float) {
|
||||
fun View.slideUp(
|
||||
duration: Long,
|
||||
delay: Long,
|
||||
@FloatRange(from = 0.0, to = 1.0) translationPercent: Float
|
||||
) {
|
||||
slideUp(duration, delay, translationPercent, null)
|
||||
}
|
||||
|
||||
fun View.slideUp(
|
||||
duration: Long,
|
||||
delay: Long = 0L,
|
||||
@FloatRange(from = 0.0, to = 1.0) translationPercent: Float = 1.0F,
|
||||
execOnEnd: Runnable? = null
|
||||
) {
|
||||
val newTranslationY = (resources.displayMetrics.heightPixels * translationPercent).toInt()
|
||||
animate().setListener(null).cancel()
|
||||
alpha = 0f
|
||||
translationY = newTranslationY.toFloat()
|
||||
visibility = View.VISIBLE
|
||||
isVisible = true
|
||||
animate()
|
||||
.alpha(1f)
|
||||
.translationY(0f)
|
||||
.setStartDelay(delay)
|
||||
.setDuration(duration)
|
||||
.setInterpolator(FastOutSlowInInterpolator())
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
execOnEnd?.run()
|
||||
}
|
||||
})
|
||||
.start()
|
||||
}
|
||||
|
||||
|
|
|
@ -78,9 +78,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (activity != null && isVisibleToUser) {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (activity != null) {
|
||||
setTitle(activity.getString(R.string.tab_bookmarks));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.schabi.newpipe.local.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
@ -17,20 +16,14 @@ import org.schabi.newpipe.R;
|
|||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.local.LocalItemListAdapter;
|
||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
|
||||
public final class PlaylistAppendDialog extends PlaylistDialog {
|
||||
private static final String TAG = PlaylistAppendDialog.class.getCanonicalName();
|
||||
|
@ -40,47 +33,8 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
|||
|
||||
private final CompositeDisposable playlistDisposables = new CompositeDisposable();
|
||||
|
||||
public static Disposable onPlaylistFound(
|
||||
final Context context, final Runnable onSuccess, final Runnable onFailed
|
||||
) {
|
||||
final LocalPlaylistManager playlistManager =
|
||||
new LocalPlaylistManager(NewPipeDatabase.getInstance(context));
|
||||
|
||||
return playlistManager.hasPlaylists()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(hasPlaylists -> {
|
||||
if (hasPlaylists) {
|
||||
onSuccess.run();
|
||||
} else {
|
||||
onFailed.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) {
|
||||
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
dialog.setInfo(Collections.singletonList(new StreamEntity(info)));
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public static PlaylistAppendDialog fromStreamInfoItems(final List<StreamInfoItem> items) {
|
||||
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
final List<StreamEntity> entities = new ArrayList<>(items.size());
|
||||
for (final StreamInfoItem item : items) {
|
||||
entities.add(new StreamEntity(item));
|
||||
}
|
||||
dialog.setInfo(entities);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public static PlaylistAppendDialog fromPlayQueueItems(final List<PlayQueueItem> items) {
|
||||
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
final List<StreamEntity> entities = new ArrayList<>(items.size());
|
||||
for (final PlayQueueItem item : items) {
|
||||
entities.add(new StreamEntity(item));
|
||||
}
|
||||
dialog.setInfo(entities);
|
||||
return dialog;
|
||||
public PlaylistAppendDialog(final List<StreamEntity> streamEntities) {
|
||||
super(streamEntities);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -104,11 +58,15 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
|||
playlistAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
|
||||
@Override
|
||||
public void selected(final LocalItem selectedItem) {
|
||||
if (!(selectedItem instanceof PlaylistMetadataEntry) || getStreams() == null) {
|
||||
if (!(selectedItem instanceof PlaylistMetadataEntry)
|
||||
|| getStreamEntities() == null) {
|
||||
return;
|
||||
}
|
||||
onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem,
|
||||
getStreams());
|
||||
onPlaylistSelected(
|
||||
playlistManager,
|
||||
(PlaylistMetadataEntry) selectedItem,
|
||||
getStreamEntities()
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -146,11 +104,17 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public void openCreatePlaylistDialog() {
|
||||
if (getStreams() == null || !isAdded()) {
|
||||
if (getStreamEntities() == null || !isAdded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlaylistCreationDialog.newInstance(getStreams()).show(getParentFragmentManager(), TAG);
|
||||
final PlaylistCreationDialog playlistCreationDialog =
|
||||
new PlaylistCreationDialog(getStreamEntities());
|
||||
// Move the dismissListener to the new dialog.
|
||||
playlistCreationDialog.setOnDismissListener(this.getOnDismissListener());
|
||||
this.setOnDismissListener(null);
|
||||
|
||||
playlistCreationDialog.show(getParentFragmentManager(), TAG);
|
||||
requireDialog().dismiss();
|
||||
}
|
||||
|
||||
|
@ -165,7 +129,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
|||
private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager,
|
||||
@NonNull final PlaylistMetadataEntry playlist,
|
||||
@NonNull final List<StreamEntity> streams) {
|
||||
if (getStreams() == null) {
|
||||
if (getStreamEntities() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,29 +7,22 @@ import android.widget.Toast;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AlertDialog.Builder;
|
||||
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.databinding.DialogEditTextBinding;
|
||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
|
||||
public final class PlaylistCreationDialog extends PlaylistDialog {
|
||||
public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) {
|
||||
final PlaylistCreationDialog dialog = new PlaylistCreationDialog();
|
||||
dialog.setInfo(streams);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public static PlaylistCreationDialog newInstance(final PlaylistAppendDialog appendDialog) {
|
||||
final PlaylistCreationDialog dialog = new PlaylistCreationDialog();
|
||||
dialog.setInfo(appendDialog.getStreams());
|
||||
return dialog;
|
||||
public PlaylistCreationDialog(final List<StreamEntity> streamEntities) {
|
||||
super(streamEntities);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -39,16 +32,18 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
|
|||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
|
||||
if (getStreams() == null) {
|
||||
if (getStreamEntities() == null) {
|
||||
return super.onCreateDialog(savedInstanceState);
|
||||
}
|
||||
|
||||
final DialogEditTextBinding dialogBinding
|
||||
= DialogEditTextBinding.inflate(getLayoutInflater());
|
||||
dialogBinding.getRoot().getContext().setTheme(ThemeHelper.getDialogTheme(requireContext()));
|
||||
dialogBinding.dialogEditText.setHint(R.string.name);
|
||||
dialogBinding.dialogEditText.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
|
||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireContext())
|
||||
final Builder dialogBuilder = new Builder(requireContext(),
|
||||
ThemeHelper.getDialogTheme(requireContext()))
|
||||
.setTitle(R.string.create_playlist)
|
||||
.setView(dialogBinding.getRoot())
|
||||
.setCancelable(true)
|
||||
|
@ -61,11 +56,10 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
|
|||
R.string.playlist_creation_success,
|
||||
Toast.LENGTH_SHORT);
|
||||
|
||||
playlistManager.createPlaylist(name, getStreams())
|
||||
playlistManager.createPlaylist(name, getStreamEntities())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(longs -> successToast.show());
|
||||
});
|
||||
|
||||
return dialogBuilder.create();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package org.schabi.newpipe.local.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.view.Window;
|
||||
|
||||
|
@ -8,23 +10,29 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
|
||||
public abstract class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead {
|
||||
|
||||
@Nullable
|
||||
private DialogInterface.OnDismissListener onDismissListener = null;
|
||||
|
||||
private List<StreamEntity> streamEntities;
|
||||
|
||||
private org.schabi.newpipe.util.SavedState savedState;
|
||||
|
||||
protected void setInfo(final List<StreamEntity> entities) {
|
||||
this.streamEntities = entities;
|
||||
}
|
||||
|
||||
protected List<StreamEntity> getStreams() {
|
||||
return streamEntities;
|
||||
public PlaylistDialog(final List<StreamEntity> streamEntities) {
|
||||
this.streamEntities = streamEntities;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -43,6 +51,10 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
|
|||
StateSaver.onDestroy(savedState);
|
||||
}
|
||||
|
||||
public List<StreamEntity> getStreamEntities() {
|
||||
return streamEntities;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(final Bundle savedInstanceState) {
|
||||
|
@ -55,6 +67,14 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
|
|||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(@NonNull final DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
if (onDismissListener != null) {
|
||||
onDismissListener.onDismiss(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// State Saving
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -84,4 +104,47 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
|
|||
savedState, outState, this);
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Getter + Setter
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Nullable
|
||||
public DialogInterface.OnDismissListener getOnDismissListener() {
|
||||
return onDismissListener;
|
||||
}
|
||||
|
||||
public void setOnDismissListener(
|
||||
@Nullable final DialogInterface.OnDismissListener onDismissListener
|
||||
) {
|
||||
this.onDismissListener = onDismissListener;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Dialog creation
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* Creates a {@link PlaylistAppendDialog} when playlists exists,
|
||||
* otherwise a {@link PlaylistCreationDialog}.
|
||||
*
|
||||
* @param context context used for accessing the database
|
||||
* @param streamEntities used for crating the dialog
|
||||
* @param onExec execution that should occur after a dialog got created, e.g. showing it
|
||||
* @return Disposable
|
||||
*/
|
||||
public static Disposable createCorrespondingDialog(
|
||||
final Context context,
|
||||
final List<StreamEntity> streamEntities,
|
||||
final Consumer<PlaylistDialog> onExec
|
||||
) {
|
||||
return new LocalPlaylistManager(NewPipeDatabase.getInstance(context))
|
||||
.hasPlaylists()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(hasPlaylists ->
|
||||
onExec.accept(hasPlaylists
|
||||
? new PlaylistAppendDialog(streamEntities)
|
||||
: new PlaylistCreationDialog(streamEntities))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ class FeedDatabaseManager(context: Context) {
|
|||
fun getStreams(
|
||||
groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
getPlayedStreams: Boolean = true
|
||||
): Flowable<List<StreamWithState>> {
|
||||
): Maybe<List<StreamWithState>> {
|
||||
return when (groupId) {
|
||||
FeedGroupEntity.GROUP_ALL_ID -> {
|
||||
if (getPlayedStreams) feedTable.getAllStreams()
|
||||
|
|
|
@ -21,16 +21,23 @@ package org.schabi.newpipe.local.feed
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.Nullable
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
|
@ -40,8 +47,10 @@ import androidx.core.view.isVisible
|
|||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.xwray.groupie.GroupieAdapter
|
||||
import com.xwray.groupie.Item
|
||||
import com.xwray.groupie.OnAsyncUpdateListener
|
||||
import com.xwray.groupie.OnItemClickListener
|
||||
import com.xwray.groupie.OnItemLongClickListener
|
||||
import icepick.State
|
||||
|
@ -65,10 +74,12 @@ import org.schabi.newpipe.fragments.BaseStateFragment
|
|||
import org.schabi.newpipe.info_list.InfoItemDialog
|
||||
import org.schabi.newpipe.ktx.animate
|
||||
import org.schabi.newpipe.ktx.animateHideRecyclerViewAllowingScrolling
|
||||
import org.schabi.newpipe.ktx.slideUp
|
||||
import org.schabi.newpipe.local.feed.item.StreamItem
|
||||
import org.schabi.newpipe.local.feed.service.FeedLoadService
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
||||
import org.schabi.newpipe.player.helper.PlayerHolder
|
||||
import org.schabi.newpipe.util.DeviceUtils
|
||||
import org.schabi.newpipe.util.Localization
|
||||
import org.schabi.newpipe.util.NavigationHelper
|
||||
import org.schabi.newpipe.util.StreamDialogEntry
|
||||
|
@ -76,6 +87,7 @@ import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams
|
|||
import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.ArrayList
|
||||
import java.util.function.Consumer
|
||||
|
||||
class FeedFragment : BaseStateFragment<FeedState>() {
|
||||
private var _feedBinding: FragmentFeedBinding? = null
|
||||
|
@ -97,6 +109,8 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
private var updateListViewModeOnResume = false
|
||||
private var isRefreshing = false
|
||||
|
||||
private var lastNewItemsCount = 0
|
||||
|
||||
init {
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
@ -126,8 +140,9 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
_feedBinding = FragmentFeedBinding.bind(rootView)
|
||||
super.onViewCreated(rootView, savedInstanceState)
|
||||
|
||||
val factory = FeedViewModel.Factory(requireContext(), groupId, showPlayedItems)
|
||||
val factory = FeedViewModel.Factory(requireContext(), groupId)
|
||||
viewModel = ViewModelProvider(this, factory).get(FeedViewModel::class.java)
|
||||
showPlayedItems = viewModel.getShowPlayedItemsFromPreferences()
|
||||
viewModel.stateLiveData.observe(viewLifecycleOwner, { it?.let(::handleResult) })
|
||||
|
||||
groupAdapter = GroupieAdapter().apply {
|
||||
|
@ -135,6 +150,20 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
setOnItemLongClickListener(listenerStreamItem)
|
||||
}
|
||||
|
||||
feedBinding.itemsList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
// Check if we scrolled to the top
|
||||
if (newState == RecyclerView.SCROLL_STATE_IDLE &&
|
||||
!recyclerView.canScrollVertically(-1)
|
||||
) {
|
||||
|
||||
if (tryGetNewItemsLoadedButton()?.isVisible == true) {
|
||||
hideNewItemsLoaded(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
feedBinding.itemsList.adapter = groupAdapter
|
||||
setupListViewMode()
|
||||
}
|
||||
|
@ -158,7 +187,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
}
|
||||
}
|
||||
|
||||
fun setupListViewMode() {
|
||||
private fun setupListViewMode() {
|
||||
// does everything needed to setup the layouts for grid or list modes
|
||||
groupAdapter.spanCount = if (shouldUseGridLayout(context)) getGridSpanCountStreams(context) else 1
|
||||
feedBinding.itemsList.layoutManager = GridLayoutManager(requireContext(), groupAdapter.spanCount).apply {
|
||||
|
@ -170,6 +199,10 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
super.initListeners()
|
||||
feedBinding.refreshRootView.setOnClickListener { reloadContent() }
|
||||
feedBinding.swipeRefreshLayout.setOnRefreshListener { reloadContent() }
|
||||
feedBinding.newItemsLoadedButton.setOnClickListener {
|
||||
hideNewItemsLoaded(true)
|
||||
feedBinding.itemsList.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
@ -213,6 +246,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
showPlayedItems = !item.isChecked
|
||||
updateTogglePlayedItemsButton(item)
|
||||
viewModel.togglePlayedItems(showPlayedItems)
|
||||
viewModel.saveShowPlayedItemsToPreferences(showPlayedItems)
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
|
@ -236,6 +270,9 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
// Ensure that all animations are canceled
|
||||
feedBinding.newItemsLoadedButton?.clearAnimation()
|
||||
|
||||
feedBinding.itemsList.adapter = null
|
||||
_feedBinding = null
|
||||
super.onDestroyView()
|
||||
|
@ -355,13 +392,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
}
|
||||
|
||||
// show "mark as watched" only when watch history is enabled
|
||||
val isWatchHistoryEnabled = PreferenceManager
|
||||
.getDefaultSharedPreferences(context)
|
||||
.getBoolean(getString(R.string.enable_watch_history_key), false)
|
||||
if (item.streamType != StreamType.AUDIO_LIVE_STREAM &&
|
||||
item.streamType != StreamType.LIVE_STREAM &&
|
||||
isWatchHistoryEnabled
|
||||
) {
|
||||
if (StreamDialogEntry.shouldAddMarkAsWatched(item.streamType, context)) {
|
||||
entries.add(
|
||||
StreamDialogEntry.mark_as_watched
|
||||
)
|
||||
|
@ -404,7 +435,17 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
}
|
||||
loadedState.items.forEach { it.itemVersion = itemVersion }
|
||||
|
||||
groupAdapter.updateAsync(loadedState.items, false, null)
|
||||
// This need to be saved in a variable as the update occurs async
|
||||
val oldOldestSubscriptionUpdate = oldestSubscriptionUpdate
|
||||
|
||||
groupAdapter.updateAsync(
|
||||
loadedState.items, false,
|
||||
OnAsyncUpdateListener {
|
||||
oldOldestSubscriptionUpdate?.run {
|
||||
highlightNewItemsAfter(oldOldestSubscriptionUpdate)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
listState?.run {
|
||||
feedBinding.itemsList.layoutManager?.onRestoreInstanceState(listState)
|
||||
|
@ -464,7 +505,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
errors.subList(i + 1, errors.size)
|
||||
)
|
||||
},
|
||||
{ throwable -> throwable.printStackTrace() }
|
||||
{ throwable -> Log.e(TAG, "Unable to process", throwable) }
|
||||
)
|
||||
return // this will be called on the remaining errors by handleFeedNotAvailable()
|
||||
}
|
||||
|
@ -526,6 +567,125 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights all items that are after the specified time
|
||||
*/
|
||||
private fun highlightNewItemsAfter(updateTime: OffsetDateTime) {
|
||||
var highlightCount = 0
|
||||
|
||||
var doCheck = true
|
||||
|
||||
for (i in 0 until groupAdapter.itemCount) {
|
||||
val item = groupAdapter.getItem(i) as StreamItem
|
||||
|
||||
var typeface = Typeface.DEFAULT
|
||||
var backgroundSupplier = { ctx: Context ->
|
||||
resolveDrawable(ctx, R.attr.selectableItemBackground)
|
||||
}
|
||||
if (doCheck) {
|
||||
// If the uploadDate is null or true we should highlight the item
|
||||
if (item.streamWithState.stream.uploadDate?.isAfter(updateTime) != false) {
|
||||
highlightCount++
|
||||
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
backgroundSupplier = { ctx: Context ->
|
||||
// Merge the drawables together. Otherwise we would lose the "select" effect
|
||||
LayerDrawable(
|
||||
arrayOf(
|
||||
resolveDrawable(ctx, R.attr.dashed_border),
|
||||
resolveDrawable(ctx, R.attr.selectableItemBackground)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Decreases execution time due to the order of the items (newest always on top)
|
||||
// Once a item is is before the updateTime we can skip all following items
|
||||
doCheck = false
|
||||
}
|
||||
}
|
||||
|
||||
// The highlighter has to be always set
|
||||
// When it's only set on items that are highlighted it will highlight all items
|
||||
// due to the fact that itemRoot is getting recycled
|
||||
item.execBindEnd = Consumer { viewBinding ->
|
||||
val context = viewBinding.itemRoot.context
|
||||
viewBinding.itemRoot.background = backgroundSupplier.invoke(context)
|
||||
viewBinding.itemVideoTitleView.typeface = typeface
|
||||
}
|
||||
}
|
||||
|
||||
// Force updates all items so that the highlighting is correct
|
||||
// If this isn't done visible items that are already highlighted will stay in a highlighted
|
||||
// state until the user scrolls them out of the visible area which causes a update/bind-call
|
||||
groupAdapter.notifyItemRangeChanged(
|
||||
0,
|
||||
minOf(groupAdapter.itemCount, maxOf(highlightCount, lastNewItemsCount))
|
||||
)
|
||||
|
||||
if (highlightCount > 0) {
|
||||
showNewItemsLoaded()
|
||||
}
|
||||
|
||||
lastNewItemsCount = highlightCount
|
||||
}
|
||||
|
||||
private fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? {
|
||||
return androidx.core.content.ContextCompat.getDrawable(
|
||||
context,
|
||||
android.util.TypedValue().apply {
|
||||
context.theme.resolveAttribute(
|
||||
attrResId,
|
||||
this,
|
||||
true
|
||||
)
|
||||
}.resourceId
|
||||
)
|
||||
}
|
||||
|
||||
private fun showNewItemsLoaded() {
|
||||
tryGetNewItemsLoadedButton()?.clearAnimation()
|
||||
tryGetNewItemsLoadedButton()
|
||||
?.slideUp(
|
||||
250L,
|
||||
delay = 100,
|
||||
execOnEnd = {
|
||||
// Disabled animations would result in immediately hiding the button
|
||||
// after it showed up
|
||||
if (DeviceUtils.hasAnimationsAnimatorDurationEnabled(context)) {
|
||||
// Hide the new items-"popup" after 10s
|
||||
hideNewItemsLoaded(true, 10000)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun hideNewItemsLoaded(animate: Boolean, delay: Long = 0) {
|
||||
tryGetNewItemsLoadedButton()?.clearAnimation()
|
||||
if (animate) {
|
||||
tryGetNewItemsLoadedButton()?.animate(
|
||||
false,
|
||||
200,
|
||||
delay = delay,
|
||||
execOnEnd = {
|
||||
// Make the layout invisible so that the onScroll toTop method
|
||||
// only does necessary work
|
||||
tryGetNewItemsLoadedButton()?.isVisible = false
|
||||
}
|
||||
)
|
||||
} else {
|
||||
tryGetNewItemsLoadedButton()?.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The view/button can be disposed/set to null under certain circumstances.
|
||||
* E.g. when the animation is still in progress but the view got destroyed.
|
||||
* This method is a helper for such states and can be used in affected code blocks.
|
||||
*/
|
||||
private fun tryGetNewItemsLoadedButton(): Button? {
|
||||
return _feedBinding?.newItemsLoadedButton
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Load Service Handling
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
@ -533,6 +693,8 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
override fun doInitialLoadLogic() {}
|
||||
|
||||
override fun reloadContent() {
|
||||
hideNewItemsLoaded(false)
|
||||
|
||||
getActivity()?.startService(
|
||||
Intent(requireContext(), FeedLoadService::class.java).apply {
|
||||
putExtra(FeedLoadService.EXTRA_GROUP_ID, groupId)
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
package org.schabi.newpipe.local.feed
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.preference.PreferenceManager
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.functions.Function4
|
||||
import io.reactivex.rxjava3.processors.BehaviorProcessor
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.database.stream.StreamWithState
|
||||
import org.schabi.newpipe.local.feed.item.StreamItem
|
||||
|
@ -23,19 +26,16 @@ import java.time.OffsetDateTime
|
|||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class FeedViewModel(
|
||||
applicationContext: Context,
|
||||
private val applicationContext: Context,
|
||||
groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
initialShowPlayedItems: Boolean = true
|
||||
) : ViewModel() {
|
||||
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext)
|
||||
|
||||
private val toggleShowPlayedItems = BehaviorProcessor.create<Boolean>()
|
||||
private val streamItems = toggleShowPlayedItems
|
||||
private val toggleShowPlayedItemsFlowable = toggleShowPlayedItems
|
||||
.startWithItem(initialShowPlayedItems)
|
||||
.distinctUntilChanged()
|
||||
.switchMap { showPlayedItems ->
|
||||
feedDatabaseManager.getStreams(groupId, showPlayedItems)
|
||||
}
|
||||
|
||||
private val mutableStateLiveData = MutableLiveData<FeedState>()
|
||||
val stateLiveData: LiveData<FeedState> = mutableStateLiveData
|
||||
|
@ -43,17 +43,28 @@ class FeedViewModel(
|
|||
private var combineDisposable = Flowable
|
||||
.combineLatest(
|
||||
FeedEventManager.events(),
|
||||
streamItems,
|
||||
toggleShowPlayedItemsFlowable,
|
||||
feedDatabaseManager.notLoadedCount(groupId),
|
||||
feedDatabaseManager.oldestSubscriptionUpdate(groupId),
|
||||
|
||||
Function4 { t1: FeedEventManager.Event, t2: List<StreamWithState>,
|
||||
Function4 { t1: FeedEventManager.Event, t2: Boolean,
|
||||
t3: Long, t4: List<OffsetDateTime> ->
|
||||
return@Function4 CombineResultHolder(t1, t2, t3, t4.firstOrNull())
|
||||
return@Function4 CombineResultEventHolder(t1, t2, t3, t4.firstOrNull())
|
||||
}
|
||||
)
|
||||
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io())
|
||||
.map { (event, showPlayedItems, notLoadedCount, oldestUpdate) ->
|
||||
var streamItems = if (event is SuccessResultEvent || event is IdleEvent)
|
||||
feedDatabaseManager
|
||||
.getStreams(groupId, showPlayedItems)
|
||||
.blockingGet(arrayListOf())
|
||||
else
|
||||
arrayListOf()
|
||||
|
||||
CombineResultDataHolder(event, streamItems, notLoadedCount, oldestUpdate)
|
||||
}
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { (event, listFromDB, notLoadedCount, oldestUpdate) ->
|
||||
mutableStateLiveData.postValue(
|
||||
|
@ -75,20 +86,50 @@ class FeedViewModel(
|
|||
combineDisposable.dispose()
|
||||
}
|
||||
|
||||
private data class CombineResultHolder(val t1: FeedEventManager.Event, val t2: List<StreamWithState>, val t3: Long, val t4: OffsetDateTime?)
|
||||
private data class CombineResultEventHolder(
|
||||
val t1: FeedEventManager.Event,
|
||||
val t2: Boolean,
|
||||
val t3: Long,
|
||||
val t4: OffsetDateTime?
|
||||
)
|
||||
|
||||
private data class CombineResultDataHolder(
|
||||
val t1: FeedEventManager.Event,
|
||||
val t2: List<StreamWithState>,
|
||||
val t3: Long,
|
||||
val t4: OffsetDateTime?
|
||||
)
|
||||
|
||||
fun togglePlayedItems(showPlayedItems: Boolean) {
|
||||
toggleShowPlayedItems.onNext(showPlayedItems)
|
||||
}
|
||||
|
||||
fun saveShowPlayedItemsToPreferences(showPlayedItems: Boolean) =
|
||||
PreferenceManager.getDefaultSharedPreferences(applicationContext).edit {
|
||||
this.putBoolean(applicationContext.getString(R.string.feed_show_played_items_key), showPlayedItems)
|
||||
this.apply()
|
||||
}
|
||||
|
||||
fun getShowPlayedItemsFromPreferences() = getShowPlayedItemsFromPreferences(applicationContext)
|
||||
|
||||
companion object {
|
||||
private fun getShowPlayedItemsFromPreferences(context: Context) =
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(context.getString(R.string.feed_show_played_items_key), true)
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val context: Context,
|
||||
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
private val showPlayedItems: Boolean
|
||||
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return FeedViewModel(context.applicationContext, groupId, showPlayedItems) as T
|
||||
return FeedViewModel(
|
||||
context.applicationContext,
|
||||
groupId,
|
||||
// Read initial value from preferences
|
||||
getShowPlayedItemsFromPreferences(context.applicationContext)
|
||||
) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.schabi.newpipe.util.Localization
|
|||
import org.schabi.newpipe.util.PicassoHelper
|
||||
import org.schabi.newpipe.util.StreamTypeUtil
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.function.Consumer
|
||||
|
||||
data class StreamItem(
|
||||
val streamWithState: StreamWithState,
|
||||
|
@ -31,6 +32,12 @@ data class StreamItem(
|
|||
private val stream: StreamEntity = streamWithState.stream
|
||||
private val stateProgressTime: Long? = streamWithState.stateProgressMillis
|
||||
|
||||
/**
|
||||
* Will be executed at the end of the [StreamItem.bind] (with (ListStreamItemBinding,Int)).
|
||||
* Can be used e.g. for highlighting a item.
|
||||
*/
|
||||
var execBindEnd: Consumer<ListStreamItemBinding>? = null
|
||||
|
||||
override fun getId(): Long = stream.uid
|
||||
|
||||
enum class ItemVersion { NORMAL, MINI, GRID }
|
||||
|
@ -97,6 +104,8 @@ data class StreamItem(
|
|||
viewBinding.itemAdditionalDetails.text =
|
||||
getStreamInfoDetailLine(viewBinding.itemAdditionalDetails.context)
|
||||
}
|
||||
|
||||
execBindEnd?.accept(viewBinding)
|
||||
}
|
||||
|
||||
override fun isLongClickable() = when (stream.streamType) {
|
||||
|
|
|
@ -120,19 +120,11 @@ public class HistoryRecordManager {
|
|||
}
|
||||
|
||||
// Update the stream progress to the full duration of the video
|
||||
final List<StreamStateEntity> states = streamStateTable.getState(streamId)
|
||||
.blockingFirst();
|
||||
if (!states.isEmpty()) {
|
||||
final StreamStateEntity entity = states.get(0);
|
||||
entity.setProgressMillis(duration * 1000);
|
||||
streamStateTable.update(entity);
|
||||
} else {
|
||||
final StreamStateEntity entity = new StreamStateEntity(
|
||||
streamId,
|
||||
duration * 1000
|
||||
);
|
||||
streamStateTable.insert(entity);
|
||||
}
|
||||
final StreamStateEntity entity = new StreamStateEntity(
|
||||
streamId,
|
||||
duration * 1000
|
||||
);
|
||||
streamStateTable.upsert(entity);
|
||||
|
||||
// Add a history entry
|
||||
final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
|
||||
|
@ -334,9 +326,9 @@ public class HistoryRecordManager {
|
|||
.getState(entities.get(0).getUid()).blockingFirst();
|
||||
if (states.isEmpty()) {
|
||||
result.add(null);
|
||||
continue;
|
||||
} else {
|
||||
result.add(states.get(0));
|
||||
}
|
||||
result.add(states.get(0));
|
||||
}
|
||||
return result;
|
||||
}).subscribeOn(Schedulers.io());
|
||||
|
@ -362,9 +354,9 @@ public class HistoryRecordManager {
|
|||
.blockingFirst();
|
||||
if (states.isEmpty()) {
|
||||
result.add(null);
|
||||
continue;
|
||||
} else {
|
||||
result.add(states.get(0));
|
||||
}
|
||||
result.add(states.get(0));
|
||||
}
|
||||
return result;
|
||||
}).subscribeOn(Schedulers.io());
|
||||
|
|
|
@ -101,9 +101,9 @@ public class StatisticsPlaylistFragment
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (activity != null && isVisibleToUser) {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (activity != null) {
|
||||
setTitle(activity.getString(R.string.title_activity_history));
|
||||
}
|
||||
}
|
||||
|
@ -366,6 +366,16 @@ public class StatisticsPlaylistFragment
|
|||
if (KoreUtils.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
|
||||
entries.add(StreamDialogEntry.play_with_kodi);
|
||||
}
|
||||
|
||||
// show "mark as watched" only when watch history is enabled
|
||||
if (StreamDialogEntry.shouldAddMarkAsWatched(
|
||||
item.getStreamEntity().getStreamType(),
|
||||
context
|
||||
)) {
|
||||
entries.add(
|
||||
StreamDialogEntry.mark_as_watched
|
||||
);
|
||||
}
|
||||
entries.add(StreamDialogEntry.show_channel_details);
|
||||
|
||||
StreamDialogEntry.setEnabledEntries(entries);
|
||||
|
|
|
@ -709,8 +709,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
return false;
|
||||
}
|
||||
|
||||
final int sourceIndex = source.getAdapterPosition();
|
||||
final int targetIndex = target.getAdapterPosition();
|
||||
final int sourceIndex = source.getBindingAdapterPosition();
|
||||
final int targetIndex = target.getBindingAdapterPosition();
|
||||
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
|
||||
if (isSwapped) {
|
||||
saveChanges();
|
||||
|
@ -782,6 +782,16 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
if (KoreUtils.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
|
||||
entries.add(StreamDialogEntry.play_with_kodi);
|
||||
}
|
||||
|
||||
// show "mark as watched" only when watch history is enabled
|
||||
if (StreamDialogEntry.shouldAddMarkAsWatched(
|
||||
item.getStreamEntity().getStreamType(),
|
||||
context
|
||||
)) {
|
||||
entries.add(
|
||||
StreamDialogEntry.mark_as_watched
|
||||
);
|
||||
}
|
||||
entries.add(StreamDialogEntry.show_channel_details);
|
||||
|
||||
StreamDialogEntry.setEnabledEntries(entries);
|
||||
|
|
|
@ -97,11 +97,9 @@ public class SubscriptionsImportFragment extends BaseFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (isVisibleToUser) {
|
||||
setTitle(getString(R.string.import_title));
|
||||
}
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setTitle(getString(R.string.import_title));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
|
|
@ -112,8 +112,8 @@ class FeedGroupReorderDialog : DialogFragment() {
|
|||
source: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
val sourceIndex = source.adapterPosition
|
||||
val targetIndex = target.adapterPosition
|
||||
val sourceIndex = source.bindingAdapterPosition
|
||||
val targetIndex = target.bindingAdapterPosition
|
||||
|
||||
groupAdapter.notifyItemMoved(sourceIndex, targetIndex)
|
||||
Collections.swap(groupOrderedIdList, sourceIndex, targetIndex)
|
||||
|
|
|
@ -23,11 +23,11 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.databinding.ActivityPlayerQueueControlBinding;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
|
@ -43,6 +43,7 @@ import org.schabi.newpipe.util.ServiceHelper;
|
|||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
|
||||
|
@ -452,12 +453,12 @@ public final class PlayQueueActivity extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
|
||||
private void openPlaylistAppendDialog(final List<PlayQueueItem> playlist) {
|
||||
final PlaylistAppendDialog d = PlaylistAppendDialog.fromPlayQueueItems(playlist);
|
||||
|
||||
PlaylistAppendDialog.onPlaylistFound(getApplicationContext(),
|
||||
() -> d.show(getSupportFragmentManager(), TAG),
|
||||
() -> PlaylistCreationDialog.newInstance(d).show(getSupportFragmentManager(), TAG));
|
||||
private void openPlaylistAppendDialog(final List<PlayQueueItem> playQueueItems) {
|
||||
PlaylistDialog.createCorrespondingDialog(
|
||||
getApplicationContext(),
|
||||
playQueueItems.stream().map(StreamEntity::new).collect(Collectors.toList()),
|
||||
dialog -> dialog.show(getSupportFragmentManager(), TAG)
|
||||
);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package org.schabi.newpipe.player;
|
||||
|
||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_AD_INSERTION;
|
||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_AUTO_TRANSITION;
|
||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL;
|
||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
|
||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_REMOVE;
|
||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
|
||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
|
||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SKIP;
|
||||
import static com.google.android.exoplayer2.Player.DiscontinuityReason;
|
||||
import static com.google.android.exoplayer2.Player.EventListener;
|
||||
import static com.google.android.exoplayer2.Player.Listener;
|
||||
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
|
||||
import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF;
|
||||
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
|
||||
|
@ -96,7 +97,6 @@ import android.widget.ProgressBar;
|
|||
import android.widget.RelativeLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -116,6 +116,7 @@ import com.google.android.exoplayer2.C;
|
|||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player.PositionInfo;
|
||||
import com.google.android.exoplayer2.RenderersFactory;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
|
@ -123,13 +124,14 @@ import com.google.android.exoplayer2.source.BehindLiveWindowException;
|
|||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.text.CaptionStyleCompat;
|
||||
import com.google.android.exoplayer2.text.Cue;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
import com.google.android.exoplayer2.ui.CaptionStyleCompat;
|
||||
import com.google.android.exoplayer2.ui.SubtitleView;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoListener;
|
||||
import com.google.android.exoplayer2.video.VideoSize;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.squareup.picasso.Target;
|
||||
|
@ -163,6 +165,7 @@ import org.schabi.newpipe.player.playback.MediaSourceManager;
|
|||
import org.schabi.newpipe.player.playback.PlaybackListener;
|
||||
import org.schabi.newpipe.player.playback.PlayerMediaSession;
|
||||
import org.schabi.newpipe.player.playback.SurfaceHolderCallback;
|
||||
import org.schabi.newpipe.player.playererror.PlayerErrorHandler;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
|
@ -174,12 +177,12 @@ import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
|||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||
import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper;
|
||||
import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder;
|
||||
import org.schabi.newpipe.util.StreamTypeUtil;
|
||||
import org.schabi.newpipe.util.DeviceUtils;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PicassoHelper;
|
||||
import org.schabi.newpipe.util.SerializedCache;
|
||||
import org.schabi.newpipe.util.StreamTypeUtil;
|
||||
import org.schabi.newpipe.util.external_communication.KoreUtils;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
import org.schabi.newpipe.views.ExpandableSurfaceView;
|
||||
|
@ -197,9 +200,8 @@ import io.reactivex.rxjava3.disposables.Disposable;
|
|||
import io.reactivex.rxjava3.disposables.SerialDisposable;
|
||||
|
||||
public final class Player implements
|
||||
EventListener,
|
||||
PlaybackListener,
|
||||
VideoListener,
|
||||
Listener,
|
||||
SeekBar.OnSeekBarChangeListener,
|
||||
View.OnClickListener,
|
||||
PopupMenu.OnMenuItemClickListener,
|
||||
|
@ -266,7 +268,7 @@ public final class Player implements
|
|||
@Nullable private MediaSourceTag currentMetadata;
|
||||
@Nullable private Bitmap currentThumbnail;
|
||||
|
||||
@Nullable private Toast errorToast;
|
||||
@NonNull private PlayerErrorHandler playerErrorHandler;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Player
|
||||
|
@ -411,6 +413,8 @@ public final class Player implements
|
|||
videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
|
||||
audioResolver = new AudioPlaybackResolver(context, dataSource);
|
||||
|
||||
playerErrorHandler = new PlayerErrorHandler(context);
|
||||
|
||||
windowManager = ContextCompat.getSystemService(context, WindowManager.class);
|
||||
}
|
||||
|
||||
|
@ -501,10 +505,6 @@ public final class Player implements
|
|||
|
||||
// Setup video view
|
||||
setupVideoSurface();
|
||||
simpleExoPlayer.addVideoListener(this);
|
||||
|
||||
// Setup subtitle view
|
||||
simpleExoPlayer.addTextOutput(binding.subtitleView);
|
||||
|
||||
// enable media tunneling
|
||||
if (DEBUG && PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
@ -513,7 +513,7 @@ public final class Player implements
|
|||
+ "media tunneling disabled in debug preferences");
|
||||
} else if (DeviceUtils.shouldSupportMediaTunneling()) {
|
||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||
.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
|
||||
.setTunnelingEnabled(true));
|
||||
} else if (DEBUG) {
|
||||
Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] does not support media tunneling");
|
||||
}
|
||||
|
@ -695,7 +695,7 @@ public final class Player implements
|
|||
},
|
||||
error -> {
|
||||
if (DEBUG) {
|
||||
error.printStackTrace();
|
||||
Log.w(TAG, "Failed to start playback", error);
|
||||
}
|
||||
// In case any error we can start playback without history
|
||||
initPlayback(newQueue, repeatMode, playbackSpeed, playbackPitch,
|
||||
|
@ -774,6 +774,8 @@ public final class Player implements
|
|||
destroyPlayer();
|
||||
initPlayer(playOnReady);
|
||||
setRepeatMode(repeatMode);
|
||||
// #6825 - Ensure that the shuffle-button is in the correct state on the UI
|
||||
setShuffleButton(binding.shuffleButton, simpleExoPlayer.getShuffleModeEnabled());
|
||||
setPlaybackParameters(playbackSpeed, playbackPitch, playbackSkipSilence);
|
||||
|
||||
playQueue = queue;
|
||||
|
@ -807,7 +809,6 @@ public final class Player implements
|
|||
|
||||
if (!exoPlayerIsNull()) {
|
||||
simpleExoPlayer.removeListener(this);
|
||||
simpleExoPlayer.removeVideoListener(this);
|
||||
simpleExoPlayer.stop();
|
||||
simpleExoPlayer.release();
|
||||
}
|
||||
|
@ -858,10 +859,10 @@ public final class Player implements
|
|||
|
||||
final int queuePos = playQueue.getIndex();
|
||||
final long windowPos = simpleExoPlayer.getCurrentPosition();
|
||||
final long duration = simpleExoPlayer.getDuration();
|
||||
|
||||
if (windowPos > 0 && windowPos <= simpleExoPlayer.getDuration()) {
|
||||
setRecovery(queuePos, windowPos);
|
||||
}
|
||||
// No checks due to https://github.com/TeamNewPipe/NewPipe/pull/7195#issuecomment-962624380
|
||||
setRecovery(queuePos, Math.max(0, Math.min(windowPos, duration)));
|
||||
}
|
||||
|
||||
private void setRecovery(final int queuePos, final long windowPos) {
|
||||
|
@ -896,7 +897,7 @@ public final class Player implements
|
|||
|
||||
public void smoothStopPlayer() {
|
||||
// Pausing would make transition from one stream to a new stream not smooth, so only stop
|
||||
simpleExoPlayer.stop(false);
|
||||
simpleExoPlayer.stop();
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
@ -2435,7 +2436,9 @@ public final class Player implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onPositionDiscontinuity(@DiscontinuityReason final int discontinuityReason) {
|
||||
public void onPositionDiscontinuity(
|
||||
final PositionInfo oldPosition, final PositionInfo newPosition,
|
||||
@DiscontinuityReason final int discontinuityReason) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "ExoPlayer - onPositionDiscontinuity() called with "
|
||||
+ "discontinuityReason = [" + discontinuityReason + "]");
|
||||
|
@ -2447,7 +2450,8 @@ public final class Player implements
|
|||
// Refresh the playback if there is a transition to the next video
|
||||
final int newWindowIndex = simpleExoPlayer.getCurrentWindowIndex();
|
||||
switch (discontinuityReason) {
|
||||
case DISCONTINUITY_REASON_PERIOD_TRANSITION:
|
||||
case DISCONTINUITY_REASON_AUTO_TRANSITION:
|
||||
case DISCONTINUITY_REASON_REMOVE:
|
||||
// When player is in single repeat mode and a period transition occurs,
|
||||
// we need to register a view count here since no metadata has changed
|
||||
if (getRepeatMode() == REPEAT_MODE_ONE && newWindowIndex == playQueue.getIndex()) {
|
||||
|
@ -2468,7 +2472,7 @@ public final class Player implements
|
|||
playQueue.setIndex(newWindowIndex);
|
||||
}
|
||||
break;
|
||||
case DISCONTINUITY_REASON_AD_INSERTION:
|
||||
case DISCONTINUITY_REASON_SKIP:
|
||||
break; // only makes Android Studio linter happy, as there are no ads
|
||||
}
|
||||
|
||||
|
@ -2480,6 +2484,11 @@ public final class Player implements
|
|||
//TODO check if this causes black screen when switching to fullscreen
|
||||
animate(binding.surfaceForeground, false, DEFAULT_CONTROLS_DURATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCues(final List<Cue> cues) {
|
||||
binding.subtitleView.onCues(cues);
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
||||
|
@ -2501,34 +2510,37 @@ public final class Player implements
|
|||
* </ul>
|
||||
*
|
||||
* @see #processSourceError(IOException)
|
||||
* @see com.google.android.exoplayer2.Player.EventListener#onPlayerError(ExoPlaybackException)
|
||||
* @see com.google.android.exoplayer2.Player.Listener#onPlayerError(ExoPlaybackException)
|
||||
*/
|
||||
@Override
|
||||
public void onPlayerError(@NonNull final ExoPlaybackException error) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "ExoPlayer - onPlayerError() called with: " + "error = [" + error + "]");
|
||||
}
|
||||
if (errorToast != null) {
|
||||
errorToast.cancel();
|
||||
errorToast = null;
|
||||
}
|
||||
Log.e(TAG, "ExoPlayer - onPlayerError() called with:", error);
|
||||
|
||||
saveStreamProgressState();
|
||||
|
||||
switch (error.type) {
|
||||
case ExoPlaybackException.TYPE_SOURCE:
|
||||
processSourceError(error.getSourceException());
|
||||
showStreamError(error);
|
||||
playerErrorHandler.showPlayerError(
|
||||
error,
|
||||
currentMetadata.getMetadata(),
|
||||
R.string.player_stream_failure);
|
||||
break;
|
||||
case ExoPlaybackException.TYPE_UNEXPECTED:
|
||||
showRecoverableError(error);
|
||||
playerErrorHandler.showPlayerError(
|
||||
error,
|
||||
currentMetadata.getMetadata(),
|
||||
R.string.player_recoverable_failure);
|
||||
setRecovery();
|
||||
reloadPlayQueueManager();
|
||||
break;
|
||||
case ExoPlaybackException.TYPE_REMOTE:
|
||||
case ExoPlaybackException.TYPE_RENDERER:
|
||||
default:
|
||||
showUnrecoverableError(error);
|
||||
playerErrorHandler.showPlayerError(
|
||||
error,
|
||||
currentMetadata.getMetadata(),
|
||||
R.string.player_unrecoverable_failure);
|
||||
onPlaybackShutdown();
|
||||
break;
|
||||
}
|
||||
|
@ -2550,37 +2562,6 @@ public final class Player implements
|
|||
playQueue.error();
|
||||
}
|
||||
}
|
||||
|
||||
private void showStreamError(final Exception exception) {
|
||||
exception.printStackTrace();
|
||||
|
||||
if (errorToast == null) {
|
||||
errorToast = Toast
|
||||
.makeText(context, R.string.player_stream_failure, Toast.LENGTH_SHORT);
|
||||
errorToast.show();
|
||||
}
|
||||
}
|
||||
|
||||
private void showRecoverableError(final Exception exception) {
|
||||
exception.printStackTrace();
|
||||
|
||||
if (errorToast == null) {
|
||||
errorToast = Toast
|
||||
.makeText(context, R.string.player_recoverable_failure, Toast.LENGTH_SHORT);
|
||||
errorToast.show();
|
||||
}
|
||||
}
|
||||
|
||||
private void showUnrecoverableError(final Exception exception) {
|
||||
exception.printStackTrace();
|
||||
|
||||
if (errorToast != null) {
|
||||
errorToast.cancel();
|
||||
}
|
||||
errorToast = Toast
|
||||
.makeText(context, R.string.player_unrecoverable_failure, Toast.LENGTH_SHORT);
|
||||
errorToast.show();
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
||||
|
@ -3865,19 +3846,17 @@ public final class Player implements
|
|||
}
|
||||
|
||||
@Override // exoplayer listener
|
||||
public void onVideoSizeChanged(final int width, final int height,
|
||||
final int unappliedRotationDegrees,
|
||||
final float pixelWidthHeightRatio) {
|
||||
public void onVideoSizeChanged(final VideoSize videoSize) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onVideoSizeChanged() called with: "
|
||||
+ "width / height = [" + width + " / " + height
|
||||
+ " = " + (((float) width) / height) + "], "
|
||||
+ "unappliedRotationDegrees = [" + unappliedRotationDegrees + "], "
|
||||
+ "pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
|
||||
+ "width / height = [" + videoSize.width + " / " + videoSize.height
|
||||
+ " = " + (((float) videoSize.width) / videoSize.height) + "], "
|
||||
+ "unappliedRotationDegrees = [" + videoSize.unappliedRotationDegrees + "], "
|
||||
+ "pixelWidthHeightRatio = [" + videoSize.pixelWidthHeightRatio + "]");
|
||||
}
|
||||
|
||||
binding.surfaceView.setAspectRatio(((float) width) / height);
|
||||
isVerticalVideo = width < height;
|
||||
binding.surfaceView.setAspectRatio(((float) videoSize.width) / videoSize.height);
|
||||
isVerticalVideo = videoSize.width < videoSize.height;
|
||||
|
||||
if (globalScreenOrientationLocked(context)
|
||||
&& isFullscreen
|
||||
|
@ -4182,8 +4161,7 @@ public final class Player implements
|
|||
} catch (@NonNull final IndexOutOfBoundsException e) {
|
||||
// Why would this even happen =(... but lets log it anyway, better safe than sorry
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "player.isCurrentWindowDynamic() failed: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
Log.d(TAG, "player.isCurrentWindowDynamic() failed: ", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -69,26 +69,18 @@ public class PlayerGestureListener
|
|||
if (DEBUG) {
|
||||
Log.d(TAG, "onSingleTap called with playerType = [" + player.getPlayerType() + "]");
|
||||
}
|
||||
if (playerType == MainPlayer.PlayerType.POPUP) {
|
||||
|
||||
if (player.isControlsVisible()) {
|
||||
player.hideControls(100, 100);
|
||||
} else {
|
||||
player.getPlayPauseButton().requestFocus();
|
||||
player.showControlsThenHide();
|
||||
}
|
||||
if (player.isControlsVisible()) {
|
||||
player.hideControls(150, 0);
|
||||
return;
|
||||
}
|
||||
// -- Controls are not visible --
|
||||
|
||||
} else /* playerType == MainPlayer.PlayerType.VIDEO */ {
|
||||
|
||||
if (player.isControlsVisible()) {
|
||||
player.hideControls(150, 0);
|
||||
} else {
|
||||
if (player.getCurrentState() == Player.STATE_COMPLETED) {
|
||||
player.showControls(0);
|
||||
} else {
|
||||
player.showControlsThenHide();
|
||||
}
|
||||
}
|
||||
// When player is completed show controls and don't hide them later
|
||||
if (player.getCurrentState() == Player.STATE_COMPLETED) {
|
||||
player.showControls(0);
|
||||
} else {
|
||||
player.showControlsThenHide();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,6 +95,8 @@ public class PlayerGestureListener
|
|||
+ player.getPlayerType() + "], portion = [" + portion + "]");
|
||||
}
|
||||
if (playerType == MainPlayer.PlayerType.VIDEO) {
|
||||
|
||||
// -- Brightness and Volume control --
|
||||
final boolean isBrightnessGestureEnabled =
|
||||
PlayerHelper.isBrightnessGestureEnabled(service);
|
||||
final boolean isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service);
|
||||
|
@ -121,15 +115,14 @@ public class PlayerGestureListener
|
|||
}
|
||||
|
||||
} else /* MainPlayer.PlayerType.POPUP */ {
|
||||
|
||||
// -- Determine if the ClosingOverlayView (red X) has to be shown or hidden --
|
||||
final View closingOverlayView = player.getClosingOverlayView();
|
||||
if (player.isInsideClosingRadius(movingEvent)) {
|
||||
if (closingOverlayView.getVisibility() == View.GONE) {
|
||||
animate(closingOverlayView, true, 200);
|
||||
}
|
||||
} else {
|
||||
if (closingOverlayView.getVisibility() == View.VISIBLE) {
|
||||
animate(closingOverlayView, false, 200);
|
||||
}
|
||||
final boolean showClosingOverlayView = player.isInsideClosingRadius(movingEvent);
|
||||
// Check if an view is in expected state and if not animate it into the correct state
|
||||
final int expectedVisibility = showClosingOverlayView ? View.VISIBLE : View.GONE;
|
||||
if (closingOverlayView.getVisibility() != expectedVisibility) {
|
||||
animate(closingOverlayView, showClosingOverlayView, 200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -210,11 +203,12 @@ public class PlayerGestureListener
|
|||
Log.d(TAG, "onScrollEnd called with playerType = ["
|
||||
+ player.getPlayerType() + "]");
|
||||
}
|
||||
if (playerType == MainPlayer.PlayerType.VIDEO) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onScrollEnd() called");
|
||||
}
|
||||
|
||||
if (player.isControlsVisible() && player.getCurrentState() == STATE_PLAYING) {
|
||||
player.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
|
||||
}
|
||||
|
||||
if (playerType == MainPlayer.PlayerType.VIDEO) {
|
||||
if (player.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
|
||||
animate(player.getVolumeRelativeLayout(), false, 200, SCALE_AND_ALPHA,
|
||||
200);
|
||||
|
@ -223,15 +217,7 @@ public class PlayerGestureListener
|
|||
animate(player.getBrightnessRelativeLayout(), false, 200, SCALE_AND_ALPHA,
|
||||
200);
|
||||
}
|
||||
|
||||
if (player.isControlsVisible() && player.getCurrentState() == STATE_PLAYING) {
|
||||
player.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
|
||||
}
|
||||
} else {
|
||||
if (player.isControlsVisible() && player.getCurrentState() == STATE_PLAYING) {
|
||||
player.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
|
||||
}
|
||||
|
||||
} else /* Popup-Player */ {
|
||||
if (player.isInsideClosingRadius(event)) {
|
||||
player.closePopup();
|
||||
} else if (!player.isPopupClosing()) {
|
||||
|
|
|
@ -16,7 +16,6 @@ import androidx.media.AudioManagerCompat;
|
|||
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.analytics.AnalyticsListener;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
|
||||
public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, AnalyticsListener {
|
||||
|
||||
|
@ -150,15 +149,9 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onAudioSessionId(final EventTime eventTime, final int audioSessionId) {
|
||||
public void onAudioSessionIdChanged(final EventTime eventTime, final int audioSessionId) {
|
||||
notifyAudioSessionUpdate(true, audioSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioDisabled(final EventTime eventTime, final DecoderCounters counters) {
|
||||
notifyAudioSessionUpdate(false, player.getAudioSessionId());
|
||||
}
|
||||
|
||||
private void notifyAudioSessionUpdate(final boolean active, final int audioSessionId) {
|
||||
if (!PlayerHelper.isUsingDSP()) {
|
||||
return;
|
||||
|
|
|
@ -1,81 +1,28 @@
|
|||
package org.schabi.newpipe.player.helper;
|
||||
|
||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||
import com.google.android.exoplayer2.LoadControl;
|
||||
import com.google.android.exoplayer2.Renderer;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
|
||||
public class LoadController implements LoadControl {
|
||||
public class LoadController extends DefaultLoadControl {
|
||||
|
||||
public static final String TAG = "LoadController";
|
||||
|
||||
private final long initialPlaybackBufferUs;
|
||||
private final LoadControl internalLoadControl;
|
||||
private boolean preloadingEnabled = true;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Default Load Control
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public LoadController() {
|
||||
this(PlayerHelper.getPlaybackStartBufferMs());
|
||||
}
|
||||
|
||||
private LoadController(final int initialPlaybackBufferMs) {
|
||||
this.initialPlaybackBufferUs = initialPlaybackBufferMs * 1000;
|
||||
|
||||
final DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
|
||||
builder.setBufferDurationsMs(
|
||||
DefaultLoadControl.DEFAULT_MIN_BUFFER_MS,
|
||||
DefaultLoadControl.DEFAULT_MAX_BUFFER_MS,
|
||||
initialPlaybackBufferMs,
|
||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS);
|
||||
internalLoadControl = builder.build();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Custom behaviours
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onPrepared() {
|
||||
preloadingEnabled = true;
|
||||
internalLoadControl.onPrepared();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksSelected(final Renderer[] renderers, final TrackGroupArray trackGroups,
|
||||
final TrackSelectionArray trackSelections) {
|
||||
internalLoadControl.onTracksSelected(renderers, trackGroups, trackSelections);
|
||||
super.onPrepared();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopped() {
|
||||
preloadingEnabled = true;
|
||||
internalLoadControl.onStopped();
|
||||
super.onStopped();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReleased() {
|
||||
preloadingEnabled = true;
|
||||
internalLoadControl.onReleased();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Allocator getAllocator() {
|
||||
return internalLoadControl.getAllocator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBackBufferDurationUs() {
|
||||
return internalLoadControl.getBackBufferDurationUs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainBackBufferFromKeyframe() {
|
||||
return internalLoadControl.retainBackBufferFromKeyframe();
|
||||
super.onReleased();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -85,20 +32,10 @@ public class LoadController implements LoadControl {
|
|||
if (!preloadingEnabled) {
|
||||
return false;
|
||||
}
|
||||
return internalLoadControl.shouldContinueLoading(
|
||||
return super.shouldContinueLoading(
|
||||
playbackPositionUs, bufferedDurationUs, playbackSpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldStartPlayback(final long bufferedDurationUs, final float playbackSpeed,
|
||||
final boolean rebuffering) {
|
||||
final boolean isInitialPlaybackBufferFilled
|
||||
= bufferedDurationUs >= this.initialPlaybackBufferUs * playbackSpeed;
|
||||
final boolean isInternalStartingPlayback = internalLoadControl
|
||||
.shouldStartPlayback(bufferedDurationUs, playbackSpeed, rebuffering);
|
||||
return isInitialPlaybackBufferFilled || isInternalStartingPlayback;
|
||||
}
|
||||
|
||||
public void disablePreloadingOfCurrentTrack() {
|
||||
preloadingEnabled = false;
|
||||
}
|
||||
|
|
|
@ -179,9 +179,7 @@ public class MediaSessionManager {
|
|||
// If we got an album art check if the current set AlbumArt is null
|
||||
if (optAlbumArt.isPresent() && getMetadataAlbumArt() == null) {
|
||||
if (DEBUG) {
|
||||
if (getMetadataAlbumArt() == null) {
|
||||
Log.d(TAG, "N_getMetadataAlbumArt: thumb == null");
|
||||
}
|
||||
Log.d(TAG, "N_getMetadataAlbumArt: thumb == null");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -191,16 +189,19 @@ public class MediaSessionManager {
|
|||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private Bitmap getMetadataAlbumArt() {
|
||||
return mediaSession.getController().getMetadata()
|
||||
.getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getMetadataTitle() {
|
||||
return mediaSession.getController().getMetadata()
|
||||
.getString(MediaMetadataCompat.METADATA_KEY_TITLE);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getMetadataArtist() {
|
||||
return mediaSession.getController().getMetadata()
|
||||
.getString(MediaMetadataCompat.METADATA_KEY_ARTIST);
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package org.schabi.newpipe.player.helper;
|
||||
|
||||
import static org.schabi.newpipe.player.Player.DEBUG;
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
@ -18,9 +21,6 @@ import androidx.preference.PreferenceManager;
|
|||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.SliderStrategy;
|
||||
|
||||
import static org.schabi.newpipe.player.Player.DEBUG;
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
public class PlaybackParameterDialog extends DialogFragment {
|
||||
// Minimum allowable range in ExoPlayer
|
||||
private static final double MINIMUM_PLAYBACK_VALUE = 0.10f;
|
||||
|
@ -157,7 +157,6 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
setupControlViews(view);
|
||||
|
||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.playback_speed_control)
|
||||
.setView(view)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.cancel, (dialogInterface, i) ->
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
package org.schabi.newpipe.player.helper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.android.exoplayer2.source.MediaParserExtractorAdapter;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
import com.google.android.exoplayer2.source.SingleSampleMediaSource;
|
||||
import com.google.android.exoplayer2.source.chunk.MediaParserChunkExtractor;
|
||||
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
||||
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
|
||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
||||
import com.google.android.exoplayer2.source.hls.MediaParserHlsMediaChunkExtractor;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
|
@ -19,7 +23,7 @@ import com.google.android.exoplayer2.upstream.TransferListener;
|
|||
public class PlayerDataSource {
|
||||
private static final int MANIFEST_MINIMUM_RETRY = 5;
|
||||
private static final int EXTRACTOR_MINIMUM_RETRY = Integer.MAX_VALUE;
|
||||
private static final int LIVE_STREAM_EDGE_GAP_MILLIS = 10000;
|
||||
public static final int LIVE_STREAM_EDGE_GAP_MILLIS = 10000;
|
||||
|
||||
private final DataSource.Factory cacheDataSourceFactory;
|
||||
private final DataSource.Factory cachelessDataSourceFactory;
|
||||
|
@ -32,51 +36,83 @@ public class PlayerDataSource {
|
|||
}
|
||||
|
||||
public SsMediaSource.Factory getLiveSsMediaSourceFactory() {
|
||||
return new SsMediaSource.Factory(new DefaultSsChunkSource.Factory(
|
||||
cachelessDataSourceFactory), cachelessDataSourceFactory)
|
||||
return new SsMediaSource.Factory(
|
||||
new DefaultSsChunkSource.Factory(cachelessDataSourceFactory),
|
||||
cachelessDataSourceFactory
|
||||
)
|
||||
.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY))
|
||||
.setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS);
|
||||
}
|
||||
|
||||
public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() {
|
||||
return new HlsMediaSource.Factory(cachelessDataSourceFactory)
|
||||
.setAllowChunklessPreparation(true)
|
||||
final HlsMediaSource.Factory factory =
|
||||
new HlsMediaSource.Factory(cachelessDataSourceFactory)
|
||||
.setAllowChunklessPreparation(true)
|
||||
.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY));
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
factory.setExtractorFactory(MediaParserHlsMediaChunkExtractor.FACTORY);
|
||||
}
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
public DashMediaSource.Factory getLiveDashMediaSourceFactory() {
|
||||
return new DashMediaSource.Factory(
|
||||
getDefaultDashChunkSourceFactory(cachelessDataSourceFactory),
|
||||
cachelessDataSourceFactory
|
||||
)
|
||||
.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY));
|
||||
}
|
||||
|
||||
public DashMediaSource.Factory getLiveDashMediaSourceFactory() {
|
||||
return new DashMediaSource.Factory(new DefaultDashChunkSource.Factory(
|
||||
cachelessDataSourceFactory), cachelessDataSourceFactory)
|
||||
.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY))
|
||||
.setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS, true);
|
||||
}
|
||||
private DefaultDashChunkSource.Factory getDefaultDashChunkSourceFactory(
|
||||
final DataSource.Factory dataSourceFactory
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
return new DefaultDashChunkSource.Factory(
|
||||
MediaParserChunkExtractor.FACTORY,
|
||||
dataSourceFactory,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
public SsMediaSource.Factory getSsMediaSourceFactory() {
|
||||
return new SsMediaSource.Factory(new DefaultSsChunkSource.Factory(
|
||||
cacheDataSourceFactory), cacheDataSourceFactory);
|
||||
return new DefaultDashChunkSource.Factory(dataSourceFactory);
|
||||
}
|
||||
|
||||
public HlsMediaSource.Factory getHlsMediaSourceFactory() {
|
||||
return new HlsMediaSource.Factory(cacheDataSourceFactory);
|
||||
final HlsMediaSource.Factory factory = new HlsMediaSource.Factory(cacheDataSourceFactory);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
return factory;
|
||||
}
|
||||
|
||||
// *** >= Android 11 / R / API 30 ***
|
||||
return factory.setExtractorFactory(MediaParserHlsMediaChunkExtractor.FACTORY);
|
||||
}
|
||||
|
||||
public DashMediaSource.Factory getDashMediaSourceFactory() {
|
||||
return new DashMediaSource.Factory(new DefaultDashChunkSource.Factory(
|
||||
cacheDataSourceFactory), cacheDataSourceFactory);
|
||||
return new DashMediaSource.Factory(
|
||||
getDefaultDashChunkSourceFactory(cacheDataSourceFactory),
|
||||
cacheDataSourceFactory
|
||||
);
|
||||
}
|
||||
|
||||
public ProgressiveMediaSource.Factory getExtractorMediaSourceFactory() {
|
||||
return new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
|
||||
.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY));
|
||||
}
|
||||
final ProgressiveMediaSource.Factory factory;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
factory = new ProgressiveMediaSource.Factory(
|
||||
cacheDataSourceFactory,
|
||||
MediaParserExtractorAdapter.FACTORY
|
||||
);
|
||||
} else {
|
||||
factory = new ProgressiveMediaSource.Factory(cacheDataSourceFactory);
|
||||
}
|
||||
|
||||
public ProgressiveMediaSource.Factory getExtractorMediaSourceFactory(
|
||||
@NonNull final String key) {
|
||||
return getExtractorMediaSourceFactory().setCustomCacheKey(key);
|
||||
return factory.setLoadErrorHandlingPolicy(
|
||||
new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY));
|
||||
}
|
||||
|
||||
public SingleSampleMediaSource.Factory getSampleMediaSourceFactory() {
|
||||
|
|
|
@ -1,5 +1,18 @@
|
|||
package org.schabi.newpipe.player.helper;
|
||||
|
||||
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
|
||||
import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF;
|
||||
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
|
||||
import static org.schabi.newpipe.player.Player.IDLE_WINDOW_FLAGS;
|
||||
import static org.schabi.newpipe.player.Player.PLAYER_TYPE;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_ALWAYS;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_NEVER;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_WIFI;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
@ -21,11 +34,11 @@ import androidx.preference.PreferenceManager;
|
|||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player.RepeatMode;
|
||||
import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.text.CaptionStyleCompat;
|
||||
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode;
|
||||
import com.google.android.exoplayer2.ui.CaptionStyleCompat;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
@ -57,19 +70,6 @@ import java.util.Objects;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
|
||||
import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF;
|
||||
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
import static org.schabi.newpipe.player.Player.IDLE_WINDOW_FLAGS;
|
||||
import static org.schabi.newpipe.player.Player.PLAYER_TYPE;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_ALWAYS;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_NEVER;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_WIFI;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP;
|
||||
|
||||
public final class PlayerHelper {
|
||||
private static final StringBuilder STRING_BUILDER = new StringBuilder();
|
||||
private static final Formatter STRING_FORMATTER
|
||||
|
@ -305,14 +305,7 @@ public final class PlayerHelper {
|
|||
return 2 * 1024 * 1024L; // ExoPlayer CacheDataSink.MIN_RECOMMENDED_FRAGMENT_SIZE
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of milliseconds the player buffers for before starting playback
|
||||
*/
|
||||
public static int getPlaybackStartBufferMs() {
|
||||
return 500;
|
||||
}
|
||||
|
||||
public static TrackSelection.Factory getQualitySelector() {
|
||||
public static ExoTrackSelection.Factory getQualitySelector() {
|
||||
return new AdaptiveTrackSelection.Factory(
|
||||
1000,
|
||||
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
|
||||
|
|
|
@ -13,7 +13,7 @@ import com.google.android.exoplayer2.RendererCapabilities.Capabilities;
|
|||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
|
||||
/**
|
||||
|
@ -28,7 +28,7 @@ public class CustomTrackSelector extends DefaultTrackSelector {
|
|||
private String preferredTextLanguage;
|
||||
|
||||
public CustomTrackSelector(final Context context,
|
||||
final TrackSelection.Factory adaptiveTrackSelectionFactory) {
|
||||
final ExoTrackSelection.Factory adaptiveTrackSelectionFactory) {
|
||||
super(context, adaptiveTrackSelectionFactory);
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ public class CustomTrackSelector extends DefaultTrackSelector {
|
|||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Pair<TrackSelection.Definition, TextTrackScore> selectTextTrack(
|
||||
protected Pair<ExoTrackSelection.Definition, TextTrackScore> selectTextTrack(
|
||||
final TrackGroupArray groups,
|
||||
@NonNull final int[][] formatSupport,
|
||||
@NonNull final Parameters params,
|
||||
|
@ -86,7 +86,7 @@ public class CustomTrackSelector extends DefaultTrackSelector {
|
|||
}
|
||||
}
|
||||
return selectedGroup == null ? null
|
||||
: Pair.create(new TrackSelection.Definition(selectedGroup, selectedTrackIndex),
|
||||
: Pair.create(new ExoTrackSelection.Definition(selectedGroup, selectedTrackIndex),
|
||||
Assertions.checkNotNull(selectedTrackScore));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
package org.schabi.newpipe.player.playererror;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.error.EnsureExceptionSerializable;
|
||||
import org.schabi.newpipe.error.ErrorActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.Info;
|
||||
|
||||
/**
|
||||
* Handles (exoplayer)errors that occur in the player.
|
||||
*/
|
||||
public class PlayerErrorHandler {
|
||||
// This has to be <= 23 chars on devices running Android 7 or lower (API <= 25)
|
||||
// or it fails with an IllegalArgumentException
|
||||
// https://stackoverflow.com/a/54744028
|
||||
private static final String TAG = "PlayerErrorHandler";
|
||||
|
||||
@Nullable
|
||||
private Toast errorToast;
|
||||
|
||||
@NonNull
|
||||
private final Context context;
|
||||
|
||||
public PlayerErrorHandler(@NonNull final Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void showPlayerError(
|
||||
@NonNull final ExoPlaybackException exception,
|
||||
@NonNull final Info info,
|
||||
@StringRes final int textResId
|
||||
) {
|
||||
// Hide existing toast message
|
||||
if (errorToast != null) {
|
||||
Log.d(TAG, "Trying to cancel previous player error error toast");
|
||||
errorToast.cancel();
|
||||
errorToast = null;
|
||||
}
|
||||
|
||||
if (shouldReportError()) {
|
||||
try {
|
||||
reportError(exception, info);
|
||||
// When a report pops up we need no toast
|
||||
return;
|
||||
} catch (final Exception ex) {
|
||||
Log.w(TAG, "Unable to report error:", ex);
|
||||
// This will show the toast as fallback
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Showing player error toast");
|
||||
errorToast = Toast.makeText(context, textResId, Toast.LENGTH_SHORT);
|
||||
errorToast.show();
|
||||
}
|
||||
|
||||
private void reportError(@NonNull final ExoPlaybackException exception,
|
||||
@NonNull final Info info) {
|
||||
ErrorActivity.reportError(
|
||||
context,
|
||||
new ErrorInfo(
|
||||
EnsureExceptionSerializable.ensureSerializable(exception),
|
||||
UserAction.PLAY_STREAM,
|
||||
"Player error[type=" + exception.type + "] occurred while playing: "
|
||||
+ info.getUrl(),
|
||||
info
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private boolean shouldReportError() {
|
||||
return PreferenceManager
|
||||
.getDefaultSharedPreferences(context)
|
||||
.getBoolean(
|
||||
context.getString(R.string.report_player_errors_key),
|
||||
false);
|
||||
}
|
||||
}
|
|
@ -436,14 +436,16 @@ public abstract class PlayQueue implements Serializable {
|
|||
* top, so shuffling a size-2 list does nothing)
|
||||
*/
|
||||
public synchronized void shuffle() {
|
||||
// Can't shuffle an list that's empty or only has one element
|
||||
if (size() <= 2) {
|
||||
return;
|
||||
}
|
||||
// Create a backup if it doesn't already exist
|
||||
// Note: The backup-list has to be created at all cost (even when size <= 2).
|
||||
// Otherwise it's not possible to enter shuffle-mode!
|
||||
if (backup == null) {
|
||||
backup = new ArrayList<>(streams);
|
||||
}
|
||||
// Can't shuffle a list that's empty or only has one element
|
||||
if (size() <= 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int originalIndex = getIndex();
|
||||
final PlayQueueItem currentItem = getItem();
|
||||
|
|
|
@ -51,6 +51,6 @@ public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleC
|
|||
|
||||
@Override
|
||||
public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDir) {
|
||||
onSwiped(viewHolder.getAdapterPosition());
|
||||
onSwiped(viewHolder.getBindingAdapterPosition());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
|
|||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaSourceFactory;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
|
@ -41,20 +42,28 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
|
|||
@NonNull final String sourceUrl,
|
||||
@C.ContentType final int type,
|
||||
@NonNull final MediaSourceTag metadata) {
|
||||
final Uri uri = Uri.parse(sourceUrl);
|
||||
final MediaSourceFactory factory;
|
||||
switch (type) {
|
||||
case C.TYPE_SS:
|
||||
return dataSource.getLiveSsMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
factory = dataSource.getLiveSsMediaSourceFactory();
|
||||
break;
|
||||
case C.TYPE_DASH:
|
||||
return dataSource.getLiveDashMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
factory = dataSource.getLiveDashMediaSourceFactory();
|
||||
break;
|
||||
case C.TYPE_HLS:
|
||||
return dataSource.getLiveHlsMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
factory = dataSource.getLiveHlsMediaSourceFactory();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported type: " + type);
|
||||
}
|
||||
|
||||
return factory.createMediaSource(
|
||||
new MediaItem.Builder()
|
||||
.setTag(metadata)
|
||||
.setUri(Uri.parse(sourceUrl))
|
||||
.setLiveTargetOffsetMs(PlayerDataSource.LIVE_STREAM_EDGE_GAP_MILLIS)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -67,21 +76,30 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
|
|||
@C.ContentType final int type = TextUtils.isEmpty(overrideExtension)
|
||||
? Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension);
|
||||
|
||||
final MediaSourceFactory factory;
|
||||
switch (type) {
|
||||
case C.TYPE_SS:
|
||||
return dataSource.getLiveSsMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
factory = dataSource.getLiveSsMediaSourceFactory();
|
||||
break;
|
||||
case C.TYPE_DASH:
|
||||
return dataSource.getDashMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
factory = dataSource.getDashMediaSourceFactory();
|
||||
break;
|
||||
case C.TYPE_HLS:
|
||||
return dataSource.getHlsMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
factory = dataSource.getHlsMediaSourceFactory();
|
||||
break;
|
||||
case C.TYPE_OTHER:
|
||||
return dataSource.getExtractorMediaSourceFactory(cacheKey).setTag(metadata)
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
factory = dataSource.getExtractorMediaSourceFactory();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported type: " + type);
|
||||
}
|
||||
|
||||
return factory.createMediaSource(
|
||||
new MediaItem.Builder()
|
||||
.setTag(metadata)
|
||||
.setUri(uri)
|
||||
.setCustomCacheKey(cacheKey)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.schabi.newpipe.settings
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import org.schabi.newpipe.streams.io.SharpOutputStream
|
||||
import org.schabi.newpipe.streams.io.StoredFileHelper
|
||||
import org.schabi.newpipe.util.ZipHelper
|
||||
|
@ -13,6 +14,9 @@ import java.io.ObjectOutputStream
|
|||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
|
||||
companion object {
|
||||
const val TAG = "ContentSetManager"
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports given [SharedPreferences] to the file in given outputPath.
|
||||
|
@ -31,7 +35,7 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
|
|||
output.flush()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "Unable to exportDatabase", e)
|
||||
}
|
||||
|
||||
ZipHelper.addFileToZip(outZip, fileLocator.settings.path, "newpipe.settings")
|
||||
|
@ -101,9 +105,9 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
|
|||
preferenceEditor.commit()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "Unable to loadSharedPreferences", e)
|
||||
} catch (e: ClassNotFoundException) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "Unable to loadSharedPreferences", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,9 @@ public class MainSettingsFragment extends BasePreferenceFragment {
|
|||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResource(R.xml.main_settings);
|
||||
|
||||
if (!CheckForNewAppVersion.isGithubApk(App.getApp())) {
|
||||
final Preference update = findPreference(getString(R.string.update_pref_screen_key));
|
||||
if (!CheckForNewAppVersion.isReleaseApk(App.getApp())) {
|
||||
final Preference update
|
||||
= findPreference(getString(R.string.update_pref_screen_key));
|
||||
getPreferenceScreen().removePreference(update);
|
||||
|
||||
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key), false).apply();
|
||||
|
|
|
@ -303,8 +303,8 @@ public class PeertubeInstanceListFragment extends Fragment {
|
|||
return false;
|
||||
}
|
||||
|
||||
final int sourceIndex = source.getAdapterPosition();
|
||||
final int targetIndex = target.getAdapterPosition();
|
||||
final int sourceIndex = source.getBindingAdapterPosition();
|
||||
final int targetIndex = target.getBindingAdapterPosition();
|
||||
instanceListAdapter.swapItems(sourceIndex, targetIndex);
|
||||
return true;
|
||||
}
|
||||
|
@ -322,7 +322,7 @@ public class PeertubeInstanceListFragment extends Fragment {
|
|||
@Override
|
||||
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
|
||||
final int swipeDir) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final int position = viewHolder.getBindingAdapterPosition();
|
||||
// do not allow swiping the selected instance
|
||||
if (instanceList.get(position).getUrl().equals(selectedInstance.getUrl())) {
|
||||
instanceListAdapter.notifyItemChanged(position);
|
||||
|
|
|
@ -1,34 +1,48 @@
|
|||
package org.schabi.newpipe.settings;
|
||||
|
||||
import static org.schabi.newpipe.CheckForNewAppVersion.startNewVersionCheckService;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import static org.schabi.newpipe.CheckForNewAppVersion.startNewVersionCheckService;
|
||||
|
||||
public class UpdateSettingsFragment extends BasePreferenceFragment {
|
||||
private final Preference.OnPreferenceChangeListener updatePreferenceChange
|
||||
= (preference, checkForUpdates) -> {
|
||||
defaultPreferences.edit()
|
||||
.putBoolean(getString(R.string.update_app_key), (boolean) checkForUpdates).apply();
|
||||
|
||||
if ((boolean) checkForUpdates) {
|
||||
// Search for updates immediately when update checks are enabled.
|
||||
// Reset the expire time. This is necessary to check for an update immediately.
|
||||
defaultPreferences.edit()
|
||||
.putLong(getString(R.string.update_expiry_key), 0).apply();
|
||||
startNewVersionCheckService();
|
||||
}
|
||||
if ((boolean) checkForUpdates) {
|
||||
checkNewVersionNow();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
private final Preference.OnPreferenceClickListener manualUpdateClick
|
||||
= preference -> {
|
||||
Toast.makeText(getContext(), R.string.checking_updates_toast, Toast.LENGTH_SHORT).show();
|
||||
checkNewVersionNow();
|
||||
return true;
|
||||
};
|
||||
|
||||
private void checkNewVersionNow() {
|
||||
// Search for updates immediately when update checks are enabled.
|
||||
// Reset the expire time. This is necessary to check for an update immediately.
|
||||
defaultPreferences.edit()
|
||||
.putLong(getString(R.string.update_expiry_key), 0).apply();
|
||||
startNewVersionCheckService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResource(R.xml.update_settings);
|
||||
|
||||
final String updateToggleKey = getString(R.string.update_app_key);
|
||||
findPreference(updateToggleKey).setOnPreferenceChangeListener(updatePreferenceChange);
|
||||
findPreference(getString(R.string.update_app_key))
|
||||
.setOnPreferenceChangeListener(updatePreferenceChange);
|
||||
findPreference(getString(R.string.manual_update_key))
|
||||
.setOnPreferenceClickListener(manualUpdateClick);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -299,8 +299,8 @@ public class ChooseTabsFragment extends Fragment {
|
|||
return false;
|
||||
}
|
||||
|
||||
final int sourceIndex = source.getAdapterPosition();
|
||||
final int targetIndex = target.getAdapterPosition();
|
||||
final int sourceIndex = source.getBindingAdapterPosition();
|
||||
final int targetIndex = target.getBindingAdapterPosition();
|
||||
selectedTabsAdapter.swapItems(sourceIndex, targetIndex);
|
||||
return true;
|
||||
}
|
||||
|
@ -318,7 +318,7 @@ public class ChooseTabsFragment extends Fragment {
|
|||
@Override
|
||||
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
|
||||
final int swipeDir) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final int position = viewHolder.getBindingAdapterPosition();
|
||||
tabList.remove(position);
|
||||
selectedTabsAdapter.notifyItemRemoved(position);
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.content.pm.PackageManager;
|
|||
import android.content.res.Configuration;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import android.util.TypedValue;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
|
@ -144,4 +145,11 @@ public final class DeviceUtils {
|
|||
public static boolean isInMultiWindow(final AppCompatActivity activity) {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && activity.isInMultiWindowMode();
|
||||
}
|
||||
|
||||
public static boolean hasAnimationsAnimatorDurationEnabled(final Context context) {
|
||||
return Settings.System.getFloat(
|
||||
context.getContentResolver(),
|
||||
Settings.Global.ANIMATOR_DURATION_SCALE,
|
||||
1F) != 0F;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,7 +157,9 @@ public final class NavigationHelper {
|
|||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
|
||||
if (PlayerHolder.getInstance().getType() != PlayerType.POPUP) {
|
||||
Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback);
|
||||
intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.POPUP.ordinal());
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
|
@ -166,8 +168,10 @@ public final class NavigationHelper {
|
|||
public static void playOnBackgroundPlayer(final Context context,
|
||||
final PlayQueue queue,
|
||||
final boolean resumePlayback) {
|
||||
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
if (PlayerHolder.getInstance().getType() != MainPlayer.PlayerType.AUDIO) {
|
||||
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback);
|
||||
intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.AUDIO.ordinal());
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
|
@ -502,6 +506,27 @@ public final class NavigationHelper {
|
|||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens {@link ChannelFragment}.
|
||||
* Use this instead of {@link #openChannelFragment(FragmentManager, int, String, String)}
|
||||
* when no fragments are used / no FragmentManager is available.
|
||||
* @param context
|
||||
* @param serviceId
|
||||
* @param url
|
||||
* @param title
|
||||
*/
|
||||
public static void openChannelFragmentUsingIntent(final Context context,
|
||||
final int serviceId,
|
||||
final String url,
|
||||
@NonNull final String title) {
|
||||
final Intent intent = getOpenIntent(context, url, serviceId,
|
||||
StreamingService.LinkType.CHANNEL);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(Constants.KEY_TITLE, title);
|
||||
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
public static void openMainActivity(final Context context) {
|
||||
final Intent mIntent = new Intent(context, MainActivity.class);
|
||||
mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Selection;
|
||||
import android.text.Spannable;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
import org.schabi.newpipe.views.NewPipeEditText;
|
||||
import org.schabi.newpipe.views.NewPipeTextView;
|
||||
|
||||
public final class NewPipeTextViewHelper {
|
||||
private NewPipeTextViewHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Share the selected text of {@link NewPipeTextView NewPipeTextViews} and
|
||||
* {@link NewPipeEditText NewPipeEditTexts} with
|
||||
* {@link ShareUtils#shareText(Context, String, String)}.
|
||||
*
|
||||
* <p>
|
||||
* This allows EMUI users to get the Android share sheet instead of the EMUI share sheet when
|
||||
* using the {@code Share} command of the popup menu which appears when selecting text.
|
||||
* </p>
|
||||
*
|
||||
* @param textView the {@link TextView} on which sharing the selected text. It should be a
|
||||
* {@link NewPipeTextView} or a {@link NewPipeEditText} (even if
|
||||
* {@link TextView standard TextViews} are supported).
|
||||
*/
|
||||
public static void shareSelectedTextWithShareUtils(@NonNull final TextView textView) {
|
||||
final CharSequence textViewText = textView.getText();
|
||||
shareSelectedTextIfNotNullAndNotEmpty(textView, getSelectedText(textView, textViewText));
|
||||
if (textViewText instanceof Spannable) {
|
||||
Selection.setSelection((Spannable) textViewText, textView.getSelectionEnd());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static CharSequence getSelectedText(@NonNull final TextView textView,
|
||||
@Nullable final CharSequence text) {
|
||||
if (!textView.hasSelection() || text == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int start = textView.getSelectionStart();
|
||||
final int end = textView.getSelectionEnd();
|
||||
return String.valueOf(start > end ? text.subSequence(end, start)
|
||||
: text.subSequence(start, end));
|
||||
}
|
||||
|
||||
private static void shareSelectedTextIfNotNullAndNotEmpty(
|
||||
@NonNull final TextView textView,
|
||||
@Nullable final CharSequence selectedText) {
|
||||
if (selectedText != null && selectedText.length() != 0) {
|
||||
ShareUtils.shareText(textView.getContext(), "", selectedText.toString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,12 +5,15 @@ import android.net.Uri;
|
|||
import android.widget.Toast;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.util.external_communication.KoreUtils;
|
||||
|
@ -81,14 +84,16 @@ public enum StreamDialogEntry {
|
|||
delete(R.string.delete, (fragment, item) -> {
|
||||
}), // has to be set manually
|
||||
|
||||
append_playlist(R.string.append_playlist, (fragment, item) -> {
|
||||
final PlaylistAppendDialog d = PlaylistAppendDialog
|
||||
.fromStreamInfoItems(Collections.singletonList(item));
|
||||
|
||||
PlaylistAppendDialog.onPlaylistFound(fragment.getContext(),
|
||||
() -> d.show(fragment.getParentFragmentManager(), "StreamDialogEntry@append_playlist"),
|
||||
() -> PlaylistCreationDialog.newInstance(d)
|
||||
.show(fragment.getParentFragmentManager(), "StreamDialogEntry@create_playlist")
|
||||
append_playlist(R.string.add_to_playlist, (fragment, item) -> {
|
||||
PlaylistDialog.createCorrespondingDialog(
|
||||
fragment.getContext(),
|
||||
Collections.singletonList(new StreamEntity(item)),
|
||||
dialog -> dialog.show(
|
||||
fragment.getParentFragmentManager(),
|
||||
"StreamDialogEntry@"
|
||||
+ (dialog instanceof PlaylistAppendDialog ? "append" : "create")
|
||||
+ "_playlist"
|
||||
)
|
||||
);
|
||||
}),
|
||||
|
||||
|
@ -191,6 +196,16 @@ public enum StreamDialogEntry {
|
|||
void onClick(Fragment fragment, StreamInfoItem infoItem);
|
||||
}
|
||||
|
||||
public static boolean shouldAddMarkAsWatched(final StreamType streamType,
|
||||
final Context context) {
|
||||
final boolean isWatchHistoryEnabled = PreferenceManager
|
||||
.getDefaultSharedPreferences(context)
|
||||
.getBoolean(context.getString(R.string.enable_watch_history_key), false);
|
||||
return streamType != StreamType.AUDIO_LIVE_STREAM
|
||||
&& streamType != StreamType.LIVE_STREAM
|
||||
&& isWatchHistoryEnabled;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// private method to open channel fragment //
|
||||
/////////////////////////////////////////////
|
||||
|
|
|
@ -10,9 +10,8 @@ import javax.net.ssl.HttpsURLConnection;
|
|||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
|
||||
import static org.schabi.newpipe.MainActivity.DEBUG;
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -21,6 +20,7 @@ import static org.schabi.newpipe.MainActivity.DEBUG;
|
|||
*/
|
||||
public class TLSSocketFactoryCompat extends SSLSocketFactory {
|
||||
|
||||
private static final String TAG = "TLSSocketFactoryCom";
|
||||
|
||||
private static TLSSocketFactoryCompat instance = null;
|
||||
|
||||
|
@ -32,14 +32,6 @@ public class TLSSocketFactoryCompat extends SSLSocketFactory {
|
|||
internalSSLSocketFactory = context.getSocketFactory();
|
||||
}
|
||||
|
||||
|
||||
public TLSSocketFactoryCompat(final TrustManager[] tm)
|
||||
throws KeyManagementException, NoSuchAlgorithmException {
|
||||
final SSLContext context = SSLContext.getInstance("TLS");
|
||||
context.init(null, tm, new java.security.SecureRandom());
|
||||
internalSSLSocketFactory = context.getSocketFactory();
|
||||
}
|
||||
|
||||
public static TLSSocketFactoryCompat getInstance()
|
||||
throws NoSuchAlgorithmException, KeyManagementException {
|
||||
if (instance != null) {
|
||||
|
@ -53,9 +45,7 @@ public class TLSSocketFactoryCompat extends SSLSocketFactory {
|
|||
try {
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(getInstance());
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
if (DEBUG) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Log.e(TAG, "Unable to setAsDefault", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package org.schabi.newpipe.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatEditText;
|
||||
|
||||
import org.schabi.newpipe.util.NewPipeTextViewHelper;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
|
||||
/**
|
||||
* An {@link AppCompatEditText} which uses {@link ShareUtils#shareText(Context, String, String)}
|
||||
* when sharing selected text by using the {@code Share} command of the floating actions.
|
||||
* <p>
|
||||
* This allows NewPipe to show Android share sheet instead of EMUI share sheet when sharing text
|
||||
* from {@link AppCompatEditText} on EMUI devices.
|
||||
* </p>
|
||||
*/
|
||||
public class NewPipeEditText extends AppCompatEditText {
|
||||
|
||||
public NewPipeEditText(@NonNull final Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public NewPipeEditText(@NonNull final Context context, @Nullable final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public NewPipeEditText(@NonNull final Context context,
|
||||
@Nullable final AttributeSet attrs,
|
||||
final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTextContextMenuItem(final int id) {
|
||||
if (id == android.R.id.shareText) {
|
||||
NewPipeTextViewHelper.shareSelectedTextWithShareUtils(this);
|
||||
return true;
|
||||
}
|
||||
return super.onTextContextMenuItem(id);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.schabi.newpipe.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
import org.schabi.newpipe.util.NewPipeTextViewHelper;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
|
||||
/**
|
||||
* An {@link AppCompatTextView} which uses {@link ShareUtils#shareText(Context, String, String)}
|
||||
* when sharing selected text by using the {@code Share} command of the floating actions.
|
||||
* <p>
|
||||
* This allows NewPipe to show Android share sheet instead of EMUI share sheet when sharing text
|
||||
* from {@link AppCompatTextView} on EMUI devices.
|
||||
* </p>
|
||||
*/
|
||||
public class NewPipeTextView extends AppCompatTextView {
|
||||
|
||||
public NewPipeTextView(@NonNull final Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public NewPipeTextView(@NonNull final Context context, @Nullable final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public NewPipeTextView(@NonNull final Context context,
|
||||
@Nullable final AttributeSet attrs,
|
||||
final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTextContextMenuItem(final int id) {
|
||||
if (id == android.R.id.shareText) {
|
||||
NewPipeTextViewHelper.shareSelectedTextWithShareUtils(this);
|
||||
return true;
|
||||
}
|
||||
return super.onTextContextMenuItem(id);
|
||||
}
|
||||
}
|
|
@ -60,7 +60,7 @@
|
|||
android:padding="8dp"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/song_name"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -71,7 +71,7 @@
|
|||
android:textSize="14sp"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis nec aliquam augue, eget cursus est. Ut id tristique enim, ut scelerisque tellus. Sed ultricies ipsum non mauris ultricies, commodo malesuada velit porta." />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/artist_name"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -82,7 +82,7 @@
|
|||
tools:text="Duis posuere arcu condimentum lobortis mattis." />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/seek_display"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -269,7 +269,7 @@
|
|||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/current_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -291,7 +291,7 @@
|
|||
tools:progress="25"
|
||||
tools:secondaryProgress="50" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/end_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -301,7 +301,7 @@
|
|||
tools:ignore="HardcodedText"
|
||||
tools:text="1:23:49" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/live_sync"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
tools:ignore="ContentDescription"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/touch_append_detail"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -88,7 +88,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_duration_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -113,7 +113,7 @@
|
|||
tools:text="12:38"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_position_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -179,7 +179,7 @@
|
|||
android:paddingStart="12dp"
|
||||
tools:ignore="RtlSymmetry">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_video_title_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -225,7 +225,7 @@
|
|||
android:layout_below="@id/detail_title_root_layout"
|
||||
android:layout_marginTop="@dimen/video_item_detail_error_panel_margin"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
tools:visibility="gone" />
|
||||
|
||||
<!--HIDING ROOT-->
|
||||
<LinearLayout
|
||||
|
@ -291,7 +291,7 @@
|
|||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_sub_channel_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -307,7 +307,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:text="Channel" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_uploader_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -348,7 +348,7 @@
|
|||
android:paddingLeft="6dp"
|
||||
android:paddingRight="6dp">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_view_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -369,7 +369,7 @@
|
|||
android:contentDescription="@string/detail_likes_img_view_description"
|
||||
app:srcCompat="@drawable/ic_thumb_up" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_thumbs_up_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
|
@ -394,7 +394,7 @@
|
|||
app:srcCompat="@drawable/ic_thumb_down"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_thumbs_down_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
|
@ -408,7 +408,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:text="10K" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_thumbs_disabled_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
|
@ -436,7 +436,7 @@
|
|||
android:orientation="horizontal"
|
||||
android:padding="@dimen/detail_control_padding">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_controls_playlist_append"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
|
@ -444,7 +444,7 @@
|
|||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/append_playlist"
|
||||
android:contentDescription="@string/add_to_playlist"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="@dimen/detail_control_padding"
|
||||
|
@ -452,7 +452,7 @@
|
|||
android:textSize="@dimen/detail_control_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_playlist_add" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_controls_background"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
|
@ -468,7 +468,7 @@
|
|||
android:textSize="@dimen/detail_control_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_headset" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_controls_popup"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
|
@ -484,7 +484,7 @@
|
|||
android:textSize="@dimen/detail_control_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_picture_in_picture" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_controls_download"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
|
@ -515,7 +515,7 @@
|
|||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_controls_share"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
|
@ -531,7 +531,7 @@
|
|||
android:textSize="@dimen/detail_control_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_share" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_controls_open_in_browser"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
|
@ -547,7 +547,7 @@
|
|||
android:textSize="@dimen/detail_control_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_language" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_controls_play_with_kodi"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
|
@ -563,6 +563,22 @@
|
|||
android:textSize="@dimen/detail_control_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_cast" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_controls_crash_the_player"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/crash_the_player"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="@dimen/detail_control_padding"
|
||||
android:text="@string/crash_the_player"
|
||||
android:textSize="@dimen/detail_control_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_bug_report" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
|
@ -573,7 +589,7 @@
|
|||
android:layout_marginRight="8dp"
|
||||
android:background="?attr/separator_color" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_meta_info_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -654,7 +670,7 @@
|
|||
android:orientation="vertical"
|
||||
tools:ignore="RtlHardcoded">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/overlay_title_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -668,7 +684,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:text="The Video Title LONG very LONVideo Title LONG very LONG" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/overlay_channel_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -118,7 +118,7 @@
|
|||
android:orientation="vertical"
|
||||
tools:ignore="RtlHardcoded">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/titleTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -133,7 +133,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:text="The Video Title LONG very LONG" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/channelTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -147,7 +147,7 @@
|
|||
tools:text="The Video Artist LONG very LONG very Long" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/qualityTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="35dp"
|
||||
|
@ -161,7 +161,7 @@
|
|||
tools:ignore="HardcodedText,RtlHardcoded"
|
||||
tools:text="720p" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/playbackSpeed"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="35dp"
|
||||
|
@ -237,7 +237,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/resizeTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="35dp"
|
||||
|
@ -257,7 +257,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_weight="3">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/captionTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -369,7 +369,7 @@
|
|||
android:orientation="vertical"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/currentDisplaySeek"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -409,7 +409,7 @@
|
|||
android:paddingLeft="@dimen/player_main_controls_padding"
|
||||
android:paddingRight="@dimen/player_main_controls_padding">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/playbackCurrentTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -433,7 +433,7 @@
|
|||
tools:progress="25"
|
||||
tools:secondaryProgress="50" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/playbackEndTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -443,7 +443,7 @@
|
|||
tools:ignore="HardcodedText"
|
||||
tools:text="1:23:49" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/playbackLiveSync"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/errorSorryView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -35,21 +35,21 @@
|
|||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:text="@string/what_happened_headline"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/errorMessageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/info_labels"
|
||||
android:textColor="?attr/colorAccent" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
|
@ -61,7 +61,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/errorInfoLabelsView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -73,7 +73,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/errorInfosView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
@ -82,7 +82,7 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
|
@ -94,7 +94,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/errorView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -102,14 +102,14 @@
|
|||
android:typeface="monospace" />
|
||||
</HorizontalScrollView>
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:text="@string/your_comment"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<EditText
|
||||
<org.schabi.newpipe.views.NewPipeEditText
|
||||
android:id="@+id/errorCommentBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -121,7 +121,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:text="@string/error_report_button_text" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
app:layoutManager="LinearLayoutManager"
|
||||
tools:listitem="@layout/play_queue_item" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/seek_display"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -68,7 +68,7 @@
|
|||
android:padding="8dp"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/song_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -81,7 +81,7 @@
|
|||
android:textSize="14sp"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis nec aliquam augue, eget cursus est. Ut id tristique enim, ut scelerisque tellus. Sed ultricies ipsum non mauris ultricies, commodo malesuada velit porta." />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/artist_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -104,7 +104,7 @@
|
|||
android:paddingRight="12dp"
|
||||
android:layout_above="@+id/playback_controls">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/current_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -129,7 +129,7 @@
|
|||
tools:progress="25"
|
||||
tools:secondaryProgress="50" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/end_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -139,7 +139,7 @@
|
|||
tools:ignore="HardcodedText"
|
||||
tools:text="1:23:49" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/live_sync"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
tools:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/channel_title_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -66,7 +66,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:text="Lorem ipsum dolor" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/sub_channel_title_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -82,7 +82,7 @@
|
|||
tools:layout_below="@id/channel_title_view"
|
||||
tools:text="Lorem ipsum dolor" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/channel_subscriber_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
android:paddingTop="@dimen/video_item_search_padding"
|
||||
android:paddingRight="@dimen/video_item_search_padding">
|
||||
|
||||
<EditText
|
||||
<org.schabi.newpipe.views.NewPipeEditText
|
||||
android:id="@+id/dialogEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
app:layout_constraintStart_toEndOf="@+id/icon_preview"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<EditText
|
||||
<org.schabi.newpipe.views.NewPipeEditText
|
||||
android:id="@+id/group_name_input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -57,7 +57,7 @@
|
|||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/selected_subscription_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -117,7 +117,7 @@
|
|||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|center_vertical"
|
||||
|
@ -126,7 +126,7 @@
|
|||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/subscriptions_header_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -155,7 +155,7 @@
|
|||
tools:spanCount="4" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/delete_screen_message"
|
||||
style="@style/TextAppearance.AppCompat.Subhead"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="false"
|
||||
android:paddingLeft="@dimen/video_item_search_padding"
|
||||
android:paddingTop="@dimen/video_item_search_padding"
|
||||
android:paddingRight="@dimen/video_item_search_padding">
|
||||
android:paddingStart="6dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="6dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -15,7 +15,7 @@
|
|||
android:scrollbars="vertical">
|
||||
|
||||
<!-- START HERE -->
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/tempoControlText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -31,10 +31,10 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_below="@id/tempoControlText"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/tempoStepDown"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -62,7 +62,7 @@
|
|||
android:layout_toRightOf="@id/tempoStepDown"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/tempoMinimumText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -76,7 +76,7 @@
|
|||
tools:ignore="HardcodedText"
|
||||
tools:text="1.00x" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/tempoCurrentText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -88,7 +88,7 @@
|
|||
tools:ignore="HardcodedText"
|
||||
tools:text="100%" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/tempoMaximumText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -112,7 +112,7 @@
|
|||
tools:progress="50" />
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/tempoStepUp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -137,10 +137,13 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@id/tempoControl"
|
||||
android:layout_margin="@dimen/video_item_search_padding"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="?attr/separator_color" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/pitchControlText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -156,10 +159,10 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_below="@id/pitchControlText"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/pitchStepDown"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -177,6 +180,7 @@
|
|||
tools:text="-5%" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/pitchDisplay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="4dp"
|
||||
|
@ -187,7 +191,7 @@
|
|||
android:layout_toRightOf="@+id/pitchStepDown"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/pitchMinimumText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -201,7 +205,7 @@
|
|||
tools:ignore="HardcodedText"
|
||||
tools:text="25%" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/pitchCurrentText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -213,7 +217,7 @@
|
|||
tools:ignore="HardcodedText"
|
||||
tools:text="100%" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/pitchMaximumText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -237,7 +241,7 @@
|
|||
tools:progress="50" />
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/pitchStepUp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -262,17 +266,20 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/pitchControl"
|
||||
android:layout_margin="@dimen/video_item_search_padding"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="?attr/separator_color" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/stepSizeSelector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_below="@id/separatorStepSizeSelector"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
|
@ -282,7 +289,7 @@
|
|||
android:textColor="?attr/colorAccent"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/stepSizeOnePercent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -293,7 +300,7 @@
|
|||
android:gravity="center"
|
||||
android:textColor="?attr/colorAccent" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/stepSizeFivePercent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -304,7 +311,7 @@
|
|||
android:gravity="center"
|
||||
android:textColor="?attr/colorAccent" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/stepSizeTenPercent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -315,7 +322,7 @@
|
|||
android:gravity="center"
|
||||
android:textColor="?attr/colorAccent" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/stepSizeTwentyFivePercent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -343,32 +350,37 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/stepSizeSelector"
|
||||
android:layout_margin="@dimen/video_item_search_padding"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="?attr/separator_color" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/unhookCheckbox"
|
||||
<LinearLayout
|
||||
android:id="@+id/additionalOptions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/separatorCheckbox"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:checked="false"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:maxLines="1"
|
||||
android:text="@string/unhook_checkbox" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/skipSilenceCheckbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/unhookCheckbox"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:checked="false"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:maxLines="1"
|
||||
android:text="@string/skip_silence_checkbox" />
|
||||
<CheckBox
|
||||
android:id="@+id/unhookCheckbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:text="@string/unhook_checkbox" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/skipSilenceCheckbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:text="@string/skip_silence_checkbox" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- END HERE -->
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
app:srcCompat="@drawable/ic_playlist_add"
|
||||
tools:ignore="ContentDescription,RtlHardcoded" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_toRightOf="@+id/newPlaylistIcon"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
android:paddingTop="@dimen/video_item_search_padding"
|
||||
android:paddingRight="@dimen/video_item_search_padding">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/itemTitleView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -23,7 +23,7 @@
|
|||
android:textSize="@dimen/channel_item_detail_title_text_size"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. " />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/itemAdditionalDetails"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
android:id="@+id/toolbar_layout"
|
||||
layout="@layout/toolbar_layout" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/file_name_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -18,7 +18,7 @@
|
|||
android:layout_marginBottom="6dp"
|
||||
android:text="@string/msg_name" />
|
||||
|
||||
<EditText
|
||||
<org.schabi.newpipe.views.NewPipeEditText
|
||||
android:id="@+id/file_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -71,7 +71,7 @@
|
|||
android:minWidth="150dp"
|
||||
tools:listitem="@layout/stream_quality_item" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/threads_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -90,7 +90,7 @@
|
|||
android:orientation="horizontal"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/threads_count"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
app:srcCompat="@drawable/splash_foreground"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/drawer_header_newpipe_title"
|
||||
android:layout_width="@dimen/drawer_header_newpipe_title_default_width"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -88,7 +88,7 @@
|
|||
tools:ignore="ContentDescription"
|
||||
tools:srcCompat="@drawable/place_holder_youtube" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/drawer_header_service_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/error_message_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -17,7 +17,7 @@
|
|||
android:textStyle="bold"
|
||||
tools:text="Account terminated" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/error_message_service_info_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -29,7 +29,7 @@
|
|||
tools:text="YouTube provides this reason:"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/error_message_service_explanation_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
app:srcCompat="@drawable/ic_add"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
tools:ignore="ContentDescription"
|
||||
tools:src="@drawable/ic_fastfood" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
tools:ignore="ContentDescription,RtlHardcoded"
|
||||
tools:src="@drawable/ic_kiosk_hot" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/group_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
|
@ -64,7 +64,7 @@
|
|||
android:paddingBottom="6dp"
|
||||
tools:ignore="RtlSymmetry">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/subscription_import_export_title_height"
|
||||
android:gravity="left|center"
|
||||
|
@ -83,7 +83,7 @@
|
|||
android:layout_marginLeft="36dp"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/subscription_import_export_title_height"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
android:contentDescription="@string/app_name"
|
||||
app:srcCompat="@mipmap/ic_launcher" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
|
@ -30,7 +30,7 @@
|
|||
android:textAppearance="@android:style/TextAppearance.Large" />
|
||||
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/about_app_version"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -39,13 +39,13 @@
|
|||
android:textAppearance="@android:style/TextAppearance.Medium"
|
||||
tools:text="0.9.9" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="5dp"
|
||||
android:text="@string/app_description" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
|
@ -65,14 +65,14 @@
|
|||
android:layout_gravity="end"
|
||||
android:text="@string/view_on_github" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
android:text="@string/donation_title"
|
||||
android:textAppearance="@android:style/TextAppearance.Medium" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/donation_encouragement" />
|
||||
|
@ -85,14 +85,14 @@
|
|||
android:layout_gravity="end"
|
||||
android:text="@string/give_back" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
android:text="@string/website_title"
|
||||
android:textAppearance="@android:style/TextAppearance.Medium" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/website_encouragement" />
|
||||
|
@ -105,14 +105,14 @@
|
|||
android:layout_gravity="end"
|
||||
android:text="@string/open_in_browser" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
android:text="@string/privacy_policy_title"
|
||||
android:textAppearance="@android:style/TextAppearance.Medium" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/privacy_policy_encouragement" />
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/channel_kaomoji"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -41,7 +41,7 @@
|
|||
android:textSize="35sp"
|
||||
tools:ignore="HardcodedText,UnusedAttribute" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/channel_no_videos"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -49,7 +49,7 @@
|
|||
android:text="@string/empty_view_no_videos"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/error_content_not_supported"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/helpTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
|
@ -40,7 +40,7 @@
|
|||
android:textSize="35sp"
|
||||
tools:ignore="HardcodedText,UnusedAttribute" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/empty_state_desc"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_upload_date_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -52,7 +52,7 @@
|
|||
app:barrierDirection="top"
|
||||
app:constraint_referenced_ids="detail_description_note_view,detail_description_view" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_description_note_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -69,7 +69,7 @@
|
|||
app:layout_constraintTop_toBottomOf="@+id/detail_upload_date_view"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_description_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/refresh_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -36,7 +36,7 @@
|
|||
android:textSize="14sp"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/refresh_subtitle_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -87,6 +87,19 @@
|
|||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/new_items_loaded_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@id/swipeRefreshLayout"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginBottom="5sp"
|
||||
android:text="@string/feed_new_items"
|
||||
android:textSize="12sp"
|
||||
android:theme="@style/ServiceColoredButton"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -105,7 +118,7 @@
|
|||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/loading_progress_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/info_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -26,7 +26,7 @@
|
|||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<EditText
|
||||
<org.schabi.newpipe.views.NewPipeEditText
|
||||
android:id="@+id/input_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/instanceHelpTV"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
|
@ -41,7 +41,7 @@
|
|||
android:textSize="35sp"
|
||||
tools:ignore="HardcodedText,UnusedAttribute" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/activity_horizontal_margin"
|
||||
|
@ -21,7 +21,7 @@
|
|||
android:text="@string/app_license_title"
|
||||
android:textAppearance="@android:style/TextAppearance.Large" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/activity_horizontal_margin"
|
||||
|
@ -37,7 +37,7 @@
|
|||
android:layout_marginRight="@dimen/activity_vertical_margin"
|
||||
android:text="@string/read_full_license" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
|
@ -40,7 +40,7 @@
|
|||
android:textSize="35sp"
|
||||
tools:ignore="HardcodedText,UnusedAttribute" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
|
@ -40,7 +40,7 @@
|
|||
android:textSize="35sp"
|
||||
tools:ignore="HardcodedText,UnusedAttribute" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/correct_suggestion"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -15,7 +15,7 @@
|
|||
android:textSize="@dimen/search_suggestion_text_size"
|
||||
tools:text="Showing results for lorem ipsum dolor sit amet consectetur adipisci elit" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/search_meta_info_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -61,7 +61,7 @@
|
|||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
|
@ -71,7 +71,7 @@
|
|||
android:textSize="35sp"
|
||||
tools:ignore="HardcodedText,UnusedAttribute" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
tools:ignore="ContentDescription"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/touch_append_detail"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -78,7 +78,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_duration_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -103,7 +103,7 @@
|
|||
tools:text="12:38"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_position_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -167,7 +167,7 @@
|
|||
android:paddingStart="12dp"
|
||||
tools:ignore="RtlSymmetry">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_video_title_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -213,7 +213,7 @@
|
|||
android:layout_below="@id/detail_title_root_layout"
|
||||
android:layout_marginTop="@dimen/video_item_detail_error_panel_margin"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
tools:visibility="gone" />
|
||||
|
||||
<!--HIDING ROOT-->
|
||||
<LinearLayout
|
||||
|
@ -280,7 +280,7 @@
|
|||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_sub_channel_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -295,7 +295,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:text="Channel" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_uploader_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -336,7 +336,7 @@
|
|||
android:paddingLeft="6dp"
|
||||
android:paddingRight="6dp">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_view_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -357,7 +357,7 @@
|
|||
android:contentDescription="@string/detail_likes_img_view_description"
|
||||
app:srcCompat="@drawable/ic_thumb_up" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_thumbs_up_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
|
@ -382,7 +382,7 @@
|
|||
app:srcCompat="@drawable/ic_thumb_down"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_thumbs_down_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
|
@ -396,7 +396,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:text="10K" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_thumbs_disabled_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
|
@ -422,7 +422,7 @@
|
|||
android:orientation="horizontal"
|
||||
android:padding="@dimen/detail_control_padding">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_controls_playlist_append"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
|
@ -430,7 +430,7 @@
|
|||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/append_playlist"
|
||||
android:contentDescription="@string/add_to_playlist"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="@dimen/detail_control_padding"
|
||||
|
@ -438,7 +438,7 @@
|
|||
android:textSize="@dimen/detail_control_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_playlist_add" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_controls_background"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
|
@ -454,7 +454,7 @@
|
|||
android:textSize="@dimen/detail_control_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_headset" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_controls_popup"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
|
@ -470,7 +470,7 @@
|
|||
android:textSize="@dimen/detail_control_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_picture_in_picture" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_controls_download"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
|
@ -499,7 +499,7 @@
|
|||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_controls_share"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
|
@ -515,7 +515,7 @@
|
|||
android:textSize="@dimen/detail_control_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_share" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_controls_open_in_browser"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
|
@ -531,7 +531,7 @@
|
|||
android:textSize="@dimen/detail_control_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_language" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_controls_play_with_kodi"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
|
@ -547,6 +547,22 @@
|
|||
android:textSize="@dimen/detail_control_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_cast" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_controls_crash_the_player"
|
||||
android:layout_width="@dimen/detail_control_width"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/crash_the_player"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="@dimen/detail_control_padding"
|
||||
android:text="@string/crash_the_player"
|
||||
android:textSize="@dimen/detail_control_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_bug_report" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
|
@ -557,7 +573,7 @@
|
|||
android:layout_marginRight="8dp"
|
||||
android:background="?attr/separator_color" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_meta_info_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -630,7 +646,7 @@
|
|||
android:theme="@style/ContrastTintTheme"
|
||||
tools:ignore="RtlHardcoded">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/overlay_title_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -644,7 +660,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:text="The Video Title LONG very LONVideo Title LONG very LONG" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/overlay_channel_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
android:paddingRight="16dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/header_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
app:layout_constraintStart_toEndOf="@id/previewImage"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/textViewTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -45,7 +45,7 @@
|
|||
android:textSize="@dimen/video_item_search_title_text_size"
|
||||
tools:text="Lorem ipusum is widely used to create long sample text which is used here too" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/textViewChannel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -56,7 +56,7 @@
|
|||
|
||||
tools:text="Lorem ipsum creator" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/textViewStartSeconds"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
tools:ignore="ContentDescription,RtlHardcoded"
|
||||
tools:src="@drawable/ic_kiosk_hot" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/tabName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
android:src="@drawable/buddy"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/itemTitleView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -36,7 +36,7 @@
|
|||
android:textSize="@dimen/comment_item_title_text_size"
|
||||
tools:text="Author Name, Lorem ipsum" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/itemCommentContentView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -58,7 +58,7 @@
|
|||
android:contentDescription="@string/detail_likes_img_view_description"
|
||||
app:srcCompat="@drawable/ic_thumb_up" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_thumbs_up_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
|
@ -97,7 +97,7 @@
|
|||
app:srcCompat="?attr/thumbs_down"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_thumbs_down_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
|
@ -111,7 +111,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:text="10K" />-->
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/itemPublishedTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/itemCommentContentView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -40,7 +40,7 @@
|
|||
android:contentDescription="@string/detail_likes_img_view_description"
|
||||
app:srcCompat="@drawable/ic_thumb_up" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_thumbs_up_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
|
@ -66,7 +66,7 @@
|
|||
app:srcCompat="?attr/thumbs_down"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/detail_thumbs_down_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
|
@ -80,7 +80,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:text="10K" />-->
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/itemPublishedTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
android:minHeight="128dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="¯\\_(ツ)_/¯"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
android:src="@drawable/dummy_thumbnail_playlist"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/itemStreamCountView"
|
||||
android:layout_width="@dimen/playlist_item_thumbnail_stream_count_width"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -41,7 +41,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:text="314159" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/itemTitleView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -56,7 +56,7 @@
|
|||
tools:ignore="RtlHardcoded"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum" />
|
||||
|
||||
<TextView
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/itemUploaderView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue