Show toot stat inline (#3413)

* Show toot stat inline

* Correct elements position

* Format stats and show it according to setting

* inline toot statistics setting

* Code formatting

* Use kotlin functions

* Change the statistics setting description

* Use capital letters for all variants

* increase the statistics margin

* Merge fixes

* Code review fixes

* move setReblogsCount and setFavouritedCount to StatusViewHolder

* code cleaning

* code cleaning

* import lexicographical order

---------

Co-authored-by: Grigorii Ioffe <zikasaks@gmail.com>
Co-authored-by: grigoriiioffe <zikasaks@icloud.com>
This commit is contained in:
Grigorii Ioffe 2023-03-18 09:57:26 +02:00 committed by GitHub
parent 9087d0ecdd
commit 75e7b9f1a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 146 additions and 4 deletions

View file

@ -54,6 +54,7 @@ import com.keylesspalace.tusky.util.CardViewMode;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.util.NumberUtils;
import com.keylesspalace.tusky.util.StatusDisplayOptions;
import com.keylesspalace.tusky.util.TimestampUtils;
import com.keylesspalace.tusky.util.TouchDelegateHelper;
@ -388,10 +389,10 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
private void setReplyCount(int repliesCount) {
protected void setReplyCount(int repliesCount) {
// This label only exists in the non-detailed view (to match the web ui)
if (replyCountLabel != null) {
replyCountLabel.setText((repliesCount > 1 ? replyCountLabel.getContext().getString(R.string.status_count_one_plus) : Integer.toString(repliesCount)));
replyCountLabel.setText(NumberUtils.shortNumber(repliesCount));
}
}
@ -626,12 +627,18 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
avatar.setOnClickListener(profileButtonClickListener);
displayName.setOnClickListener(profileButtonClickListener);
if (replyCountLabel != null) {
replyCountLabel.setVisibility(statusDisplayOptions.showStatsInline() ? View.VISIBLE : View.INVISIBLE);
}
replyButton.setOnClickListener(v -> {
int position = getBindingAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onReply(position);
}
});
if (reblogButton != null) {
reblogButton.setEventListener((button, buttonState) -> {
// return true to play animation
@ -650,6 +657,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
});
}
favouriteButton.setEventListener((button, buttonState) -> {
// return true to play animation
int position = getBindingAdapterPosition();

View file

@ -32,6 +32,7 @@ import com.keylesspalace.tusky.entity.Filter;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.NumberUtils;
import com.keylesspalace.tusky.util.SmartLengthInputFilter;
import com.keylesspalace.tusky.util.StatusDisplayOptions;
import com.keylesspalace.tusky.util.StringUtils;
@ -47,11 +48,15 @@ public class StatusViewHolder extends StatusBaseViewHolder {
private final TextView statusInfo;
private final Button contentCollapseButton;
private final TextView favouritedCountLabel;
private final TextView reblogsCountLabel;
public StatusViewHolder(View itemView) {
super(itemView);
statusInfo = itemView.findViewById(R.id.status_info);
contentCollapseButton = itemView.findViewById(R.id.button_toggle_content);
favouritedCountLabel = itemView.findViewById(R.id.status_favourites_count);
reblogsCountLabel = itemView.findViewById(R.id.status_insets);
}
@Override
@ -77,6 +82,12 @@ public class StatusViewHolder extends StatusBaseViewHolder {
}
}
reblogsCountLabel.setVisibility(statusDisplayOptions.showStatsInline() ? View.VISIBLE : View.INVISIBLE);
favouritedCountLabel.setVisibility(statusDisplayOptions.showStatsInline() ? View.VISIBLE : View.INVISIBLE);
setFavouritedCount(status.getActionable().getFavouritesCount());
setReblogsCount(status.getActionable().getReblogsCount());
super.setupWithStatus(status, listener, statusDisplayOptions, payloads);
}
@ -102,6 +113,14 @@ public class StatusViewHolder extends StatusBaseViewHolder {
statusInfo.setVisibility(View.VISIBLE);
}
protected void setReblogsCount(int reblogsCount) {
reblogsCountLabel.setText(NumberUtils.shortNumber(reblogsCount));
}
protected void setFavouritedCount(int favouritedCount) {
favouritedCountLabel.setText(NumberUtils.shortNumber(favouritedCount));
}
protected void hideStatusInfo() {
statusInfo.setVisibility(View.GONE);
}

View file

@ -112,6 +112,7 @@ class ConversationsFragment :
confirmFavourites = preferences.getBoolean("confirmFavourites", false),
hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false),
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false),
showStatsInline = preferences.getBoolean(PrefKeys.SHOW_STATS_INLINE, false),
showSensitiveMedia = accountManager.activeAccount!!.alwaysShowSensitiveMedia,
openSpoiler = accountManager.activeAccount!!.alwaysOpenSpoiler
)

