ComposeActivity: use nodeinfo data for determining upload limits and markdown support
This commit is contained in:
parent
b4dbee0acd
commit
919c24571d
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue