diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
index f9fabfd72..755583c6a 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
@@ -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
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt
index 708e44a1a..c50d0376e 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt
@@ -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
diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java
index 7bf0cafce..879e168d8 100644
--- a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java
+++ b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java
@@ -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");
+ }
+ };
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/db/entity/AccountEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/entity/AccountEntity.kt
index d9d8f15f1..390c15f34 100644
--- a/app/src/main/java/com/keylesspalace/tusky/db/entity/AccountEntity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/db/entity/AccountEntity.kt
@@ -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,
diff --git a/app/src/main/java/com/keylesspalace/tusky/di/StorageModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/StorageModule.kt
index 4086aefd5..5f5893b3b 100644
--- a/app/src/main/java/com/keylesspalace/tusky/di/StorageModule.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/di/StorageModule.kt
@@ -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()
}
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 e6061e6b2..4861f5978 100644
--- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt
@@ -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"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0ffef2cf3..531b9185d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -335,10 +335,11 @@
<not set>
<invalid>
- Default post privacy
- Default posting language
- Always mark media as sensitive
- Publishing (synced with server)
+ Default post privacy (synced with server)
+ Default posting language (synced with server)
+ Default reply privacy (not synced with server)
+ Always mark media as sensitive (synced with server)
+ Publishing
Failed to sync preferences
Main navigation position
diff --git a/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeViewModelTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeViewModelTest.kt
new file mode 100644
index 000000000..96f85c5ed
--- /dev/null
+++ b/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeViewModelTest.kt
@@ -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)
+ }
+}