Skip to content

fix: support RTL usernames in comment header #12188

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import androidx.core.text.BidiFormatter;

import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
Expand Down Expand Up @@ -51,7 +52,7 @@ public class CommentInfoItemHolder extends InfoItemHolder {
private final TextEllipsizer textEllipsizer;

public CommentInfoItemHolder(final InfoItemBuilder infoItemBuilder,
final ViewGroup parent) {
final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_comment_item, parent);

itemRoot = itemView.findViewById(R.id.itemRoot);
Expand Down Expand Up @@ -81,13 +82,12 @@ public CommentInfoItemHolder(final InfoItemBuilder infoItemBuilder,

@Override
public void updateFromItem(final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
final HistoryRecordManager historyRecordManager) {
if (!(infoItem instanceof CommentsInfoItem)) {
return;
}
final CommentsInfoItem item = (CommentsInfoItem) infoItem;


// load the author avatar
PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(itemThumbnailView);
if (ImageStrategy.shouldLoadImages()) {
Expand All @@ -101,14 +101,16 @@ public void updateFromItem(final InfoItem infoItem,
}
itemThumbnailView.setOnClickListener(view -> openCommentAuthor(item));


// setup the top row, with pinned icon, author name and comment date
itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
itemTitleView.setText(Localization.concatenateStrings(item.getUploaderName(),
Localization.relativeTimeOrTextual(itemBuilder.getContext(), item.getUploadDate(),
final String uploaderName = BidiFormatter.getInstance().unicodeWrap(item.getUploaderName());
itemTitleView.setText(Localization.concatenateStrings(
uploaderName,
Localization.relativeTimeOrTextual(
itemBuilder.getContext(),
item.getUploadDate(),
item.getTextualUploadDate())));


// setup bottom row, with likes, heart and replies button
itemLikesCountView.setText(
Localization.likeCount(itemBuilder.getContext(), item.getLikeCount()));
Expand All @@ -119,18 +121,19 @@ public void updateFromItem(final InfoItem infoItem,
repliesButton.setOnClickListener(hasReplies ? v -> openCommentReplies(item) : null);
repliesButton.setVisibility(hasReplies ? View.VISIBLE : View.GONE);
repliesButton.setText(hasReplies
? Localization.replyCount(itemBuilder.getContext(), item.getReplyCount()) : "");
? Localization.replyCount(itemBuilder.getContext(), item.getReplyCount())
: "");
((RelativeLayout.LayoutParams) itemThumbsUpView.getLayoutParams()).topMargin =
hasReplies ? 0 : DeviceUtils.dpToPx(6, itemBuilder.getContext());

hasReplies ? 0
: DeviceUtils.dpToPx(6, itemBuilder.getContext());

// setup comment content and click listeners to expand/ellipsize it
textEllipsizer.setStreamingService(getServiceById(item.getServiceId()));
textEllipsizer.setStreamUrl(item.getUrl());
textEllipsizer.setContent(item.getCommentText());
textEllipsizer.ellipsize();

//noinspection ClickableViewAccessibility
// noinspection ClickableViewAccessibility
itemContentView.setOnTouchListener((v, event) -> {
final CharSequence text = itemContentView.getText();
if (text instanceof Spanned buffer) {
Expand Down
109 changes: 66 additions & 43 deletions app/src/main/java/org/schabi/newpipe/util/Localization.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import android.os.Build;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.BidiFormatter;
import android.util.DisplayMetrics;
import android.util.Log;

Expand Down Expand Up @@ -45,7 +46,6 @@
import java.util.Objects;
import java.util.stream.Collectors;


/*
* Created by chschtsch on 12/29/15.
*
Expand All @@ -71,17 +71,21 @@ public final class Localization {
public static final String DOT_SEPARATOR = " • ";
private static PrettyTime prettyTime;

private Localization() { }
private Localization() {
}

@NonNull
public static String concatenateStrings(final String... strings) {
return concatenateStrings(DOT_SEPARATOR, Arrays.asList(strings));
}

// Use of BidiFormater to fix text direction automatically
@NonNull
public static String concatenateStrings(final String delimiter, final List<String> strings) {
final BidiFormatter bidi = BidiFormatter.getInstance();
return strings.stream()
.filter(string -> !TextUtils.isEmpty(string))
.map((s) -> strings.indexOf(s) == 0 ? bidi.unicodeWrap(s) : s) // only wrap username
.collect(Collectors.joining(delimiter));
}

Expand Down Expand Up @@ -123,15 +127,15 @@ public static String localizeNumber(@NonNull final Context context, final double
}

public static String formatDate(@NonNull final Context context,
@NonNull final OffsetDateTime offsetDateTime) {
@NonNull final OffsetDateTime offsetDateTime) {
return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(getAppLocale(context)).format(offsetDateTime
.atZoneSameInstant(ZoneId.systemDefault()));
}

@SuppressLint("StringFormatInvalid")
public static String localizeUploadDate(@NonNull final Context context,
@NonNull final OffsetDateTime offsetDateTime) {
@NonNull final OffsetDateTime offsetDateTime) {
return context.getString(R.string.upload_date_text, formatDate(context, offsetDateTime));
}

Expand All @@ -141,7 +145,7 @@ public static String localizeViewCount(@NonNull final Context context, final lon
}

public static String localizeStreamCount(@NonNull final Context context,
final long streamCount) {
final long streamCount) {
switch ((int) streamCount) {
case (int) ListExtractor.ITEM_COUNT_UNKNOWN:
return "";
Expand All @@ -156,7 +160,7 @@ public static String localizeStreamCount(@NonNull final Context context,
}

public static String localizeStreamCountMini(@NonNull final Context context,
final long streamCount) {
final long streamCount) {
switch ((int) streamCount) {
case (int) ListExtractor.ITEM_COUNT_UNKNOWN:
return "";
Expand All @@ -170,7 +174,7 @@ public static String localizeStreamCountMini(@NonNull final Context context,
}

public static String localizeWatchingCount(@NonNull final Context context,
final long watchingCount) {
final long watchingCount) {
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount,
localizeNumber(context, watchingCount));
}
Expand Down Expand Up @@ -202,7 +206,7 @@ public static String listeningCount(@NonNull final Context context, final long l
}

public static String shortWatchingCount(@NonNull final Context context,
final long watchingCount) {
final long watchingCount) {
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount,
shortCount(context, watchingCount));
}
Expand All @@ -213,7 +217,7 @@ public static String shortViewCount(@NonNull final Context context, final long v
}

public static String shortSubscriberCount(@NonNull final Context context,
final long subscriberCount) {
final long subscriberCount) {
return getQuantity(context, R.plurals.subscribers, R.string.no_subscribers, subscriberCount,
shortCount(context, subscriberCount));
}
Expand All @@ -224,7 +228,7 @@ public static String downloadCount(@NonNull final Context context, final int dow
}

public static String deletedDownloadCount(@NonNull final Context context,
final int deletedCount) {
final int deletedCount) {
return getQuantity(context, R.plurals.deleted_downloads_toast, 0,
deletedCount, shortCount(context, deletedCount));
}
Expand All @@ -235,10 +239,12 @@ public static String replyCount(@NonNull final Context context, final int replyC
}

/**
* @param context the Android context
* @param context the Android context
* @param likeCount the like count, possibly negative if unknown
* @return if {@code likeCount} is smaller than {@code 0}, the string {@code "-"}, otherwise
* the result of calling {@link #shortCount(Context, long)} on the like count
* @return if {@code likeCount} is smaller than {@code 0}, the string
* {@code "-"}, otherwise
* the result of calling {@link #shortCount(Context, long)} on the like
* count
*/
public static String likeCount(@NonNull final Context context, final int likeCount) {
if (likeCount < 0) {
Expand All @@ -249,7 +255,8 @@ public static String likeCount(@NonNull final Context context, final int likeCou
}

/**
* Get a readable text for a duration in the format {@code hours:minutes:seconds}.
* Get a readable text for a duration in the format
* {@code hours:minutes:seconds}.
*
* @param duration the duration in seconds
* @return a formatted duration String or {@code 00:00} if the duration is zero.
Expand All @@ -259,16 +266,18 @@ public static String getDurationString(final long duration) {
}

/**
* Get a readable text for a duration in the format {@code hours:minutes:seconds+}. If the given
* Get a readable text for a duration in the format
* {@code hours:minutes:seconds+}. If the given
* duration is incomplete, a plus is appended to the duration string.
*
* @param duration the duration in seconds
* @param isDurationComplete whether the given duration is complete or whether info is missing
* @param duration the duration in seconds
* @param isDurationComplete whether the given duration is complete or whether
* info is missing
* @param showDurationPrefix whether the duration-prefix shall be shown
* @return a formatted duration String or {@code 00:00} if the duration is zero.
*/
public static String getDurationString(final long duration, final boolean isDurationComplete,
final boolean showDurationPrefix) {
final boolean showDurationPrefix) {
final String output = getDurationString(duration);
final String durationPrefix = showDurationPrefix ? "⏱ " : "";
final String durationPostfix = isDurationComplete ? "" : "+";
Expand All @@ -278,16 +287,19 @@ public static String getDurationString(final long duration, final boolean isDura
/**
* Localize an amount of seconds into a human readable string.
*
* <p>The seconds will be converted to the closest whole time unit.
* <p>For example, 60 seconds would give "1 minute", 119 would also give "1 minute".
* <p>
* The seconds will be converted to the closest whole time unit.
* <p>
* For example, 60 seconds would give "1 minute", 119 would also give "1
* minute".
*
* @param context used to get plurals resources.
* @param durationInSecs an amount of seconds.
* @return duration in a human readable string.
*/
@NonNull
public static String localizeDuration(@NonNull final Context context,
final int durationInSecs) {
final int durationInSecs) {
if (durationInSecs < 0) {
throw new IllegalArgumentException("duration can not be negative");
}
Expand All @@ -313,11 +325,13 @@ public static String localizeDuration(@NonNull final Context context,
/**
* Get the localized name of an audio track.
*
* <p>Examples of results returned by this method:</p>
* <p>
* Examples of results returned by this method:
* </p>
* <ul>
* <li>English (original)</li>
* <li>English (descriptive)</li>
* <li>Spanish (Spain) (dubbed)</li>
* <li>English (original)</li>
* <li>English (descriptive)</li>
* <li>Spanish (Spain) (dubbed)</li>
* </ul>
*
* @param context the context used to get the app language
Expand All @@ -343,7 +357,7 @@ public static String audioTrackName(@NonNull final Context context, final AudioS

@NonNull
private static String audioTrackType(@NonNull final Context context,
@NonNull final AudioTrackType trackType) {
@NonNull final AudioTrackType trackType) {
return switch (trackType) {
case ORIGINAL -> context.getString(R.string.audio_track_type_original);
case DUBBED -> context.getString(R.string.audio_track_type_dubbed);
Expand All @@ -352,9 +366,11 @@ private static String audioTrackType(@NonNull final Context context,
};
}

/*//////////////////////////////////////////////////////////////////////////
// Pretty Time
//////////////////////////////////////////////////////////////////////////*/
/*
* //////////////////////////////////////////////////////////////////////////
* // Pretty Time
* //////////////////////////////////////////////////////////////////////////
*/

public static void initPrettyTime(@NonNull final PrettyTime time) {
prettyTime = time;
Expand All @@ -371,19 +387,26 @@ public static String relativeTime(@NonNull final OffsetDateTime offsetDateTime)
}

/**
* @param context the Android context; if {@code null} then even if in debug mode and the
* setting is enabled, {@code textual} will not be shown next to {@code parsed}
* @param parsed the textual date or time ago parsed by NewPipeExtractor, or {@code null} if
* @param context the Android context; if {@code null} then even if in debug
* mode and the
* setting is enabled, {@code textual} will not be shown next to
* {@code parsed}
* @param parsed the textual date or time ago parsed by NewPipeExtractor, or
* {@code null} if
* the extractor could not parse it
* @param textual the original textual date or time ago string as provided by services
* @return {@link #relativeTime(OffsetDateTime)} is used if {@code parsed != null}, otherwise
* {@code textual} is returned. If in debug mode, {@code context != null},
* {@code parsed != null} and the relevant setting is enabled, {@code textual} will
* @param textual the original textual date or time ago string as provided by
* services
* @return {@link #relativeTime(OffsetDateTime)} is used if
* {@code parsed != null}, otherwise
* {@code textual} is returned. If in debug mode,
* {@code context != null},
* {@code parsed != null} and the relevant setting is enabled,
* {@code textual} will
* be appended to the returned string for debugging purposes.
*/
public static String relativeTimeOrTextual(@Nullable final Context context,
@Nullable final DateWrapper parsed,
final String textual) {
@Nullable final DateWrapper parsed,
final String textual) {
if (parsed == null) {
return textual;
} else if (DEBUG && context != null && PreferenceManager
Expand All @@ -404,7 +427,7 @@ public static void assureCorrectAppLanguage(final Context c) {
}

private static Locale getLocaleFromPrefs(@NonNull final Context context,
@StringRes final int prefKey) {
@StringRes final int prefKey) {
final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
final String defaultKey = context.getString(R.string.default_localization_key);
final String languageCode = sp.getString(context.getString(prefKey), defaultKey);
Expand All @@ -421,10 +444,10 @@ private static double round(final double value) {
}

private static String getQuantity(@NonNull final Context context,
@PluralsRes final int pluralId,
@StringRes final int zeroCaseStringId,
final long count,
final String formattedCount) {
@PluralsRes final int pluralId,
@StringRes final int zeroCaseStringId,
final long count,
final String formattedCount) {
if (count == 0) {
return context.getString(zeroCaseStringId);
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/layout/comment_replies_header.xml
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,4 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/commentContent" />

</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
1 change: 1 addition & 0 deletions app/src/main/res/layout/list_comment_item.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
android:textSize="@dimen/comment_item_title_text_size"
tools:text="Author Name, Lorem ipsum • 5 months ago" />


<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/itemCommentContentView"
android:layout_width="match_parent"
Expand Down