diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3ba13e0ce..5a97b3662 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,8 @@ blank_issues_enabled: false +contact_links: + - name: 💬 IRC + url: https://webchat.freenode.net/#newpipe + about: Chat with us via IRC for quick Q/A + - name: 💬 Matrix + url: https://matrix.to/#/#freenode_#newpipe:matrix.org + about: Chat with us via Matrix for quick Q/A diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f8960bdc..a3ea00e03 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,13 @@ name: CI -on: [push, pull_request] +on: + pull_request: + branches: + - dev + push: + branches: + - dev + - master jobs: build-and-test: @@ -33,3 +40,34 @@ jobs: with: name: app path: app/build/outputs/apk/debug/*.apk +# sonar: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v2 +# with: +# fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + +# - name: Set up JDK 11 +# uses: actions/setup-java@v1.4.3 +# with: +# java-version: 11 # Sonar requires JDK 11 + +# - name: Cache SonarCloud packages +# uses: actions/cache@v2 +# with: +# path: ~/.sonar/cache +# key: ${{ runner.os }}-sonar +# restore-keys: ${{ runner.os }}-sonar + +# - name: Cache Gradle packages +# uses: actions/cache@v2 +# with: +# path: ~/.gradle/caches +# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} +# restore-keys: ${{ runner.os }}-gradle + +# - name: Build and analyze +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any +# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} +# run: ./gradlew build sonarqube --info diff --git a/README.es.md b/README.es.md new file mode 100644 index 000000000..0aa198d2c --- /dev/null +++ b/README.es.md @@ -0,0 +1,140 @@ +
+Capturas de pantalla • Descripción • Características • Installación y actualizaciones • Contribución • Donar • Licencias
+Sitio web • Blog • Preguntas Frecuentes • Prensa
++ | + | 16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh | +
+ | + | + |
+ | + | + |
Website-ka • Maqaalada • Su'aalaha Aalaa La-iswaydiiyo • Warbaahinta
\n");
}
// add the logs
- for (int i = 0; i < errorList.length; i++) {
+ for (int i = 0; i < errorInfo.getStackTraces().length; i++) {
htmlErrorReport.append(" \n")
- .append("\n```\n").append(errorList[i]).append("\n```\n")
+ .append("\n```\n").append(errorInfo.getStackTraces()[i]).append("\n```\n")
.append("Crash log ");
- if (errorList.length > 1) {
+ if (errorInfo.getStackTraces().length > 1) {
htmlErrorReport.append(i + 1);
}
htmlErrorReport.append("")
.append("
@@ -82,6 +81,10 @@ public class KioskFragment extends BaseListInfoFragment
+ * This is necessary when the thumbnail's height is larger than the device's height
+ * and thus is enlarging the player's height
+ * causing the bottom playback controls to be out of the visible screen.
+ *
+ * The calculating follows these rules:
+ *
+ * It will return true if the device 's theme is dark, false otherwise.
+ *
+ * From https://developer.android.com/guide/topics/ui/look-and-feel/darktheme#java
+ *
+ * @param context the context to use
+ * @return true:dark theme, false:light or unknown
+ */
+ public static boolean isDeviceDarkThemeEnabled(final Context context) {
+ final int deviceTheme = context.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK;
+ switch (deviceTheme) {
+ case Configuration.UI_MODE_NIGHT_YES:
+ return true;
+ case Configuration.UI_MODE_NIGHT_UNDEFINED:
+ case Configuration.UI_MODE_NIGHT_NO:
+ default:
+ return false;
+ }
+ }
}
diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
index eb45485e0..919f63026 100644
--- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
+++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
@@ -45,9 +45,9 @@ import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.LocalPlayerActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.report.ErrorActivity;
-import org.schabi.newpipe.report.ErrorInfo;
-import org.schabi.newpipe.report.UserAction;
+import org.schabi.newpipe.error.ErrorActivity;
+import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils;
@@ -609,16 +609,12 @@ public class MissionAdapter extends Adapter> flowable = historyRecordManager
.getRelatedSearches(query, 3, 25);
@@ -763,8 +749,8 @@ public class SearchFragment extends BaseListFragment
disposables.add(deleteReactor
.observeOn(AndroidSchedulers.mainThread())
- .subscribe(ignored -> { /*Do nothing on success*/ }, this::onError))
- )
+ .subscribe(ignored -> { /*Do nothing on success*/ }, throwable ->
+ showError(new ErrorInfo(throwable,
+ UserAction.REQUESTED_BOOKMARK,
+ "Deleting playlist")))))
.setNegativeButton(R.string.cancel, null)
.show();
}
@@ -314,7 +307,10 @@ public final class BookmarkFragment extends BaseLocalListFragment
{ /*Do nothing on success*/ }, this::onError);
+ .subscribe(longs -> { /*Do nothing on success*/ }, throwable -> showError(
+ new ErrorInfo(throwable,
+ UserAction.REQUESTED_BOOKMARK,
+ "Changing playlist name")));
disposables.add(disposable);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt
index 04090abc6..1df999144 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt
@@ -38,17 +38,18 @@ import icepick.State
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.databinding.FragmentFeedBinding
+import org.schabi.newpipe.error.ErrorInfo
+import org.schabi.newpipe.error.UserAction
import org.schabi.newpipe.fragments.list.BaseListFragment
import org.schabi.newpipe.ktx.animate
+import org.schabi.newpipe.ktx.animateHideRecyclerViewAllowingScrolling
import org.schabi.newpipe.local.feed.service.FeedLoadService
-import org.schabi.newpipe.report.UserAction
import org.schabi.newpipe.util.Localization
-import java.util.Calendar
+import java.time.OffsetDateTime
class FeedFragment : BaseListFragment
removeWatchedStreams(false))
- .setNeutralButton(
- R.string.remove_watched_popup_yes_and_partially_watched_videos,
- (DialogInterface d, int id) -> removeWatchedStreams(true))
- .setNegativeButton(R.string.cancel,
- (DialogInterface d, int id) -> d.cancel())
- .create()
- .show();
- }
- break;
- default:
- return super.onOptionsItemSelected(item);
+ if (item.getItemId() == R.id.menu_item_remove_watched) {
+ if (!isRemovingWatched) {
+ new AlertDialog.Builder(requireContext())
+ .setMessage(R.string.remove_watched_popup_warning)
+ .setTitle(R.string.remove_watched_popup_title)
+ .setPositiveButton(R.string.yes,
+ (DialogInterface d, int id) -> removeWatchedStreams(false))
+ .setNeutralButton(
+ R.string.remove_watched_popup_yes_and_partially_watched_videos,
+ (DialogInterface d, int id) -> removeWatchedStreams(true))
+ .setNegativeButton(R.string.cancel,
+ (DialogInterface d, int id) -> d.cancel())
+ .create()
+ .show();
+ }
+ } else {
+ return super.onOptionsItemSelected(item);
}
return true;
}
@@ -455,7 +455,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
+ "Removing watched videos, partially watched=" + removePartiallyWatched))));
}
@Override
@@ -511,17 +512,6 @@ public class LocalPlaylistFragment extends BaseLocalListFragment
{ /*Do nothing on success*/ }, this::onError);
+ .subscribe(longs -> { /*Do nothing on success*/ }, throwable ->
+ showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
+ "Renaming playlist")));
disposables.add(disposable);
}
@@ -583,7 +575,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment
successToast.show(), this::onError);
+ .subscribe(ignore -> successToast.show(), throwable ->
+ showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
+ "Changing playlist thumbnail")));
disposables.add(disposable);
}
@@ -632,7 +626,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment
saveImmediate(), this::onError);
+ .subscribe(ignored -> saveImmediate(), throwable ->
+ showError(new ErrorInfo(throwable, UserAction.SOMETHING_ELSE,
+ "Debounced saver")));
}
private void saveImmediate() {
@@ -669,7 +665,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment
showError(new ErrorInfo(throwable,
+ UserAction.REQUESTED_BOOKMARK, "Saving playlist"))
);
disposables.add(disposable);
}
@@ -683,7 +680,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment
() {
binding.itemsList.adapter = groupAdapter
viewModel = ViewModelProvider(this).get(SubscriptionViewModel::class.java)
- viewModel.stateLiveData.observe(viewLifecycleOwner, { it?.let(this::handleResult) })
- viewModel.feedGroupsLiveData.observe(viewLifecycleOwner, { it?.let(this::handleFeedGroups) })
+ viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(this::handleResult) }
+ viewModel.feedGroupsLiveData.observe(viewLifecycleOwner) { it?.let(this::handleFeedGroups) }
}
private fun showLongTapDialog(selectedItem: ChannelInfoItem) {
@@ -381,7 +382,9 @@ class SubscriptionFragment : BaseStateFragment
+ *
+ *
+ * @return the maximum height for the end screen thumbnail
+ */
+ private float calculateMaxEndScreenThumbnailHeight() {
+ // ensure that screenHeight is initialized and thus not 0
+ updateScreenSize();
+
+ if (DeviceUtils.isTv(context) && !isFullscreen) {
+ final int videoInfoHeight =
+ DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(16, context);
+ return Math.min(currentThumbnail.getHeight(), screenHeight - videoInfoHeight);
+ } else if (DeviceUtils.isTablet(context) && service.isLandscape() && !isFullscreen) {
+ final int videoInfoHeight =
+ DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(15, context);
+ return Math.min(currentThumbnail.getHeight(), screenHeight - videoInfoHeight);
+ } else { // fullscreen player: max height is the device height
+ return Math.min(currentThumbnail.getHeight(), screenHeight);
+ }
+ }
+
@Override
public void onLoadingStarted(final String imageUri, final View view) {
if (DEBUG) {
@@ -1230,23 +1309,29 @@ public final class Player implements
@Override
public void onLoadingComplete(final String imageUri, final View view,
final Bitmap loadedImage) {
- final float width = Math.min(
+ // scale down the notification thumbnail for performance
+ final float notificationThumbnailWidth = Math.min(
context.getResources().getDimension(R.dimen.player_notification_thumbnail_width),
loadedImage.getWidth());
+ currentThumbnail = Bitmap.createScaledBitmap(
+ loadedImage,
+ (int) notificationThumbnailWidth,
+ (int) (loadedImage.getHeight()
+ / (loadedImage.getWidth() / notificationThumbnailWidth)),
+ true);
if (DEBUG) {
Log.d(TAG, "Thumbnail - onLoadingComplete() called with: "
+ "imageUri = [" + imageUri + "], view = [" + view + "], "
+ "loadedImage = [" + loadedImage + "], "
+ loadedImage.getWidth() + "x" + loadedImage.getHeight()
- + ", scaled width = " + width);
+ + ", scaled notification width = " + notificationThumbnailWidth);
}
- currentThumbnail = Bitmap.createScaledBitmap(loadedImage,
- (int) width,
- (int) (loadedImage.getHeight() / (loadedImage.getWidth() / width)), true);
- binding.endScreen.setImageBitmap(loadedImage);
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
+
+ // there is a new thumbnail, thus the end screen thumbnail needs to be changed, too.
+ updateEndScreenThumbnail();
}
@Override
@@ -1455,7 +1540,8 @@ public final class Player implements
}
public boolean getPlaybackSkipSilence() {
- return getPlaybackParameters().skipSilence;
+ return !exoPlayerIsNull() && simpleExoPlayer.getAudioComponent() != null
+ && simpleExoPlayer.getAudioComponent().getSkipSilenceEnabled();
}
public PlaybackParameters getPlaybackParameters() {
@@ -1480,7 +1566,10 @@ public final class Player implements
savePlaybackParametersToPrefs(this, roundedSpeed, roundedPitch, skipSilence);
simpleExoPlayer.setPlaybackParameters(
- new PlaybackParameters(roundedSpeed, roundedPitch, skipSilence));
+ new PlaybackParameters(roundedSpeed, roundedPitch));
+ if (simpleExoPlayer.getAudioComponent() != null) {
+ simpleExoPlayer.getAudioComponent().setSkipSilenceEnabled(skipSilence);
+ }
}
//endregion
@@ -2432,6 +2521,7 @@ public final class Player implements
case ExoPlaybackException.TYPE_OUT_OF_MEMORY:
case ExoPlaybackException.TYPE_REMOTE:
case ExoPlaybackException.TYPE_RENDERER:
+ case ExoPlaybackException.TYPE_TIMEOUT:
default:
showUnrecoverableError(error);
onPlaybackShutdown();
@@ -3469,7 +3559,7 @@ public final class Player implements
final List85dp
free space for {@link R.id.detail_root}
+ * and additional space for the stream title text size
+ * ({@link R.id.detail_title_root_layout}).
+ * The text size is 15sp
on tablets and 16sp
on TVs,
+ * see {@link R.id.titleTextView}.
+ * > getSubscriptionObserver() {
return new Observer
>() {
@Override
- public void onSubscribe(final Disposable d) { }
+ public void onSubscribe(@NonNull final Disposable disposable) { }
@Override
- public void onNext(final List