Add option for default reply privacy set to unlisted by default (#4496)
This PR fixes https://github.com/tuskyapp/Tusky/issues/2798 and is mostly based on and supersedes https://github.com/tuskyapp/Tusky/pull/2826 but I have fixed all merge conflicts and unit tests. I tested the changes locally and the setting takes effect immediately for replies, and persists across killing the app. --------- Co-authored-by: Eva Tatarka <eva@tatarka.me> Co-authored-by: Konrad Pozniak <connyduck@users.noreply.github.com>
This commit is contained in:
parent
8584e72f48
commit
9883bfa7c2
|
@ -182,7 +182,8 @@ class ComposeViewModel @Inject constructor(
|
|||
mediaList + mediaItem
|
||||
}
|
||||
}
|
||||
val mediaItem = stashMediaItem!! // stashMediaItem is always non-null and uncaptured at this point, but Kotlin doesn't know that
|
||||
val mediaItem =
|
||||
stashMediaItem!! // stashMediaItem is always non-null and uncaptured at this point, but Kotlin doesn't know that
|
||||
|
||||
viewModelScope.launch {
|
||||
mediaUploader
|
||||
|
@ -193,6 +194,7 @@ class ComposeViewModel @Inject constructor(
|
|||
val newMediaItem = when (event) {
|
||||
is UploadEvent.ProgressEvent ->
|
||||
item.copy(uploadPercent = event.percentage)
|
||||
|
||||
is UploadEvent.FinishedEvent ->
|
||||
item.copy(
|
||||
id = event.mediaId,
|
||||
|
@ -455,6 +457,7 @@ class ComposeViewModel @Inject constructor(
|
|||
emptyList()
|
||||
})
|
||||
}
|
||||
|
||||
':' -> {
|
||||
val emojiList = emoji.replayCache.firstOrNull() ?: return emptyList()
|
||||
val incomplete = token.substring(1)
|
||||
|
@ -467,6 +470,7 @@ class ComposeViewModel @Inject constructor(
|
|||
AutocompleteResult.EmojiResult(emoji)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.w(TAG, "Unexpected autocompletion token: $token")
|
||||
emptyList()
|
||||
|
@ -480,16 +484,17 @@ class ComposeViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
composeKind = composeOptions?.kind ?: ComposeKind.NEW
|
||||
inReplyToId = composeOptions?.inReplyToId
|
||||
|
||||
val preferredVisibility = accountManager.activeAccount!!.defaultPostPrivacy
|
||||
val activeAccount = accountManager.activeAccount!!
|
||||
val preferredVisibility =
|
||||
if (inReplyToId != null) activeAccount.defaultReplyPrivacy else activeAccount.defaultPostPrivacy
|
||||
|
||||
val replyVisibility = composeOptions?.replyVisibility ?: Status.Visibility.UNKNOWN
|
||||
startingVisibility = Status.Visibility.byNum(
|
||||
preferredVisibility.num.coerceAtLeast(replyVisibility.num)
|
||||
)
|
||||
|
||||
inReplyToId = composeOptions?.inReplyToId
|
||||
|
||||
modifiedInitialState = composeOptions?.modifiedInitialState == true
|
||||
|
||||
val contentWarning = composeOptions?.contentWarning
|
||||
|
|
|
@ -31,6 +31,7 @@ import com.keylesspalace.tusky.BuildConfig
|
|||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.TabPreferenceActivity
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||
import com.keylesspalace.tusky.components.accountlist.AccountListActivity
|
||||
import com.keylesspalace.tusky.components.domainblocks.DomainBlocksActivity
|
||||
import com.keylesspalace.tusky.components.filters.FiltersActivity
|
||||
|
@ -179,6 +180,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
|
|||
setEntries(R.array.post_privacy_names)
|
||||
setEntryValues(R.array.post_privacy_values)
|
||||
key = PrefKeys.DEFAULT_POST_PRIVACY
|
||||
isSingleLineTitle = false
|
||||
setSummaryProvider { entry }
|
||||
val visibility = accountManager.activeAccount?.defaultPostPrivacy ?: Status.Visibility.PUBLIC
|
||||
value = visibility.serverString
|
||||
|
@ -192,6 +194,31 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
}
|
||||
|
||||
val activeAccount = accountManager.activeAccount
|
||||
if (activeAccount != null) {
|
||||
listPreference {
|
||||
setTitle(R.string.pref_default_reply_privacy)
|
||||
setEntries(R.array.post_privacy_names)
|
||||
setEntryValues(R.array.post_privacy_values)
|
||||
key = PrefKeys.DEFAULT_REPLY_PRIVACY
|
||||
isSingleLineTitle = false
|
||||
setSummaryProvider { entry }
|
||||
val visibility = activeAccount.defaultReplyPrivacy
|
||||
value = visibility.serverString
|
||||
setIcon(getIconForVisibility(visibility))
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val newVisibility = Status.Visibility.byString(newValue as String)
|
||||
setIcon(getIconForVisibility(newVisibility))
|
||||
activeAccount.defaultReplyPrivacy = newVisibility
|
||||
accountManager.saveAccount(activeAccount)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
eventHub.dispatch(PreferenceChangedEvent(key))
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listPreference {
|
||||
val locales =
|
||||
getLocaleList(getInitialLanguages(null, accountManager.activeAccount))
|
||||
|
@ -204,6 +231,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
|
|||
).toTypedArray()
|
||||
entryValues = (listOf("") + locales.map { it.language }).toTypedArray()
|
||||
key = PrefKeys.DEFAULT_POST_LANGUAGE
|
||||
isSingleLineTitle = false
|
||||
icon = makeIcon(requireContext(), GoogleMaterial.Icon.gmd_translate, iconSize)
|
||||
value = accountManager.activeAccount?.defaultPostLanguage.orEmpty()
|
||||
isPersistent = false // This will be entirely server-driven
|
||||
|
|
|
@ -62,7 +62,7 @@ import java.io.File;
|
|||
},
|
||||
// Note: Starting with version 54, database versions in Tusky are always even.
|
||||
// This is to reserve odd version numbers for use by forks.
|
||||
version = 60,
|
||||
version = 62,
|
||||
autoMigrations = {
|
||||
@AutoMigration(from = 48, to = 49),
|
||||
@AutoMigration(from = 49, to = 50, spec = AppDatabase.MIGRATION_49_50.class),
|
||||
|
@ -841,4 +841,11 @@ 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");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -63,6 +63,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 defaultMediaSensitivity: Boolean = false,
|
||||
var defaultPostLanguage: String = "",
|
||||
var alwaysShowSensitiveMedia: Boolean = false,
|
||||
|
|
|
@ -65,7 +65,7 @@ object StorageModule {
|
|||
AppDatabase.MIGRATION_41_42, AppDatabase.MIGRATION_42_43, AppDatabase.MIGRATION_43_44,
|
||||
AppDatabase.MIGRATION_44_45, AppDatabase.MIGRATION_45_46, AppDatabase.MIGRATION_46_47,
|
||||
AppDatabase.MIGRATION_47_48, AppDatabase.MIGRATION_52_53, AppDatabase.MIGRATION_54_56,
|
||||
AppDatabase.MIGRATION_58_60
|
||||
AppDatabase.MIGRATION_58_60, AppDatabase.MIGRATION_60_62
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
|
|
@ -86,6 +86,7 @@ object PrefKeys {
|
|||
|
||||
const val DEFAULT_POST_PRIVACY = "defaultPostPrivacy"
|
||||
const val DEFAULT_POST_LANGUAGE = "defaultPostLanguage"
|
||||
const val DEFAULT_REPLY_PRIVACY = "defaultReplyPrivacy"
|
||||
const val DEFAULT_MEDIA_SENSITIVITY = "defaultMediaSensitivity"
|
||||
const val MEDIA_PREVIEW_ENABLED = "mediaPreviewEnabled"
|
||||
const val ALWAYS_SHOW_SENSITIVE_MEDIA = "alwaysShowSensitiveMedia"
|
||||
|
|
|
@ -335,10 +335,11 @@
|
|||
<string name="pref_summary_http_proxy_missing"><not set></string>
|
||||
<string name="pref_summary_http_proxy_invalid"><invalid></string>
|
||||
|
||||
<string name="pref_default_post_privacy">Default post privacy</string>
|
||||
<string name="pref_default_post_language">Default posting language</string>
|
||||
<string name="pref_default_media_sensitivity">Always mark media as sensitive</string>
|
||||
<string name="pref_publishing">Publishing (synced with server)</string>
|
||||
<string name="pref_default_post_privacy">Default post privacy (synced with server)</string>
|
||||
<string name="pref_default_post_language">Default posting language (synced with server)</string>
|
||||
<string name="pref_default_reply_privacy">Default reply privacy (not synced with server)</string>
|
||||
<string name="pref_default_media_sensitivity">Always mark media as sensitive (synced with server)</string>
|
||||
<string name="pref_publishing">Publishing</string>
|
||||
<string name="pref_failed_to_sync">Failed to sync preferences</string>
|
||||
|
||||
<string name="pref_main_nav_position">Main navigation position</string>
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package com.keylesspalace.tusky.components.compose
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.db.entity.AccountEntity
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@Config(sdk = [28])
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ComposeViewModelTest {
|
||||
|
||||
private lateinit var api: MastodonApi
|
||||
private lateinit var accountManager: AccountManager
|
||||
private lateinit var eventHub: EventHub
|
||||
private lateinit var viewModel: ComposeViewModel
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
api = mock()
|
||||
accountManager = mock {
|
||||
on { activeAccount } doReturn
|
||||
AccountEntity(
|
||||
id = 1,
|
||||
domain = "test.domain",
|
||||
accessToken = "fakeToken",
|
||||
clientId = "fakeId",
|
||||
clientSecret = "fakeSecret",
|
||||
isActive = true
|
||||
)
|
||||
}
|
||||
eventHub = EventHub()
|
||||
|
||||
viewModel = ComposeViewModel(
|
||||
api = api,
|
||||
accountManager = accountManager,
|
||||
mediaUploader = mock(),
|
||||
serviceClient = mock(),
|
||||
draftHelper = mock(),
|
||||
instanceInfoRepo = mock(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `startingVisibility initially set to defaultPostPrivacy for post`() {
|
||||
viewModel.setup(null)
|
||||
|
||||
assertEquals(Status.Visibility.PUBLIC, viewModel.statusVisibility.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `startingVisibility initially set to replyPostPrivacy for reply`() {
|
||||
viewModel.setup(ComposeActivity.ComposeOptions(inReplyToId = "123"))
|
||||
|
||||
assertEquals(Status.Visibility.UNLISTED, viewModel.statusVisibility.value)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue