From 75e7b9f1a5f2bb5b2f6d8d89fc1bca3cef59b48f Mon Sep 17 00:00:00 2001 From: Grigorii Ioffe Date: Sat, 18 Mar 2023 09:57:26 +0200 Subject: [PATCH] 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 Co-authored-by: grigoriiioffe --- .../tusky/adapter/StatusBaseViewHolder.java | 12 ++++- .../tusky/adapter/StatusViewHolder.java | 19 +++++++ .../conversation/ConversationsFragment.kt | 1 + .../preference/PreferencesActivity.kt | 2 +- .../preference/PreferencesFragment.kt | 7 +++ .../fragments/ReportStatusesFragment.kt | 1 + .../fragments/SearchStatusesFragment.kt | 1 + .../components/timeline/TimelineFragment.kt | 1 + .../viewthread/ViewThreadFragment.kt | 1 + .../tusky/settings/SettingsConstants.kt | 1 + .../keylesspalace/tusky/util/NumberUtils.kt | 26 ++++++++++ .../tusky/util/StatusDisplayOptions.kt | 3 ++ app/src/main/res/layout/item_status.xml | 24 ++++++++- app/src/main/res/values/strings.xml | 1 + ...ationsViewModelTestStatusDisplayOptions.kt | 1 + .../tusky/util/NumberUtilsTest.kt | 49 +++++++++++++++++++ 16 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/NumberUtils.kt create mode 100644 app/src/test/java/com/keylesspalace/tusky/util/NumberUtilsTest.kt diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java index 3364b79da..676cdf0dc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -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(); diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java index 9d45bb05e..0763b96ae 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java @@ -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); } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt index 58a1f18e3..8475aab75 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt @@ -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 ) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt index 1fdc7a650..dfb27e50a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt @@ -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 } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt index 615c9cd5b..84ba4c0c2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt @@ -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) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt index f15f19629..362680f6f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt @@ -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 ) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt index 3ee103055..1144a06dc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt @@ -88,6 +88,7 @@ class SearchStatusesFragment : SearchFragment(), 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 ) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt index 90c12307f..a76297db7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt @@ -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 ) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt index 6a244c0a7..dff918025 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt @@ -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 ) diff --git a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt index 8b8d5c3dd..a20fb4af0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt +++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt @@ -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" diff --git a/app/src/main/java/com/keylesspalace/tusky/util/NumberUtils.kt b/app/src/main/java/com/keylesspalace/tusky/util/NumberUtils.kt new file mode 100644 index 000000000..6adb4d809 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/util/NumberUtils.kt @@ -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) + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt index 7767accd0..7151f93ac 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt @@ -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 ) diff --git a/app/src/main/res/layout/item_status.xml b/app/src/main/res/layout/item_status.xml index 9b4f520f9..65f3ea403 100644 --- a/app/src/main/res/layout/item_status.xml +++ b/app/src/main/res/layout/item_status.xml @@ -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" /> + + + + Show Notifications filter Enable swipe gesture to switch between tabs + Show post statistics in timeline Poll diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestStatusDisplayOptions.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestStatusDisplayOptions.kt index 241b148e3..29e3c7a18 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestStatusDisplayOptions.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestStatusDisplayOptions.kt @@ -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 ) diff --git a/app/src/test/java/com/keylesspalace/tusky/util/NumberUtilsTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/NumberUtilsTest.kt new file mode 100644 index 000000000..3654176b8 --- /dev/null +++ b/app/src/test/java/com/keylesspalace/tusky/util/NumberUtilsTest.kt @@ -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)) + } +}