add more options to default reply visibility setting (#4568)

This adds `direct` and `match_default_post_visibility` as options to the
default reply visibility setting. `match_default_post_visibility` will
be the default for new accounts.

closes https://github.com/tuskyapp/Tusky/issues/4555

<img
src="https://github.com/user-attachments/assets/b256ff32-cd49-4274-903b-96da96451e0e"
width="320"/>
This commit is contained in:
Konrad Pozniak 2024-08-02 17:15:10 +02:00 committed by GitHub
commit 892801b83a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 135 additions and 35 deletions

View file

@ -390,7 +390,7 @@ class ComposeViewModel @Inject constructor(
val tootToSend = StatusToSend(
text = content,
warningText = spoilerText,
visibility = _statusVisibility.value.serverString,
visibility = _statusVisibility.value.stringValue,
sensitive = attachedMedia.isNotEmpty() && (_markMediaAsSensitive.value || _showContentWarning.value),
media = attachedMedia,
scheduledAt = _scheduledAt.value,
@ -487,12 +487,15 @@ class ComposeViewModel @Inject constructor(
inReplyToId = composeOptions?.inReplyToId
val activeAccount = accountManager.activeAccount!!
val preferredVisibility =
if (inReplyToId != null) activeAccount.defaultReplyPrivacy else activeAccount.defaultPostPrivacy
val preferredVisibility = if (inReplyToId != null) {
activeAccount.defaultReplyPrivacy.toVisibilityOr(activeAccount.defaultPostPrivacy)
} else {
activeAccount.defaultPostPrivacy
}
val replyVisibility = composeOptions?.replyVisibility ?: Status.Visibility.UNKNOWN
startingVisibility = Status.Visibility.byNum(
preferredVisibility.num.coerceAtLeast(replyVisibility.num)
startingVisibility = Status.Visibility.fromInt(
preferredVisibility.int.coerceAtLeast(replyVisibility.int)
)
modifiedInitialState = composeOptions?.modifiedInitialState == true
@ -534,7 +537,7 @@ class ComposeViewModel @Inject constructor(
postLanguage = composeOptions?.language
val tootVisibility = composeOptions?.visibility ?: Status.Visibility.UNKNOWN
if (tootVisibility.num != Status.Visibility.UNKNOWN.num) {
if (tootVisibility.int != Status.Visibility.UNKNOWN.int) {
startingVisibility = tootVisibility
}
_statusVisibility.value = startingVisibility

View file

@ -22,6 +22,7 @@ import android.os.Bundle
import android.util.Log
import androidx.annotation.DrawableRes
import androidx.lifecycle.lifecycleScope
import androidx.preference.ListPreference
import androidx.preference.PreferenceFragmentCompat
import at.connyduck.calladapter.networkresult.fold
import com.google.android.material.color.MaterialColors
@ -43,6 +44,7 @@ import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.settings.AccountPreferenceDataStore
import com.keylesspalace.tusky.settings.DefaultReplyVisibility
import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.settings.listPreference
import com.keylesspalace.tusky.settings.makePreferenceScreen
@ -183,13 +185,15 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
isSingleLineTitle = false
setSummaryProvider { entry }
val visibility = accountManager.activeAccount?.defaultPostPrivacy ?: Status.Visibility.PUBLIC
value = visibility.serverString
value = visibility.stringValue
setIcon(getIconForVisibility(visibility))
isPersistent = false // its saved to the account and shouldn't be in shared preferences
setOnPreferenceChangeListener { _, newValue ->
setIcon(
getIconForVisibility(Status.Visibility.byString(newValue as String))
)
val icon = getIconForVisibility(Status.Visibility.fromStringValue(newValue as String))
setIcon(icon)
if (accountManager.activeAccount?.defaultReplyPrivacy == DefaultReplyVisibility.MATCH_DEFAULT_POST_VISIBILITY) {
findPreference<ListPreference>(PrefKeys.DEFAULT_REPLY_PRIVACY)?.setIcon(icon)
}
syncWithServer(visibility = newValue)
true
}
@ -199,18 +203,18 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
if (activeAccount != null) {
listPreference {
setTitle(R.string.pref_default_reply_privacy)
setEntries(R.array.post_privacy_names)
setEntryValues(R.array.post_privacy_values)
setEntries(R.array.reply_privacy_names)
setEntryValues(R.array.reply_privacy_values)
key = PrefKeys.DEFAULT_REPLY_PRIVACY
isSingleLineTitle = false
setSummaryProvider { entry }
val visibility = activeAccount.defaultReplyPrivacy
value = visibility.serverString
setIcon(getIconForVisibility(visibility))
value = visibility.stringValue
setIcon(getIconForVisibility(visibility.toVisibilityOr(activeAccount.defaultPostPrivacy)))
isPersistent = false // its saved to the account and shouldn't be in shared preferences
setOnPreferenceChangeListener { _, newValue ->
val newVisibility = Status.Visibility.byString(newValue as String)
setIcon(getIconForVisibility(newVisibility))
val newVisibility = DefaultReplyVisibility.fromStringValue(newValue as String)
setIcon(getIconForVisibility(newVisibility.toVisibilityOr(activeAccount.defaultPostPrivacy)))
activeAccount.defaultReplyPrivacy = newVisibility
accountManager.saveAccount(activeAccount)
viewLifecycleOwner.lifecycleScope.launch {
@ -219,6 +223,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
true
}
}
preference {
setSummary(R.string.pref_default_reply_privacy_explanation)
isEnabled = false
}
}
listPreference {

View file

@ -846,7 +846,7 @@ public abstract class AppDatabase extends RoomDatabase {
public static final Migration MIGRATION_60_62 = new Migration(60, 62) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `defaultReplyPrivacy` INTEGER NOT NULL DEFAULT 2");
database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `defaultReplyPrivacy` INTEGER NOT NULL DEFAULT 0");
}
};
}

View file

@ -29,6 +29,7 @@ import com.keylesspalace.tusky.entity.HashTag
import com.keylesspalace.tusky.entity.NewPoll
import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.settings.DefaultReplyVisibility
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import java.net.URLDecoder
@ -56,12 +57,22 @@ class Converters @Inject constructor(
@TypeConverter
fun visibilityToInt(visibility: Status.Visibility?): Int {
return visibility?.num ?: Status.Visibility.UNKNOWN.num
return visibility?.int ?: Status.Visibility.UNKNOWN.int
}
@TypeConverter
fun intToVisibility(visibility: Int): Status.Visibility {
return Status.Visibility.byNum(visibility)
return Status.Visibility.fromInt(visibility)
}
@TypeConverter
fun defaultReplyVisibilityToInt(visibility: DefaultReplyVisibility?): Int {
return visibility?.int ?: DefaultReplyVisibility.MATCH_DEFAULT_POST_VISIBILITY.int
}
@TypeConverter
fun intToDefaultReplyVisibility(visibility: Int): DefaultReplyVisibility {
return DefaultReplyVisibility.fromInt(visibility)
}
@TypeConverter

View file

@ -25,6 +25,7 @@ import com.keylesspalace.tusky.db.Converters
import com.keylesspalace.tusky.defaultTabs
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.settings.DefaultReplyVisibility
@Entity(
indices = [
@ -63,7 +64,7 @@ data class AccountEntity(
var notificationVibration: Boolean = true,
var notificationLight: Boolean = true,
var defaultPostPrivacy: Status.Visibility = Status.Visibility.PUBLIC,
var defaultReplyPrivacy: Status.Visibility = Status.Visibility.UNLISTED,
var defaultReplyPrivacy: DefaultReplyVisibility = DefaultReplyVisibility.MATCH_DEFAULT_POST_VISIBILITY,
var defaultMediaSensitivity: Boolean = false,
var defaultPostLanguage: String = "",
var alwaysShowSensitiveMedia: Boolean = false,

View file

@ -68,7 +68,7 @@ data class Status(
get() = reblog ?: this
@JsonClass(generateAdapter = false)
enum class Visibility(val num: Int) {
enum class Visibility(val int: Int) {
UNKNOWN(0),
@Json(name = "public")
@ -83,7 +83,7 @@ data class Status(
@Json(name = "direct")
DIRECT(4);
val serverString: String
val stringValue: String
get() = when (this) {
PUBLIC -> "public"
UNLISTED -> "unlisted"
@ -93,10 +93,8 @@ data class Status(
}
companion object {
@JvmStatic
fun byNum(num: Int): Visibility {
return when (num) {
fun fromInt(int: Int): Visibility {
return when (int) {
4 -> DIRECT
3 -> PRIVATE
2 -> UNLISTED
@ -106,8 +104,7 @@ data class Status(
}
}
@JvmStatic
fun byString(s: String): Visibility {
fun fromStringValue(s: String): Visibility {
return when (s) {
"public" -> PUBLIC
"unlisted" -> UNLISTED

View file

@ -93,7 +93,7 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
StatusToSend(
text = text,
warningText = spoiler,
visibility = visibility.serverString,
visibility = visibility.stringValue,
sensitive = false,
media = emptyList(),
scheduledAt = null,

View file

@ -393,7 +393,7 @@ class SendStatusService : Service() {
content = status.text,
contentWarning = status.warningText,
sensitive = status.sensitive,
visibility = Status.Visibility.byString(status.visibility),
visibility = Status.Visibility.fromStringValue(status.visibility),
mediaUris = status.media.map { it.uri },
mediaDescriptions = status.media.map { it.description },
mediaFocus = status.media.map { it.focus },

View file

@ -0,0 +1,51 @@
package com.keylesspalace.tusky.settings
import com.keylesspalace.tusky.entity.Status
enum class DefaultReplyVisibility(val int: Int) {
MATCH_DEFAULT_POST_VISIBILITY(0),
PUBLIC(1),
UNLISTED(2),
PRIVATE(3),
DIRECT(4);
val stringValue: String
get() = when (this) {
MATCH_DEFAULT_POST_VISIBILITY -> "match_default_post_visibility"
PUBLIC -> "public"
UNLISTED -> "unlisted"
PRIVATE -> "private"
DIRECT -> "direct"
}
fun toVisibilityOr(default: Status.Visibility): Status.Visibility {
return when (this) {
PUBLIC -> Status.Visibility.PUBLIC
UNLISTED -> Status.Visibility.UNLISTED
PRIVATE -> Status.Visibility.PRIVATE
DIRECT -> Status.Visibility.DIRECT
else -> default
}
}
companion object {
fun fromInt(int: Int): DefaultReplyVisibility {
return when (int) {
4 -> DIRECT
3 -> PRIVATE
2 -> UNLISTED
1 -> PUBLIC
else -> MATCH_DEFAULT_POST_VISIBILITY
}
}
fun fromStringValue(s: String): DefaultReplyVisibility {
return when (s) {
"public" -> PUBLIC
"unlisted" -> UNLISTED
"private" -> PRIVATE
"direct" -> DIRECT
else -> MATCH_DEFAULT_POST_VISIBILITY
}
}
}
}