Uniform localizing view counts

This commit is contained in:
Stypox 2025-08-28 18:26:50 +02:00
parent b50db477a1
commit 4b90295aab
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
8 changed files with 79 additions and 71 deletions

View File

@ -1424,14 +1424,9 @@ class VideoDetailFragment :
}
if (info.viewCount >= 0) {
binding.detailViewCountView.text =
if (info.streamType == StreamType.AUDIO_LIVE_STREAM) {
Localization.listeningCount(activity, info.viewCount)
} else if (info.streamType == StreamType.LIVE_STREAM) {
Localization.localizeWatchingCount(activity, info.viewCount)
} else {
Localization.localizeViewCount(activity, info.viewCount)
}
binding.detailViewCountView.text = Localization.localizeViewCount(
activity, false, info.streamType, info.viewCount
)
binding.detailViewCountView.visibility = View.VISIBLE
} else {
binding.detailViewCountView.visibility = View.GONE

View File

@ -7,7 +7,6 @@ import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.Localization;
@ -65,16 +64,8 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
private String getStreamInfoDetailLine(final StreamInfoItem infoItem) {
String viewsAndDate = "";
if (infoItem.getViewCount() >= 0) {
if (infoItem.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) {
viewsAndDate = Localization
.listeningCount(itemBuilder.getContext(), infoItem.getViewCount());
} else if (infoItem.getStreamType().equals(StreamType.LIVE_STREAM)) {
viewsAndDate = Localization
.shortWatchingCount(itemBuilder.getContext(), infoItem.getViewCount());
} else {
viewsAndDate = Localization
.shortViewCount(itemBuilder.getContext(), infoItem.getViewCount());
}
viewsAndDate = Localization.localizeViewCount(itemBuilder.getContext(), true,
infoItem.getStreamType(), infoItem.getViewCount());
}
final String uploadDate = Localization.relativeTimeOrTextual(itemBuilder.getContext(),

View File

@ -1,7 +1,6 @@
package org.schabi.newpipe.local.feed.item
import android.content.Context
import android.text.TextUtils
import android.view.View
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
@ -117,23 +116,16 @@ data class StreamItem(
}
private fun getStreamInfoDetailLine(context: Context): String {
var viewsAndDate = ""
val viewCount = stream.viewCount
if (viewCount != null && viewCount >= 0) {
viewsAndDate = when (stream.streamType) {
AUDIO_LIVE_STREAM -> Localization.listeningCount(context, viewCount)
LIVE_STREAM -> Localization.shortWatchingCount(context, viewCount)
else -> Localization.shortViewCount(context, viewCount)
}
}
val views = stream.viewCount
?.takeIf { it >= 0 }
?.let { Localization.localizeViewCount(context, true, stream.streamType, it) }
?: ""
val uploadDate = getFormattedRelativeUploadDate(context)
return when {
!TextUtils.isEmpty(uploadDate) -> when {
viewsAndDate.isEmpty() -> uploadDate!!
else -> Localization.concatenateStrings(viewsAndDate, uploadDate)
}
else -> viewsAndDate
uploadDate.isNullOrEmpty() -> views
views.isEmpty() -> uploadDate
else -> Localization.concatenateStrings(views, uploadDate)
}
}

View File

@ -73,7 +73,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
final DateTimeFormatter dateTimeFormatter) {
return Localization.concatenateStrings(
// watchCount
Localization.shortViewCount(itemBuilder.getContext(), entry.getWatchCount()),
Localization.localizeWatchCount(itemBuilder.getContext(), entry.getWatchCount()),
dateTimeFormatter.format(entry.getLatestAccessDate()),
// serviceName
ServiceHelper.getNameOfServiceById(entry.getStreamEntity().getServiceId()));

View File

@ -37,16 +37,10 @@ internal fun getStreamInfoDetail(stream: StreamInfoItem): String {
val context = LocalContext.current
return rememberSaveable(stream) {
val count = stream.viewCount
val views = if (count >= 0) {
when (stream.streamType) {
StreamType.AUDIO_LIVE_STREAM -> Localization.listeningCount(context, count)
StreamType.LIVE_STREAM -> Localization.shortWatchingCount(context, count)
else -> Localization.shortViewCount(context, count)
}
} else {
""
}
val views = stream.viewCount
.takeIf { it >= 0 }
?.let { Localization.localizeViewCount(context, true, stream.streamType, it) }
?: ""
val date =
Localization.relativeTimeOrTextual(context, stream.uploadDate, stream.textualUploadDate)

View File

@ -72,6 +72,7 @@ import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.ktx.popFirst
import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.EnqueueNext
import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.ShowChannelDetails
@ -441,7 +442,9 @@ fun getSubtitleAnnotatedString(
append(uploadDate)
}
val viewCount = item.viewCount?.let { Localization.localizeViewCount(ctx, it) }
val viewCount = item.viewCount?.let {
Localization.localizeViewCount(ctx, true, item.streamType, it)
}
if (!viewCount.isNullOrBlank()) {
if (shouldAddSeparator) {
append(Localization.DOT_SEPARATOR)
@ -545,6 +548,7 @@ private class LongPressablePreviews : CollectionPreviewParameterProvider<LongPre
uploader = "Blender",
uploaderUrl = "https://www.youtube.com/@BlenderOfficial",
viewCount = 8765432,
streamType = null,
uploadDate = Either.left("16 years ago"),
decoration = LongPressable.Decoration.Playlist(12),
),
@ -555,6 +559,7 @@ private class LongPressablePreviews : CollectionPreviewParameterProvider<LongPre
uploader = "Blender",
uploaderUrl = "https://www.youtube.com/@BlenderOfficial",
viewCount = 8765432,
streamType = StreamType.VIDEO_STREAM,
uploadDate = Either.left("16 years ago"),
decoration = LongPressable.Decoration.Duration(500),
),
@ -565,6 +570,7 @@ private class LongPressablePreviews : CollectionPreviewParameterProvider<LongPre
uploader = null,
uploaderUrl = "https://www.youtube.com/@BlenderOfficial",
viewCount = null,
streamType = null,
uploadDate = null,
decoration = null,
),
@ -575,6 +581,7 @@ private class LongPressablePreviews : CollectionPreviewParameterProvider<LongPre
uploader = null,
uploaderUrl = null,
viewCount = null,
streamType = StreamType.AUDIO_STREAM,
uploadDate = null,
decoration = LongPressable.Decoration.Duration(500),
),
@ -585,6 +592,7 @@ private class LongPressablePreviews : CollectionPreviewParameterProvider<LongPre
uploader = null,
uploaderUrl = null,
viewCount = null,
streamType = StreamType.LIVE_STREAM,
uploadDate = null,
decoration = LongPressable.Decoration.Live,
),
@ -595,6 +603,7 @@ private class LongPressablePreviews : CollectionPreviewParameterProvider<LongPre
uploader = null,
uploaderUrl = null,
viewCount = null,
streamType = StreamType.AUDIO_LIVE_STREAM,
uploadDate = Either.right(OffsetDateTime.now().minusSeconds(12)),
decoration = LongPressable.Decoration.Playlist(1500),
),

View File

@ -23,6 +23,7 @@ data class LongPressable(
val uploader: String?,
val uploaderUrl: String?,
val viewCount: Long?,
val streamType: StreamType?, // only used to format the view count properly
val uploadDate: Either<String, OffsetDateTime>?,
val decoration: Decoration?,
) {
@ -50,6 +51,7 @@ data class LongPressable(
uploader = item.uploaderName?.takeIf { it.isNotBlank() },
uploaderUrl = item.uploaderUrl?.takeIf { it.isNotBlank() },
viewCount = item.viewCount.takeIf { it >= 0 },
streamType = item.streamType,
uploadDate = item.uploadDate?.let { Either.right(it.offsetDateTime()) }
?: item.textualUploadDate?.let { Either.left(it) },
decoration = Decoration.from(item.streamType, item.duration),
@ -63,6 +65,7 @@ data class LongPressable(
uploader = item.uploader.takeIf { it.isNotBlank() },
uploaderUrl = item.uploaderUrl?.takeIf { it.isNotBlank() },
viewCount = item.viewCount?.takeIf { it >= 0 },
streamType = item.streamType,
uploadDate = item.uploadDate?.let { Either.right(it) }
?: item.textualUploadDate?.let { Either.left(it) },
decoration = Decoration.from(item.streamType, item.duration),
@ -77,6 +80,7 @@ data class LongPressable(
uploader = null,
uploaderUrl = null,
viewCount = null,
streamType = null,
uploadDate = null,
decoration = Decoration.Playlist(item.streamCount),
)
@ -89,6 +93,7 @@ data class LongPressable(
uploader = item.uploader,
uploaderUrl = null,
viewCount = null,
streamType = null,
uploadDate = null,
decoration = Decoration.Playlist(
item.streamCount ?: ListExtractor.ITEM_COUNT_UNKNOWN
@ -103,6 +108,7 @@ data class LongPressable(
uploader = null,
uploaderUrl = item.url?.takeIf { it.isNotBlank() },
viewCount = null,
streamType = null,
uploadDate = null,
decoration = null,
)
@ -115,6 +121,7 @@ data class LongPressable(
uploader = item.uploaderName.takeIf { it.isNotBlank() },
uploaderUrl = item.uploaderUrl?.takeIf { it.isNotBlank() },
viewCount = null,
streamType = null,
uploadDate = null,
decoration = Decoration.Playlist(item.streamCount),
)

View File

@ -31,6 +31,7 @@ import org.schabi.newpipe.extractor.localization.ContentCountry;
import org.schabi.newpipe.extractor.localization.DateWrapper;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.AudioTrackType;
import org.schabi.newpipe.extractor.stream.StreamType;
import java.math.BigDecimal;
import java.math.RoundingMode;
@ -183,9 +184,50 @@ public final class Localization {
return context.getString(R.string.upload_date_text, formatDate(offsetDateTime));
}
public static String localizeViewCount(@NonNull final Context context, final long viewCount) {
/**
* Localizes the number of views of a stream reported by the service,
* with different words based on the stream type.
*
* @param context the Android context
* @param shortForm whether the number of views should be formatted in a short approximated form
* @param streamType influences the accompanying text, i.e. views/watching/listening
* @param viewCount the number of views reported by the service to localize
* @return the formatted and localized view count
*/
public static String localizeViewCount(@NonNull final Context context,
final boolean shortForm,
@Nullable final StreamType streamType,
final long viewCount) {
final String localizedNumber;
if (shortForm) {
localizedNumber = shortCount(context, viewCount);
} else {
localizedNumber = localizeNumber(viewCount);
}
if (streamType == StreamType.AUDIO_LIVE_STREAM) {
return getQuantity(context, R.plurals.listening, R.string.no_one_listening, viewCount,
localizedNumber);
} else if (streamType == StreamType.LIVE_STREAM) {
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, viewCount,
localizedNumber);
} else {
return getQuantity(context, R.plurals.views, R.string.no_views, viewCount,
localizedNumber);
}
}
/**
* Localizes the number of times the user watched a video that they have in the history.
*
* @param context the Android context
* @param viewCount the number of times (stored in the database) the user watched a video
* @return the formatted and localized watch count
*/
public static String localizeWatchCount(@NonNull final Context context,
final long viewCount) {
return getQuantity(context, R.plurals.views, R.string.no_views, viewCount,
localizeNumber(viewCount));
shortCount(context, viewCount));
}
public static String localizeStreamCount(@NonNull final Context context,
@ -217,12 +259,6 @@ public final class Localization {
}
}
public static String localizeWatchingCount(@NonNull final Context context,
final long watchingCount) {
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount,
localizeNumber(watchingCount));
}
public static String shortCount(@NonNull final Context context, final long count) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return CompactDecimalFormat.getInstance(getAppLocale(),
@ -250,22 +286,6 @@ public final class Localization {
}
}
public static String listeningCount(@NonNull final Context context, final long listeningCount) {
return getQuantity(context, R.plurals.listening, R.string.no_one_listening, listeningCount,
shortCount(context, listeningCount));
}
public static String shortWatchingCount(@NonNull final Context context,
final long watchingCount) {
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount,
shortCount(context, watchingCount));
}
public static String shortViewCount(@NonNull final Context context, final long viewCount) {
return getQuantity(context, R.plurals.views, R.string.no_views, viewCount,
shortCount(context, viewCount));
}
public static String shortSubscriberCount(@NonNull final Context context,
final long subscriberCount) {
return getQuantity(context, R.plurals.subscribers, R.string.no_subscribers, subscriberCount,