ComposeActivity: use nodeinfo data for determining upload limits and markdown support

This commit is contained in:
Alibek Omarov 2020-01-16 16:44:32 +03:00
parent b4dbee0acd
commit 919c24571d
3 changed files with 117 additions and 43 deletions

View File

@ -321,13 +321,11 @@ class ComposeActivity : BaseActivity(),
}
}
private var hasNoAttachmentLimits: Boolean = false
private fun reenableAttachments() {
// in case of we already had disabled attachments
// but got information about extension later
enableButton(composeAddMediaButton, true, true)
enablePollButton(viewModel.poll == null)
hasNoAttachmentLimits = true
enablePollButton(viewModel.poll != null)
}
private fun subscribeToUpdates(mediaAdapter: MediaPreviewAdapter) {
@ -336,9 +334,12 @@ class ComposeActivity : BaseActivity(),
maximumTootCharacters = instanceData.maxChars
updateVisibleCharactersLeft()
composeScheduleButton.visible(instanceData.supportsScheduled)
composeMarkdownButton.visible(instanceData.supportsFormatting)
if(instanceData.hasNoAttachmentLimits)
}
viewModel.instanceMetadata.observe { instanceData ->
composeMarkdownButton.visible(instanceData.supportsMarkdown)
if(instanceData.software.equals("pleroma")) {
reenableAttachments()
}
}
viewModel.emoji.observe { emoji -> setEmojiList(emoji) }
combineLiveData(viewModel.markMediaAsSensitive, viewModel.showContentWarning) { markSensitive, showContentWarning ->
@ -366,11 +367,12 @@ class ComposeActivity : BaseActivity(),
updateScheduleButton()
}
combineOptionalLiveData(viewModel.media, viewModel.poll) { media, poll ->
val active = (hasNoAttachmentLimits) || (poll == null
&& media!!.size != 4
&& media.firstOrNull()?.type != QueuedMedia.Type.VIDEO)
enableButton(composeAddMediaButton, active, active)
enablePollButton(active && poll == null)
if(!viewModel.hasNoAttachmentLimits) {
val active = (poll == null && media!!.size != 4
&& media.firstOrNull()?.type != QueuedMedia.Type.VIDEO)
enableButton(composeAddMediaButton, active, active)
enablePollButton(media.isNullOrEmpty())
}
}.subscribe()
viewModel.uploadError.observe {
displayTransientError(R.string.error_media_upload_sending)
@ -910,7 +912,7 @@ class ComposeActivity : BaseActivity(),
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
if(!hasNoAttachmentLimits) {
if(!viewModel.hasNoAttachmentLimits) {
val mimeTypes = arrayOf("image/*", "video/*")
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
}

View File

@ -62,18 +62,51 @@ class ComposeViewModel
private var inReplyToId: String? = null
private var startingVisibility: Status.Visibility = Status.Visibility.UNKNOWN
private val instance: MutableLiveData<InstanceEntity?> = MutableLiveData()
private val nodeinfo: MutableLiveData<NodeInfo?> = MutableLiveData()
public var markdownMode: Boolean = false
public var hasNoAttachmentLimits = false
val instanceParams: LiveData<ComposeInstanceParams> = instance.map { instance ->
ComposeInstanceParams(
maxChars = instance?.maximumTootCharacters ?: DEFAULT_CHARACTER_LIMIT,
pollMaxOptions = instance?.maxPollOptions ?: DEFAULT_MAX_OPTION_COUNT,
pollMaxLength = instance?.maxPollOptionLength ?: DEFAULT_MAX_OPTION_LENGTH,
supportsScheduled = instance?.version?.let { VersionUtils(it).supportsScheduledToots() } ?: false,
supportsFormatting = instance?.version?.let { VersionUtils(it).isPleroma() } ?: false,
hasNoAttachmentLimits = instance?.version?.let { VersionUtils(it).isPleroma() } ?: false
supportsScheduled = instance?.version?.let { VersionUtils(it).supportsScheduledToots() } ?: false
)
}
val instanceMetadata: LiveData<ComposeInstanceMetadata> = nodeinfo.map { nodeinfo ->
val software = nodeinfo?.software?.name ?: "mastodon"
if(software.equals("pleroma")) {
hasNoAttachmentLimits = true
ComposeInstanceMetadata(
software = "pleroma",
supportsMarkdown = nodeinfo?.metadata?.postFormats?.contains("text/markdown") ?: false,
supportsBBcode = nodeinfo?.metadata?.postFormats?.contains("text/bbcode") ?: false,
supportsHTML = nodeinfo?.metadata?.postFormats?.contains("text/html") ?: false,
videoLimit = nodeinfo?.metadata?.uploadLimits?.general ?: STATUS_VIDEO_SIZE_LIMIT,
imageLimit = nodeinfo?.metadata?.uploadLimits?.general ?: STATUS_IMAGE_SIZE_LIMIT
)
} else if(software.equals("pixelfed")) {
ComposeInstanceMetadata(
software = "pixelfed",
supportsMarkdown = false,
supportsBBcode = false,
supportsHTML = false,
videoLimit = nodeinfo?.metadata?.config?.uploader?.maxPhotoSize ?: STATUS_VIDEO_SIZE_LIMIT,
imageLimit = nodeinfo?.metadata?.config?.uploader?.maxPhotoSize ?: STATUS_IMAGE_SIZE_LIMIT
)
} else {
ComposeInstanceMetadata(
software = "mastodon",
supportsMarkdown = false,
supportsBBcode = false,
supportsHTML = false,
videoLimit = STATUS_VIDEO_SIZE_LIMIT,
imageLimit = STATUS_IMAGE_SIZE_LIMIT
)
}
}
val emoji: MutableLiveData<List<Emoji>?> = MutableLiveData()
val markMediaAsSensitive =
mutableLiveData(accountManager.activeAccount?.defaultMediaSensitivity ?: false)
@ -110,7 +143,7 @@ class ComposeViewModel
db.instanceDao().insertOrReplace(it)
}
.onErrorResumeNext(
db.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!)
db.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!)
)
.subscribe ({ instanceEntity ->
emoji.postValue(instanceEntity.emojiList)
@ -120,16 +153,34 @@ class ComposeViewModel
Log.w(TAG, "error loading instance data", throwable)
})
.autoDispose()
api.getNodeinfoLinks().subscribe({ links ->
if(links.links.size > 0) {
api.getNodeinfo(links.links[0].href).subscribe({ni ->
nodeinfo.postValue(ni)
}, {
err -> Log.d(TAG, "Failed to get nodeinfo", err)
}
)
}
}, {
err -> Log.d(TAG, "Failed to get nodeinfo links", err)
}
)
}
fun pickMedia(uri: Uri, filename: String?): LiveData<Either<Throwable, QueuedMedia>> {
// We are not calling .toLiveData() here because we don't want to stop the process when
// the Activity goes away temporarily (like on screen rotation).
val liveData = MutableLiveData<Either<Throwable, QueuedMedia>>()
mediaUploader.prepareMedia(uri, instanceParams.value!!.hasNoAttachmentLimits)
val imageLimit = instanceMetadata.value?.videoLimit ?: STATUS_VIDEO_SIZE_LIMIT
val videoLimit = instanceMetadata.value?.imageLimit ?: STATUS_IMAGE_SIZE_LIMIT
mediaUploader.prepareMedia(uri, videoLimit, imageLimit)
.map { (type, uri, size) ->
val mediaItems = media.value!!
if (!instanceParams.value!!.hasNoAttachmentLimits
if (!hasNoAttachmentLimits
&& type == QueuedMedia.Type.VIDEO
&& mediaItems.isNotEmpty()
&& mediaItems[0].type == QueuedMedia.Type.IMAGE) {
@ -149,10 +200,13 @@ class ComposeViewModel
private fun addMediaToQueue(type: Int, uri: Uri, mediaSize: Long, filename: String): QueuedMedia {
val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, mediaSize, filename,
instanceParams.value!!.hasNoAttachmentLimits)
hasNoAttachmentLimits)
val imageLimit = instanceMetadata.value?.videoLimit ?: STATUS_VIDEO_SIZE_LIMIT
val videoLimit = instanceMetadata.value?.imageLimit ?: STATUS_IMAGE_SIZE_LIMIT
media.value = media.value!! + mediaItem
mediaToDisposable[mediaItem.localId] = mediaUploader
.uploadMedia(mediaItem)
.uploadMedia(mediaItem, videoLimit, imageLimit )
.subscribe ({ event ->
val item = media.value?.find { it.localId == mediaItem.localId }
?: return@subscribe
@ -180,7 +234,7 @@ class ComposeViewModel
private fun addUploadedMedia(id: String, type: Int, uri: Uri, description: String?) {
val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, 0, "unknown",
instanceParams.value!!.hasNoAttachmentLimits, -1, id, description)
hasNoAttachmentLimits, -1, id, description)
media.value = media.value!! + mediaItem
}
@ -453,12 +507,22 @@ 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 = 25
private const val STATUS_VIDEO_SIZE_LIMIT = 41943040 // 40MiB
private const val STATUS_IMAGE_SIZE_LIMIT = 8388608 // 8MiB
data class ComposeInstanceParams(
val maxChars: Int,
val pollMaxOptions: Int,
val pollMaxLength: Int,
val supportsScheduled: Boolean,
val supportsFormatting: Boolean,
val hasNoAttachmentLimits: Boolean
val supportsScheduled: Boolean
)
data class ComposeInstanceMetadata(
val software: String,
val supportsMarkdown: Boolean,
val supportsBBcode: Boolean,
val supportsHTML: Boolean,
val videoLimit: Int,
val imageLimit: Int
)

View File

@ -59,8 +59,8 @@ fun createNewImageFile(context: Context): File {
data class PreparedMedia(val type: Int, val uri: Uri, val size: Long)
interface MediaUploader {
fun prepareMedia(inUri: Uri, hasNoLimits: Boolean): Single<PreparedMedia>
fun uploadMedia(media: QueuedMedia): Observable<UploadEvent>
fun prepareMedia(inUri: Uri, videoLimit: Int, imageLimit: Int): Single<PreparedMedia>
fun uploadMedia(media: QueuedMedia, videoLimit: Int, imageLimit: Int): Observable<UploadEvent>
}
class VideoSizeException : Exception()
@ -71,19 +71,19 @@ class MediaUploaderImpl(
private val context: Context,
private val mastodonApi: MastodonApi
) : MediaUploader {
override fun uploadMedia(media: QueuedMedia): Observable<UploadEvent> {
override fun uploadMedia(media: QueuedMedia, videoLimit: Int, imageLimit: Int): Observable<UploadEvent> {
return Observable
.fromCallable {
if (shouldResizeMedia(media)) {
downsize(media)
if (shouldResizeMedia(media, imageLimit)) {
downsize(media, imageLimit)
}
media
}
.switchMap { upload(it) }
.switchMap { upload(it, videoLimit, imageLimit) }
.subscribeOn(Schedulers.io())
}
override fun prepareMedia(inUri: Uri, hasNoLimits: Boolean): Single<PreparedMedia> {
override fun prepareMedia(inUri: Uri, videoLimit: Int, imageLimit: Int): Single<PreparedMedia> {
return Single.fromCallable {
var mediaSize = getMediaSize(contentResolver, inUri)
var uri = inUri
@ -120,7 +120,7 @@ class MediaUploaderImpl(
val topLevelType = mimeType.substring(0, mimeType.indexOf('/'))
when (topLevelType) {
"video" -> {
if (!hasNoLimits && mediaSize > STATUS_VIDEO_SIZE_LIMIT) {
if (mediaSize > videoLimit) {
throw VideoSizeException()
}
PreparedMedia(QueuedMedia.Type.VIDEO, uri, mediaSize)
@ -141,7 +141,7 @@ class MediaUploaderImpl(
private val contentResolver = context.contentResolver
private fun upload(media: QueuedMedia): Observable<UploadEvent> {
private fun upload(media: QueuedMedia, videoLimit: Int, imageLimit: Int): Observable<UploadEvent> {
return Observable.create { emitter ->
var mimeType = contentResolver.getType(media.uri)
val map = MimeTypeMap.getSingleton()
@ -156,7 +156,6 @@ class MediaUploaderImpl(
if (mimeType == null) mimeType = "multipart/form-data"
var lastProgress = -1
val fileBody = ProgressRequestBody(stream, media.mediaSize,
mimeType.toMediaTypeOrNull()) { percentage ->
@ -181,24 +180,33 @@ class MediaUploaderImpl(
}
}
private fun downsize(media: QueuedMedia): QueuedMedia {
private fun downsize(media: QueuedMedia, imageLimit: Int): QueuedMedia {
val file = createNewImageFile(context)
DownsizeImageTask.resize(arrayOf(media.uri),
STATUS_IMAGE_SIZE_LIMIT, context.contentResolver, file)
DownsizeImageTask.resize(arrayOf(media.uri), imageLimit, context.contentResolver, file)
return media.copy(uri = file.toUri(), mediaSize = file.length())
}
private fun shouldResizeMedia(media: QueuedMedia): Boolean {
return !media.noChanges && media.type == QueuedMedia.Type.IMAGE
&& (media.mediaSize > STATUS_IMAGE_SIZE_LIMIT
|| getImageSquarePixels(context.contentResolver, media.uri) > STATUS_IMAGE_PIXEL_SIZE_LIMIT)
private fun shouldResizeMedia(media: QueuedMedia, imageLimit: Int): Boolean {
// resize only images
if(media.type == QueuedMedia.Type.IMAGE) {
// resize when exceed image limit
if(media.mediaSize < imageLimit)
return true
// don't resize when instance permits any image resolution(Pleroma)
if(media.noChanges)
return false
// resize when exceed pixel limit
if(getImageSquarePixels(context.contentResolver, media.uri) > STATUS_IMAGE_PIXEL_SIZE_LIMIT)
return true
}
return false
}
private companion object {
private const val TAG = "MediaUploaderImpl"
private const val STATUS_VIDEO_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
}
}