Handle even more instance defaults (#2612)

* handle media size instance limits

* remove unused attributes from Instance entity

* support max_media_attachments

* support pleroma field limits, remove max_bio_chars support

* improve field input margin

* fix tests

* MAX_ACCOUNT_FIELDS -> DEFAULT_MAX_ACCOUNT_FIELDS

* improve "add field" button behavior

* fix copy paste mistake in AccountFieldEditAdapter

* refactor sendStatus to be a suspending function
This commit is contained in:
Konrad Pozniak 2022-07-26 20:24:50 +02:00 committed by GitHub
commit 1b6a0908f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1219 additions and 308 deletions

View file

@ -52,7 +52,6 @@ import androidx.core.view.ContentInfoCompat
import androidx.core.view.OnReceiveContentListener
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.lifecycle.asLiveData
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
@ -85,8 +84,6 @@ import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.util.PickMediaFiles
import com.keylesspalace.tusky.util.ThemeUtils
import com.keylesspalace.tusky.util.afterTextChanged
import com.keylesspalace.tusky.util.combineLiveData
import com.keylesspalace.tusky.util.combineOptionalLiveData
import com.keylesspalace.tusky.util.getMediaSize
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.highlightSpans
@ -95,11 +92,13 @@ import com.keylesspalace.tusky.util.onTextChanged
import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.util.visible
import com.keylesspalace.tusky.util.withLifecycleContext
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import java.io.File
@ -138,8 +137,7 @@ class ComposeActivity :
private val binding by viewBinding(ActivityComposeBinding::inflate)
private val maxUploadMediaNumber = 4
private var mediaCount = 0
private var maxUploadMediaNumber = InstanceInfoRepository.DEFAULT_MAX_MEDIA_ATTACHMENTS
private val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
@ -147,7 +145,7 @@ class ComposeActivity :
}
}
private val pickMediaFile = registerForActivityResult(PickMediaFiles()) { uris ->
if (mediaCount + uris.size > maxUploadMediaNumber) {
if (viewModel.media.value.size + uris.size > maxUploadMediaNumber) {
Toast.makeText(this, resources.getQuantityString(R.plurals.error_upload_max_media_reached, maxUploadMediaNumber, maxUploadMediaNumber), Toast.LENGTH_SHORT).show()
} else {
uris.forEach { uri ->
@ -224,8 +222,8 @@ class ComposeActivity :
binding.composeMediaPreviewBar.adapter = mediaAdapter
binding.composeMediaPreviewBar.itemAnimator = null
subscribeToUpdates(mediaAdapter)
setupButtons()
subscribeToUpdates(mediaAdapter)
photoUploadUri = savedInstanceState?.getParcelable(PHOTO_UPLOAD_URI_KEY)
@ -363,36 +361,48 @@ class ComposeActivity :
}
private fun subscribeToUpdates(mediaAdapter: MediaPreviewAdapter) {
withLifecycleContext {
viewModel.instanceInfo.observe { instanceData ->
lifecycleScope.launch {
viewModel.instanceInfo.collect { instanceData ->
maximumTootCharacters = instanceData.maxChars
charactersReservedPerUrl = instanceData.charactersReservedPerUrl
maxUploadMediaNumber = instanceData.maxMediaAttachments
updateVisibleCharactersLeft()
}
viewModel.emoji.observe { emoji -> setEmojiList(emoji) }
combineLiveData(viewModel.markMediaAsSensitive, viewModel.showContentWarning) { markSensitive, showContentWarning ->
}
lifecycleScope.launch {
viewModel.emoji.collect(::setEmojiList)
}
lifecycleScope.launch {
viewModel.showContentWarning.combine(viewModel.markMediaAsSensitive) { showContentWarning, markSensitive ->
updateSensitiveMediaToggle(markSensitive, showContentWarning)
showContentWarning(showContentWarning)
}.subscribe()
viewModel.statusVisibility.observe { visibility ->
setStatusVisibility(visibility)
}
lifecycleScope.launch {
viewModel.media.collect { media ->
mediaAdapter.submitList(media)
if (media.size != mediaCount) {
mediaCount = media.size
binding.composeMediaPreviewBar.visible(media.isNotEmpty())
updateSensitiveMediaToggle(viewModel.markMediaAsSensitive.value != false, viewModel.showContentWarning.value != false)
}
}
}
}.collect()
}
viewModel.poll.observe { poll ->
lifecycleScope.launch {
viewModel.statusVisibility.collect(::setStatusVisibility)
}
lifecycleScope.launch {
viewModel.media.collect { media ->
mediaAdapter.submitList(media)
binding.composeMediaPreviewBar.visible(media.isNotEmpty())
updateSensitiveMediaToggle(viewModel.markMediaAsSensitive.value, viewModel.showContentWarning.value)
}
}
lifecycleScope.launch {
viewModel.poll.collect { poll ->
binding.pollPreview.visible(poll != null)
poll?.let(binding.pollPreview::setPoll)
}
viewModel.scheduledAt.observe { scheduledAt ->
}
lifecycleScope.launch {
viewModel.scheduledAt.collect { scheduledAt ->
if (scheduledAt == null) {
binding.composeScheduleView.resetSchedule()
} else {
@ -400,22 +410,30 @@ class ComposeActivity :
}
updateScheduleButton()
}
combineOptionalLiveData(viewModel.media.asLiveData(), viewModel.poll) { media, poll ->
}
lifecycleScope.launch {
viewModel.media.combine(viewModel.poll) { media, poll ->
val active = poll == null &&
media!!.size != 4 &&
media.size < maxUploadMediaNumber &&
(media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE)
enableButton(binding.composeAddMediaButton, active, active)
enablePollButton(media.isNullOrEmpty())
}.subscribe()
viewModel.uploadError.observe { throwable ->
Log.w(TAG, "media upload failed", throwable)
enablePollButton(media.isEmpty())
}.collect()
}
lifecycleScope.launch {
viewModel.uploadError.collect { throwable ->
if (throwable is UploadServerError) {
displayTransientError(throwable.errorMessage)
} else {
displayTransientError(R.string.error_media_upload_sending)
}
}
viewModel.setupComplete.observe {
}
lifecycleScope.launch {
viewModel.setupComplete.collect {
// Focus may have changed during view model setup, ensure initial focus is on the edit field
binding.composeEditField.requestFocus()
}
@ -711,13 +729,17 @@ class ComposeActivity :
addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
private fun openPollDialog() {
private fun openPollDialog() = lifecycleScope.launch {
addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
val instanceParams = viewModel.instanceInfo.value!!
val instanceParams = viewModel.instanceInfo.first()
showAddPollDialog(
this, viewModel.poll.value, instanceParams.pollMaxOptions,
instanceParams.pollMaxLength, instanceParams.pollMinDuration, instanceParams.pollMaxDuration,
viewModel::updatePoll
context = this@ComposeActivity,
poll = viewModel.poll.value,
maxOptionCount = instanceParams.pollMaxOptions,
maxOptionLength = instanceParams.pollMaxLength,
minDuration = instanceParams.pollMinDuration,
maxDuration = instanceParams.pollMaxDuration,
onUpdatePoll = viewModel::updatePoll
)
}
@ -768,7 +790,7 @@ class ComposeActivity :
}
}
var length = binding.composeEditField.length() - offset
if (viewModel.showContentWarning.value!!) {
if (viewModel.showContentWarning.value) {
length += binding.composeContentWarningField.length()
}
return length
@ -822,7 +844,7 @@ class ComposeActivity :
enableButtons(false)
val contentText = binding.composeEditField.text.toString()
var spoilerText = ""
if (viewModel.showContentWarning.value!!) {
if (viewModel.showContentWarning.value) {
spoilerText = binding.composeContentWarningField.text.toString()
}
val characterCount = calculateTextLength()
@ -837,9 +859,8 @@ class ComposeActivity :
)
}
viewModel.sendStatus(contentText, spoilerText).observe(
this
) {
lifecycleScope.launch {
viewModel.sendStatus(contentText, spoilerText)
finishingUploadDialog?.dismiss()
deleteDraftAndFinish()
}

View file

@ -18,10 +18,7 @@ package com.keylesspalace.tusky.components.compose
import android.net.Uri
import android.util.Log
import androidx.core.net.toUri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import at.connyduck.calladapter.networkresult.fold
import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia
@ -38,30 +35,34 @@ import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.service.ServiceClient
import com.keylesspalace.tusky.service.StatusToSend
import com.keylesspalace.tusky.util.combineLiveData
import com.keylesspalace.tusky.util.randomAlphanumericString
import com.keylesspalace.tusky.util.toLiveData
import io.reactivex.rxjava3.core.Observable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.updateAndGet
import kotlinx.coroutines.launch
import kotlinx.coroutines.rx3.rxSingle
import kotlinx.coroutines.withContext
import javax.inject.Inject
@OptIn(FlowPreview::class)
class ComposeViewModel @Inject constructor(
private val api: MastodonApi,
private val accountManager: AccountManager,
private val mediaUploader: MediaUploader,
private val serviceClient: ServiceClient,
private val draftHelper: DraftHelper,
private val instanceInfoRepo: InstanceInfoRepository
instanceInfoRepo: InstanceInfoRepository
) : ViewModel() {
private var replyingStatusAuthor: String? = null
@ -76,40 +77,32 @@ class ComposeViewModel @Inject constructor(
private var contentWarningStateChanged: Boolean = false
private var modifiedInitialState: Boolean = false
val instanceInfo: MutableLiveData<InstanceInfo> = MutableLiveData()
val instanceInfo: SharedFlow<InstanceInfo> = instanceInfoRepo::getInstanceInfo.asFlow()
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
val emoji: MutableLiveData<List<Emoji>?> = MutableLiveData()
val markMediaAsSensitive =
mutableLiveData(accountManager.activeAccount?.defaultMediaSensitivity ?: false)
val emoji: SharedFlow<List<Emoji>> = instanceInfoRepo::getEmojis.asFlow()
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
val statusVisibility = mutableLiveData(Status.Visibility.UNKNOWN)
val showContentWarning = mutableLiveData(false)
val setupComplete = mutableLiveData(false)
val poll: MutableLiveData<NewPoll?> = mutableLiveData(null)
val scheduledAt: MutableLiveData<String?> = mutableLiveData(null)
val markMediaAsSensitive: MutableStateFlow<Boolean> =
MutableStateFlow(accountManager.activeAccount?.defaultMediaSensitivity ?: false)
val statusVisibility: MutableStateFlow<Status.Visibility> = MutableStateFlow(Status.Visibility.UNKNOWN)
val showContentWarning: MutableStateFlow<Boolean> = MutableStateFlow(false)
val setupComplete: MutableStateFlow<Boolean> = MutableStateFlow(false)
val poll: MutableStateFlow<NewPoll?> = MutableStateFlow(null)
val scheduledAt: MutableStateFlow<String?> = MutableStateFlow(null)
val media: MutableStateFlow<List<QueuedMedia>> = MutableStateFlow(emptyList())
val uploadError = MutableLiveData<Throwable>()
val uploadError = MutableSharedFlow<Throwable>(replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
private val mediaToJob = mutableMapOf<Int, Job>()
private val isEditingScheduledToot get() = !scheduledTootId.isNullOrEmpty()
// Used in ComposeActivity to pass state to result function when cropImage contract inflight
var cropImageItemOld: QueuedMedia? = null
init {
viewModelScope.launch {
emoji.postValue(instanceInfoRepo.getEmojis())
}
viewModelScope.launch {
instanceInfo.postValue(instanceInfoRepo.getInstanceInfo())
}
}
suspend fun pickMedia(mediaUri: Uri, description: String? = null): Result<QueuedMedia> = withContext(Dispatchers.IO) {
try {
val (type, uri, size) = mediaUploader.prepareMedia(mediaUri)
val (type, uri, size) = mediaUploader.prepareMedia(mediaUri, instanceInfo.first())
val mediaItems = media.value
if (type != QueuedMedia.Type.IMAGE &&
mediaItems.isNotEmpty() &&
@ -157,10 +150,10 @@ class ComposeViewModel @Inject constructor(
mediaToJob[mediaItem.localId] = viewModelScope.launch {
mediaUploader
.uploadMedia(mediaItem)
.uploadMedia(mediaItem, instanceInfo.first())
.catch { error ->
media.update { mediaValue -> mediaValue.filter { it.localId != mediaItem.localId } }
uploadError.postValue(error)
uploadError.emit(error)
}
.collect { event ->
val item = media.value.find { it.localId == mediaItem.localId }
@ -216,7 +209,7 @@ class ComposeViewModel @Inject constructor(
startingText?.startsWith(content.toString()) ?: false
)
val contentWarningChanged = showContentWarning.value!! &&
val contentWarningChanged = showContentWarning.value &&
!contentWarning.isNullOrEmpty() &&
!startingContentWarning.startsWith(contentWarning.toString())
val mediaChanged = media.value.isNotEmpty()
@ -259,8 +252,8 @@ class ComposeViewModel @Inject constructor(
inReplyToId = inReplyToId,
content = content,
contentWarning = contentWarning,
sensitive = markMediaAsSensitive.value!!,
visibility = statusVisibility.value!!,
sensitive = markMediaAsSensitive.value,
visibility = statusVisibility.value,
mediaUris = mediaUris,
mediaDescriptions = mediaDescriptions,
poll = poll.value,
@ -271,38 +264,34 @@ class ComposeViewModel @Inject constructor(
/**
* Send status to the server.
* Uses current state plus provided arguments.
* @return LiveData which will signal once the screen can be closed or null if there are errors
*/
fun sendStatus(
suspend fun sendStatus(
content: String,
spoilerText: String
): LiveData<Unit> {
) {
val deletionObservable = if (isEditingScheduledToot) {
rxSingle { api.deleteScheduledStatus(scheduledTootId.toString()) }.toObservable().map { }
} else {
Observable.just(Unit)
}.toLiveData()
if (!scheduledTootId.isNullOrEmpty()) {
api.deleteScheduledStatus(scheduledTootId!!)
}
val sendFlow = media
media
.filter { items -> items.all { it.uploadPercent == -1 } }
.map {
.first {
val mediaIds: MutableList<String> = mutableListOf()
val mediaUris: MutableList<Uri> = mutableListOf()
val mediaDescriptions: MutableList<String> = mutableListOf()
val mediaProcessed: MutableList<Boolean> = mutableListOf()
for (item in media.value) {
media.value.forEach { item ->
mediaIds.add(item.id!!)
mediaUris.add(item.uri)
mediaDescriptions.add(item.description ?: "")
mediaProcessed.add(false)
}
val tootToSend = StatusToSend(
text = content,
warningText = spoilerText,
visibility = statusVisibility.value!!.serverString(),
sensitive = mediaUris.isNotEmpty() && (markMediaAsSensitive.value!! || showContentWarning.value!!),
visibility = statusVisibility.value.serverString(),
sensitive = mediaUris.isNotEmpty() && (markMediaAsSensitive.value || showContentWarning.value),
mediaIds = mediaIds,
mediaUris = mediaUris.map { it.toString() },
mediaDescriptions = mediaDescriptions,
@ -319,9 +308,8 @@ class ComposeViewModel @Inject constructor(
)
serviceClient.sendToot(tootToSend)
true
}
return combineLiveData(deletionObservable, sendFlow.asLiveData()) { _, _ -> }
}
suspend fun updateDescription(localId: Int, description: String): Boolean {
@ -369,7 +357,7 @@ class ComposeViewModel @Inject constructor(
})
}
':' -> {
val emojiList = emoji.value ?: return emptyList()
val emojiList = emoji.replayCache.firstOrNull() ?: return emptyList()
val incomplete = token.substring(1)
return emojiList.filter { emoji ->
@ -389,7 +377,7 @@ class ComposeViewModel @Inject constructor(
fun setup(composeOptions: ComposeActivity.ComposeOptions?) {
if (setupComplete.value == true) {
if (setupComplete.value) {
return
}
@ -476,8 +464,6 @@ class ComposeViewModel @Inject constructor(
}
}
fun <T> mutableLiveData(default: T) = MutableLiveData<T>().apply { value = default }
/**
* Thrown when trying to add an image when video is already present or the other way around
*/

View file

@ -27,6 +27,7 @@ import at.connyduck.calladapter.networkresult.fold
import com.keylesspalace.tusky.BuildConfig
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia
import com.keylesspalace.tusky.components.instanceinfo.InstanceInfo
import com.keylesspalace.tusky.network.MediaUploadApi
import com.keylesspalace.tusky.network.ProgressRequestBody
import com.keylesspalace.tusky.util.MEDIA_SIZE_UNKNOWN
@ -82,10 +83,10 @@ class MediaUploader @Inject constructor(
) {
@OptIn(ExperimentalCoroutinesApi::class)
fun uploadMedia(media: QueuedMedia): Flow<UploadEvent> {
fun uploadMedia(media: QueuedMedia, instanceInfo: InstanceInfo): Flow<UploadEvent> {
return flow {
if (shouldResizeMedia(media)) {
emit(downsize(media))
if (shouldResizeMedia(media, instanceInfo)) {
emit(downsize(media, instanceInfo))
} else {
emit(media)
}
@ -94,7 +95,7 @@ class MediaUploader @Inject constructor(
.flowOn(Dispatchers.IO)
}
fun prepareMedia(inUri: Uri): PreparedMedia {
fun prepareMedia(inUri: Uri, instanceInfo: InstanceInfo): PreparedMedia {
var mediaSize = MEDIA_SIZE_UNKNOWN
var uri = inUri
val mimeType: String?
@ -164,7 +165,7 @@ class MediaUploader @Inject constructor(
if (mimeType != null) {
return when (mimeType.substring(0, mimeType.indexOf('/'))) {
"video" -> {
if (mediaSize > STATUS_VIDEO_SIZE_LIMIT) {
if (mediaSize > instanceInfo.videoSizeLimit) {
throw VideoSizeException()
}
PreparedMedia(QueuedMedia.Type.VIDEO, uri, mediaSize)
@ -173,7 +174,7 @@ class MediaUploader @Inject constructor(
PreparedMedia(QueuedMedia.Type.IMAGE, uri, mediaSize)
}
"audio" -> {
if (mediaSize > STATUS_AUDIO_SIZE_LIMIT) {
if (mediaSize > instanceInfo.videoSizeLimit) {
throw AudioSizeException()
}
PreparedMedia(QueuedMedia.Type.AUDIO, uri, mediaSize)
@ -239,22 +240,18 @@ class MediaUploader @Inject constructor(
}
}
private fun downsize(media: QueuedMedia): QueuedMedia {
private fun downsize(media: QueuedMedia, instanceInfo: InstanceInfo): QueuedMedia {
val file = createNewImageFile(context)
downsizeImage(media.uri, STATUS_IMAGE_SIZE_LIMIT, contentResolver, file)
downsizeImage(media.uri, instanceInfo.imageSizeLimit, contentResolver, file)
return media.copy(uri = file.toUri(), mediaSize = file.length())
}
private fun shouldResizeMedia(media: QueuedMedia): Boolean {
private fun shouldResizeMedia(media: QueuedMedia, instanceInfo: InstanceInfo): Boolean {
return media.type == QueuedMedia.Type.IMAGE &&
(media.mediaSize > STATUS_IMAGE_SIZE_LIMIT || getImageSquarePixels(context.contentResolver, media.uri) > STATUS_IMAGE_PIXEL_SIZE_LIMIT)
(media.mediaSize > instanceInfo.imageSizeLimit || getImageSquarePixels(context.contentResolver, media.uri) > instanceInfo.imageMatrixLimit)
}
private companion object {
private const val TAG = "MediaUploader"
private const val STATUS_VIDEO_SIZE_LIMIT = 41943040 // 40MiB
private const val STATUS_AUDIO_SIZE_LIMIT = 41943040 // 40MiB
private const val STATUS_IMAGE_SIZE_LIMIT = 8388608 // 8MiB
private const val STATUS_IMAGE_PIXEL_SIZE_LIMIT = 16777216 // 4096^2 Pixels
}
}

View file

@ -21,5 +21,12 @@ data class InstanceInfo(
val pollMaxLength: Int,
val pollMinDuration: Int,
val pollMaxDuration: Int,
val charactersReservedPerUrl: Int
val charactersReservedPerUrl: Int,
val videoSizeLimit: Int,
val imageSizeLimit: Int,
val imageMatrixLimit: Int,
val maxMediaAttachments: Int,
val maxFields: Int,
val maxFieldNameLength: Int?,
val maxFieldValueLength: Int?
)

View file

@ -69,7 +69,14 @@ class InstanceInfoRepository @Inject constructor(
minPollDuration = instance.configuration?.polls?.minExpiration ?: instance.pollConfiguration?.minExpiration,
maxPollDuration = instance.configuration?.polls?.maxExpiration ?: instance.pollConfiguration?.maxExpiration,
charactersReservedPerUrl = instance.configuration?.statuses?.charactersReservedPerUrl,
version = instance.version
version = instance.version,
videoSizeLimit = instance.configuration?.mediaAttachments?.videoSizeLimit,
imageSizeLimit = instance.configuration?.mediaAttachments?.imageSizeLimit,
imageMatrixLimit = instance.configuration?.mediaAttachments?.imageMatrixLimit,
maxMediaAttachments = instance.configuration?.statuses?.maxMediaAttachments ?: instance.maxMediaAttachments,
maxFields = instance.pleroma?.metadata?.fieldLimits?.maxFields,
maxFieldNameLength = instance.pleroma?.metadata?.fieldLimits?.nameLength,
maxFieldValueLength = instance.pleroma?.metadata?.fieldLimits?.valueLength
)
dao.insertOrReplace(instanceEntity)
instanceEntity
@ -85,7 +92,14 @@ class InstanceInfoRepository @Inject constructor(
pollMaxLength = instanceInfo?.maxPollOptionLength ?: DEFAULT_MAX_OPTION_LENGTH,
pollMinDuration = instanceInfo?.minPollDuration ?: DEFAULT_MIN_POLL_DURATION,
pollMaxDuration = instanceInfo?.maxPollDuration ?: DEFAULT_MAX_POLL_DURATION,
charactersReservedPerUrl = instanceInfo?.charactersReservedPerUrl ?: DEFAULT_CHARACTERS_RESERVED_PER_URL
charactersReservedPerUrl = instanceInfo?.charactersReservedPerUrl ?: DEFAULT_CHARACTERS_RESERVED_PER_URL,
videoSizeLimit = instanceInfo?.videoSizeLimit ?: DEFAULT_VIDEO_SIZE_LIMIT,
imageSizeLimit = instanceInfo?.imageSizeLimit ?: DEFAULT_IMAGE_SIZE_LIMIT,
imageMatrixLimit = instanceInfo?.imageMatrixLimit ?: DEFAULT_IMAGE_MATRIX_LIMIT,
maxMediaAttachments = instanceInfo?.maxMediaAttachments ?: DEFAULT_MAX_MEDIA_ATTACHMENTS,
maxFields = instanceInfo?.maxFields ?: DEFAULT_MAX_ACCOUNT_FIELDS,
maxFieldNameLength = instanceInfo?.maxFieldNameLength,
maxFieldValueLength = instanceInfo?.maxFieldValueLength
)
}
}
@ -99,7 +113,14 @@ class InstanceInfoRepository @Inject constructor(
private const val DEFAULT_MIN_POLL_DURATION = 300
private const val DEFAULT_MAX_POLL_DURATION = 604800
private const val DEFAULT_VIDEO_SIZE_LIMIT = 41943040 // 40MiB
private const val DEFAULT_IMAGE_SIZE_LIMIT = 10485760 // 10MiB
private const val DEFAULT_IMAGE_MATRIX_LIMIT = 16777216 // 4096^2 Pixels
// Mastodon only counts URLs as this long in terms of status character limits
const val DEFAULT_CHARACTERS_RESERVED_PER_URL = 23
const val DEFAULT_MAX_MEDIA_ATTACHMENTS = 4
const val DEFAULT_MAX_ACCOUNT_FIELDS = 4
}
}