Instance configuration: the easy parts (#2341)
* Add data model for instance configuration * Support instance.configuration.statuses.max_characters * Support instance.configuration.statuses.characters_reserved_per_url * Support instance.configuration.polls.max_options and max_characters_per_option * Pacify ktlint * Support instance-configured poll durations * Fixup versions for migration after rebase
This commit is contained in:
parent
cdefcc441f
commit
7114575497
14 changed files with 1016 additions and 52 deletions
|
|
@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import com.keylesspalace.tusky.appstore.AnnouncementReadEvent
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.components.compose.DEFAULT_MAXIMUM_URL_LENGTH
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.db.AppDatabase
|
||||
import com.keylesspalace.tusky.db.InstanceEntity
|
||||
|
|
@ -57,19 +58,21 @@ class AnnouncementsViewModel @Inject constructor(
|
|||
.onErrorResumeNext {
|
||||
mastodonApi.getInstance()
|
||||
.map { Either.Right(it) }
|
||||
},
|
||||
{ emojis, either ->
|
||||
either.asLeftOrNull()?.copy(emojiList = emojis)
|
||||
?: InstanceEntity(
|
||||
accountManager.activeAccount?.domain!!,
|
||||
emojis,
|
||||
either.asRight().maxTootChars,
|
||||
either.asRight().pollLimits?.maxOptions,
|
||||
either.asRight().pollLimits?.maxOptionChars,
|
||||
either.asRight().version
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
) { emojis, either ->
|
||||
either.asLeftOrNull()?.copy(emojiList = emojis)
|
||||
?: InstanceEntity(
|
||||
accountManager.activeAccount?.domain!!,
|
||||
emojis,
|
||||
either.asRight().configuration?.statuses?.maxCharacters ?: either.asRight().maxTootChars,
|
||||
either.asRight().configuration?.polls?.maxOptions ?: either.asRight().pollConfiguration?.maxOptions,
|
||||
either.asRight().configuration?.polls?.maxCharactersPerOption ?: either.asRight().pollConfiguration?.maxOptionChars,
|
||||
either.asRight().configuration?.polls?.minExpiration ?: either.asRight().pollConfiguration?.minExpiration,
|
||||
either.asRight().configuration?.polls?.maxExpiration ?: either.asRight().pollConfiguration?.maxExpiration,
|
||||
either.asRight().configuration?.statuses?.charactersReservedPerUrl ?: DEFAULT_MAXIMUM_URL_LENGTH,
|
||||
either.asRight().version
|
||||
)
|
||||
}
|
||||
.doOnSuccess {
|
||||
appDatabase.instanceDao().insertOrReplace(it)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ class ComposeActivity :
|
|||
|
||||
@VisibleForTesting
|
||||
var maximumTootCharacters = DEFAULT_CHARACTER_LIMIT
|
||||
var charactersReservedPerUrl = DEFAULT_MAXIMUM_URL_LENGTH
|
||||
|
||||
private val viewModel: ComposeViewModel by viewModels { viewModelFactory }
|
||||
|
||||
|
|
@ -316,6 +317,7 @@ class ComposeActivity :
|
|||
withLifecycleContext {
|
||||
viewModel.instanceParams.observe { instanceData ->
|
||||
maximumTootCharacters = instanceData.maxChars
|
||||
charactersReservedPerUrl = instanceData.charactersReservedPerUrl
|
||||
updateVisibleCharactersLeft()
|
||||
binding.composeScheduleButton.visible(instanceData.supportsScheduled)
|
||||
}
|
||||
|
|
@ -654,7 +656,8 @@ class ComposeActivity :
|
|||
val instanceParams = viewModel.instanceParams.value!!
|
||||
showAddPollDialog(
|
||||
this, viewModel.poll.value, instanceParams.pollMaxOptions,
|
||||
instanceParams.pollMaxLength, viewModel::updatePoll
|
||||
instanceParams.pollMaxLength, instanceParams.pollMinDuration, instanceParams.pollMaxDuration,
|
||||
viewModel::updatePoll
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -699,7 +702,7 @@ class ComposeActivity :
|
|||
val urlSpans = binding.composeEditField.urls
|
||||
if (urlSpans != null) {
|
||||
for (span in urlSpans) {
|
||||
offset += max(0, span.url.length - MAXIMUM_URL_LENGTH)
|
||||
offset += max(0, span.url.length - charactersReservedPerUrl)
|
||||
}
|
||||
}
|
||||
var length = binding.composeEditField.length() - offset
|
||||
|
|
@ -1042,10 +1045,6 @@ class ComposeActivity :
|
|||
internal const val COMPOSE_OPTIONS_EXTRA = "COMPOSE_OPTIONS"
|
||||
private const val PHOTO_UPLOAD_URI_KEY = "PHOTO_UPLOAD_URI"
|
||||
|
||||
// Mastodon only counts URLs as this long in terms of status character limits
|
||||
@VisibleForTesting
|
||||
const val MAXIMUM_URL_LENGTH = 23
|
||||
|
||||
@JvmStatic
|
||||
fun startIntent(context: Context, options: ComposeOptions): Intent {
|
||||
return Intent(context, ComposeActivity::class.java).apply {
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ class ComposeViewModel @Inject constructor(
|
|||
maxChars = instance?.maximumTootCharacters ?: DEFAULT_CHARACTER_LIMIT,
|
||||
pollMaxOptions = instance?.maxPollOptions ?: DEFAULT_MAX_OPTION_COUNT,
|
||||
pollMaxLength = instance?.maxPollOptionLength ?: DEFAULT_MAX_OPTION_LENGTH,
|
||||
pollMinDuration = instance?.minPollDuration ?: DEFAULT_MIN_POLL_DURATION,
|
||||
pollMaxDuration = instance?.maxPollDuration ?: DEFAULT_MAX_POLL_DURATION,
|
||||
charactersReservedPerUrl = instance?.charactersReservedPerUrl ?: DEFAULT_MAXIMUM_URL_LENGTH,
|
||||
supportsScheduled = instance?.version?.let { VersionUtils(it).supportsScheduledToots() } ?: false
|
||||
)
|
||||
}
|
||||
|
|
@ -102,18 +105,20 @@ class ComposeViewModel @Inject constructor(
|
|||
init {
|
||||
|
||||
Single.zip(
|
||||
api.getCustomEmojis(), api.getInstance(),
|
||||
{ emojis, instance ->
|
||||
InstanceEntity(
|
||||
instance = accountManager.activeAccount?.domain!!,
|
||||
emojiList = emojis,
|
||||
maximumTootCharacters = instance.maxTootChars,
|
||||
maxPollOptions = instance.pollLimits?.maxOptions,
|
||||
maxPollOptionLength = instance.pollLimits?.maxOptionChars,
|
||||
version = instance.version
|
||||
)
|
||||
}
|
||||
)
|
||||
api.getCustomEmojis(), api.getInstance()
|
||||
) { emojis, instance ->
|
||||
InstanceEntity(
|
||||
instance = accountManager.activeAccount?.domain!!,
|
||||
emojiList = emojis,
|
||||
maximumTootCharacters = instance.configuration?.statuses?.maxCharacters ?: instance.maxTootChars,
|
||||
maxPollOptions = instance.configuration?.polls?.maxOptions ?: instance.pollConfiguration?.maxOptions,
|
||||
maxPollOptionLength = instance.configuration?.polls?.maxCharactersPerOption ?: instance.pollConfiguration?.maxOptionChars,
|
||||
minPollDuration = instance.configuration?.polls?.minExpiration ?: instance.pollConfiguration?.minExpiration,
|
||||
maxPollDuration = instance.configuration?.polls?.maxExpiration ?: instance.pollConfiguration?.maxExpiration,
|
||||
charactersReservedPerUrl = instance.configuration?.statuses?.charactersReservedPerUrl,
|
||||
version = instance.version
|
||||
)
|
||||
}
|
||||
.doOnSuccess {
|
||||
db.instanceDao().insertOrReplace(it)
|
||||
}
|
||||
|
|
@ -506,11 +511,19 @@ fun <T> mutableLiveData(default: T) = MutableLiveData<T>().apply { value = defau
|
|||
const val DEFAULT_CHARACTER_LIMIT = 500
|
||||
private const val DEFAULT_MAX_OPTION_COUNT = 4
|
||||
private const val DEFAULT_MAX_OPTION_LENGTH = 50
|
||||
private const val DEFAULT_MIN_POLL_DURATION = 300
|
||||
private const val DEFAULT_MAX_POLL_DURATION = 604800
|
||||
|
||||
// Mastodon only counts URLs as this long in terms of status character limits
|
||||
const val DEFAULT_MAXIMUM_URL_LENGTH = 23
|
||||
|
||||
data class ComposeInstanceParams(
|
||||
val maxChars: Int,
|
||||
val pollMaxOptions: Int,
|
||||
val pollMaxLength: Int,
|
||||
val pollMinDuration: Int,
|
||||
val pollMaxDuration: Int,
|
||||
val charactersReservedPerUrl: Int,
|
||||
val supportsScheduled: Boolean
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ package com.keylesspalace.tusky.components.compose.dialog
|
|||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.WindowManager
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.DialogAddPollBinding
|
||||
|
|
@ -30,6 +31,8 @@ fun showAddPollDialog(
|
|||
poll: NewPoll?,
|
||||
maxOptionCount: Int,
|
||||
maxOptionLength: Int,
|
||||
minDuration: Int,
|
||||
maxDuration: Int,
|
||||
onUpdatePoll: (NewPoll) -> Unit
|
||||
) {
|
||||
|
||||
|
|
@ -57,6 +60,13 @@ fun showAddPollDialog(
|
|||
|
||||
binding.pollChoices.adapter = adapter
|
||||
|
||||
var durations = context.resources.getIntArray(R.array.poll_duration_values).toList()
|
||||
val durationLabels = context.resources.getStringArray(R.array.poll_duration_names).filterIndexed { index, _ -> durations[index] in minDuration..maxDuration }
|
||||
binding.pollDurationSpinner.adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, durationLabels).apply {
|
||||
setDropDownViewResource(R.layout.support_simple_spinner_dropdown_item)
|
||||
}
|
||||
durations = durations.filter { it in minDuration..maxDuration }
|
||||
|
||||
binding.addChoiceButton.setOnClickListener {
|
||||
if (adapter.itemCount < maxOptionCount) {
|
||||
adapter.addChoice()
|
||||
|
|
@ -66,7 +76,7 @@ fun showAddPollDialog(
|
|||
}
|
||||
}
|
||||
|
||||
val pollDurationId = context.resources.getIntArray(R.array.poll_duration_values).indexOfLast {
|
||||
val pollDurationId = durations.indexOfLast {
|
||||
it <= poll?.expiresIn ?: 0
|
||||
}
|
||||
|
||||
|
|
@ -79,13 +89,10 @@ fun showAddPollDialog(
|
|||
button.setOnClickListener {
|
||||
val selectedPollDurationId = binding.pollDurationSpinner.selectedItemPosition
|
||||
|
||||
val pollDuration = context.resources
|
||||
.getIntArray(R.array.poll_duration_values)[selectedPollDurationId]
|
||||
|
||||
onUpdatePoll(
|
||||
NewPoll(
|
||||
options = adapter.pollOptions,
|
||||
expiresIn = pollDuration,
|
||||
expiresIn = durations[selectedPollDurationId],
|
||||
multiple = binding.multipleChoicesCheckBox.isChecked
|
||||
)
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue