diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt index 64909fab..5b1edaee 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt @@ -92,6 +92,7 @@ class ActPost : AsyncActivity(), private const val REQUEST_CODE_VIDEO = 4 private const val REQUEST_CODE_ATTACHMENT_OLD = 5 private const val REQUEST_CODE_SOUND = 6 + private const val REQUEST_CODE_CUSTOM_THUMBNAIL = 7 private const val PERMISSION_REQUEST_CODE = 1 @@ -532,6 +533,12 @@ class ActPost : AsyncActivity(), } } else { when(requestCode) { + REQUEST_CODE_CUSTOM_THUMBNAIL -> checkCustomThumbnail( + data?.handleGetContentResult( + contentResolver + ) + ) + REQUEST_CODE_ATTACHMENT_OLD -> checkAttachments( data?.handleGetContentResult( contentResolver @@ -1674,6 +1681,17 @@ class ActPost : AsyncActivity(), openFocusPoint(pa) } } + if(account?.isMastodon == true) { + when(pa.attachment?.type) { + TootAttachmentType.Audio, TootAttachmentType.GIFV, TootAttachmentType.Video -> + a.addAction(getString(R.string.custom_thumbnail)) { + openCustomThumbnail(pa) + } + + else -> { + } + } + } a.addAction(getString(R.string.delete)) { deleteAttachment(pa) @@ -1683,51 +1701,196 @@ class ActPost : AsyncActivity(), } private fun openFocusPoint(pa : PostAttachment) { - val attachment = pa.attachment - if(attachment != null) { - DlgFocusPoint(this, attachment) - .setCallback(object : FocusPointView.Callback { - override fun onFocusPointUpdate(x : Float, y : Float) { - val account = this@ActPost.account ?: return - - TootTaskRunner(this@ActPost, TootTaskRunner.PROGRESS_NONE).run(account, - object : TootTask { - override fun background(client : TootApiClient) : TootApiResult? { - try { - val result = client.request( - "/api/v1/media/${attachment.id}", - jsonObject { - put("focus", "%.2f,%.2f".format(x, y)) - } - .toPutRequestBuilder() + val attachment = pa.attachment ?: return + DlgFocusPoint(this, attachment) + .setCallback(object : FocusPointView.Callback { + override fun onFocusPointUpdate(x : Float, y : Float) { + val account = this@ActPost.account ?: return + + TootTaskRunner(this@ActPost, TootTaskRunner.PROGRESS_NONE).run(account, + object : TootTask { + override fun background(client : TootApiClient) : TootApiResult? { + try { + val result = client.request( + "/api/v1/media/${attachment.id}", + jsonObject { + put("focus", "%.2f,%.2f".format(x, y)) + } + .toPutRequestBuilder() + ) + new_attachment = + parseItem( + ::TootAttachment, + ServiceType.MASTODON, + result?.jsonObject ) - new_attachment = - parseItem( - ::TootAttachment, - ServiceType.MASTODON, - result?.jsonObject - ) - return result - } catch(ex : Throwable) { - return TootApiResult(ex.withCaption("set focus point failed.")) - } + return result + } catch(ex : Throwable) { + return TootApiResult(ex.withCaption("set focus point failed.")) } - - var new_attachment : TootAttachment? = null - - override fun handleResult(result : TootApiResult?) { - result ?: return - if(new_attachment != null) { - pa.attachment = attachment - } else { - showToast(this@ActPost, true, result.error) - } + } + + var new_attachment : TootAttachment? = null + + override fun handleResult(result : TootApiResult?) { + result ?: return + if(new_attachment != null) { + pa.attachment = attachment + } else { + showToast(this@ActPost, true, result.error) } - }) - } - }) - .show() + } + }) + } + }) + .show() + } + + private fun openCustomThumbnail(pa : PostAttachment) { + + lastPostAttachment = pa + + val permissionCheck = + ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + if(permissionCheck != PackageManager.PERMISSION_GRANTED) { + preparePermission() + return } + + // SAFのIntentで開く + try { + val intent = + intentGetContent(false, getString(R.string.pick_images), arrayOf("image/*")) + startActivityForResult(intent, REQUEST_CODE_CUSTOM_THUMBNAIL) + } catch(ex : Throwable) { + log.trace(ex) + showToast(this, ex, "ACTION_GET_CONTENT failed.") + } + } + + private var lastPostAttachment : PostAttachment? = null + + private fun checkCustomThumbnail(srcList : ArrayList?) { + val src = srcList?.elementAtOrNull(0) ?: return + + val account = this@ActPost.account + if(account == null) { + showToast(this, false, R.string.account_select_please) + return + } + + val mime_type = getMimeType(src.uri, src.mimeType) + if(mime_type?.isEmpty() != false) { + showToast(this, false, R.string.mime_type_missing) + return + } + + val pa = lastPostAttachment + if( pa == null || !attachment_list.contains(pa)){ + showToast(this, true, "lost attachment information") + return + } + + TootTaskRunner(this).run(account,object:TootTask{ + override fun background(client : TootApiClient) : TootApiResult? { + try { + val (ti, tiResult) = TootInstance.get(client) + ti ?: return tiResult + + val resizeConfig = ResizeConfig(ResizeType.SquarePixel,400) + + val opener = createOpener(src.uri,mime_type, resizeConfig) + + val media_size_max = 1000000 + + val content_length = getStreamSize(true, opener.open()) + if(content_length > media_size_max) { + return TootApiResult( + getString( + R.string.file_size_too_big, + media_size_max / 1000000 + ) + ) + } + + fun fixDocumentName(s : String) : String { + val s_length = s.length + val m = """([^\x20-\x7f])""".asciiPattern().matcher(s) + m.reset() + val sb = StringBuilder(s_length) + var lastEnd = 0 + while(m.find()) { + sb.append(s.substring(lastEnd, m.start())) + val escaped = m.groupEx(1) !!.encodeUTF8().encodeHex() + sb.append(escaped) + lastEnd = m.end() + } + if(lastEnd < s_length) sb.append(s.substring(lastEnd, s_length)) + return sb.toString() + } + + val fileName = fixDocumentName(getDocumentName(contentResolver, src.uri)) + + return if(account.isMisskey) { + opener.deleteTempFile() + TootApiResult("custom thumbnail is not supported on misskey account.") + } else { + val result =client.request( + "/api/v1/media/${pa.attachment?.id}", + MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart( + "thumbnail", + fileName, + object : RequestBody() { + override fun contentType() : MediaType? { + return opener.mimeType.toMediaType() + } + + @Throws(IOException::class) + override fun contentLength() : Long { + return content_length + } + + @Throws(IOException::class) + override fun writeTo(sink : BufferedSink) { + opener.open().use { inData -> + val tmp = ByteArray(4096) + while(true) { + val r = inData.read(tmp, 0, tmp.size) + if(r <= 0) break + sink.write(tmp, 0, r) + } + } + } + } + ) + .build().toPut() + ) + opener.deleteTempFile() + + val jsonObject = result?.jsonObject + if(jsonObject != null) { + val a = parseItem(::TootAttachment, ServiceType.MASTODON, jsonObject) + if(a == null) { + result.error = "TootAttachment.parse failed" + } else { + pa.attachment = a + } + } + result + } + + } catch(ex : Throwable) { + return TootApiResult(ex.withCaption("read failed.")) + } + } + + override fun handleResult(result : TootApiResult?) { + showMediaAttachment() + result?.error?.let{ showToast(this@ActPost,true,it)} + } + }) } private fun deleteAttachment(pa : PostAttachment) { @@ -1885,7 +2048,7 @@ class ActPost : AsyncActivity(), fun deleteTempFile() } - private fun createOpener(uri : Uri, mime_type : String) : InputStreamOpener { + private fun createOpener(uri : Uri, mime_type : String,resizeConfig: ResizeConfig) : InputStreamOpener { while(true) { try { @@ -1898,8 +2061,6 @@ class ActPost : AsyncActivity(), break } - // 設定からリサイズ指定を読む - val resizeConfig = resizeConfigList[Pref.ipResizeImage(pref)] val bitmap = createResizedBitmap( this, @@ -2137,8 +2298,10 @@ class ActPost : AsyncActivity(), return TootApiResult(getString(R.string.mime_type_not_acceptable, mimeType)) } } + // 設定からリサイズ指定を読む + val resizeConfig = resizeConfigList[Pref.ipResizeImage(pref)] - val opener = createOpener(uri, mimeType) + val opener = createOpener(uri, mimeType, resizeConfig) val media_size_max = when { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt index 391f8a16..6bb4936d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt @@ -1723,7 +1723,7 @@ internal class ItemViewHolder( TootAttachmentType.Audio -> { iv.setMediaType(0) iv.setDefaultImage(defaultColorIcon(activity, R.drawable.wide_music)) - iv.setImageUrl(activity.pref, 0f, null) + iv.setImageUrl(activity.pref, 0f, ta.urlForThumbnail) showUrl = true } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAttachment.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAttachment.kt index a5477b20..4f46438a 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAttachment.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAttachment.kt @@ -97,7 +97,10 @@ class TootAttachment : TootAttachmentLike { get() = remote_url.notEmpty() ?: url override val urlForThumbnail : String? - get() = preview_url.notEmpty() ?: remote_url.notEmpty() ?: url + get() = preview_url.notEmpty() ?: when(type){ + TootAttachmentType.Image -> remote_url.notEmpty() ?: url + else -> null + } constructor(serviceType : ServiceType, src : JsonObject) { diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 04788cb5..e5c66c52 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -1030,4 +1030,5 @@ 画面下部の余白(単位:dp。デフォルト:0。アプリ再起動が必要) 覚え書き %1$sに関する覚え書き + カスタムサムネイル diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f052ebe8..bec3ad20 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1037,4 +1037,5 @@ Screen bottom padding (Unit: dp. Default:0. App restart required) Personal notes Personal notes of %1$s + Custom thumbnail