Wellbeing mode (#1992)

* Add wellbeing mode settings toggle

* Translate wellbeing mode string to german

* Disable fav/boost count on toots if wellbeing is enabled

* Hide follow/post stats on profiles

* Reload notifications when wellbeing mode is toggled

* Add wellbeing mode explainer dialog

* Move wellbeing filter timeline into own category

* Add toggles for quantitative stats

* Hide announcement badge counts if wellbeing is enabled

* Move fetching of wellbeing setting to activity

* Add wellbeing option to statusDisplayOptions

* Update post filters for all accounts

* Remove local translations

* Revert "Remove local translations"

This reverts commit e92e636a5c759b09649174ab68ec91bc13680287.

* Remove german translations
main
Garrit Franke 3 years ago committed by Alibek Omarov
parent 7cbe2d7531
commit 719dcb13fc
  1. 30
      app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt
  2. 3
      app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
  3. 13
      app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java
  4. 3
      app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java
  5. 12
      app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt
  6. 15
      app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt
  7. 3
      app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt
  8. 46
      app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt
  9. 3
      app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt
  10. 3
      app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt
  11. 3
      app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt
  12. 10
      app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java
  13. 2
      app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt
  14. 10
      app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
  15. 4
      app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
  16. 4
      app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java
  17. 3
      app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt
  18. 4
      app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt
  19. 3
      app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java
  20. 10
      app/src/main/res/values/strings.xml

@ -65,6 +65,7 @@ import com.keylesspalace.tusky.interfaces.ActionButtonActivity
import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.interfaces.ReselectableFragment
import com.keylesspalace.tusky.pager.AccountPagerAdapter
import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.util.*
import com.keylesspalace.tusky.view.showMuteAccountDialog
import com.keylesspalace.tusky.viewmodel.AccountViewModel
@ -198,6 +199,17 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
poorTabView.isPressed = true
accountTabLayout.postDelayed({ poorTabView.isPressed = false }, 300)
}
// If wellbeing mode is enabled, follow stats and posts count should be hidden
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_PROFILE, false)
if (wellbeingEnabled) {
accountStatuses.hide()
accountFollowers.hide()
accountFollowing.hide()
}
}
/**
@ -212,8 +224,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
val pageTitles = arrayOf(getString(R.string.title_statuses), getString(R.string.title_statuses_with_replies), getString(R.string.title_statuses_pinned), getString(R.string.title_media))
TabLayoutMediator(accountTabLayout, accountFragmentViewPager) {
tab, position ->
TabLayoutMediator(accountTabLayout, accountFragmentViewPager) { tab, position ->
tab.text = pageTitles[position]
}.attach()
@ -632,7 +643,11 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
blockingDomain = relation.blockingDomain
showingReblogs = relation.showingReblogs
accountFollowsYouTextView.visible(relation.followedBy)
// If wellbeing mode is enabled, "follows you" text should not be visible
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_PROFILE, false)
accountFollowsYouTextView.visible(relation.followedBy && !wellbeingEnabled)
// because subscribing is Pleroma extension, enable it __only__ when we have non-null subscribing field
// it's also now supported in Mastodon 3.3.0rc but called notifying and use different API call
@ -849,10 +864,11 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
if (viewModel.relationshipData.value?.data?.muting != true) {
loadedAccount?.let {
showMuteAccountDialog(
this,
it.username,
{ notifications -> viewModel.muteAccount(notifications) }
)
this,
it.username
) { notifications ->
viewModel.muteAccount(notifications)
}
}
} else {
viewModel.unmuteAccount()

@ -281,7 +281,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
statusDisplayOptions.useBlurhash(),
CardViewMode.NONE,
statusDisplayOptions.confirmReblogs(),
statusDisplayOptions.renderStatusAsMention()
statusDisplayOptions.renderStatusAsMention(),
statusDisplayOptions.hideStats()
);
}

@ -114,7 +114,12 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
super.setupWithStatus(status, listener, statusDisplayOptions, payloads);
setupCard(status, CardViewMode.FULL_WIDTH, statusDisplayOptions); // Always show card for detailed status
if (payloads == null) {
setReblogAndFavCount(status.getReblogsCount(), status.getFavouritesCount(), listener);
if (!statusDisplayOptions.hideStats()) {
setReblogAndFavCount(status.getReblogsCount(), status.getFavouritesCount(), listener);
} else {
hideQuantitativeStats();
}
setApplication(status.getApplication());
View.OnLongClickListener longClickListener = view -> {
@ -179,4 +184,10 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
null
);
}
private void hideQuantitativeStats() {
reblogs.setVisibility(View.GONE);
favourites.setVisibility(View.GONE);
infoDivider.setVisibility(View.GONE);
}
}

@ -67,7 +67,8 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
statusDisplayOptions.useBlurhash(),
statusDisplayOptions.cardViewMode(),
statusDisplayOptions.confirmReblogs(),
statusDisplayOptions.renderStatusAsMention()
statusDisplayOptions.renderStatusAsMention(),
statusDisplayOptions.hideStats()
);
}

@ -32,6 +32,7 @@ import com.keylesspalace.tusky.util.LinkHelper
import com.keylesspalace.tusky.util.emojify
import kotlinx.android.synthetic.main.item_announcement.view.*
interface AnnouncementActionListener: LinkListener {
fun openReactionPicker(announcementId: String, target: View)
fun addReaction(announcementId: String, name: String)
@ -40,7 +41,8 @@ interface AnnouncementActionListener: LinkListener {
class AnnouncementAdapter(
private var items: List<Announcement> = emptyList(),
private val listener: AnnouncementActionListener
private val listener: AnnouncementActionListener,
private val wellbeingEnabled: Boolean = false
) : RecyclerView.Adapter<AnnouncementAdapter.AnnouncementViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnnouncementViewHolder {
@ -68,6 +70,14 @@ class AnnouncementAdapter(
fun bind(item: Announcement) {
LinkHelper.setClickableText(text, item.content, null, listener)
// If wellbeing mode is enabled, announcement badge counts should not be shown.
if (wellbeingEnabled) {
// Since reactions are not visible in wellbeing mode,
// we shouldn't be able to add any ourselves.
addReactionChip.visibility = View.GONE
return
}
item.reactions.forEachIndexed { i, reaction ->
(chips.getChildAt(i)?.takeUnless { it.id == R.id.addReactionChip } as Chip?
?: Chip(ContextThemeWrapper(view.context, R.style.Widget_MaterialComponents_Chip_Choice)).apply {

@ -17,18 +17,23 @@ package com.keylesspalace.tusky.components.announcements
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.widget.PopupWindow
import androidx.activity.viewModels
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.keylesspalace.tusky.*
import com.keylesspalace.tusky.BottomSheetActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.ViewTagActivity
import com.keylesspalace.tusky.adapter.EmojiAdapter
import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.util.*
import com.keylesspalace.tusky.view.EmojiPicker
import kotlinx.android.synthetic.main.activity_announcements.*
@ -42,7 +47,7 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener,
private val viewModel: AnnouncementsViewModel by viewModels { viewModelFactory }
private val adapter = AnnouncementAdapter(emptyList(), this)
private lateinit var adapter: AnnouncementAdapter
private val picker by lazy { EmojiPicker(this) }
private val pickerDialog by lazy {
@ -75,6 +80,12 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener,
announcementsList.layoutManager = LinearLayoutManager(this)
val divider = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
announcementsList.addItemDecoration(divider)
val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false)
adapter = AnnouncementAdapter(emptyList(), this, wellbeingEnabled)
announcementsList.adapter = adapter
viewModel.announcements.observe(this) {

@ -69,7 +69,8 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
useBlurhash = preferences.getBoolean("useBlurhash", true),
cardViewMode = CardViewMode.NONE,
confirmReblogs = preferences.getBoolean("confirmReblogs", true),
renderStatusAsMention = preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true)
renderStatusAsMention = preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true),
hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false)
)
adapter = ConversationAdapter(statusDisplayOptions, this, ::onTopLoaded, viewModel::retry)

@ -19,10 +19,14 @@ import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.entity.Notification
import com.keylesspalace.tusky.settings.*
import com.keylesspalace.tusky.util.ThemeUtils
import com.keylesspalace.tusky.util.deserialize
import com.keylesspalace.tusky.util.getNonNullString
import com.keylesspalace.tusky.util.serialize
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
@ -35,6 +39,9 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
@Inject
lateinit var okhttpclient: OkHttpClient
@Inject
lateinit var accountManager: AccountManager
private val iconSize by lazy { resources.getDimensionPixelSize(R.dimen.preference_icon_size) }
private var httpProxyPref: Preference? = null
@ -236,6 +243,45 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
}
}
preferenceCategory(R.string.pref_title_wellbeing_mode) {
switchPreference {
title = getString(R.string.limit_notifications)
setDefaultValue(false)
key = PrefKeys.WELLBEING_LIMITED_NOTIFICATIONS
setOnPreferenceChangeListener { _, value ->
for (account in accountManager.accounts) {
val notificationFilter = deserialize(account.notificationsFilter).toMutableSet()
if (value == true) {
notificationFilter.add(Notification.Type.FAVOURITE)
notificationFilter.add(Notification.Type.FOLLOW)
notificationFilter.add(Notification.Type.REBLOG)
} else {
notificationFilter.remove(Notification.Type.FAVOURITE)
notificationFilter.remove(Notification.Type.FOLLOW)
notificationFilter.remove(Notification.Type.REBLOG)
}
account.notificationsFilter = serialize(notificationFilter)
accountManager.saveAccount(account)
}
true
}
}
switchPreference {
title = getString(R.string.wellbeing_hide_stats_posts)
setDefaultValue(false)
key = PrefKeys.WELLBEING_HIDE_STATS_POSTS
}
switchPreference {
title = getString(R.string.wellbeing_hide_stats_profile)
setDefaultValue(false)
key = PrefKeys.WELLBEING_HIDE_STATS_PROFILE
}
}
preferenceCategory(R.string.pref_title_proxy_settings) {
httpProxyPref = preference {
setTitle(R.string.pref_title_http_proxy_settings)

@ -119,7 +119,8 @@ class ReportStatusesFragment : Fragment(), Injectable, AdapterHandler {
useBlurhash = preferences.getBoolean("useBlurhash", true),
cardViewMode = CardViewMode.NONE,
confirmReblogs = preferences.getBoolean("confirmReblogs", true),
renderStatusAsMention = preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true)
renderStatusAsMention = preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true),
hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false)
)
adapter = StatusesAdapter(statusDisplayOptions,

@ -89,7 +89,8 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
useBlurhash = preferences.getBoolean("useBlurhash", true),
cardViewMode = CardViewMode.NONE,
confirmReblogs = preferences.getBoolean("confirmReblogs", true),
renderStatusAsMention = preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true)
renderStatusAsMention = preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true),
hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false)
)
searchRecyclerView.addItemDecoration(DividerItemDecoration(searchRecyclerView.context, DividerItemDecoration.VERTICAL))

@ -36,7 +36,8 @@ class AccountManager @Inject constructor(db: AppDatabase) {
@Volatile
var activeAccount: AccountEntity? = null
private var accounts: MutableList<AccountEntity> = mutableListOf()
var accounts: MutableList<AccountEntity> = mutableListOf()
private set
private val accountDao: AccountDao = db.accountDao()
init {

@ -15,14 +15,14 @@
package com.keylesspalace.tusky.db;
import com.keylesspalace.tusky.TabDataKt;
import com.keylesspalace.tusky.components.conversation.ConversationEntity;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import androidx.annotation.NonNull;
import androidx.sqlite.db.SupportSQLiteDatabase;
import com.keylesspalace.tusky.TabDataKt;
import com.keylesspalace.tusky.components.conversation.ConversationEntity;
/**
* DB version & declare DAO

@ -48,7 +48,7 @@ import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.io.IOException
import java.util.HashMap
import java.util.*
import javax.inject.Inject
class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {

@ -251,7 +251,8 @@ public class NotificationsFragment extends SFragment implements
preferences.getBoolean("useBlurhash", true),
CardViewMode.NONE,
preferences.getBoolean("confirmReblogs", true),
preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true)
preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true),
preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false)
);
withMuted = !preferences.getBoolean(PrefKeys.HIDE_MUTED_USERS, false);
@ -907,6 +908,7 @@ public class NotificationsFragment extends SFragment implements
private void loadNotificationsFilter() {
AccountEntity account = accountManager.getActiveAccount();
if (account != null) {
notificationFilter.clear();
notificationFilter.addAll(NotificationTypeConverterKt.deserialize(
account.getNotificationsFilter()));
}
@ -1404,6 +1406,12 @@ public class NotificationsFragment extends SFragment implements
@Override
public void onResume() {
super.onResume();
String rawAccountNotificationFilter = accountManager.getActiveAccount().getNotificationsFilter();
Set<Notification.Type> accountNotificationFilter = NotificationTypeConverterKt.deserialize(rawAccountNotificationFilter);
if (!notificationFilter.equals(accountNotificationFilter)) {
loadNotificationsFilter();
fullyRefreshWithProgressBar(true);
}
startUpdateTimestamp();
}

@ -71,7 +71,6 @@ import com.keylesspalace.tusky.util.ListUtils;
import com.keylesspalace.tusky.util.PairedList;
import com.keylesspalace.tusky.util.StatusDisplayOptions;
import com.keylesspalace.tusky.util.StringUtils;
import com.keylesspalace.tusky.util.ThemeUtils;
import com.keylesspalace.tusky.util.ViewDataUtils;
import com.keylesspalace.tusky.view.BackgroundMessageView;
import com.keylesspalace.tusky.view.EndlessOnScrollListener;
@ -242,7 +241,8 @@ public class TimelineFragment extends SFragment implements
CardViewMode.INDENTED :
CardViewMode.NONE,
preferences.getBoolean("confirmReblogs", true),
preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true)
preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true),
preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false)
);
adapter = new TimelineAdapter(dataSource, statusDisplayOptions, this);

@ -118,6 +118,7 @@ public final class ViewThreadFragment extends SFragment implements
thisThreadsStatusId = getArguments().getString("id");
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(getActivity());
StatusDisplayOptions statusDisplayOptions = new StatusDisplayOptions(
preferences.getBoolean("animateGifAvatars", false),
accountManager.getActiveAccount().getMediaPreviewEnabled(),
@ -128,7 +129,8 @@ public final class ViewThreadFragment extends SFragment implements
CardViewMode.INDENTED :
CardViewMode.NONE,
preferences.getBoolean("confirmReblogs", true),
preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true)
preferences.getBoolean(PrefKeys.RENDER_STATUS_AS_MENTION, true),
preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false)
);
adapter = new ThreadAdapter(statusDisplayOptions, this);
}

@ -39,6 +39,9 @@ object PrefKeys {
const val RENDER_STATUS_AS_MENTION = "renderStatusAsMention"
const val CUSTOM_TABS = "customTabs"
const val WELLBEING_LIMITED_NOTIFICATIONS = "wellbeingModeLimitedNotifications"
const val WELLBEING_HIDE_STATS_POSTS = "wellbeingHideStatsPosts"
const val WELLBEING_HIDE_STATS_PROFILE = "wellbeingHideStatsProfile"
const val HTTP_PROXY_ENABLED = "httpProxyEnabled"
const val HTTP_PROXY_SERVER = "httpProxyServer"

@ -17,4 +17,6 @@ data class StatusDisplayOptions(
val confirmReblogs: Boolean,
@get:JvmName("renderStatusAsMention")
val renderStatusAsMention: Boolean,
)
@get:JvmName("hideStats")
val hideStats: Boolean
)

@ -20,11 +20,12 @@ import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatDelegate;
import android.util.TypedValue;
/**
* Provides runtime compatibility to obtain theme information and re-theme views, especially where

@ -585,6 +585,16 @@
<string name="pref_title_wellbeing_mode">Wellbeing</string>
<string name="account_note_hint">Your private note about this account</string>
<string name="account_note_saved">Saved!</string>
<string name="wellbeing_mode_notice">Some information that might affect your mental wellbeing will be hidden. This includes:\n\n
- Favorite/Boost/Follow notifications\n
- Favorite/Boost count on toots\n
- Follower/Post stats on profiles\n\n
Push-notifications will not be affected, but you can review your notification preferences manually.
</string>
<string name="review_notifications">Review Notifications</string>
<string name="limit_notifications">Limit timeline notifications</string>
<string name="wellbeing_hide_stats_posts">Hide quantitative stats on posts</string>
<string name="wellbeing_hide_stats_profile">Hide quantitative stats on profiles</string>
<string name="wellbeing_mode_notice">Some information that might affect your mental wellbeing will be hidden. This includes:\n\n
- Favorite/Boost/Follow notifications\n

Loading…
Cancel
Save