View file

@ -151,7 +151,7 @@ class PreferencesActivity :
}
"statusTextSize", "absoluteTimeView", "showBotOverlay", "animateGifAvatars", "useBlurhash",
"showSelfUsername", "showCardsInTimelines", "confirmReblogs", "confirmFavourites",
"enableSwipeForTabs", "mainNavPosition", PrefKeys.HIDE_TOP_TOOLBAR -> {
"enableSwipeForTabs", "mainNavPosition", PrefKeys.HIDE_TOP_TOOLBAR, PrefKeys.SHOW_STATS_INLINE -> {
restartActivitiesOnBackPressedCallback.isEnabled = true
}
}

View file

@ -221,6 +221,13 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
setTitle(R.string.pref_title_enable_swipe_for_tabs)
isSingleLineTitle = false
}
switchPreference {
setDefaultValue(false)
key = PrefKeys.SHOW_STATS_INLINE
setTitle(R.string.pref_title_show_stat_inline)
isSingleLineTitle = false
}
}
preferenceCategory(R.string.pref_title_browser_settings) {

View file

@ -157,6 +157,7 @@ class ReportStatusesFragment :
confirmFavourites = preferences.getBoolean("confirmFavourites", false),
hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false),
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false),
showStatsInline = preferences.getBoolean(PrefKeys.SHOW_STATS_INLINE, false),
showSensitiveMedia = accountManager.activeAccount!!.alwaysShowSensitiveMedia,
openSpoiler = accountManager.activeAccount!!.alwaysOpenSpoiler
)

View file

@ -88,6 +88,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
confirmFavourites = preferences.getBoolean("confirmFavourites", false),
hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false),
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false),
showStatsInline = preferences.getBoolean(PrefKeys.SHOW_STATS_INLINE, false),
showSensitiveMedia = accountManager.activeAccount!!.alwaysShowSensitiveMedia,
openSpoiler = accountManager.activeAccount!!.alwaysOpenSpoiler
)

View file

@ -197,6 +197,7 @@ class TimelineFragment :
confirmFavourites = preferences.getBoolean(PrefKeys.CONFIRM_FAVOURITES, false),
hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false),
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false),
showStatsInline = preferences.getBoolean(PrefKeys.SHOW_STATS_INLINE, false),
showSensitiveMedia = accountManager.activeAccount!!.alwaysShowSensitiveMedia,
openSpoiler = accountManager.activeAccount!!.alwaysOpenSpoiler
)

View file

@ -113,6 +113,7 @@ class ViewThreadFragment :
confirmFavourites = preferences.getBoolean("confirmFavourites", false),
hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false),
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false),
showStatsInline = preferences.getBoolean(PrefKeys.SHOW_STATS_INLINE, false),
showSensitiveMedia = accountManager.activeAccount!!.alwaysShowSensitiveMedia,
openSpoiler = accountManager.activeAccount!!.alwaysOpenSpoiler
)

View file

@ -67,6 +67,7 @@ object PrefKeys {
const val CONFIRM_FAVOURITES = "confirmFavourites"
const val ENABLE_SWIPE_FOR_TABS = "enableSwipeForTabs"
const val ANIMATE_CUSTOM_EMOJIS = "animateCustomEmojis"
const val SHOW_STATS_INLINE = "showStatsInline"
const val CUSTOM_TABS = "customTabs"
const val WELLBEING_LIMITED_NOTIFICATIONS = "wellbeingModeLimitedNotifications"

View file

@ -0,0 +1,26 @@
@file:JvmName("NumberUtils")
package com.keylesspalace.tusky.util
import java.text.DecimalFormat
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.log10
import kotlin.math.pow
import kotlin.math.sign
val shortLetters = arrayOf(' ', 'K', 'M', 'B', 'T', 'P', 'E')
fun shortNumber(number: Number): String {
val numberAsDouble = number.toDouble()
val nonNegativeValue = abs(numberAsDouble)
var sign = ""
if (numberAsDouble.sign < 0) { sign = "-" }
val value = floor(log10(nonNegativeValue)).toInt()
val base = value / 3
if (value >= 3 && base < shortLetters.size) {
return DecimalFormat("$sign#0.0").format(nonNegativeValue / 10.0.pow((base * 3).toDouble())) + shortLetters[base]
} else {
return DecimalFormat("$sign#,##0").format(nonNegativeValue)
}
}

View file

@ -42,6 +42,8 @@ data class StatusDisplayOptions(
val hideStats: Boolean,
@get:JvmName("animateEmojis")
val animateEmojis: Boolean,
@get:JvmName("showStatsInline")
val showStatsInline: Boolean,
@get:JvmName("showSensitiveMedia")
val showSensitiveMedia: Boolean,
@get:JvmName("openSpoiler")
@ -119,6 +121,7 @@ data class StatusDisplayOptions(
confirmReblogs = preferences.getBoolean(PrefKeys.CONFIRM_REBLOGS, true),
confirmFavourites = preferences.getBoolean(PrefKeys.CONFIRM_FAVOURITES, false),
hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false),
showStatsInline = preferences.getBoolean(PrefKeys.SHOW_STATS_INLINE, false),
showSensitiveMedia = account.alwaysShowSensitiveMedia,
openSpoiler = account.alwaysOpenSpoiler
)

View file

@ -329,7 +329,7 @@
android:id="@+id/status_replies"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:layout_marginStart="45dp"
android:textSize="?attr/status_text_medium"
app:layout_constraintBottom_toBottomOf="@id/status_reply"
app:layout_constraintStart_toStartOf="@id/status_reply"
@ -353,6 +353,17 @@
sparkbutton:primaryColor="@color/tusky_blue"
sparkbutton:secondaryColor="@color/tusky_blue_light" />
<TextView
android:id="@+id/status_insets"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="45dp"
android:textSize="?attr/status_text_medium"
app:layout_constraintBottom_toBottomOf="@id/status_inset"
app:layout_constraintStart_toStartOf="@id/status_inset"
app:layout_constraintTop_toTopOf="@id/status_inset"
tools:text="1+" />
<at.connyduck.sparkbutton.SparkButton
android:id="@+id/status_favourite"
android:layout_width="52dp"
@ -370,6 +381,17 @@
sparkbutton:primaryColor="@color/tusky_orange"
sparkbutton:secondaryColor="@color/tusky_orange_light" />
<TextView
android:id="@+id/status_favourites_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="45dp"
android:textSize="?attr/status_text_medium"
app:layout_constraintBottom_toBottomOf="@id/status_inset"
app:layout_constraintStart_toStartOf="@id/status_favourite"
app:layout_constraintTop_toTopOf="@id/status_inset"
tools:text="" />
<at.connyduck.sparkbutton.SparkButton
android:id="@+id/status_bookmark"
android:layout_width="52dp"

View file

@ -664,6 +664,7 @@
<string name="pref_title_show_notifications_filter">Show Notifications filter</string>
<string name="pref_title_enable_swipe_for_tabs">Enable swipe gesture to switch between tabs</string>
<string name="pref_title_show_stat_inline">Show post statistics in timeline</string>
<string name="create_poll_title">Poll</string>

View file

@ -48,6 +48,7 @@ class NotificationsViewModelTestStatusDisplayOptions : NotificationsViewModelTes
confirmFavourites = false,
hideStats = false,
animateEmojis = false,
showStatsInline = false,
showSensitiveMedia = true, // setting in NotificationsViewModelTestBase
openSpoiler = true // setting in NotificationsViewModelTestBase
)

View file

@ -0,0 +1,49 @@
package com.keylesspalace.tusky.util
import org.junit.Assert
import org.junit.Test
import kotlin.math.pow
class NumberUtilsTest {
@Test
fun zeroShouldBeFormattedAsZero() {
val shortNumber = shortNumber(0)
Assert.assertEquals("0", shortNumber)
}
@Test
fun negativeValueShouldBeFormattedToNegativeValue() {
val shortNumber = shortNumber(-1)
Assert.assertEquals("-1", shortNumber)
}
@Test
fun positiveValueShouldBeFormattedToPositiveValue() {
val shortNumber = shortNumber(1)
Assert.assertEquals("1", shortNumber)
}
@Test
fun bigNumbersShouldBeShortened() {
var shortNumber = 1L
Assert.assertEquals("1", shortNumber(shortNumber))
for (i in shortLetters.indices) {
if (i == 0) {
continue
}
shortNumber = 1000.0.pow(i.toDouble()).toLong()
Assert.assertEquals("1.0" + shortLetters[i], shortNumber(shortNumber))
}
}
@Test
fun roundingForNegativeAndPositiveValuesShouldBeTheSame() {
var value = 3492
Assert.assertEquals("-3.5K", shortNumber(-value))
Assert.assertEquals("3.5K", shortNumber(value))
value = 1501
Assert.assertEquals("-1.5K", shortNumber(-value))
Assert.assertEquals("1.5K", shortNumber(value))
}
}