FALLBACK_INITIAL_TABS_LIST = List.of(
Tab.Type.DEFAULT_KIOSK.getTab(),
+ Tab.Type.FEED.getTab(),
Tab.Type.SUBSCRIPTIONS.getTab(),
Tab.Type.BOOKMARKS.getTab());
diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java
index 2836fe52b..c885b803c 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java
@@ -73,7 +73,7 @@ public final class TabsManager {
private SharedPreferences.OnSharedPreferenceChangeListener getPreferenceChangeListener() {
return (sp, key) -> {
- if (key.equals(savedTabsKey)) {
+ if (savedTabsKey.equals(key)) {
if (savedTabsChangeListener != null) {
savedTabsChangeListener.onTabsChanged();
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
deleted file mode 100644
index 7c87e664b..000000000
--- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package org.schabi.newpipe.util;
-
-import android.text.Layout;
-import android.text.Selection;
-import android.text.Spannable;
-import android.text.Spanned;
-import android.text.style.ClickableSpan;
-import android.text.style.URLSpan;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.TextView;
-
-import org.schabi.newpipe.util.external_communication.ShareUtils;
-import org.schabi.newpipe.util.external_communication.InternalUrlsHandler;
-
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-
-public class CommentTextOnTouchListener implements View.OnTouchListener {
- public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener();
-
- @Override
- public boolean onTouch(final View v, final MotionEvent event) {
- if (!(v instanceof TextView)) {
- return false;
- }
- final TextView widget = (TextView) v;
- final Object text = widget.getText();
- if (text instanceof Spanned) {
- final Spannable buffer = (Spannable) text;
-
- final int action = event.getAction();
-
- if (action == MotionEvent.ACTION_UP
- || action == MotionEvent.ACTION_DOWN) {
- int x = (int) event.getX();
- int y = (int) event.getY();
-
- x -= widget.getTotalPaddingLeft();
- y -= widget.getTotalPaddingTop();
-
- x += widget.getScrollX();
- y += widget.getScrollY();
-
- final Layout layout = widget.getLayout();
- final int line = layout.getLineForVertical(y);
- final int off = layout.getOffsetForHorizontal(line, x);
-
- final ClickableSpan[] link = buffer.getSpans(off, off,
- ClickableSpan.class);
-
- if (link.length != 0) {
- if (action == MotionEvent.ACTION_UP) {
- if (link[0] instanceof URLSpan) {
- final String url = ((URLSpan) link[0]).getURL();
- if (!InternalUrlsHandler.handleUrlCommentsTimestamp(
- new CompositeDisposable(), v.getContext(), url)) {
- ShareUtils.openUrlInBrowser(v.getContext(), url, false);
- }
- }
- } else if (action == MotionEvent.ACTION_DOWN) {
- Selection.setSelection(buffer,
- buffer.getSpanStart(link[0]),
- buffer.getSpanEnd(link[0]));
- }
- return true;
- }
- }
- }
- return false;
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java
index 46ab6da51..4b08cfcb5 100644
--- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java
@@ -1,5 +1,7 @@
package org.schabi.newpipe.util;
+import static android.content.Context.INPUT_SERVICE;
+
import android.annotation.SuppressLint;
import android.app.UiModeManager;
import android.content.Context;
@@ -27,11 +29,10 @@ import org.schabi.newpipe.R;
import java.lang.reflect.Method;
-import static android.content.Context.INPUT_SERVICE;
-
public final class DeviceUtils {
private static final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv";
+ private static final boolean SAMSUNG = Build.MANUFACTURER.equals("samsung");
private static Boolean isTV = null;
private static Boolean isFireTV = null;
@@ -120,6 +121,10 @@ public final class DeviceUtils {
return true;
}
+ if (!SAMSUNG) {
+ return false;
+ // DeX is Samsung-specific, skip the checks below on non-Samsung devices
+ }
// DeX check for standalone and multi-window mode, from:
// https://developer.samsung.com/samsung-dex/modify-optimizing.html
try {
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
index 27009efd1..d5d472d6f 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
@@ -20,6 +20,7 @@
package org.schabi.newpipe.util;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
+import static org.schabi.newpipe.util.text.TextLinkifier.SET_LINK_MOVEMENT_METHOD;
import android.content.Context;
import android.util.Log;
@@ -51,7 +52,7 @@ import org.schabi.newpipe.extractor.search.SearchInfo;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
-import org.schabi.newpipe.util.external_communication.TextLinkifier;
+import org.schabi.newpipe.util.text.TextLinkifier;
import java.util.Collections;
import java.util.List;
@@ -319,8 +320,9 @@ public final class ExtractorHelper {
}
metaInfoSeparator.setVisibility(View.VISIBLE);
- TextLinkifier.createLinksFromHtmlBlock(metaInfoTextView, stringBuilder.toString(),
- HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING, null, disposables);
+ TextLinkifier.fromHtml(metaInfoTextView, stringBuilder.toString(),
+ HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING, null, null, disposables,
+ SET_LINK_MOVEMENT_METHOD);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index 13413e89d..b4556507c 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -156,8 +156,7 @@ public final class NavigationHelper {
public static void playOnPopupPlayer(final Context context,
final PlayQueue queue,
final boolean resumePlayback) {
- if (!PermissionHelper.isPopupEnabled(context)) {
- PermissionHelper.showPopupEnablementToast(context);
+ if (!PermissionHelper.isPopupEnabledElseAsk(context)) {
return;
}
@@ -183,6 +182,10 @@ public final class NavigationHelper {
public static void enqueueOnPlayer(final Context context,
final PlayQueue queue,
final PlayerType playerType) {
+ if (playerType == PlayerType.POPUP && !PermissionHelper.isPopupEnabledElseAsk(context)) {
+ return;
+ }
+
Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show();
final Intent intent = getPlayerEnqueueIntent(context, PlayerService.class, queue);
diff --git a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java
index f47494770..55193599e 100644
--- a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java
@@ -9,8 +9,6 @@ import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
-import android.view.Gravity;
-import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.RequiresApi;
@@ -128,18 +126,21 @@ public final class PermissionHelper {
}
}
- public static boolean isPopupEnabled(final Context context) {
- return Build.VERSION.SDK_INT < Build.VERSION_CODES.M
- || checkSystemAlertWindowPermission(context);
- }
-
- public static void showPopupEnablementToast(final Context context) {
- final Toast toast =
- Toast.makeText(context, R.string.msg_popup_permission, Toast.LENGTH_LONG);
- final TextView messageView = toast.getView().findViewById(android.R.id.message);
- if (messageView != null) {
- messageView.setGravity(Gravity.CENTER);
+ /**
+ * Determines whether the popup is enabled, and if it is not, starts the system activity to
+ * request the permission with {@link #checkSystemAlertWindowPermission(Context)} and shows a
+ * toast to the user explaining why the permission is needed.
+ *
+ * @param context the Android context
+ * @return whether the popup is enabled
+ */
+ public static boolean isPopupEnabledElseAsk(final Context context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M
+ || checkSystemAlertWindowPermission(context)) {
+ return true;
+ } else {
+ Toast.makeText(context, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
+ return false;
}
- toast.show();
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
index ea22e9368..ab74e0305 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
@@ -41,6 +41,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+import org.schabi.newpipe.info_list.ItemViewMode;
public final class ThemeHelper {
private ThemeHelper() {
@@ -332,7 +333,6 @@ public final class ThemeHelper {
}
}
-
/**
* Returns whether the grid layout or the list layout should be used. If the user set "auto"
* mode in settings, decides based on screen orientation (landscape) and size.
@@ -341,19 +341,8 @@ public final class ThemeHelper {
* @return true:use grid layout, false:use list layout
*/
public static boolean shouldUseGridLayout(final Context context) {
- final String listMode = PreferenceManager.getDefaultSharedPreferences(context)
- .getString(context.getString(R.string.list_view_mode_key),
- context.getString(R.string.list_view_mode_value));
-
- if (listMode.equals(context.getString(R.string.list_view_mode_list_key))) {
- return false;
- } else if (listMode.equals(context.getString(R.string.list_view_mode_grid_key))) {
- return true;
- } else /* listMode.equals("auto") */ {
- final Configuration configuration = context.getResources().getConfiguration();
- return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
- && configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
- }
+ final ItemViewMode mode = getItemViewMode(context);
+ return mode == ItemViewMode.GRID;
}
/**
@@ -367,6 +356,36 @@ public final class ThemeHelper {
context.getResources().getDimensionPixelSize(R.dimen.channel_item_grid_min_width));
}
+ /**
+ * Returns item view mode.
+ * @param context to read preference and parse string
+ * @return Returns one of ItemViewMode
+ */
+ public static ItemViewMode getItemViewMode(final Context context) {
+ final String listMode = PreferenceManager.getDefaultSharedPreferences(context)
+ .getString(context.getString(R.string.list_view_mode_key),
+ context.getString(R.string.list_view_mode_value));
+ final ItemViewMode result;
+ if (listMode.equals(context.getString(R.string.list_view_mode_list_key))) {
+ result = ItemViewMode.LIST;
+ } else if (listMode.equals(context.getString(R.string.list_view_mode_grid_key))) {
+ result = ItemViewMode.GRID;
+ } else if (listMode.equals(context.getString(R.string.list_view_mode_card_key))) {
+ result = ItemViewMode.CARD;
+ } else {
+ // Auto mode - evaluate whether to use Grid based on screen real estate.
+ final Configuration configuration = context.getResources().getConfiguration();
+ final boolean useGrid = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+ && configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
+ if (useGrid) {
+ result = ItemViewMode.GRID;
+ } else {
+ result = ItemViewMode.LIST;
+ }
+ }
+ return result;
+ }
+
/**
* Calculates the number of grid stream info items that can fit horizontally on the screen. The
* width of a grid stream info item is obtained from the thumbnail width plus the right and left
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
index 9829ddd2e..06dd3f945 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
@@ -90,19 +90,16 @@ public final class ShareUtils {
// No browser set as default (doesn't work on some devices)
openAppChooser(context, intent, true);
} else {
- if (defaultPackageName.isEmpty()) {
- // No app installed to open a web url
- Toast.makeText(context, R.string.no_app_to_open_intent, Toast.LENGTH_LONG).show();
- return false;
- } else {
- try {
+ try {
+ // will be empty on Android 12+
+ if (!defaultPackageName.isEmpty()) {
intent.setPackage(defaultPackageName);
- context.startActivity(intent);
- } catch (final ActivityNotFoundException e) {
- // Not a browser but an app chooser because of OEMs changes
- intent.setPackage(null);
- openAppChooser(context, intent, true);
}
+ context.startActivity(intent);
+ } catch (final ActivityNotFoundException e) {
+ // Not a browser but an app chooser because of OEMs changes
+ intent.setPackage(null);
+ openAppChooser(context, intent, true);
}
}
@@ -313,10 +310,15 @@ public final class ShareUtils {
return;
}
- clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text));
- if (Build.VERSION.SDK_INT < 33) {
- // Android 13 has its own "copied to clipboard" dialog
- Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
+ try {
+ clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text));
+ if (Build.VERSION.SDK_INT < 33) {
+ // Android 13 has its own "copied to clipboard" dialog
+ Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
+ }
+ } catch (final Exception e) {
+ Log.e(TAG, "Error when trying to copy text to clipboard", e);
+ Toast.makeText(context, R.string.msg_failed_to_copy, Toast.LENGTH_SHORT).show();
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
deleted file mode 100644
index 8b8eb265b..000000000
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
+++ /dev/null
@@ -1,289 +0,0 @@
-package org.schabi.newpipe.util.external_communication;
-
-import android.content.Context;
-import android.text.SpannableStringBuilder;
-import android.text.method.LinkMovementMethod;
-import android.text.style.ClickableSpan;
-import android.text.style.URLSpan;
-import android.text.util.Linkify;
-import android.util.Log;
-import android.view.View;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.text.HtmlCompat;
-
-import org.schabi.newpipe.extractor.Info;
-import org.schabi.newpipe.extractor.stream.StreamInfo;
-import org.schabi.newpipe.util.NavigationHelper;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import io.noties.markwon.Markwon;
-import io.noties.markwon.linkify.LinkifyPlugin;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-import static org.schabi.newpipe.util.external_communication.InternalUrlsHandler.playOnPopup;
-
-public final class TextLinkifier {
- public static final String TAG = TextLinkifier.class.getSimpleName();
-
- // Looks for hashtags with characters from any language (\p{L}), numbers, or underscores
- private static final Pattern HASHTAGS_PATTERN =
- Pattern.compile("(#[\\p{L}0-9_]+)");
-
- private TextLinkifier() {
- }
-
- /**
- * Create web links for contents with an HTML description.
- *
- * This will call {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence,
- * Info, CompositeDisposable)} after having linked the URLs with
- * {@link HtmlCompat#fromHtml(String, int)}.
- *
- * @param textView the TextView to set the htmlBlock linked
- * @param htmlBlock the htmlBlock to be linked
- * @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)}
- * will be called
- * @param relatedInfo if given, handle timestamps to open the stream in the popup player at
- * the specific time, and hashtags to search for the term in the correct
- * service
- * @param disposables disposables created by the method are added here and their lifecycle
- * should be handled by the calling class
- */
- public static void createLinksFromHtmlBlock(@NonNull final TextView textView,
- final String htmlBlock,
- final int htmlCompatFlag,
- @Nullable final Info relatedInfo,
- final CompositeDisposable disposables) {
- changeIntentsOfDescriptionLinks(
- textView, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), relatedInfo, disposables);
- }
-
- /**
- * Create web links for contents with a plain text description.
- *
- * This will call {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence,
- * Info, CompositeDisposable)} after having linked the URLs with
- * {@link TextView#setAutoLinkMask(int)} and
- * {@link TextView#setText(CharSequence, TextView.BufferType)}.
- *
- * @param textView the TextView to set the plain text block linked
- * @param plainTextBlock the block of plain text to be linked
- * @param relatedInfo if given, handle timestamps to open the stream in the popup player at
- * the specific time, and hashtags to search for the term in the correct
- * service
- * @param disposables disposables created by the method are added here and their lifecycle
- * should be handled by the calling class
- */
- public static void createLinksFromPlainText(@NonNull final TextView textView,
- final String plainTextBlock,
- @Nullable final Info relatedInfo,
- final CompositeDisposable disposables) {
- textView.setAutoLinkMask(Linkify.WEB_URLS);
- textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE);
- changeIntentsOfDescriptionLinks(textView, textView.getText(), relatedInfo, disposables);
- }
-
- /**
- * Create web links for contents with a markdown description.
- *
- * This will call {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence,
- * Info, CompositeDisposable)} after creating an {@link Markwon} object and using
- * {@link Markwon#setMarkdown(TextView, String)}.
- *
- * @param textView the TextView to set the plain text block linked
- * @param markdownBlock the block of markdown text to be linked
- * @param relatedInfo if given, handle timestamps to open the stream in the popup player at
- * the specific time, and hashtags to search for the term in the correct
- * @param disposables disposables created by the method are added here and their lifecycle
- * should be handled by the calling class
- */
- public static void createLinksFromMarkdownText(@NonNull final TextView textView,
- final String markdownBlock,
- @Nullable final Info relatedInfo,
- final CompositeDisposable disposables) {
- final Markwon markwon = Markwon.builder(textView.getContext())
- .usePlugin(LinkifyPlugin.create()).build();
- changeIntentsOfDescriptionLinks(textView, markwon.toMarkdown(markdownBlock), relatedInfo,
- disposables);
- }
-
- /**
- * Add click listeners which opens a search on hashtags in a plain text.
- *
- * This method finds all timestamps in the {@link SpannableStringBuilder} of the description
- * using a regular expression, adds for each a {@link ClickableSpan} which opens
- * {@link NavigationHelper#openSearch(Context, int, String)} and makes a search on the hashtag,
- * in the service of the content.
- *
- * @param context the context to use
- * @param spannableDescription the SpannableStringBuilder with the text of the
- * content description
- * @param relatedInfo used to search for the term in the correct service
- */
- private static void addClickListenersOnHashtags(final Context context,
- @NonNull final SpannableStringBuilder
- spannableDescription,
- final Info relatedInfo) {
- final String descriptionText = spannableDescription.toString();
- final Matcher hashtagsMatches = HASHTAGS_PATTERN.matcher(descriptionText);
-
- while (hashtagsMatches.find()) {
- final int hashtagStart = hashtagsMatches.start(1);
- final int hashtagEnd = hashtagsMatches.end(1);
- final String parsedHashtag = descriptionText.substring(hashtagStart, hashtagEnd);
-
- // don't add a ClickableSpan if there is already one, which should be a part of an URL,
- // already parsed before
- if (spannableDescription.getSpans(hashtagStart, hashtagEnd,
- ClickableSpan.class).length == 0) {
- spannableDescription.setSpan(new ClickableSpan() {
- @Override
- public void onClick(@NonNull final View view) {
- NavigationHelper.openSearch(context, relatedInfo.getServiceId(),
- parsedHashtag);
- }
- }, hashtagStart, hashtagEnd, 0);
- }
- }
- }
-
- /**
- * Add click listeners which opens the popup player on timestamps in a plain text.
- *
- * This method finds all timestamps in the {@link SpannableStringBuilder} of the description
- * using a regular expression, adds for each a {@link ClickableSpan} which opens the popup
- * player at the time indicated in the timestamps.
- *
- * @param context the context to use
- * @param spannableDescription the SpannableStringBuilder with the text of the
- * content description
- * @param relatedInfo what to open in the popup player when timestamps are clicked
- * @param disposables disposables created by the method are added here and their
- * lifecycle should be handled by the calling class
- */
- private static void addClickListenersOnTimestamps(final Context context,
- @NonNull final SpannableStringBuilder
- spannableDescription,
- final Info relatedInfo,
- final CompositeDisposable disposables) {
- final String descriptionText = spannableDescription.toString();
- final Matcher timestampsMatches =
- TimestampExtractor.TIMESTAMPS_PATTERN.matcher(descriptionText);
-
- while (timestampsMatches.find()) {
- final TimestampExtractor.TimestampMatchDTO timestampMatchDTO =
- TimestampExtractor.getTimestampFromMatcher(
- timestampsMatches,
- descriptionText);
-
- if (timestampMatchDTO == null) {
- continue;
- }
-
- spannableDescription.setSpan(
- new ClickableSpan() {
- @Override
- public void onClick(@NonNull final View view) {
- playOnPopup(
- context,
- relatedInfo.getUrl(),
- relatedInfo.getService(),
- timestampMatchDTO.seconds(),
- disposables);
- }
- },
- timestampMatchDTO.timestampStart(),
- timestampMatchDTO.timestampEnd(),
- 0);
- }
- }
-
- /**
- * Change links generated by libraries in the description of a content to a custom link action
- * and add click listeners on timestamps in this description.
- *
- * Instead of using an {@link android.content.Intent#ACTION_VIEW} intent in the description of
- * a content, this method will parse the {@link CharSequence} and replace all current web links
- * with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
- * This method will also add click listeners on timestamps in this description, which will play
- * the content in the popup player at the time indicated in the timestamp, by using
- * {@link TextLinkifier#addClickListenersOnTimestamps(Context, SpannableStringBuilder, Info,
- * CompositeDisposable)} method and click listeners on hashtags, by using
- * {@link TextLinkifier#addClickListenersOnHashtags(Context, SpannableStringBuilder, Info)},
- * which will open a search on the current service with the hashtag.
- *
- * This method is required in order to intercept links and e.g. show a confirmation dialog
- * before opening a web link.
- *
- * @param textView the TextView in which the converted CharSequence will be applied
- * @param chars the CharSequence to be parsed
- * @param relatedInfo if given, handle timestamps to open the stream in the popup player at
- * the specific time, and hashtags to search for the term in the correct
- * service
- * @param disposables disposables created by the method are added here and their lifecycle
- * should be handled by the calling class
- */
- private static void changeIntentsOfDescriptionLinks(final TextView textView,
- final CharSequence chars,
- @Nullable final Info relatedInfo,
- final CompositeDisposable disposables) {
- disposables.add(Single.fromCallable(() -> {
- final Context context = textView.getContext();
-
- // add custom click actions on web links
- final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars);
- final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class);
-
- for (final URLSpan span : urls) {
- final String url = span.getURL();
- final ClickableSpan clickableSpan = new ClickableSpan() {
- public void onClick(@NonNull final View view) {
- if (!InternalUrlsHandler.handleUrlDescriptionTimestamp(
- new CompositeDisposable(), context, url)) {
- ShareUtils.openUrlInBrowser(context, url, false);
- }
- }
- };
-
- textBlockLinked.setSpan(clickableSpan, textBlockLinked.getSpanStart(span),
- textBlockLinked.getSpanEnd(span), textBlockLinked.getSpanFlags(span));
- textBlockLinked.removeSpan(span);
- }
-
- // add click actions on plain text timestamps only for description of contents,
- // unneeded for meta-info or other TextViews
- if (relatedInfo != null) {
- if (relatedInfo instanceof StreamInfo) {
- addClickListenersOnTimestamps(context, textBlockLinked, relatedInfo,
- disposables);
- }
- addClickListenersOnHashtags(context, textBlockLinked, relatedInfo);
- }
-
- return textBlockLinked;
- }).subscribeOn(Schedulers.computation())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- textBlockLinked -> setTextViewCharSequence(textView, textBlockLinked),
- throwable -> {
- Log.e(TAG, "Unable to linkify text", throwable);
- // this should never happen, but if it does, just fallback to it
- setTextViewCharSequence(textView, chars);
- }));
- }
-
- private static void setTextViewCharSequence(@NonNull final TextView textView,
- final CharSequence charSequence) {
- textView.setText(charSequence);
- textView.setMovementMethod(LinkMovementMethod.getInstance());
- textView.setVisibility(View.VISIBLE);
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/util/text/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/text/CommentTextOnTouchListener.java
new file mode 100644
index 000000000..5018a6120
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/text/CommentTextOnTouchListener.java
@@ -0,0 +1,42 @@
+package org.schabi.newpipe.util.text;
+
+import static org.schabi.newpipe.util.text.TouchUtils.getOffsetForHorizontalLine;
+
+import android.annotation.SuppressLint;
+import android.text.Spanned;
+import android.text.style.ClickableSpan;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.TextView;
+
+public class CommentTextOnTouchListener implements View.OnTouchListener {
+ public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener();
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouch(final View v, final MotionEvent event) {
+ if (!(v instanceof TextView)) {
+ return false;
+ }
+ final TextView widget = (TextView) v;
+ final CharSequence text = widget.getText();
+ if (text instanceof Spanned) {
+ final Spanned buffer = (Spanned) text;
+ final int action = event.getAction();
+
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
+ final int offset = getOffsetForHorizontalLine(widget, event);
+ final ClickableSpan[] links = buffer.getSpans(offset, offset, ClickableSpan.class);
+
+ if (links.length != 0) {
+ if (action == MotionEvent.ACTION_UP) {
+ links[0].onClick(widget);
+ }
+ // we handle events that intersect links, so return true
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/util/text/HashtagLongPressClickableSpan.java b/app/src/main/java/org/schabi/newpipe/util/text/HashtagLongPressClickableSpan.java
new file mode 100644
index 000000000..8a0363ecb
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/text/HashtagLongPressClickableSpan.java
@@ -0,0 +1,36 @@
+package org.schabi.newpipe.util.text;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import org.schabi.newpipe.util.NavigationHelper;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
+
+final class HashtagLongPressClickableSpan extends LongPressClickableSpan {
+
+ @NonNull
+ private final Context context;
+ @NonNull
+ private final String parsedHashtag;
+ private final int relatedInfoServiceId;
+
+ HashtagLongPressClickableSpan(@NonNull final Context context,
+ @NonNull final String parsedHashtag,
+ final int relatedInfoServiceId) {
+ this.context = context;
+ this.parsedHashtag = parsedHashtag;
+ this.relatedInfoServiceId = relatedInfoServiceId;
+ }
+
+ @Override
+ public void onClick(@NonNull final View view) {
+ NavigationHelper.openSearch(context, relatedInfoServiceId, parsedHashtag);
+ }
+
+ @Override
+ public void onLongClick(@NonNull final View view) {
+ ShareUtils.copyToClipboard(context, parsedHashtag);
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java b/app/src/main/java/org/schabi/newpipe/util/text/InternalUrlsHandler.java
similarity index 99%
rename from app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
rename to app/src/main/java/org/schabi/newpipe/util/text/InternalUrlsHandler.java
index c46e6636d..b87618922 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
+++ b/app/src/main/java/org/schabi/newpipe/util/text/InternalUrlsHandler.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe.util.external_communication;
+package org.schabi.newpipe.util.text;
import android.content.Context;
import android.util.Log;
diff --git a/app/src/main/java/org/schabi/newpipe/util/text/LongPressClickableSpan.java b/app/src/main/java/org/schabi/newpipe/util/text/LongPressClickableSpan.java
new file mode 100644
index 000000000..5c94a5850
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/text/LongPressClickableSpan.java
@@ -0,0 +1,12 @@
+package org.schabi.newpipe.util.text;
+
+import android.text.style.ClickableSpan;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+public abstract class LongPressClickableSpan extends ClickableSpan {
+
+ public abstract void onLongClick(@NonNull View view);
+
+}
diff --git a/app/src/main/java/org/schabi/newpipe/util/text/LongPressLinkMovementMethod.java b/app/src/main/java/org/schabi/newpipe/util/text/LongPressLinkMovementMethod.java
new file mode 100644
index 000000000..bd57621cb
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/text/LongPressLinkMovementMethod.java
@@ -0,0 +1,77 @@
+package org.schabi.newpipe.util.text;
+
+import static org.schabi.newpipe.util.text.TouchUtils.getOffsetForHorizontalLine;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.method.LinkMovementMethod;
+import android.text.method.MovementMethod;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+// Class adapted from https://stackoverflow.com/a/31786969
+
+public class LongPressLinkMovementMethod extends LinkMovementMethod {
+
+ private static final int LONG_PRESS_TIME = ViewConfiguration.getLongPressTimeout();
+
+ private static LongPressLinkMovementMethod instance;
+
+ private Handler longClickHandler;
+ private boolean isLongPressed = false;
+
+ @Override
+ public boolean onTouchEvent(@NonNull final TextView widget,
+ @NonNull final Spannable buffer,
+ @NonNull final MotionEvent event) {
+ final int action = event.getAction();
+
+ if (action == MotionEvent.ACTION_CANCEL && longClickHandler != null) {
+ longClickHandler.removeCallbacksAndMessages(null);
+ }
+
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
+ final int offset = getOffsetForHorizontalLine(widget, event);
+ final LongPressClickableSpan[] link = buffer.getSpans(offset, offset,
+ LongPressClickableSpan.class);
+
+ if (link.length != 0) {
+ if (action == MotionEvent.ACTION_UP) {
+ if (longClickHandler != null) {
+ longClickHandler.removeCallbacksAndMessages(null);
+ }
+ if (!isLongPressed) {
+ link[0].onClick(widget);
+ }
+ isLongPressed = false;
+ } else {
+ Selection.setSelection(buffer, buffer.getSpanStart(link[0]),
+ buffer.getSpanEnd(link[0]));
+ if (longClickHandler != null) {
+ longClickHandler.postDelayed(() -> {
+ link[0].onLongClick(widget);
+ isLongPressed = true;
+ }, LONG_PRESS_TIME);
+ }
+ }
+ return true;
+ }
+ }
+
+ return super.onTouchEvent(widget, buffer, event);
+ }
+
+ public static MovementMethod getInstance() {
+ if (instance == null) {
+ instance = new LongPressLinkMovementMethod();
+ instance.longClickHandler = new Handler(Looper.myLooper());
+ }
+
+ return instance;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/util/text/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/text/TextLinkifier.java
new file mode 100644
index 000000000..e59a3dc05
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/text/TextLinkifier.java
@@ -0,0 +1,369 @@
+package org.schabi.newpipe.util.text;
+
+import android.content.Context;
+import android.text.SpannableStringBuilder;
+import android.text.style.URLSpan;
+import android.text.util.Linkify;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.text.HtmlCompat;
+
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.extractor.stream.Description;
+import org.schabi.newpipe.util.NavigationHelper;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
+
+import java.util.function.Consumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import io.noties.markwon.Markwon;
+import io.noties.markwon.linkify.LinkifyPlugin;
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+
+public final class TextLinkifier {
+ public static final String TAG = TextLinkifier.class.getSimpleName();
+
+ // Looks for hashtags with characters from any language (\p{L}), numbers, or underscores
+ private static final Pattern HASHTAGS_PATTERN = Pattern.compile("(#[\\p{L}0-9_]+)");
+
+ public static final Consumer SET_LINK_MOVEMENT_METHOD =
+ v -> v.setMovementMethod(LongPressLinkMovementMethod.getInstance());
+
+ private TextLinkifier() {
+ }
+
+ /**
+ * Create links for contents with an {@link Description} in the various possible formats.
+ *
+ * This will call one of these three functions based on the format: {@link #fromHtml},
+ * {@link #fromMarkdown} or {@link #fromPlainText}.
+ *
+ * @param textView the TextView to set the htmlBlock linked
+ * @param description the htmlBlock to be linked
+ * @param htmlCompatFlag the int flag to be set if {@link HtmlCompat#fromHtml(String, int)}
+ * will be called (not used for formats different than HTML)
+ * @param relatedInfoService if given, handle hashtags to search for the term in the correct
+ * service
+ * @param relatedStreamUrl if given, used alongside {@code relatedInfoService} to handle
+ * timestamps to open the stream in the popup player at the specific
+ * time
+ * @param disposables disposables created by the method are added here and their
+ * lifecycle should be handled by the calling class
+ * @param onCompletion will be run when setting text to the textView completes; use {@link
+ * #SET_LINK_MOVEMENT_METHOD} to make links clickable and focusable
+ */
+ public static void fromDescription(@NonNull final TextView textView,
+ @NonNull final Description description,
+ final int htmlCompatFlag,
+ @Nullable final StreamingService relatedInfoService,
+ @Nullable final String relatedStreamUrl,
+ @NonNull final CompositeDisposable disposables,
+ @Nullable final Consumer onCompletion) {
+ switch (description.getType()) {
+ case Description.HTML:
+ TextLinkifier.fromHtml(textView, description.getContent(), htmlCompatFlag,
+ relatedInfoService, relatedStreamUrl, disposables, onCompletion);
+ break;
+ case Description.MARKDOWN:
+ TextLinkifier.fromMarkdown(textView, description.getContent(),
+ relatedInfoService, relatedStreamUrl, disposables, onCompletion);
+ break;
+ case Description.PLAIN_TEXT: default:
+ TextLinkifier.fromPlainText(textView, description.getContent(),
+ relatedInfoService, relatedStreamUrl, disposables, onCompletion);
+ break;
+ }
+ }
+
+ /**
+ * Create links for contents with an HTML description.
+ *
+ *
+ * This method will call {@link #changeLinkIntents(TextView, CharSequence, StreamingService,
+ * String, CompositeDisposable, Consumer)} after having linked the URLs with
+ * {@link HtmlCompat#fromHtml(String, int)}.
+ *
+ *
+ * @param textView the {@link TextView} to set the the HTML string block linked
+ * @param htmlBlock the HTML string block to be linked
+ * @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String,
+ * int)} will be called
+ * @param relatedInfoService if given, handle hashtags to search for the term in the correct
+ * service
+ * @param relatedStreamUrl if given, used alongside {@code relatedInfoService} to handle
+ * timestamps to open the stream in the popup player at the specific
+ * time
+ * @param disposables disposables created by the method are added here and their
+ * lifecycle should be handled by the calling class
+ * @param onCompletion will be run when setting text to the textView completes; use {@link
+ * #SET_LINK_MOVEMENT_METHOD} to make links clickable and focusable
+ */
+ public static void fromHtml(@NonNull final TextView textView,
+ @NonNull final String htmlBlock,
+ final int htmlCompatFlag,
+ @Nullable final StreamingService relatedInfoService,
+ @Nullable final String relatedStreamUrl,
+ @NonNull final CompositeDisposable disposables,
+ @Nullable final Consumer onCompletion) {
+ changeLinkIntents(
+ textView, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), relatedInfoService,
+ relatedStreamUrl, disposables, onCompletion);
+ }
+
+ /**
+ * Create links for contents with a plain text description.
+ *
+ *
+ * This method will call {@link #changeLinkIntents(TextView, CharSequence, StreamingService,
+ * String, CompositeDisposable, Consumer)} after having linked the URLs with
+ * {@link TextView#setAutoLinkMask(int)} and
+ * {@link TextView#setText(CharSequence, TextView.BufferType)}.
+ *
+ *
+ * @param textView the {@link TextView} to set the plain text block linked
+ * @param plainTextBlock the block of plain text to be linked
+ * @param relatedInfoService if given, handle hashtags to search for the term in the correct
+ * service
+ * @param relatedStreamUrl if given, used alongside {@code relatedInfoService} to handle
+ * timestamps to open the stream in the popup player at the specific
+ * time
+ * @param disposables disposables created by the method are added here and their
+ * lifecycle should be handled by the calling class
+ * @param onCompletion will be run when setting text to the textView completes; use {@link
+ * #SET_LINK_MOVEMENT_METHOD} to make links clickable and focusable
+ */
+ public static void fromPlainText(@NonNull final TextView textView,
+ @NonNull final String plainTextBlock,
+ @Nullable final StreamingService relatedInfoService,
+ @Nullable final String relatedStreamUrl,
+ @NonNull final CompositeDisposable disposables,
+ @Nullable final Consumer onCompletion) {
+ textView.setAutoLinkMask(Linkify.WEB_URLS);
+ textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE);
+ changeLinkIntents(textView, textView.getText(), relatedInfoService,
+ relatedStreamUrl, disposables, onCompletion);
+ }
+
+ /**
+ * Create links for contents with a markdown description.
+ *
+ *
+ * This method will call {@link #changeLinkIntents(TextView, CharSequence, StreamingService,
+ * String, CompositeDisposable, Consumer)} after creating a {@link Markwon} object and using
+ * {@link Markwon#setMarkdown(TextView, String)}.
+ *
+ *
+ * @param textView the {@link TextView} to set the plain text block linked
+ * @param markdownBlock the block of markdown text to be linked
+ * @param relatedInfoService if given, handle hashtags to search for the term in the correct
+ * service
+ * @param relatedStreamUrl if given, used alongside {@code relatedInfoService} to handle
+ * timestamps to open the stream in the popup player at the specific
+ * time
+ * @param disposables disposables created by the method are added here and their
+ * lifecycle should be handled by the calling class
+ * @param onCompletion will be run when setting text to the textView completes; use {@link
+ * #SET_LINK_MOVEMENT_METHOD} to make links clickable and focusable
+ */
+ public static void fromMarkdown(@NonNull final TextView textView,
+ @NonNull final String markdownBlock,
+ @Nullable final StreamingService relatedInfoService,
+ @Nullable final String relatedStreamUrl,
+ @NonNull final CompositeDisposable disposables,
+ @Nullable final Consumer onCompletion) {
+ final Markwon markwon = Markwon.builder(textView.getContext())
+ .usePlugin(LinkifyPlugin.create()).build();
+ changeLinkIntents(textView, markwon.toMarkdown(markdownBlock),
+ relatedInfoService, relatedStreamUrl, disposables, onCompletion);
+ }
+
+ /**
+ * Change links generated by libraries in the description of a content to a custom link action
+ * and add click listeners on timestamps in this description.
+ *
+ *
+ * Instead of using an {@link android.content.Intent#ACTION_VIEW} intent in the description of
+ * a content, this method will parse the {@link CharSequence} and replace all current web links
+ * with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
+ *
+ *
+ *
+ * This method will also add click listeners on timestamps in this description, which will play
+ * the content in the popup player at the time indicated in the timestamp, by using
+ * {@link TextLinkifier#addClickListenersOnTimestamps(Context, SpannableStringBuilder,
+ * StreamingService, String, CompositeDisposable)} method and click listeners on hashtags, by
+ * using {@link TextLinkifier#addClickListenersOnHashtags(Context, SpannableStringBuilder,
+ * StreamingService)}, which will open a search on the current service with the hashtag.
+ *
+ *
+ *
+ * This method is required in order to intercept links and e.g. show a confirmation dialog
+ * before opening a web link.
+ *
+ *
+ * @param textView the {@link TextView} to which the converted {@link CharSequence}
+ * will be applied
+ * @param chars the {@link CharSequence} to be parsed
+ * @param relatedInfoService if given, handle hashtags to search for the term in the correct
+ * service
+ * @param relatedStreamUrl if given, used alongside {@code relatedInfoService} to handle
+ * timestamps to open the stream in the popup player at the specific
+ * time
+ * @param disposables disposables created by the method are added here and their
+ * lifecycle should be handled by the calling class
+ * @param onCompletion will be run when setting text to the textView completes; use {@link
+ * #SET_LINK_MOVEMENT_METHOD} to make links clickable and focusable
+ */
+ private static void changeLinkIntents(@NonNull final TextView textView,
+ @NonNull final CharSequence chars,
+ @Nullable final StreamingService relatedInfoService,
+ @Nullable final String relatedStreamUrl,
+ @NonNull final CompositeDisposable disposables,
+ @Nullable final Consumer onCompletion) {
+ disposables.add(Single.fromCallable(() -> {
+ final Context context = textView.getContext();
+
+ // add custom click actions on web links
+ final SpannableStringBuilder textBlockLinked =
+ new SpannableStringBuilder(chars);
+ final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(),
+ URLSpan.class);
+
+ for (final URLSpan span : urls) {
+ final String url = span.getURL();
+ final LongPressClickableSpan longPressClickableSpan =
+ new UrlLongPressClickableSpan(context, disposables, url);
+
+ textBlockLinked.setSpan(longPressClickableSpan,
+ textBlockLinked.getSpanStart(span),
+ textBlockLinked.getSpanEnd(span),
+ textBlockLinked.getSpanFlags(span));
+ textBlockLinked.removeSpan(span);
+ }
+
+ // add click actions on plain text timestamps only for description of contents,
+ // unneeded for meta-info or other TextViews
+ if (relatedInfoService != null) {
+ if (relatedStreamUrl != null) {
+ addClickListenersOnTimestamps(context, textBlockLinked,
+ relatedInfoService, relatedStreamUrl, disposables);
+ }
+ addClickListenersOnHashtags(context, textBlockLinked, relatedInfoService);
+ }
+
+ return textBlockLinked;
+ }).subscribeOn(Schedulers.computation())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ textBlockLinked ->
+ setTextViewCharSequence(textView, textBlockLinked, onCompletion),
+ throwable -> {
+ Log.e(TAG, "Unable to linkify text", throwable);
+ // this should never happen, but if it does, just fallback to it
+ setTextViewCharSequence(textView, chars, onCompletion);
+ }));
+ }
+
+ /**
+ * Add click listeners which opens a search on hashtags in a plain text.
+ *
+ *
+ * This method finds all timestamps in the {@link SpannableStringBuilder} of the description
+ * using a regular expression, adds for each a {@link LongPressClickableSpan} which opens
+ * {@link NavigationHelper#openSearch(Context, int, String)} and makes a search on the hashtag,
+ * in the service of the content when pressed, and copy the hashtag to clipboard when
+ * long-pressed, if allowed by the caller method (parameter {@code addLongClickCopyListener}).
+ *
+ *
+ * @param context the {@link Context} to use
+ * @param spannableDescription the {@link SpannableStringBuilder} with the text of the
+ * content description
+ * @param relatedInfoService used to search for the term in the correct service
+ */
+ private static void addClickListenersOnHashtags(
+ @NonNull final Context context,
+ @NonNull final SpannableStringBuilder spannableDescription,
+ @NonNull final StreamingService relatedInfoService) {
+ final String descriptionText = spannableDescription.toString();
+ final Matcher hashtagsMatches = HASHTAGS_PATTERN.matcher(descriptionText);
+
+ while (hashtagsMatches.find()) {
+ final int hashtagStart = hashtagsMatches.start(1);
+ final int hashtagEnd = hashtagsMatches.end(1);
+ final String parsedHashtag = descriptionText.substring(hashtagStart, hashtagEnd);
+
+ // Don't add a LongPressClickableSpan if there is already one, which should be a part
+ // of an URL, already parsed before
+ if (spannableDescription.getSpans(hashtagStart, hashtagEnd,
+ LongPressClickableSpan.class).length == 0) {
+ final int serviceId = relatedInfoService.getServiceId();
+ spannableDescription.setSpan(
+ new HashtagLongPressClickableSpan(context, parsedHashtag, serviceId),
+ hashtagStart, hashtagEnd, 0);
+ }
+ }
+ }
+
+ /**
+ * Add click listeners which opens the popup player on timestamps in a plain text.
+ *
+ *
+ * This method finds all timestamps in the {@link SpannableStringBuilder} of the description
+ * using a regular expression, adds for each a {@link LongPressClickableSpan} which opens the
+ * popup player at the time indicated in the timestamps and copy the timestamp in clipboard
+ * when long-pressed.
+ *
+ *
+ * @param context the {@link Context} to use
+ * @param spannableDescription the {@link SpannableStringBuilder} with the text of the
+ * content description
+ * @param relatedInfoService the service of the {@code relatedStreamUrl}
+ * @param relatedStreamUrl what to open in the popup player when timestamps are clicked
+ * @param disposables disposables created by the method are added here and their
+ * lifecycle should be handled by the calling class
+ */
+ private static void addClickListenersOnTimestamps(
+ @NonNull final Context context,
+ @NonNull final SpannableStringBuilder spannableDescription,
+ @NonNull final StreamingService relatedInfoService,
+ @NonNull final String relatedStreamUrl,
+ @NonNull final CompositeDisposable disposables) {
+ final String descriptionText = spannableDescription.toString();
+ final Matcher timestampsMatches = TimestampExtractor.TIMESTAMPS_PATTERN.matcher(
+ descriptionText);
+
+ while (timestampsMatches.find()) {
+ final TimestampExtractor.TimestampMatchDTO timestampMatchDTO =
+ TimestampExtractor.getTimestampFromMatcher(timestampsMatches, descriptionText);
+
+ if (timestampMatchDTO == null) {
+ continue;
+ }
+
+ spannableDescription.setSpan(
+ new TimestampLongPressClickableSpan(context, descriptionText, disposables,
+ relatedInfoService, relatedStreamUrl, timestampMatchDTO),
+ timestampMatchDTO.timestampStart(),
+ timestampMatchDTO.timestampEnd(),
+ 0);
+ }
+ }
+
+ private static void setTextViewCharSequence(@NonNull final TextView textView,
+ @Nullable final CharSequence charSequence,
+ @Nullable final Consumer onCompletion) {
+ textView.setText(charSequence);
+ textView.setVisibility(View.VISIBLE);
+ if (onCompletion != null) {
+ onCompletion.accept(textView);
+ }
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/TimestampExtractor.java b/app/src/main/java/org/schabi/newpipe/util/text/TimestampExtractor.java
similarity index 78%
rename from app/src/main/java/org/schabi/newpipe/util/external_communication/TimestampExtractor.java
rename to app/src/main/java/org/schabi/newpipe/util/text/TimestampExtractor.java
index a13c66402..be603f41a 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/TimestampExtractor.java
+++ b/app/src/main/java/org/schabi/newpipe/util/text/TimestampExtractor.java
@@ -1,4 +1,7 @@
-package org.schabi.newpipe.util.external_communication;
+package org.schabi.newpipe.util.text;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -15,17 +18,18 @@ public final class TimestampExtractor {
}
/**
- * Get's a single timestamp from a matcher.
+ * Gets a single timestamp from a matcher.
*
- * @param timestampMatches The matcher which was created using {@link #TIMESTAMPS_PATTERN}
- * @param baseText The text where the pattern was applied to /
- * where the matcher is based upon
- * @return If a match occurred: a {@link TimestampMatchDTO} filled with information.
- * If not null
.
+ * @param timestampMatches the matcher which was created using {@link #TIMESTAMPS_PATTERN}
+ * @param baseText the text where the pattern was applied to / where the matcher is
+ * based upon
+ * @return if a match occurred, a {@link TimestampMatchDTO} filled with information, otherwise
+ * {@code null}.
*/
+ @Nullable
public static TimestampMatchDTO getTimestampFromMatcher(
- final Matcher timestampMatches,
- final String baseText) {
+ @NonNull final Matcher timestampMatches,
+ @NonNull final String baseText) {
int timestampStart = timestampMatches.start(1);
if (timestampStart == -1) {
timestampStart = timestampMatches.start(2);
diff --git a/app/src/main/java/org/schabi/newpipe/util/text/TimestampLongPressClickableSpan.java b/app/src/main/java/org/schabi/newpipe/util/text/TimestampLongPressClickableSpan.java
new file mode 100644
index 000000000..f5864794a
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/text/TimestampLongPressClickableSpan.java
@@ -0,0 +1,78 @@
+package org.schabi.newpipe.util.text;
+
+import static org.schabi.newpipe.util.text.InternalUrlsHandler.playOnPopup;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import org.schabi.newpipe.extractor.ServiceList;
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
+
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+
+final class TimestampLongPressClickableSpan extends LongPressClickableSpan {
+
+ @NonNull
+ private final Context context;
+ @NonNull
+ private final String descriptionText;
+ @NonNull
+ private final CompositeDisposable disposables;
+ @NonNull
+ private final StreamingService relatedInfoService;
+ @NonNull
+ private final String relatedStreamUrl;
+ @NonNull
+ private final TimestampExtractor.TimestampMatchDTO timestampMatchDTO;
+
+ TimestampLongPressClickableSpan(
+ @NonNull final Context context,
+ @NonNull final String descriptionText,
+ @NonNull final CompositeDisposable disposables,
+ @NonNull final StreamingService relatedInfoService,
+ @NonNull final String relatedStreamUrl,
+ @NonNull final TimestampExtractor.TimestampMatchDTO timestampMatchDTO) {
+ this.context = context;
+ this.descriptionText = descriptionText;
+ this.disposables = disposables;
+ this.relatedInfoService = relatedInfoService;
+ this.relatedStreamUrl = relatedStreamUrl;
+ this.timestampMatchDTO = timestampMatchDTO;
+ }
+
+ @Override
+ public void onClick(@NonNull final View view) {
+ playOnPopup(context, relatedStreamUrl, relatedInfoService,
+ timestampMatchDTO.seconds(), disposables);
+ }
+
+ @Override
+ public void onLongClick(@NonNull final View view) {
+ ShareUtils.copyToClipboard(context, getTimestampTextToCopy(
+ relatedInfoService, relatedStreamUrl, descriptionText, timestampMatchDTO));
+ }
+
+ @NonNull
+ private static String getTimestampTextToCopy(
+ @NonNull final StreamingService relatedInfoService,
+ @NonNull final String relatedStreamUrl,
+ @NonNull final String descriptionText,
+ @NonNull final TimestampExtractor.TimestampMatchDTO timestampMatchDTO) {
+ // TODO: use extractor methods to get timestamps when this feature will be implemented in it
+ if (relatedInfoService == ServiceList.YouTube) {
+ return relatedStreamUrl + "&t=" + timestampMatchDTO.seconds();
+ } else if (relatedInfoService == ServiceList.SoundCloud
+ || relatedInfoService == ServiceList.MediaCCC) {
+ return relatedStreamUrl + "#t=" + timestampMatchDTO.seconds();
+ } else if (relatedInfoService == ServiceList.PeerTube) {
+ return relatedStreamUrl + "?start=" + timestampMatchDTO.seconds();
+ }
+
+ // Return timestamp text for other services
+ return descriptionText.subSequence(timestampMatchDTO.timestampStart(),
+ timestampMatchDTO.timestampEnd()).toString();
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/util/text/TouchUtils.java b/app/src/main/java/org/schabi/newpipe/util/text/TouchUtils.java
new file mode 100644
index 000000000..5c0db20a3
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/text/TouchUtils.java
@@ -0,0 +1,38 @@
+package org.schabi.newpipe.util.text;
+
+import android.text.Layout;
+import android.view.MotionEvent;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+public final class TouchUtils {
+
+ private TouchUtils() {
+ }
+
+ /**
+ * Get the character offset on the closest line to the position pressed by the user of a
+ * {@link TextView} from a {@link MotionEvent} which was fired on this {@link TextView}.
+ *
+ * @param textView the {@link TextView} on which the {@link MotionEvent} was fired
+ * @param event the {@link MotionEvent} which was fired
+ * @return the character offset on the closest line to the position pressed by the user
+ */
+ public static int getOffsetForHorizontalLine(@NonNull final TextView textView,
+ @NonNull final MotionEvent event) {
+
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ x -= textView.getTotalPaddingLeft();
+ y -= textView.getTotalPaddingTop();
+
+ x += textView.getScrollX();
+ y += textView.getScrollY();
+
+ final Layout layout = textView.getLayout();
+ final int line = layout.getLineForVertical(y);
+ return layout.getOffsetForHorizontal(line, x);
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/util/text/UrlLongPressClickableSpan.java b/app/src/main/java/org/schabi/newpipe/util/text/UrlLongPressClickableSpan.java
new file mode 100644
index 000000000..eb0d7425e
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/text/UrlLongPressClickableSpan.java
@@ -0,0 +1,41 @@
+package org.schabi.newpipe.util.text;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import org.schabi.newpipe.util.external_communication.ShareUtils;
+
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+
+final class UrlLongPressClickableSpan extends LongPressClickableSpan {
+
+ @NonNull
+ private final Context context;
+ @NonNull
+ private final CompositeDisposable disposables;
+ @NonNull
+ private final String url;
+
+ UrlLongPressClickableSpan(@NonNull final Context context,
+ @NonNull final CompositeDisposable disposables,
+ @NonNull final String url) {
+ this.context = context;
+ this.disposables = disposables;
+ this.url = url;
+ }
+
+ @Override
+ public void onClick(@NonNull final View view) {
+ if (!InternalUrlsHandler.handleUrlDescriptionTimestamp(
+ disposables, context, url)) {
+ ShareUtils.openUrlInBrowser(context, url, false);
+ }
+ }
+
+ @Override
+ public void onLongClick(@NonNull final View view) {
+ ShareUtils.copyToClipboard(context, url);
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/views/NewPipeEditText.java b/app/src/main/java/org/schabi/newpipe/views/NewPipeEditText.java
index 2adc28d0e..f0993055e 100644
--- a/app/src/main/java/org/schabi/newpipe/views/NewPipeEditText.java
+++ b/app/src/main/java/org/schabi/newpipe/views/NewPipeEditText.java
@@ -13,9 +13,10 @@ 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.
+ *
*
- * This allows NewPipe to show Android share sheet instead of EMUI share sheet when sharing text
- * from {@link AppCompatEditText} on EMUI devices.
+ * This class allows NewPipe to show Android share sheet instead of EMUI share sheet when sharing
+ * text from {@link AppCompatEditText} on EMUI devices.
*
*/
public class NewPipeEditText extends AppCompatEditText {
diff --git a/app/src/main/java/org/schabi/newpipe/views/NewPipeTextView.java b/app/src/main/java/org/schabi/newpipe/views/NewPipeTextView.java
index 8fdac32db..dd3f20f40 100644
--- a/app/src/main/java/org/schabi/newpipe/views/NewPipeTextView.java
+++ b/app/src/main/java/org/schabi/newpipe/views/NewPipeTextView.java
@@ -1,6 +1,7 @@
package org.schabi.newpipe.views;
import android.content.Context;
+import android.text.method.MovementMethod;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
@@ -13,9 +14,11 @@ 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.
+ *
*
- * This allows NewPipe to show Android share sheet instead of EMUI share sheet when sharing text
- * from {@link AppCompatTextView} on EMUI devices.
+ * This class allows NewPipe to show Android share sheet instead of EMUI share sheet when sharing
+ * text from {@link AppCompatTextView} on EMUI devices and also to keep movement method set when a
+ * text change occurs, if the text cannot be selected and text links are clickable.
*
*/
public class NewPipeTextView extends AppCompatTextView {
@@ -34,6 +37,16 @@ public class NewPipeTextView extends AppCompatTextView {
super(context, attrs, defStyleAttr);
}
+ @Override
+ public void setText(final CharSequence text, final BufferType type) {
+ // We need to set again the movement method after a text change because Android resets the
+ // movement method to the default one in the case where the text cannot be selected and
+ // text links are clickable (which is the default case in NewPipe).
+ final MovementMethod movementMethod = this.getMovementMethod();
+ super.setText(text, type);
+ setMovementMethod(movementMethod);
+ }
+
@Override
public boolean onTextContextMenuItem(final int id) {
if (id == android.R.id.shareText) {
diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
index dc56ee205..edc6bb6fd 100755
--- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
+++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
@@ -310,7 +310,7 @@ public class DownloadManagerService extends Service {
}
private void handlePreferenceChange(SharedPreferences prefs, @NonNull String key) {
- if (key.equals(getString(R.string.downloads_maximum_retry))) {
+ if (getString(R.string.downloads_maximum_retry).equals(key)) {
try {
String value = prefs.getString(key, getString(R.string.downloads_maximum_retry_default));
mManager.mPrefMaxRetry = value == null ? 0 : Integer.parseInt(value);
@@ -318,13 +318,13 @@ public class DownloadManagerService extends Service {
mManager.mPrefMaxRetry = 0;
}
mManager.updateMaximumAttempts();
- } else if (key.equals(getString(R.string.downloads_cross_network))) {
+ } else if (getString(R.string.downloads_cross_network).equals(key)) {
mManager.mPrefMeteredDownloads = prefs.getBoolean(key, false);
- } else if (key.equals(getString(R.string.downloads_queue_limit))) {
+ } else if (getString(R.string.downloads_queue_limit).equals(key)) {
mManager.mPrefQueueLimit = prefs.getBoolean(key, true);
- } else if (key.equals(getString(R.string.download_path_video_key))) {
+ } else if (getString(R.string.download_path_video_key).equals(key)) {
mManager.mMainStorageVideo = loadMainVideoStorage();
- } else if (key.equals(getString(R.string.download_path_audio_key))) {
+ } else if (getString(R.string.download_path_audio_key).equals(key)) {
mManager.mMainStorageAudio = loadMainAudioStorage();
}
}
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 26eb2f3b0..bfb6a15e2 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
@@ -48,6 +48,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
+import androidx.core.os.HandlerCompat;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.Adapter;
@@ -91,6 +92,10 @@ public class MissionAdapter extends Adapter implements Handler.Callb
private static final String UNDEFINED_PROGRESS = "--.-%";
private static final String DEFAULT_MIME_TYPE = "*/*";
private static final String UNDEFINED_ETA = "--:--";
+
+ private static final String UPDATER = "updater";
+ private static final String DELETE = "deleteFinishedDownloads";
+
private static final int HASH_NOTIFICATION_ID = 123790;
private final Context mContext;
@@ -110,9 +115,6 @@ public class MissionAdapter extends Adapter implements Handler.Callb
private final ArrayList mHidden;
private Snackbar mSnackbar;
- private final Runnable rUpdater = this::updater;
- private final Runnable rDelete = this::deleteFinishedDownloads;
-
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public MissionAdapter(Context context, @NonNull DownloadManager downloadManager, View emptyMessage, View root) {
@@ -595,12 +597,12 @@ public class MissionAdapter extends Adapter implements Handler.Callb
i.remove();
}
applyChanges();
- mHandler.removeCallbacks(rDelete);
+ mHandler.removeCallbacksAndMessages(DELETE);
});
mSnackbar.setActionTextColor(Color.YELLOW);
mSnackbar.show();
- mHandler.postDelayed(rDelete, 5000);
+ HandlerCompat.postDelayed(mHandler, this::deleteFinishedDownloads, DELETE, 5000);
} else if (!delete) {
mDownloadManager.forgetFinishedDownloads();
applyChanges();
@@ -786,15 +788,14 @@ public class MissionAdapter extends Adapter implements Handler.Callb
public void onResume() {
mDeleter.resume();
- mHandler.post(rUpdater);
+ HandlerCompat.postDelayed(mHandler, this::updater, UPDATER, 0);
}
public void onPaused() {
mDeleter.pause();
- mHandler.removeCallbacks(rUpdater);
+ mHandler.removeCallbacksAndMessages(UPDATER);
}
-
public void recoverMission(DownloadMission mission) {
ViewHolderItem h = getViewHolder(mission);
if (h == null) return;
@@ -817,7 +818,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb
updateProgress(h);
}
- mHandler.postDelayed(rUpdater, 1000);
+ HandlerCompat.postDelayed(mHandler, this::updater, UPDATER, 1000);
}
private boolean isNotFinite(double value) {
diff --git a/app/src/main/java/us/shandian/giga/ui/common/Deleter.java b/app/src/main/java/us/shandian/giga/ui/common/Deleter.java
index c554766ff..1902076d6 100644
--- a/app/src/main/java/us/shandian/giga/ui/common/Deleter.java
+++ b/app/src/main/java/us/shandian/giga/ui/common/Deleter.java
@@ -6,6 +6,8 @@ import android.graphics.Color;
import android.os.Handler;
import android.view.View;
+import androidx.core.os.HandlerCompat;
+
import com.google.android.material.snackbar.Snackbar;
import org.schabi.newpipe.R;
@@ -19,6 +21,10 @@ import us.shandian.giga.service.DownloadManager.MissionIterator;
import us.shandian.giga.ui.adapter.MissionAdapter;
public class Deleter {
+ private static final String COMMIT = "commit";
+ private static final String NEXT = "next";
+ private static final String SHOW = "show";
+
private static final int TIMEOUT = 5000;// ms
private static final int DELAY = 350;// ms
private static final int DELAY_RESUME = 400;// ms
@@ -34,10 +40,6 @@ public class Deleter {
private final Handler mHandler;
private final View mView;
- private final Runnable rShow;
- private final Runnable rNext;
- private final Runnable rCommit;
-
public Deleter(View v, Context c, MissionAdapter a, DownloadManager d, MissionIterator i, Handler h) {
mView = v;
mContext = c;
@@ -46,21 +48,15 @@ public class Deleter {
mIterator = i;
mHandler = h;
- // use variables to know the reference of the lambdas
- rShow = this::show;
- rNext = this::next;
- rCommit = this::commit;
-
items = new ArrayList<>(2);
}
public void append(Mission item) {
-
/* If a mission is removed from the list while the Snackbar for a previously
* removed item is still showing, commit the action for the previous item
* immediately. This prevents Snackbars from stacking up in reverse order.
*/
- mHandler.removeCallbacks(rCommit);
+ mHandler.removeCallbacksAndMessages(COMMIT);
commit();
mIterator.hide(item);
@@ -82,7 +78,7 @@ public class Deleter {
pause();
running = true;
- mHandler.postDelayed(rNext, DELAY);
+ HandlerCompat.postDelayed(mHandler, this::next, NEXT, DELAY);
}
private void next() {
@@ -95,7 +91,7 @@ public class Deleter {
snackbar.setActionTextColor(Color.YELLOW);
snackbar.show();
- mHandler.postDelayed(rCommit, TIMEOUT);
+ HandlerCompat.postDelayed(mHandler, this::commit, COMMIT, TIMEOUT);
}
private void commit() {
@@ -124,15 +120,16 @@ public class Deleter {
public void pause() {
running = false;
- mHandler.removeCallbacks(rNext);
- mHandler.removeCallbacks(rShow);
- mHandler.removeCallbacks(rCommit);
+ mHandler.removeCallbacksAndMessages(NEXT);
+ mHandler.removeCallbacksAndMessages(SHOW);
+ mHandler.removeCallbacksAndMessages(COMMIT);
if (snackbar != null) snackbar.dismiss();
}
public void resume() {
- if (running) return;
- mHandler.postDelayed(rShow, DELAY_RESUME);
+ if (!running) {
+ HandlerCompat.postDelayed(mHandler, this::show, SHOW, DELAY_RESUME);
+ }
}
public void dispose() {
diff --git a/app/src/main/res/drawable/ic_format_list_numbered.xml b/app/src/main/res/drawable/ic_format_list_numbered.xml
deleted file mode 100644
index b11666c56..000000000
--- a/app/src/main/res/drawable/ic_format_list_numbered.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_menu_book.xml b/app/src/main/res/drawable/ic_menu_book.xml
new file mode 100644
index 000000000..4cd4fb3a4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu_book.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_subscriptions.xml b/app/src/main/res/drawable/ic_subscriptions.xml
new file mode 100644
index 000000000..f2ac7bec2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_subscriptions.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/dialog_playlists.xml b/app/src/main/res/layout/dialog_playlists.xml
index 18b08d93c..ab4691fa9 100644
--- a/app/src/main/res/layout/dialog_playlists.xml
+++ b/app/src/main/res/layout/dialog_playlists.xml
@@ -34,11 +34,26 @@
tools:ignore="RtlHardcoded" />
+
+
diff --git a/app/src/main/res/layout/item_metadata.xml b/app/src/main/res/layout/item_metadata.xml
index 31dedd880..251b9e832 100644
--- a/app/src/main/res/layout/item_metadata.xml
+++ b/app/src/main/res/layout/item_metadata.xml
@@ -6,7 +6,7 @@
android:layout_height="wrap_content"
android:paddingVertical="6dp">
-
-
+ tools:text="10M subscribers • 100 videos" />
+
+
diff --git a/app/src/main/res/layout/list_playlist_card_item.xml b/app/src/main/res/layout/list_playlist_card_item.xml
new file mode 100644
index 000000000..c7dd4f17c
--- /dev/null
+++ b/app/src/main/res/layout/list_playlist_card_item.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/list_stream_card_item.xml b/app/src/main/res/layout/list_stream_card_item.xml
new file mode 100644
index 000000000..968dca082
--- /dev/null
+++ b/app/src/main/res/layout/list_stream_card_item.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/list_stream_playlist_card_item.xml b/app/src/main/res/layout/list_stream_playlist_card_item.xml
new file mode 100644
index 000000000..9cc6b326c
--- /dev/null
+++ b/app/src/main/res/layout/list_stream_playlist_card_item.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml
index 60cbcf7c4..b528e4e9b 100644
--- a/app/src/main/res/layout/player.xml
+++ b/app/src/main/res/layout/player.xml
@@ -212,11 +212,11 @@
android:clickable="true"
android:focusable="true"
android:paddingStart="6dp"
- android:paddingTop="5dp"
android:paddingEnd="6dp"
android:paddingBottom="3dp"
+ android:paddingTop="3dp"
android:scaleType="fitCenter"
- android:src="@drawable/ic_format_list_numbered"
+ android:src="@drawable/ic_menu_book"
android:visibility="gone"
app:tint="@color/white"
tools:ignore="ContentDescription,RtlHardcoded" />
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index d7bbcd9c0..02c471d63 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -778,4 +778,10 @@
انقر للتنزيل %s
الوضع السريع
استيراد الاشتراكات أو تصديرها من القائمة المكونة من 3 نقاط
+ هذا الخيار متاح فقط إذا تم تحديد %s للسمة
+ إلغاء تعيين الصورة المصغرة الدائمة
+ فشل النسخ إلى الحافظة
+ البطاقة
+ تمت إضافة وقت (أوقات) مكررة %d
+ تحتوي قوائم التشغيل رمادية اللون بالفعل على هذا العنصر.
\ No newline at end of file
diff --git a/app/src/main/res/values-as/strings.xml b/app/src/main/res/values-as/strings.xml
new file mode 100644
index 000000000..be845845a
--- /dev/null
+++ b/app/src/main/res/values-as/strings.xml
@@ -0,0 +1,101 @@
+
+
+ কোনো ষ্ট্ৰিম প্লেয়াৰ পোৱা নগ\'ল (আপুনি ইয়াক বজাবলৈ VLC ইনষ্টল কৰিব পাৰে)।
+ ইনষ্টল
+ বাতিল কৰক
+ ঠিক আছে
+ ব্ৰাউজাৰত খোলক
+ POPUP অৱস্থাত খোলক
+ ...ৰ সৈতে খোলক
+ চেয়াৰ
+ %1$s ত প্ৰকাশ কৰা হৈছে
+ কোনো ষ্ট্ৰিম প্লেয়াৰ পোৱা নগ\'ল। VLC ইনষ্টল কৰক\?
+ ডাউনল’ড
+ ষ্ট্ৰিম কৰা ফাইল ডাউনলোড কৰক
+ সন্ধান কৰক
+ ছেটিংছ
+ %s ৰ বাবে ফলাফল দেখুৱা হৈছে
+ চেয়াৰ কৰক
+ কিছু ৰিজ’লিউচনত অডিঅ’ আঁতৰাওক
+ চাবস্ক্ৰাইব কৰা হ\'ল
+ আনচাবস্ক্ৰাইব
+ আৰম্ভ কৰিবলৈ মেগনিফাইং গ্লাছৰ চিহ্নত টিপক।
+ চাবস্ক্ৰাইব
+ চোৱা হ\'ল (চিহ্নিত কৰক)
+ আপুনি \"%1$s\" বুজাইছিল নেকি\?
+ বাহ্যিক ভিডিঅ’ প্লেয়াৰ ব্যৱহাৰ কৰক
+ বাহ্যিক অডিঅ’ প্লেয়াৰ ব্যৱহাৰ কৰক
+ Channel আনচাবস্ক্ৰাইব কৰা হ\'ল
+ subscription সলনি কৰিব পৰা নগ\'ল
+ subscription আপডেট কৰিব পৰা নগ\'ল
+ তথ্য দেখুৱাওক
+ চাবস্ক্ৰিপচন
+ বুকমাৰ্ক কৰা প্লেলিষ্ট
+ টেব নিৰ্বাচন কৰক
+ বেকগ্ৰাউণ্ড
+ পপ-আপ
+ স্থায়ী ৰিজ\'লিউচন
+ স্থায়ী পপআপ ৰিজোলিউচন
+ উচ্চ ৰিজ\'লিউচন দেখুৱাওক
+ কেৱল কিছুমান ডিভাইচেহে 2K/4K ভিডিঅ’ বজাব পাৰে
+ Kodi ৰ সৈতে বজাওক
+ Kore এপ ইনষ্টল\?
+ \"Kodi ৰ সৈতে খোলক\" বিকল্প দেখুৱাওক
+ Kodi মিডিয়া চেণ্টাৰৰ জৰিয়তে এটা ভিডিঅ\' চলাবলৈ এটা বিকল্প প্ৰদৰ্শন কৰক
+ প্লেয়াৰটো ক্ৰেচ কৰক
+ বাফাৰিং
+ নথিং
+ জাননী ৰঙিণ কৰক
+ অডিঅ\'
+ অডিঅ\' ৰ প্ৰকাৰ
+ ভিডিঅ\'ৰ প্ৰকাৰ
+ থিম
+ নিশাৰ থিম
+ পোহৰ
+ অন্ধকাৰ
+ ক\'লা
+ পপ-আপ বৈশিষ্ট্যসমূহ মনত ৰাখিব
+ পপ-আপৰ অন্তিম আকাৰ আৰু অৱস্থান মনত ৰাখিব
+ Inexact seek য়ে প্লেয়াৰটোক দ্ৰুত গতিত স্থান সলনি কৰিবলৈ অনুমতি দিয়ে। ৫, ১৫ বা ২৫ ছেকেণ্ড সলনি কৰিবলৈ বিচাৰিলে ইয়াৰ প্ৰয়োজন নহয়
+ ফাষ্ট-ফৰৱাৰ্ড/-ৰিৱাইণ্ড কৰিবলৈ বিচৰা সময়সীমা
+ প্লেবেক লোড কৰাৰ ব্যৱধানৰ আকাৰ
+ লোড ব্যৱধানৰ আকাৰ সলনি কৰক (বৰ্তমানে %s) । এটা কম মানে প্ৰাৰম্ভিক ভিডিঅ\' লোডিং দ্ৰুত কৰিব পাৰে। পৰিৱৰ্তনৰ বাবে এটা খেলুৱৈ পুনৰাৰম্ভৰ প্ৰয়োজন
+ থাম্বনেইলত থকা মূল ৰং অনুসৰি এণ্ড্ৰইডক জাননীৰ ৰং কাষ্টমাইজ কৰিবলৈ কওক (মন কৰিব যে এইটো সকলো ডিভাইচতে উপলব্ধ নহয়)
+ সক্ৰিয় প্লেয়াৰৰ queue সলনি কৰা হ’ব
+ থাম্বনেইল লোড কৰক
+ মন্তব্য দেখুৱাওক
+ বিৱৰণ দেখুৱাওক
+ মেটা তথ্য দেখুৱাওক
+ সংৰক্ষিত ছবি মচি পেলোৱা হ\'ল
+ সংৰক্ষিত কৰি থোৱা মেটাডাটা মচি পেলাওক
+ সকলো সংৰক্ষণ কৰি ৰখা ৱেবপেজৰ তথ্য আঁতৰাওক
+ সংৰক্ষণ কৰি থোৱা মেটাডাটা মচি পেলোৱা হ\'ল
+ পৰৱৰ্তী ষ্ট্ৰিম স্বয়ংক্ৰিয়ভাৱে enque কৰক
+ সজোৱা
+ ভিডিঅ\' ডাউনলোড folder
+ যোগ কৰক
+ ডাউনলোড কৰা অডিঅ\' ফাইলসমূহ ইয়াত সংৰক্ষণ কৰা হয়
+ থাম্বনেইলক ১:১ অনুপাত লৈ ক্ৰপ কৰক
+ ডাউনলোড কৰা ভিডিঅ’ ফাইলসমূহ ইয়াত সংৰক্ষণ কৰা হয়
+ ভিডিঅ\' ফাইলসমূহৰ বাবে ডাউনলোড folder বাছক
+ অডিঅ\' ডাউনলোড folder
+ অডিঅ\' ফাইলসমূহৰ বাবে ডাউনলোড folder নিৰ্বাচন কৰক
+ জাননীত দেখুওৱা ভিডিঅ’ থাম্বনেইলটো ১৬:৯ৰ পৰা ১:১ অনুপাতলৈ ক্ৰপ কৰক
+ First action button
+ Fifth action button
+ Edit each notification action below by tapping on it. Select up to three of them to be shown in the compact notification by using the checkboxes on the right
+ Second action button
+ You can select at most three actions to show in the compact notification!
+ পুনৰাবৃত্তি
+ শ্বাফেল
+ দ্ৰুত inexact seek ব্যৱহাৰ কৰক
+ এটা queue বিলুপ্তি কৰাৰ আগতে নিশ্চিতকৰণৰ বাবে সুধিব
+ এটা প্লেয়াৰ পৰা আন এটালৈ সলনি কৰিলে আপোনাৰ queue সলনি হ\'ব পাৰে
+ Fourth action button
+ Third action button
+ ভিডিঅ\'ৰ বিৱৰণ আৰু অতিৰিক্ত তথ্য লুকুৱাবলৈ বন্ধ কৰক
+ মন্তব্য লুকুৱাবলৈ বন্ধ কৰক
+ \'পৰৱৰ্তী\' আৰু \'সাদৃশ্য থকা\' ভিডিঅ\' দেখুৱাওক
+ থাম্বনেইলসমূহ লোড কৰা, তথ্য আৰু মেমৰি ব্যৱহাৰ সংৰক্ষণ কৰা ৰোধ কৰিবলে বন্ধ কৰক। পৰিবৰ্তনসমূহে ইন-মেমৰি আৰু অন-ডিস্ক কেশ্ব দুয়োটা পৰিষ্কাৰ কৰে
+ ষ্ট্ৰিমৰ সৃষ্টিকৰ্তা, ষ্ট্ৰিমৰ বিষয়বস্তু বা এটা সন্ধান অনুৰোধৰ বিষয়ে অতিৰিক্ত তথ্যৰ সৈতে মেটা তথ্যৰ বাকচসমূহ লুকুৱাবলৈ বন্ধ কৰক
+
\ No newline at end of file
diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml
index 22f3ba5e5..0cce71fb0 100644
--- a/app/src/main/res/values-az/strings.xml
+++ b/app/src/main/res/values-az/strings.xml
@@ -1,9 +1,9 @@
- Başlamaq üçün böyüdücüyə toxun.
+ Başlamaq üçün böyüdücü güzgüyə toxun.
%1$s tarixində yayımlanıb
- Yayım oynadıcı tapılmadı. \"VLC\" yüklənilsin\?
- Yayım oynadıcı tapılmadı (Oynatmaq üçün VLC\'ni quraşdıra bilərsiniz).
+ Yayım oynadıcı tapılmadı. \"VLC\" quraşdırılsın\?
+ Yayım oynadıcı tapılmadı (Oynatmaq üçün VLC quraşdıra bilərsiniz).
Yüklə
Ləğv et
Brauzerdə aç
@@ -12,59 +12,59 @@
Yayım faylını endir
Axtarış
Tənzimləmələr
- Bunu nəzərdə tuturdunuz: \"%1$s\"\?
+ Bunu demək istəyirdiniz: \"%1$s\"\?
ilə paylaş
Xarici video oynadıcı istifadə et
- Bəzi qətnamələrdə səsi silir
+ Bəzi ayırdetmələrdə səsi silir
Xarici səs oynadıcı istifadə et
Abunə Ol
Abunə olundu
Kanal abunəliyi ləğv edildi
Məlumat göstər
- Abunəliklər
+ Abunələr
Əlfəcinlənmiş Pleylistlər
Yeniliklər
Fon
Video endirmə qovluğu
Endirilmiş video fayllar burada saxlanılır
- Video faylları üçün endirmə qovluğunu seç
+ Video fayllar üçün endirmə qovluğu seç
Səs endirmə qovluğu
Endirilmiş səs faylları burada saxlanılır
Səs faylları üçün endirmə qovluğu seç
- Defolt keyfiyyət
- Daha böyük keyfiyyət seçimləri göstər
+ Standart ayırdetmə
+ Daha böyük ayırdetmələr göstər
\"Kodi\" ilə Oynat
Çatışmayan \"Kore\" tətbiqi yüklənilsin\?
\"Kodi ilə Oynat\" seçimini göstər
- Videonu Kodi media mərkəzi ilə oynatmaq üçün seçim göstər
+ Kodi media mərkəzindən video oynatmaq üçün seçim göstər
Səs
- Defolt səs formatı
- Defolt video formatı
+ Standart səs formatı
+ Standart video formatı
Tema
İşıqlı
Qaranlıq
Qara
- Abunəlikdən çıxın
- Ani pəncərə rejimində aç
+ Abunə olma
+ Ani görüntü rejimində aç
Avtomatik oynat
- Endir
- Fasilələrdən sonra (məsələn, telefon zəngləri) oynatmağa davam etdir
+ Yüklə
+ Fasilələr ardınca (məsələn, telefon zəngləri) oynatmağa davam etdir
Oynatmanı davam etdir
Baxılmış videoların saxlanılması
Məlumat təmizlə
- Siyahılarda oynatma mövqelərini göstər
+ Siyahılarda oynatma mövqe göstəricilərini göstər
Siyahılardakı mövqelər
Son oynatma mövqeyini qaytar
Oynatmanı davam etdir
Baxış tarixçəsi
Axtarış sorğularını yerli olaraq saxla
Axtarış tarixçəsi
- Axtarış edərkən göstəriləcək təklifləri seç
+ Axtarış zamanı göstərmək üçün təklifləri seç
Axtarış təklifləri
- Oynadıcının parlaqlığını nizamlamaq üçün jestləri istifadə et
- Parlaqlığı jestlə nizamlamaq
- Oynadıcı səsini nizamlamaq üçün jestləri istifadə et
- Səsi jestlə idarə etmək
+ Oynadıcı parlaqlığını nizamlamaq üçün jestlər istifadə et
+ Parlaqlıq jesti idarəetməsi
+ Oynadıcı səsini nizamlamaq üçün jestlər istifadə et
+ Səs səviyyəsi jesti idarəetməsi
Avto-növbələ
Növbəti Yayımı Avto-növbələ
Üst məlumat keşi silindi
@@ -73,10 +73,10 @@
Şəkil keşi silindi
Şərhləri gizlətmək üçün bağla
Şərhləri göstər
- Aktiv oynadıcının növbəsi dəyişdiriləcək
+ Aktiv oynadıcı növbəsi dəyişdiriləcək
Bir oynadıcıdan digərinə keçid növbənizi dəyişdirə bilər
Növbəni təmizləməzdən əvvəl təsdiq üçün soruş
- Sürətli qeyri-dəqiq axtarışdan istifadə et
+ Sürətli qeyri-dəqiq axtarış istifadə et
Qeyri-dəqiq axtarış oynadıcıya azaldılmış dəqiqliklə mövqeləri daha sürətli axtarmağa imkan verir. 5, 15 və ya 25 saniyəlik axtarış bununla işləmir
Sürətli irəli/geri çəkmə axtarış müddəti
Heç nə
@@ -89,9 +89,9 @@
İkinci fəaliyyət düyməsi
Birinci fəaliyyət düyməsi
Yalnız bəzi cihazlar 2K/4K videoları oynada bilir
- Defolt ani pəncərə keyfiyyəti
+ Standart ani görüntü ayırdetməsi
Əlavə Et
- Ani Pəncərə
+ Ani Görüntü
Paneli Seç
Abunəliyi yeniləmək alınmadı
Abunəliyi dəyişmək alınmadı
@@ -102,7 +102,7 @@
Yaş həddi səbəbiylə (məsələn, 18+) uşaqlar üçün uyğun olmayan məzmunu göstər
Yaş məhdudiyyətli məzmunu göstər
Məzmun
- Ani pəncərə rejimində oynadılır
+ Ani görüntü rejimində oynadılır
Fonda oynadılır
Yeniləmələr
Sazlama
@@ -111,23 +111,23 @@
Video və səs
Davranış
Oynadıcı
- Defolt məzmun dili
- Defolt məzmun ölkəsi
+ Cari məzmun dili
+ Cari məzmun ölkəsi
URL\'i tanımaq olmadı. Başqa tətbiqlə açılsın\?
Dəstəklənməyən URL\'i
\"Növbələmək üçün basılı saxla\" tövsiyəsin göstər
\"Növbəti\" və \"Bənzər\" videoları göstər
- Tarixçəni, abunəlikləri, pleylistləri və tənzimləmələri ixrac edin
+ Tarixçəni, abunəlikləri, pleylistləri və tənzimləmələri ixrac et
Cari tarixçənizi, abunəliklərinizi, pleylistlərinizi və (könüllü) tənzimləmələrinizi etibarsız edir
- reCAPTCHA kukiləri təmizləndi
- reCAPTCHA kukilərini təmizlə
+ reCAPTCHA bazaları təmizləndi
+ reCAPTCHA bazalarını təmizlə
Məlumat bazasını ixrac et
Məlumat bazasını idxal et
Əsas Görünüşə Keçid
- Ani Pəncərəyə Keçid
+ Ani Görüntüyə Keçid
Fona Keçid
[Naməlum]
- Yeni \"NewPipe\" versiyası üçün bildirişlər
+ Yeni \"NewPipe\" versiyaları üçün bildirişlər
Tətbiq yeniləmə bildirişi
NewPipe oynadıcısı üçün bildirişlər
Hamısı
@@ -141,7 +141,7 @@
YouTube potensial yetkin məzmunu gizlədən \"Məhdud Rejim\" təmin edir
\"PeerTube\" nümunələri
Miniatürləri yüklə
- Siz yığcam bildirişdə göstərilməsi üçün ən çoxu üç fəaliyyət seçə bilərsiniz!
+ Yığcam bildirişdə göstərmək üçün ən çoxu üç fəaliyyət seçə bilərsiniz!
Həmişə yenilə
Axın
Yalnız qruplaşdırılmamış abunəlikləri göstər
@@ -153,7 +153,7 @@
- %d seçildi
Abunəlik seçilməyib
- Abunəlikləri seçin
+ Abunəlikləri seç
Axın emal edilir…
Axın yüklənir…
Yüklənmədi: %d
@@ -179,8 +179,8 @@
Xəta
Axtarış tarixçəsi silindi
Bütün axtarış tarixçəsi silinsin\?
- Açar sözləri axtarışının tarixçəsini silir
- Axtarış tarixçəsini silin
+ Açar sözləri axtarışı tarixçəsini silir
+ Axtarış tarixçəsini sil
Oynatma mövqeləri silindi
Bütün oynatma mövqeləri silinsin\?
Bütün oynatma mövqelərini silir
@@ -188,7 +188,7 @@
Baxış tarixçəsi silindi
Bütün baxış tarixçəsi silinsin\?
Baxış tarixçəsini təmizlə
- reCAPTCHA həll edərkən NewPipe\'ın saxladığı kukiləri silin
+ reCAPTCHA həll edərkən NewPipe saxladığı bazaları sil
%s tərəfindən yaradıldı
Yaxınlaşdır
Doldur
@@ -198,22 +198,22 @@
Hələ ki, kanal abunəliyi yoxdur
Kanal seç
Kanal Səhifəsi
- Defolt Köşk
- Köşk Səhifəsi
+ Standart Köşk
+ Köşk Səhifə
Boş Səhifə
Əsas səhifədə hansı tablar göstərilir
- Əsas səhifənin məzmunu
+ Əsas səhifə məzmunu
Yeni versiya mövcud olduqda tətbiq yeniləməsini xatırlatmaq üçün bildiriş göstər
Yeniləmələr
- Mobil internet istifadə edərkən görüntü keyfiyyətini məhdudlaşdır
+ Mobil internet istifadə edərkən ayırdetməni məhdudlaşdır
Limitsiz
1 element silindi.
Nümunə əlavə et
Sevimli \"PeerTube\" nümunələrinizi seçin
- Endirilmiş faylları silin
- Endirmə tarixçənizi təmizləmək və ya endirilmiş bütün faylları silmək istəyirsiniz\?
+ Endirilmiş faylları sil
+ Endirmə tarixçənizi təmizləmək və ya bütün endirilmiş faylları silmək istəyirsiniz\?
Endirmə tarixçəsini təmizlə
- Endirmələrə başla
+ Endirmələri başlat
Endirmələri dayandır
Haraya endiriləcəyini soruş
Sizdən hər endirmənin harada saxlanılacağı soruşulacaq.
@@ -241,13 +241,13 @@
Axın yeniləmə astanası
Sürətli rejimi aktivləşdir
Sürətli rejimi deaktiv et
- Axının çox yavaş yükləndiyini düşünürsünüz\? Əgər elədirsə, sürətli yükləməni işə salmağı sınayın (tənzimləmələrdən dəyişə və ya aşağıdakı düyməni basa bilərsiniz).
+ Axının çox yavaş yükləndiyini düşünürsünüz\? Əgər elədirsə, sürətli yükləməni işə salmağı sınayın (tənzimləmələrdə dəyişə və ya aşağıdakı düyməni basa bilərsiniz).
\n
\nNewPipe axını yükləmək üçün 2 metod təklif edir:
\n• Bütün abunəlik kanallarını gətirtmək, bu yavaş olsa da tamdır;
\n• Ayrılmış xidmət uc nöqtəsi istifadə etmək, bu sürətlidir, amma tam deyil.
\n
-\nBu ikisi arasında fərq odur ki, sürətlisində, adətən elementin müddəti və növü kimi bəzi məlumatlar çatışmır (canlı video ilə adisini ayırd edə bilmir) və daha az element gətirir.
+\nBu ikisi arasında fərq odur ki, sürətlisində, adətən elementin müddəti və növü kimi bəzi məlumatlar çatışmır (canlı video ilə adisini ayırd edə bilmir) və daha az elementlər gətirir.
\n
\nYouTube öz RSS axını ilə bu sürətli metodu təklif edən xidmətlərdən biridir.
\n
@@ -262,9 +262,9 @@
Xarici yaddaş əlçatan deyil
Oynadılmış yayımlar tarixçəsini və oynatma mövqelərini silir
Üst məlumatı göstər
- Video açıqlamasını və əlavə məlumatı gizlətmək üçün bağla
+ Video açıqlamanı və əlavə məlumatı gizlətmək üçün bağla
Açıqlamanı göstər
- Bildirişi rəngləndir
+ Bildirişi rənglə
Belə qovluq yoxdur
Əsas oynadıcını tam ekranda başlat
Xarici oynadıcılar bu cür linkləri dəstəkləmir
@@ -281,7 +281,7 @@
Açıqlama
Burada kriketlərdən başqa heç nə yoxdur
Nəticə yoxdur
- İlkin tənzimləmələri qaytar
+ Standartları qaytar
Fayl köçürüldü və ya silindi
Oynadıcı xətası bərpa edilir
Bərpa olunmayan oynatma xətası baş verdi
@@ -291,28 +291,28 @@
Səs yayımı tapılmadı
Digər tətbiqlərin üzərində göstərməyə icazə ver
İlkin tənzimləmələri qaytarmaq istəyirsiniz\?
- Miniatürlərin yüklənməsini, dataya qənaət etmək və yaddaşdan istifadəni azaltmaq üçün söndürün. Dəyişikliklər həm yaddaşdaxili, həm də diskdə olan təsvir keşini təmizləyir
+ Miniatürləri yükləməyi, məlumata qənaət və yaddaş istifadəsin azaltmaq üçün söndür. Dəyişikliklər həm yaddaşdaxilində, həm də diskdə təsvir keşini təmizləyir
Növbətini növbələ
- Yenidən Cəhd Et
+ Təkrar Cəhd Et
Cari oynatma yayımı bildirişini konfiqurasiya et
Bildirişlər
Video fayl xülasəsi bildirişi
Abunəliklər üçün yeni yayımlar haqqında bildirişlər
Xəta hesabatları üçün bildirişlər
Fayl adı boş ola bilməz
- Yadda saxlanmış tabları oxumaq mümkün olmadı, buna görə defolt tablardan istifadə edin
+ Saxlanmış tabları oxumaq mümkün olmadı, buna görə standart tabları istifadə et
NewPipe xəta ilə qarşılaşdı, bildirmək üçün toxun
- Bağışlayın, o baş verməməli idi.
- Bu xətanı e-poçt vasitəsilə bildirin
- GitHub\'da Hesabat Ver
+ Bağışla, o baş verməməli idi.
+ Bu xətanı e-poçt-dan bildir
+ GitHub\'da Məlumat Ver
Zəhmət olmasa, xətanızı müzakirə edən məsələnin mövcud olub-olmadığını yoxlayın. Dublikat biletləri yaradarkən, bizdən faktiki səhvi düzəltməyə sərf edəcəyimiz vaxt alırsınız.
- Hesabat Bildir
+ Məlumat Ver
Məlumat:
Nə baş verdi:
Yükləyənin avatar miniatürü
Bəyən
Bəyənmə
- Yenidən sıralamaq üçün sürüşdür
+ Yenidən sıralamaq üçün sürüklə
min
Mln
Mlrd
@@ -324,13 +324,13 @@
Video yoxdur
Şərhlər qeyri-aktivdir
Başlat
- Fasilə
+ Dayandır
Təsdiqləmə
İmtina
Xəta
Detallar üçün toxun
Zəhmət olmasa, gözləyin…
- Hələ endirmə qovluğu təyin edilməyib, indi defolt endirmə qovluğunu seç
+ Hələ endirmə qovluğu təyin edilməyib, indi standart endirmə qovluğu seç
reCAPTCHA çağırışı
reCAPTCHA sorğusu göndərildi
Bitdi
@@ -340,15 +340,15 @@
Üçüncü Tərəf Lisenziyaları
Haqqında & T-TSS
Töhfə Ver
- Fikirlərinizin olub-olmaması, tərcümə, dizayn dəyişiklikləri, kodun təmizlənməsi və ya real ağırlıqlı kod dəyişiklikləri və.s kömək həmişə xoşdur. Nə qədər çox edilsə, bir o qədər yaxşı olar!
+ Fikirlərinizin olub-olmaması, tərcümə, dizayn dəyişiklikləri, kod təmizlənməsi və ya real ağır kod dəyişiklikləri və.s kömək həmişə xoşdur. Nə qədər çox edilsə, bir o qədər yaxşı olar!
İanə Et
Veb sayt
- Əlavə məlumat və xəbərlər üçün NewPipe Veb saytına daxil olun.
- NewPipe\'ın Məxfilik Siyasəti
- NewPipe layihəsi məxfiliyinizə çox ciddi yanaşır. Buna görə də, tətbiq sizin razılığınız olmadan heç bir məlumat toplamır.
-\nNewPipe\'ın məxfilik siyasəti qəza hesabatı göndərdiyiniz zaman hansı məlumatların göndərildiyini və saxlanıldığını ətraflı izah edir.
- Məxfilik siyasətini oxu
- NewPipe\'ın Lisenziyası
+ Əlavə məlumat və xəbərlər üçün NewPipe Veb saytını ziyarət et.
+ NewPipe Məxfilik Siyasəti
+ NewPipe layihəsi məxfiliyinizə çox ciddi yanaşır. Nəticə etibarı ilə, tətbiq sizin razılığınız olmadan heç bir məlumat toplamır.
+\nNewPipe məxfilik siyasəti xəta məlumatı göndərdiyiniz zaman hansı məlumatların göndərildiyini və saxlanıldığını ətraflı izah edir.
+ Məxfilik Siyasətini Oxu
+ NewPipe Lisenziyası
Tarixçə
Bu elementi axtarış tarixçəsindən silmək istəyirsiniz\?
Son Oynadılan
@@ -367,7 +367,7 @@
Oynatma növbəsi
Detallar
Kanal təfərrüatlarını göstər
- Ani pəncərədə oynatmağa başla
+ Ani görüntüdə oynatmağa başla
\"Açıq\" fəaliyyətə üstünlük verilir
Fon oynadıcı
Həmişə soruş
@@ -403,7 +403,7 @@
\nDavam etmək istəyirsiniz\?
Səssizlik zamanı sürətlə irəli
Yeni yayım bildirişləri
- Abunəliklərdən yeni yayımlar haqqında bildiriş göndər
+ Abunəliklərdən yeni yayımlar haqqında bildir
Yoxlama tezliyi
Tələb olunan şəbəkə bağlantısı
İstənilən şəbəkə
@@ -426,7 +426,7 @@
Bu məzmun hələ NewPipe tərəfindən dəstəklənmir.
\n
\nÜmid edirik ki, gələcək versiyada dəstəklənəcək.
- Həm kilid ekranı fonu, həm də bildirişlər üçün miniatürdən istifadə et
+ Həm kilid ekranı fonu, həm də bildirişlər üçün miniatür istifadə et
Ən Yeni
Bu məzmun ölkənizdə mövcud deyil.
Bu məzmun yalnız ödəniş etmiş istifadəçilər üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlana və ya endirilə bilməz.
@@ -468,10 +468,10 @@
Android\'in bildiriş rəngini miniatürdəki əsas rəngə uyğun fərdiləşdirməsini təmin et (qeyd edək ki, bu, bütün cihazlarda mövcud deyil)
GitHub\'da Bax
İanə Et
- NewPipe, sizə ən yaxşı istifadəçi təcrübəsini göstərmək üçün boş vaxtlarını sərf edən könüllülər tərəfindən hazırlanmışdır. Tərtibatçılara bir fincan qəhvə içərkən NewPipe-ı daha da yaxşılaşdırmağa ianə etməklə kömək edin.
+ NewPipe, sizə ən yaxşı istifadəçi təcrübəsi göstərmək üçün boş vaxtlarını sərf edən könüllülər tərəfindən hazırlanmışdır. Tərtibatçılara bir fincan qəhvə içərkən NewPipe-ı daha da yaxşılaşdırmağa ianə etməklə kömək edin.
Ən çox bəyənildi
Növbəyə salındı
- Məzmunu açarkən defolt hərəkət — %s
+ Məzmunu açarkən standart hərəkət — %s
Ad
Pleylist miniatürü kimi təyin et
Yalnız Wi-Fi\'da
@@ -488,7 +488,7 @@
Lisenziya
Müəllifin hesabı bağlanıb.
\nNewPipe gələcəkdə bu axını yükləyə bilməyəcək.
-\nBu kanala abunəlikdən çıxmaq istəyirsiniz\?
+\nBu kanaldan abunəliyi çıxarmaq istəyirsiniz\?
Baxılan elementləri göstər
Seçilmiş
Çəkməcəni Bağla
@@ -496,12 +496,12 @@
Video fayl xülasəsi prosesi üçün bildirişlər
Aç
Miniatürü 1:1 görünüş nisbətinə kəs
- Yükləmə intervalının həcmini dəyişdir (hazırda %s). Daha aşağı dəyər ilkin video yükləməni sürətləndirə bilər. Dəyişikliklər oynadıcının yenidən başladılmasını tələb edir
- Yayım yaradıcısı, məzmunu və ya axtarış sorğusu haqqında əlavə məlumat olan üst məlumat qutularını gizlətmək üçün söndürün
+ Yükləmə intervalı həcmini dəyişdir (hazırda %s). Daha aşağı dəyər ilkin video yükləməni sürətləndirə bilər. Dəyişikliklər oynadıcını yenidən başlatmağı tələb edir
+ Yayım yaradıcısı, məzmunu və ya axtarış sorğusu haqqında əlavə məlumat olan üst məlumat qutularını gizlətmək üçün söndür
Əlaqəli yayımı əlavə etməklə (təkrarlanmayan) sonlanacaq oynatma növbəsini davam etdir
Kənar axtarış təklifləri
Nümunə artıq mövcuddur
- Videoları mini oynadıcıda başlatma, avtomatik fırlatma kilidlidirsə, birbaşa tam ekran rejiminə keçid. Siz hələ də tam ekrandan çıxmaqla mini oynadıcıya daxil ola bilərsiniz
+ Videoları kiçik oynadıcıda başlatma, avtomatik fırlatma kilidlidirsə, birbaşa tam ekran rejiminə keçid. Siz hələ də tam ekrandan çıxmaqla mini oynadıcıya daxil ola bilərsiniz
100+ video
∞ video
Şərhlər yoxdur
@@ -532,7 +532,7 @@
Etiketlər
Planşet rejimi
Bağla
- Müəllifə ürəkləndi
+ Müəllifdən ürəkləndi
Veb saytı aç
- %s baxış
@@ -550,11 +550,11 @@
- Endirmə tamamlandı
- %s endirmə tamamlandı
- Defolt ExoPlayer
+ Standart ExoPlayer
Mövcud olduqda xüsusi axından al
Baxılmış videolar silinsin\?
İzləniləni sil
- Sistem qovluğu seçicisini (SAF) istifadə edin
+ Sistem qovluğu seçicisini (SAF) istifadə et
Bağlantı fasiləsi
Cihazda yer qalmayıb
Fayl üzərində işləyərkən NewPipe bağlandı
@@ -582,7 +582,7 @@
İdxal edilir…
Pleylistə salındı
Səsi bağla
- Ani pəncərə oynadıcı
+ Ani görüntü oynadıcı
Çəkməcəni Aç
Növbələşdirmək üçün basılı tut
Sil
@@ -590,7 +590,7 @@
© %1$s, %2$s tərəfindən %3$s altında
Bu faylı oynatmaq üçün heç bir tətbiq quraşdırılmayıb
Endirmə
- Bu icazə, ani pəncərə rejimində
+ Bu icazə, ani görüntü rejimində
\naçmaq üçün lazımdır
Buferə kopyalandı
Parçalar
@@ -608,22 +608,22 @@
Nümunə URL\'sini daxil et
Nümunəni doğrulamaq mümkün olmadı
%s-də bəyəndiyiniz nümunələri tapın
- Video \"Təfsilatlar:\"səhifəsində fon və ya ani pəncərə düyməsini basarkən ipucu göstər
- Oynadıcı altyazı mətn miqyasını və arxa fon üslublarını dəyişdirin. Effektiv olması üçün tətbiqi yenidən başlatmaq tələb olunur
+ Video \"Təfsilatlar:\"səhifəsində fon və ya ani görüntü düyməsin basarkən ipucu göstər
+ Oynadıcı altyazı mətn miqyasını və arxa fon üslublarını dəyişdir. Effektiv olması üçün tətbiqi yenidən başlatmaq tələb olunur
Xəta baş verdi: %1$s
Fayl mövcud deyil, yaxud oxumaq və ya yazmaq icazəsi yoxdur
Veb saytı təhlil etmək alınmadı
- Səs ucalığı
+ Ucalıq
Radio
\"Oynadıcını çökdür\" Göstər
- Oynadıcıdan istifadə edərkən çökdürmə seçimini göstər
+ Oynadıcını istifadə edərkən çökdürmə seçimini göstər
Xəta balonu göstər
Xəta bildirişi yarat
- Burdan idxal edin
- Bura ixrac edin
+ Burdan idxal et
+ Bura ixrac et
Faylı idxal et
Abunəlikləri idxal etmək mümkün olmadı
- Avropa Ümumi Məlumat Mühafizəsi Qaydasına (GDPR) riayət etmək üçün diqqətinizi NewPipe\'ın məxfilik siyasətinə cəlb edirik. Zəhmət olmasa, diqqətlə oxuyun. Xəta hesabatını bizə göndərmək üçün onu qəbul etməlisiniz.
+ Avropa Ümumi Məlumat Mühafizəsi Qaydasına (GDPR) riayət etmək üçün diqqətinizi NewPipe məxfilik siyasətinə cəlb edirik. Zəhmət olmasa, diqqətlə oxuyun. Xəta hesabatın bizə göndərmək üçün qəbul etməlisiniz.
Bu adda fayl artıq mövcuddur
Bu adla gözlənilən bir endirmə var
Təyinat qovluğu yaradıla bilməz
@@ -643,7 +643,7 @@
Dil
İctimai
Abunəçi sayı əlçatan deyil
- Lisenziyanı oxuyun
+ Lisenziyanı Oxu
Tarixçə
Hərflər və rəqəmlər
Oynadıcını çökdür
@@ -651,18 +651,18 @@
Oynadıcı bildirişi
Yeni yayımlar
Xəta hesabatı bildirişi
- Video URL\'i imzasının şifrəsi qırılmadı
- Endirmək üçün heç bir yayım yoxdur
- Xəta baş verdi, bildirişə baxın
+ Video URL\'i imzası şifrəsi qırılmadı
+ Endirmək üçün yayım mövcud deyil
+ Xəta baş verdi, bildirişi gör
Şərhiniz (İngiliscə):
Video oynat, müddət:
- Zəhmət olmasa, daha sonra tənzimləmələrdə endirmə qovluğunu təyin et
+ Zəhmət olmasa, endirmə qovluğunu daha sonra tənzimləmələrdə təyin et
NewPipe Endirilir
Hash hesablanır
Fayl adlarında icazə verilən simvollar
NewPipe Haqqında
Lisenziyalar
- NewPipe müəllif hüquqlu sərbəst tətbiqdir: Siz onu istədiyiniz zaman istifadə edə, öyrənə, paylaşa və təkmilləşdirə bilərsiniz. Xüsusilə, siz Lisenziyanın 3-cü versiyası və ya (seçiminizə görə) hər hansı sonrakı versiyada Azad Proqram Təminatı Fondu tərəfindən dərc edilən GNU Ümumi İctimai Lisenziyanın şərtlərinə uyğun olaraq onu yenidən paylaya və/yaxud dəyişdirə bilərsiniz.
+ NewPipe müəllif hüquqlu sərbəst tətbiqdir: Siz onu istədiyiniz zaman istifadə edə, öyrənə, paylaşa və təkmilləşdirə bilərsiniz. Xüsusilə, siz Lisenziyanın 3-cü versiyası və ya (seçiminizə görə) hər hansı sonrakı versiyada Azad Proqram Təminatı Fondu tərəfindən dərc edilən GNU Ümumi İctimai Lisenziya şərtlərinə uyğun olaraq onu yenidən paylaya və/yaxud dəyişdirə bilərsiniz.
İxrac edildi
Elementləri silmək üçün sürüşdür
Hələ,əlfəcinlənmiş pleylistlər yoxdur
@@ -705,7 +705,7 @@
Bu video yalnız YouTube Music Premium üzvləri üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil.
İndi açıqlamadakı mətni seçə bilərsiniz. Nəzərə alın ki, seçim rejimində səhifə titrəyə və keçidlər kliklənməyə bilər.
Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 görünüş nisbətinə qədər kəs
- Aşağıdakı bildiriş fəaliyyətini hər birinin üzərinə toxunaraq redaktə edin. Sağdakı təsdiq qutularından istifadə edərək yığcam bildirişdə göstərilməsi üçün onların üçə qədərini seç
+ Aşağıdakı hər bir bildiriş fəaliyyətini üzərinə toxunaraq redaktə et. Sağdakı təsdiq qutularından istifadə edərək yığcam bildirişdə göstərmək üçün onların üçünü seç
Belə fayl/məzmun mənbəyi yoxdur
Seçilmiş yayım xarici oynadıcılar tərəfindən dəstəklənmir
Yükləyici tərəfindən hələ dəstəklənməyən yayımlar göstərilmir
@@ -718,7 +718,7 @@
Gələcək elementləri göstər
Baxılan elementləri gizlət
Gələcək elementləri gizlət
- Tətbiqdən istifadə etməkdə çətinlik çəkirsinizsə, ümumi suallara bu cavabların yoxlanıldığına əmin olun!
+ Tətbiqi istifadə etməkdə çətinlik çəkirsinizsə, ümumi suallara bu cavabları yoxladığınıza əmin olun!
Tez-tez soruşulan suallar
Veb Saytında bax
Çeşidlə
@@ -726,4 +726,10 @@
Sürətli rejim
3 nöqtə menyudan abunələri idxal və ya ixrac et
%s endirmək üçün toxun
+ Bu seçim yalnız tema üçün %s seçildikdə əlçatandır
+ Daimi miniatürü ləğv et
+ Kart
+ Buferə kopyalamaq alınmadı
+ Boz rəngdə olan pleylistlərdə artıq bu element var.
+ Dublikat %d dəfə əlavə edildi
\ No newline at end of file
diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml
index 899103c9d..46d12a8b6 100644
--- a/app/src/main/res/values-bn/strings.xml
+++ b/app/src/main/res/values-bn/strings.xml
@@ -644,4 +644,5 @@
ভুক্তি মুছতে ডানে-বামে সরাও
সম্প্রচার বিষয়ক তথ্য প্রক্রিয়ারত…
ভবিষ্যৎ ভুক্তি দেখাও
+ প্লেব্যাক লোড বিরতির আকার
\ No newline at end of file
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index ae4e4f018..fc9abd891 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -701,4 +701,6 @@
Format desconegut
Cualitat desconeguda
Ordenar
+ Configura la notificació de reproducció actual.
+ Canvia la mida de l\'interval de càrrega (actualment %s). Un valor inferior pot accelerar la càrrega inicial del vídeo. Els canvis requereixen un reinici del jugador.
\ No newline at end of file
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index fdbc81d43..abc77e66c 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -11,16 +11,16 @@
Nastavení
Mysleli jste „%1$s“\?
Sdílet s
- Použít externí video přehrávač
+ Použít externí přehrávač videí
Použít externí audio přehrávač
- Stažené audio je uloženo zde
+ Sem bude ukládáno stažené audio
Zvolte adresář pro stažené audio soubory
Adresář pro stažené audio
Výchozí rozlišení
Přehrát pomocí Kodi
Nainstalovat chybějící aplikaci Kore\?
Adresář pro stažená videa
- Stažená videa jsou uložena tady
+ Sem budou ukládána stažená videa
Zvolte adresář pro stažená videa
Zobrazit možnost „Přehrát pomocí Kodi“
Zobrazit možnost přehrání videa pomocí multimediálního centra Kodi
@@ -213,7 +213,7 @@
Žádný platný soubor ZIP
Upozornění: Nelze importovat všechny soubory.
Tímto se anuluje vaše aktuální nastavení.
- Video přehrávač
+ Přehrávač videa
Přehrávač na pozadí
Přehrávač v okně
Získávám informace…
@@ -298,7 +298,7 @@
Výška tónu
Odpojit (může způsobit zkreslení)
Ke stažení nejsou dostupné žádné streamy
- Preferovaná \'otevřít\' akce
+ Preferovaná akce „otevření“
Výchozí chování při otevírání obsahu — %s
Titulky
Upravuje velikost textu titulků a styly pozadí. Změny se projeví po restartu aplikace
@@ -550,7 +550,7 @@
Ukazuji výsledky pro: %s
Nikdy
Pouze na Wi-Fi
- Zahájit playback automaticky — %s
+ Automaticky zahájit přehrávání — %s
Přehrát frontu
Nelze rozpoznat zadané URL. Otevřít pomocí jiné aplikace\?
Auto-fronta
@@ -598,7 +598,7 @@
Vypnout pro skrytí popisu videa a doplňkové informace
Zbořit aplikaci
Stahování bylo zahájeno
- Můžete si zvolit svůj oblíbený motiv níže
+ Níže si můžete zvolit svůj oblíbený motiv
Zvolte si svůj oblíbený noční motiv - %s
Automatický (motiv zařízení)
Radio
@@ -668,7 +668,7 @@
Ukázat indikátory obrázků
Vzdálené návrhy vyhledávání
Lokální návrhy vyhledávání
- Pokud je vypnuté automatické otáčení, nespouštějte video v mini přehrávači, ale přepněte se přímo do režimu celé obrazovky. Do mini přehrávače se lze i nadále dostat ukončením režimu celé obrazovky
+ Pokud je vypnuté automatické otáčení, nespouštět video v mini přehrávači, ale přepnout se přímo do režimu celé obrazovky. Do mini přehrávače se lze i nadále dostat ukončením režimu celé obrazovky
Další ve frontě
Přidat do fronty (další)
Tažením položky odstraníte
@@ -728,7 +728,7 @@
Zobrazit nadcházející položky
Streamy, které zatím nejsou podporovány systémem stahování, nebudou zobrazeny
Vyberte kvalitu pro externí přehrávače
- U externích přehrávačů nejsou dostupné žádné video streamy
+ U externích přehrávačů nejsou k dispozici žádné videostreamy
Skrýt zhlédnuté položky
Skrýt nadcházející položky
Často kladené dotazy
@@ -739,4 +739,10 @@
Rychlý režim
Používáte nejnovější verzi NewPipe
Import nebo export odběrů z 3-tečkové nabídky
+ Tato možnost je dostupná pouze při vybraném motivu %s
+ Zrušení nastavení trvalého náhledu
+ Karta
+ Kopírování do schránky se nezdařilo
+ Zašedlé playlisty již obsahují tuto položku.
+ Duplikát přidán %dkrát
\ No newline at end of file
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 96ae5299d..f4601ec2d 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -32,12 +32,12 @@
Baggrund
Pop op
Føj til
- Placering af videodownloads
+ Mappe til download af video
Downloadede videoer gemmes her
- Angiv downloadmappe for videofiler
- Downloadmappe for lydfiler
+ Angiv download-mappe for videofiler
+ Download-mappe for lydfiler
Downloadede lydfiler gemmes her
- Angiv downloadmappe for lydfiler
+ Angiv download-mappe for lydfiler
Standardopløsning
Standardopløsning for pop op
Vis højere opløsninger
@@ -45,7 +45,7 @@
Afspil med Kodi
Installer manglede Kore-app\?
Vis valgmuligheden \"Afspil med Kodi\"
- Vis en knap til at afspille en video via Kodi
+ Vis en knap til at afspille en video via Kodi-mediecenteret
Lyd
Standardformat for lydfiler
Standardformat for videofiler
@@ -56,7 +56,7 @@
Husk størrelse og placering af pop op
Husk sidste størrelse og placering af pop op-afspiller
Brug hurtig og upræcis søgning
- Upræcis søgning lader afspilleren finde placeringer hurtigere, men mindre præcist. Søgninger på 5, 15 eller 25 sekunder fungerer ikke med denne indstilling, slået til
+ Upræcis søgning lader afspilleren finde placeringer hurtigere, men mindre præcist. Søgninger på 5, 15 eller 25 sekunder fungerer ikke med denne indstilling slået til
Indlæs miniaturebilleder
Slå fra for at undgå indlæsning af billeder, hvorved der spares data og hukommelse. Ændringer sletter billedcachen i både ram og lager
Billedcache slettet
@@ -64,7 +64,7 @@
Slet alle websidedata fra cachen
Metadata-cache slettet
Føj automatisk næste stream til køen
- Fortsæt nedlukningen af en (ikke-gentagende) playback kø ved at tilføje et relateret stream
+ Fortsæt en afspilningskø, der afsluttes (ikke-gentagende), ved at tilføje en lignende stream
Juster lydstyrke ved hjælp af fingerbevægelser
Brug fingerbevægelser til at kontrollere afspillerens lydstyrke
Styr lysstyrken med fingerbevægelser
@@ -101,7 +101,7 @@
Fejlrapport
Alle
Kanaler
- Playlister
+ Spillelister
- Én video
- %s videoer
@@ -130,7 +130,7 @@
Overskriver din nuværende historik, abonnementer, spillelister og (hvis det ønskes) indstillinger
Eksporter historik, abonnementer, spillelister og indstillinger
Slet visningshistorik
- Sletter historikken og positioner af tidligere viste videoer
+ Sletter historikken over afspillede streams og afspilningspositionerne
Slet hele visningshistorikken\?
Visningshistorikken blev slettet
Slet søgehistorik
@@ -217,7 +217,7 @@
Om NewPipe
Tredjepartslicenser
© %1$s af %2$s under %3$s
- Om
+ Om & Ofte stillede spørgsmål
Licenser
Åben letvægtsstreaming på Android.
Bidrag til projektet
@@ -330,7 +330,7 @@
Luk skuffe
Hvad:\\nForespørgsel:\\nIndholdssprog:\\nIndholdsland:\\nAppsprog:\\nTjeneste:\\nGMT-tid:\\nPakke:\\nVersion:\\nOS-version:
Standardhandling når indhold åbnes – %s
- Anvend som playlistens miniature
+ Anvend som spillelistens miniaturebillede
Bogmærk spilleliste
Fjern bogmærke
Føjet til spillelisten
@@ -386,7 +386,7 @@
Afspil automatisk
Ryd data
Positioner i lister
- Genopret forrige afspilningsposition
+ Gendan sidste afspilningsposition
Fortsæt afspilning
Slå fra for at skjule kommentarer
Vis kommentarer
@@ -398,7 +398,7 @@
Anden handlingstast
Tredje handlingstast
Viser resultater for: %s
- Åben med
+ Åbn med
LeakCanary er ikke tilgængelig
Markér som set
Beskrivelse
@@ -447,7 +447,7 @@
Nye streams
Notifikationer om nye streams fra abonnementer
reCAPTCHA cookies er ryddet
- Slet alle playback positioner\?
+ Slet alle afspilningspositioner\?
Filen er flyttet eller slettet
NewPipe stødte ind i en fejl, tryk for at rapportere
Rapporter på GitHub
@@ -463,9 +463,9 @@
- Download fuldført
- %s downloads fuldført
- Lav indlæsningsintervallets størrelse, (som nu ligger på %s) om. En højere værdi kan øge videoindlæsningshastigheden. Ændringer af værdien kræver genstart.
+ Ændr indlæsningsintervallets størrelse (som nu er på %s). En lavere værdi kan øge videoindlæsningshastigheden. Ændringer kræver en genstart af afspiller
Den aktive spilleliste bliver udskiftet
- At skifte fra en afspiller til en anden kan udskifte din kø
+ Hvis du skifter fra en spiller til en anden, kan din kø blive erstattet
Vis metainformation
Lokale søgeforslag
Fjerne søgeforslag
@@ -475,8 +475,8 @@
Notifikationer om videohashfunktioners status
Fejlrapport-notifikation
Notifikationer for at rapportere fejl
- Slet playback positioner
- Sletter alle playback positioner
+ Slet afspilningspositioner
+ Sletter alle afspilningspositioner
Spørg hvor filen skal downloades
Et download ad gangen
Slet downloadede filer
@@ -487,23 +487,23 @@
\nNewPipes fortrolighedspolitik forklarer i detaljer, hvilke data der bliver sendt og opbevaret når du sender en nedbrudsrapport.