mirror of
https://github.com/tateisu/SubwayTooter
synced 2025-02-07 06:04:23 +01:00
添付メディアのアップロード順序を改善
This commit is contained in:
parent
fd22ad6874
commit
e7620557da
@ -207,11 +207,11 @@ class ActAccountSetting
|
||||
data.handleGetContentResult(contentResolver).firstOrNull()?.let{
|
||||
addAttachment(
|
||||
requestCode,
|
||||
it.first,
|
||||
if( it.second?.isNotEmpty() == true)
|
||||
it.second
|
||||
it.uri,
|
||||
if( it.mimeType?.isNotEmpty() == true)
|
||||
it.mimeType
|
||||
else
|
||||
contentResolver.getType(it.first)
|
||||
contentResolver.getType(it.uri)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -918,8 +918,8 @@ class ActAppSetting : AppCompatActivity()
|
||||
|
||||
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
|
||||
if(resultCode == RESULT_OK && data != null && requestCode == REQUEST_CODE_TIMELINE_FONT) {
|
||||
data.handleGetContentResult(contentResolver).firstOrNull()?.first?.let { uri ->
|
||||
val file = saveTimelineFont(uri, "TimelineFont")
|
||||
data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let {
|
||||
val file = saveTimelineFont(it, "TimelineFont")
|
||||
if(file != null) {
|
||||
timeline_font = file.absolutePath
|
||||
saveUIToData()
|
||||
@ -927,8 +927,8 @@ class ActAppSetting : AppCompatActivity()
|
||||
}
|
||||
}
|
||||
} else if(resultCode == RESULT_OK && data != null && requestCode == REQUEST_CODE_TIMELINE_FONT_BOLD) {
|
||||
data.handleGetContentResult(contentResolver).firstOrNull()?.first?.let { uri ->
|
||||
val file = saveTimelineFont(uri, "TimelineFontBold")
|
||||
data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let {
|
||||
val file = saveTimelineFont(it, "TimelineFontBold")
|
||||
if(file != null) {
|
||||
timeline_font_bold = file.absolutePath
|
||||
saveUIToData()
|
||||
@ -936,12 +936,8 @@ class ActAppSetting : AppCompatActivity()
|
||||
}
|
||||
}
|
||||
} else if(resultCode == RESULT_OK && data != null && requestCode == REQUEST_CODE_APP_DATA_IMPORT) {
|
||||
data.handleGetContentResult(contentResolver).firstOrNull()?.first?.let { uri ->
|
||||
contentResolver.takePersistableUriPermission(
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
importAppData(false, uri)
|
||||
data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let {
|
||||
importAppData(false, it)
|
||||
}
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
@ -72,7 +72,6 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
||||
private lateinit var tvSampleAcct : TextView
|
||||
private lateinit var tvSampleContent : TextView
|
||||
|
||||
|
||||
internal var loading_busy : Boolean = false
|
||||
|
||||
private var last_image_uri : String? = null
|
||||
@ -108,9 +107,9 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
||||
}
|
||||
|
||||
override fun onClick(v : View) {
|
||||
|
||||
|
||||
val builder : ColorPickerDialog.Builder
|
||||
|
||||
|
||||
when(v.id) {
|
||||
|
||||
R.id.btnHeaderBackgroundEdit -> {
|
||||
@ -227,9 +226,8 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
||||
|
||||
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
|
||||
if(requestCode == REQUEST_CODE_PICK_BACKGROUND && data != null && resultCode == RESULT_OK) {
|
||||
data.handleGetContentResult(contentResolver).firstOrNull()?.let { pair ->
|
||||
updateBackground(pair.first)
|
||||
}
|
||||
data.handleGetContentResult(contentResolver).firstOrNull()
|
||||
?.uri?.let { updateBackground(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -428,7 +426,7 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
||||
|
||||
loadImage(ivColumnBackground, column.column_bg_image)
|
||||
|
||||
tvSampleAcct.setTextColor( column.getAcctColor())
|
||||
tvSampleAcct.setTextColor(column.getAcctColor())
|
||||
tvSampleContent.setTextColor(column.getContentColor())
|
||||
|
||||
} finally {
|
||||
|
@ -9,6 +9,7 @@ import android.content.ContentValues
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.database.Cursor
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.AsyncTask
|
||||
@ -53,6 +54,8 @@ import org.json.JSONObject
|
||||
import java.io.*
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callback {
|
||||
|
||||
@ -334,15 +337,12 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
|
||||
|
||||
if(requestCode == REQUEST_CODE_ATTACHMENT_OLD && resultCode == Activity.RESULT_OK) {
|
||||
data?.handleGetContentResult(contentResolver)?.forEach {
|
||||
addAttachment(it.first, it.second)
|
||||
}
|
||||
checkAttachments(data?.handleGetContentResult(contentResolver))
|
||||
|
||||
} else if(requestCode == REQUEST_CODE_ATTACHMENT && resultCode == Activity.RESULT_OK) {
|
||||
data?.handleGetContentResult(contentResolver)?.forEach {
|
||||
addAttachment(it.first, it.second)
|
||||
}
|
||||
checkAttachments(data?.handleGetContentResult(contentResolver))
|
||||
} else if(requestCode == REQUEST_CODE_CAMERA) {
|
||||
|
||||
if(resultCode != Activity.RESULT_OK) {
|
||||
@ -466,19 +466,16 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
this.attachment_list.clear()
|
||||
|
||||
try {
|
||||
val array = sv.toJsonArray()
|
||||
for(i in 0 until array.length()) {
|
||||
sv.toJsonArray().forEach {
|
||||
if(it !is JSONObject) return@forEach
|
||||
try {
|
||||
val a = parseItem(
|
||||
::TootAttachment,
|
||||
ServiceType.MASTODON,
|
||||
array.optJSONObject(i)
|
||||
)
|
||||
if(a != null) attachment_list.add(PostAttachment(a))
|
||||
val a = TootAttachment.decodeJson(it)
|
||||
attachment_list.add(PostAttachment(a))
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
}
|
||||
attachment_list.sort()
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
@ -518,13 +515,13 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
Intent.ACTION_VIEW -> {
|
||||
val uri = sent_intent.data
|
||||
val type = sent_intent.type
|
||||
if(uri != null) addAttachment(uri, type)
|
||||
if(uri != null) addAttachment(uri,type)
|
||||
}
|
||||
|
||||
Intent.ACTION_SEND -> {
|
||||
val uri = sent_intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
val type = sent_intent.type
|
||||
if(uri != null) addAttachment(uri, type)
|
||||
if(uri != null) addAttachment(uri,type)
|
||||
}
|
||||
|
||||
Intent.ACTION_SEND_MULTIPLE -> {
|
||||
@ -547,18 +544,18 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
if(sv != null && account != null) {
|
||||
try {
|
||||
val reply_status = TootParser(this@ActPost, account).status(sv.toJsonObject())
|
||||
|
||||
|
||||
val isQuoterRenote = intent.getBooleanExtra(KEY_QUOTED_RENOTE, false)
|
||||
|
||||
|
||||
if(reply_status != null) {
|
||||
|
||||
if(isQuoterRenote) {
|
||||
cbQuoteRenote.isChecked = true
|
||||
|
||||
|
||||
// 引用リノートはCWやメンションを引き継がない
|
||||
|
||||
}else{
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
// CW をリプライ元に合わせる
|
||||
if(reply_status.spoiler_text?.isNotEmpty() == true) {
|
||||
cbContentWarning.isChecked = true
|
||||
@ -661,6 +658,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
this.attachment_list.add(pa)
|
||||
}
|
||||
}
|
||||
this.attachment_list.sort()
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
@ -737,9 +735,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
|
||||
override fun onDestroy() {
|
||||
post_helper.onDestroy()
|
||||
|
||||
attachment_worker?.cancel()
|
||||
super.onDestroy()
|
||||
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState : Bundle?) {
|
||||
@ -767,10 +764,10 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
if(! attachment_list.isEmpty()) {
|
||||
val array = JSONArray()
|
||||
for(pa in attachment_list) {
|
||||
if(pa.status == PostAttachment.STATUS_UPLOADED) {
|
||||
// アップロード完了したものだけ保持する
|
||||
array.put(pa.attachment?.json)
|
||||
}
|
||||
// アップロード完了したものだけ保持する
|
||||
if(pa.status != PostAttachment.STATUS_UPLOADED) continue
|
||||
val json = pa.attachment?.encodeJson() ?: continue
|
||||
array.put(json)
|
||||
}
|
||||
outState.putString(KEY_ATTACHMENT_LIST, array.toString())
|
||||
}
|
||||
@ -1237,12 +1234,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
llAttachment.visibility = View.GONE
|
||||
} else {
|
||||
llAttachment.visibility = View.VISIBLE
|
||||
var i = 0
|
||||
val ie = ivMedia.size
|
||||
while(i < ie) {
|
||||
showAttachment_sub(ivMedia[i], i)
|
||||
++ i
|
||||
}
|
||||
ivMedia.forEachIndexed { i,v ->showAttachment_sub(v, i) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1600,6 +1592,73 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
return null
|
||||
}
|
||||
|
||||
private fun checkAttachments(srcList : ArrayList<GetContentResultEntry>?) {
|
||||
srcList ?: return
|
||||
|
||||
fun Cursor.optLong(name : String) : Long? {
|
||||
val idx = getColumnIndex(name)
|
||||
val v = if(idx < 0) null else when(getType(idx)) {
|
||||
Cursor.FIELD_TYPE_INTEGER -> getLong(idx)
|
||||
Cursor.FIELD_TYPE_FLOAT -> getDouble(idx).toLong()
|
||||
Cursor.FIELD_TYPE_STRING -> try {
|
||||
getString(idx).toLong()
|
||||
} catch(ex : Throwable) {
|
||||
null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
return if(v == 0L) null else v
|
||||
}
|
||||
|
||||
var countHasTime = 0
|
||||
|
||||
srcList.forEachIndexed { i, it ->
|
||||
contentResolver.query(it.uri, null, null, null, null)?.use { cursor ->
|
||||
if(! cursor.moveToNext()) return@forEachIndexed
|
||||
for(name in cursor.columnNames.sorted()) {
|
||||
val idx = cursor.getColumnIndex(name)
|
||||
when(cursor.getType(idx)) {
|
||||
Cursor.FIELD_TYPE_NULL -> log.d("[$i]$name=null")
|
||||
Cursor.FIELD_TYPE_BLOB -> log.d("[$i]$name=(blob)")
|
||||
Cursor.FIELD_TYPE_STRING -> log.d("[$i]$name=${cursor.getString(idx)}")
|
||||
Cursor.FIELD_TYPE_INTEGER -> log.d("[$i]$name=(integer)${cursor.getLong(idx)}")
|
||||
Cursor.FIELD_TYPE_FLOAT -> log.d("[$i]$name=(float)${cursor.getDouble(idx)}")
|
||||
}
|
||||
}
|
||||
val t = cursor.optLong("date_modified")
|
||||
?: cursor.optLong("last_modified")
|
||||
?: cursor.optLong("date_added")
|
||||
if(t != null) {
|
||||
++ countHasTime
|
||||
it.time = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( countHasTime == srcList.size ) {
|
||||
srcList.sortBy { it.time }
|
||||
}
|
||||
|
||||
srcList.forEach {
|
||||
addAttachment(it.uri, it.mimeType)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AttachmentRequest(
|
||||
val account : SavedAccount,
|
||||
val pa : PostAttachment,
|
||||
val uri : Uri,
|
||||
val mimeType : String,
|
||||
|
||||
val onUploadEnd : () -> Unit
|
||||
)
|
||||
|
||||
val attachment_queue = ConcurrentLinkedQueue<AttachmentRequest>()
|
||||
private var attachment_worker: AttachmentWorker? = null
|
||||
var lastAttachmentAdd : Long = 0L
|
||||
var lastAttachmentComplete : Long = 0L
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private fun addAttachment(
|
||||
uri : Uri,
|
||||
@ -1631,53 +1690,153 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
|
||||
app_state.attachment_list = this.attachment_list
|
||||
|
||||
val pa = PostAttachment(this)
|
||||
val pa = PostAttachment( this)
|
||||
attachment_list.add(pa)
|
||||
attachment_list.sort()
|
||||
showMediaAttachment()
|
||||
showToast(this, false, R.string.attachment_uploading)
|
||||
|
||||
// アップロード開始トースト(連発しない)
|
||||
val now = System.currentTimeMillis()
|
||||
if(now - lastAttachmentAdd >= 5000L) {
|
||||
showToast(this, false, R.string.attachment_uploading)
|
||||
}
|
||||
lastAttachmentAdd = now
|
||||
|
||||
TootTaskRunner(this, TootTaskRunner.PROGRESS_NONE).run(account, object : TootTask {
|
||||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
if(mime_type.isEmpty()) {
|
||||
return TootApiResult("mime_type is empty.")
|
||||
// マストドンは添付メディアをID順に表示するため
|
||||
// 画像が複数ある場合は一つずつ処理する必要がある
|
||||
// 投稿画面ごとに1スレッドだけ作成してバックグラウンド処理を行う
|
||||
attachment_queue.add(AttachmentRequest(account,pa,uri,mime_type,onUploadEnd))
|
||||
val oldWorker = attachment_worker
|
||||
if( oldWorker == null || !oldWorker.isAlive || oldWorker.isInterrupted ){
|
||||
oldWorker?.cancel()
|
||||
attachment_worker = AttachmentWorker().apply{ start() }
|
||||
}else{
|
||||
oldWorker.notifyEx()
|
||||
}
|
||||
}
|
||||
|
||||
inner class AttachmentWorker : WorkerBase(){
|
||||
|
||||
private val isCancelled = AtomicBoolean(false)
|
||||
override fun cancel() {
|
||||
isCancelled.set(true)
|
||||
notifyEx()
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
try {
|
||||
while(! isCancelled.get()) {
|
||||
val item = attachment_queue.poll()
|
||||
if(item == null) {
|
||||
waitEx(86400)
|
||||
continue
|
||||
}
|
||||
val result = item.upload()
|
||||
handler.post {
|
||||
item.handleResult(result)
|
||||
}
|
||||
}
|
||||
}catch(ex:Throwable){
|
||||
log.trace(ex)
|
||||
log.e(ex,"AttachmentWorker")
|
||||
}
|
||||
}
|
||||
|
||||
private fun AttachmentRequest.upload():TootApiResult?{
|
||||
|
||||
if(mimeType.isEmpty()) {
|
||||
return TootApiResult("mime_type is empty.")
|
||||
}
|
||||
|
||||
try {
|
||||
val client = TootApiClient(this@ActPost,callback = object:TootApiCallback{
|
||||
override val isApiCancelled : Boolean
|
||||
get() = isCancelled.get()
|
||||
})
|
||||
|
||||
client.account = account
|
||||
|
||||
val opener = createOpener(uri, mimeType)
|
||||
|
||||
val media_size_max = when {
|
||||
mimeType.startsWith("video") -> {
|
||||
1000000 * Math.max(1, Pref.spMovieSizeMax.toInt(pref))
|
||||
}
|
||||
|
||||
else -> {
|
||||
1000000 * Math.max(1, Pref.spMediaSizeMax.toInt(pref))
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val opener = createOpener(uri, mime_type)
|
||||
|
||||
val media_size_max = when {
|
||||
mime_type.startsWith("video") -> {
|
||||
1000000 * Math.max(1, Pref.spMovieSizeMax.toInt(pref))
|
||||
}
|
||||
|
||||
else -> {
|
||||
1000000 * Math.max(1, Pref.spMediaSizeMax.toInt(pref))
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
if(account.isMisskey) {
|
||||
val multipart_builder = MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
|
||||
val apiKey = account.token_info?.parseString(TootApiClient.KEY_API_KEY_MISSKEY)
|
||||
if(apiKey?.isNotEmpty() == true) {
|
||||
multipart_builder.addFormDataPart("i", apiKey)
|
||||
}
|
||||
|
||||
|
||||
if(account.isMisskey) {
|
||||
val multipart_builder = MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
|
||||
val apiKey =
|
||||
account.token_info?.parseString(TootApiClient.KEY_API_KEY_MISSKEY)
|
||||
if(apiKey?.isNotEmpty() == true) {
|
||||
multipart_builder.addFormDataPart("i", apiKey)
|
||||
multipart_builder.addFormDataPart(
|
||||
"file", getDocumentName(contentResolver, uri), object : RequestBody() {
|
||||
override fun contentType() : MediaType? {
|
||||
return MediaType.parse(opener.mimeType)
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
multipart_builder.addFormDataPart(
|
||||
"file", getDocumentName(contentResolver, uri), object : RequestBody() {
|
||||
)
|
||||
|
||||
val request_builder = Request.Builder().post(multipart_builder.build())
|
||||
|
||||
val result = client.request("/api/drive/files/create", request_builder)
|
||||
|
||||
opener.deleteTempFile()
|
||||
onUploadEnd()
|
||||
|
||||
val jsonObject = result?.jsonObject
|
||||
if(jsonObject != null) {
|
||||
val a = parseItem(::TootAttachment, ServiceType.MISSKEY, jsonObject)
|
||||
if(a == null) {
|
||||
result.error = "TootAttachment.parse failed"
|
||||
} else {
|
||||
pa.attachment = a
|
||||
}
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
val multipart_body = MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart(
|
||||
"file",
|
||||
getDocumentName(contentResolver, uri),
|
||||
object : RequestBody() {
|
||||
override fun contentType() : MediaType? {
|
||||
return MediaType.parse(opener.mimeType)
|
||||
}
|
||||
@ -1700,94 +1859,47 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val request_builder = Request.Builder().post(multipart_builder.build())
|
||||
|
||||
val result = client.request("/api/drive/files/create", request_builder)
|
||||
|
||||
opener.deleteTempFile()
|
||||
onUploadEnd()
|
||||
|
||||
val jsonObject = result?.jsonObject
|
||||
if(jsonObject != null) {
|
||||
val a = parseItem(::TootAttachment, ServiceType.MISSKEY, jsonObject)
|
||||
if(a == null) {
|
||||
result.error = "TootAttachment.parse failed"
|
||||
} else {
|
||||
pa.attachment = a
|
||||
}
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
val multipart_body = MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart(
|
||||
"file",
|
||||
getDocumentName(contentResolver, uri),
|
||||
object : RequestBody() {
|
||||
override fun contentType() : MediaType? {
|
||||
return MediaType.parse(opener.mimeType)
|
||||
}
|
||||
|
||||
@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()
|
||||
|
||||
val request_builder = Request.Builder()
|
||||
.post(multipart_body)
|
||||
|
||||
val result = client.request("/api/v1/media", request_builder)
|
||||
|
||||
opener.deleteTempFile()
|
||||
onUploadEnd()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
.build()
|
||||
|
||||
} catch(ex : Throwable) {
|
||||
return TootApiResult(ex.withCaption("read failed."))
|
||||
val request_builder = Request.Builder()
|
||||
.post(multipart_body)
|
||||
|
||||
val result = client.request("/api/v1/media", request_builder)
|
||||
|
||||
opener.deleteTempFile()
|
||||
onUploadEnd()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
} catch(ex : Throwable) {
|
||||
return TootApiResult(ex.withCaption("read failed."))
|
||||
}
|
||||
|
||||
override fun handleResult(result : TootApiResult?) {
|
||||
if(pa.attachment == null) {
|
||||
pa.status = PostAttachment.STATUS_UPLOAD_FAILED
|
||||
if(result != null) {
|
||||
showToast(this@ActPost, true, result.error)
|
||||
}
|
||||
} else {
|
||||
pa.status = PostAttachment.STATUS_UPLOADED
|
||||
}
|
||||
|
||||
fun AttachmentRequest.handleResult(result : TootApiResult?) {
|
||||
|
||||
if(pa.attachment == null) {
|
||||
pa.status = PostAttachment.STATUS_UPLOAD_FAILED
|
||||
if(result != null) {
|
||||
showToast(this@ActPost, true, result.error)
|
||||
}
|
||||
// 投稿中に画面回転があった場合、新しい画面のコールバックを呼び出す必要がある
|
||||
pa.callback?.onPostAttachmentComplete(pa)
|
||||
} else {
|
||||
pa.status = PostAttachment.STATUS_UPLOADED
|
||||
}
|
||||
})
|
||||
// 投稿中に画面回転があった場合、新しい画面のコールバックを呼び出す必要がある
|
||||
pa.callback?.onPostAttachmentComplete(pa)
|
||||
}
|
||||
}
|
||||
|
||||
// 添付メディア投稿が完了したら呼ばれる
|
||||
@ -1808,7 +1920,12 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
val a = pa.attachment
|
||||
if(a != null) {
|
||||
// アップロード完了
|
||||
showToast(this@ActPost, false, R.string.attachment_uploaded)
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
if(now - lastAttachmentComplete >= 5000L) {
|
||||
showToast(this@ActPost, false, R.string.attachment_uploaded)
|
||||
}
|
||||
lastAttachmentComplete = now
|
||||
|
||||
if(Pref.bpAppendAttachmentUrlToContent(pref)) {
|
||||
// 投稿欄の末尾に追記する
|
||||
@ -2093,8 +2210,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
try {
|
||||
val tmp_attachment_list = JSONArray()
|
||||
for(pa in attachment_list) {
|
||||
val a = pa.attachment
|
||||
if(a != null) tmp_attachment_list.put(a.json)
|
||||
val json = pa.attachment?.encodeJson()
|
||||
if(json != null) tmp_attachment_list.put(json)
|
||||
}
|
||||
|
||||
val json = JSONObject()
|
||||
@ -2148,34 +2265,28 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
|
||||
var content = draft.parseString(DRAFT_CONTENT) ?: ""
|
||||
val account_db_id = draft.parseLong(DRAFT_ACCOUNT_DB_ID) ?: - 1L
|
||||
var tmp_attachment_list = draft.optJSONArray(DRAFT_ATTACHMENT_LIST)
|
||||
val tmp_attachment_list = draft.optJSONArray(DRAFT_ATTACHMENT_LIST)?.toObjectList()
|
||||
|
||||
val account = SavedAccount.loadAccount(this@ActPost, account_db_id)
|
||||
if(account == null) {
|
||||
list_warning.add(getString(R.string.account_in_draft_is_lost))
|
||||
try {
|
||||
var i = 0
|
||||
val ie = tmp_attachment_list.length()
|
||||
while(i < ie) {
|
||||
val ta =
|
||||
parseItem(
|
||||
::TootAttachment,
|
||||
ServiceType.MASTODON,
|
||||
tmp_attachment_list.optJSONObject(i)
|
||||
)
|
||||
val text_url = ta?.text_url
|
||||
if(text_url?.isNotEmpty() == true) {
|
||||
content = content.replace(text_url, "")
|
||||
if(tmp_attachment_list != null) {
|
||||
// 本文からURLを除去する
|
||||
tmp_attachment_list.forEach {
|
||||
val text_url = TootAttachment.decodeJson(it).text_url
|
||||
if(text_url?.isNotEmpty() == true) {
|
||||
content = content.replace(text_url, "")
|
||||
}
|
||||
}
|
||||
++ i
|
||||
tmp_attachment_list.clear()
|
||||
draft.put(DRAFT_ATTACHMENT_LIST, tmp_attachment_list.toJsonArray())
|
||||
draft.put(DRAFT_CONTENT, content)
|
||||
draft.remove(DRAFT_REPLY_ID)
|
||||
draft.remove(DRAFT_REPLY_TEXT)
|
||||
draft.remove(DRAFT_REPLY_IMAGE)
|
||||
draft.remove(DRAFT_REPLY_URL)
|
||||
}
|
||||
tmp_attachment_list = JSONArray()
|
||||
draft.put(DRAFT_ATTACHMENT_LIST, tmp_attachment_list)
|
||||
draft.put(DRAFT_CONTENT, content)
|
||||
draft.remove(DRAFT_REPLY_ID)
|
||||
draft.remove(DRAFT_REPLY_TEXT)
|
||||
draft.remove(DRAFT_REPLY_IMAGE)
|
||||
draft.remove(DRAFT_REPLY_URL)
|
||||
} catch(ignored : JSONException) {
|
||||
}
|
||||
|
||||
@ -2208,30 +2319,27 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
}
|
||||
}
|
||||
try {
|
||||
var isSomeAttachmentRemoved = false
|
||||
for(i in tmp_attachment_list.length() - 1 downTo 0) {
|
||||
if(isCancelled) return null
|
||||
val ta = parseItem(
|
||||
::TootAttachment,
|
||||
ServiceType.MASTODON,
|
||||
tmp_attachment_list.optJSONObject(i)
|
||||
)
|
||||
if(ta == null) {
|
||||
if(tmp_attachment_list != null) {
|
||||
// 添付メディアの存在確認
|
||||
var isSomeAttachmentRemoved = false
|
||||
val it = tmp_attachment_list.iterator()
|
||||
while(it.hasNext()) {
|
||||
if(isCancelled) return null
|
||||
val ta = TootAttachment.decodeJson(it.next())
|
||||
if(check_exist(ta.url)) continue
|
||||
it.remove()
|
||||
isSomeAttachmentRemoved = true
|
||||
tmp_attachment_list.remove(i)
|
||||
} else if(! check_exist(ta.url)) {
|
||||
isSomeAttachmentRemoved = true
|
||||
tmp_attachment_list.remove(i)
|
||||
// 本文からURLを除去する
|
||||
val text_url = ta.text_url
|
||||
if(text_url?.isNotEmpty() == true) {
|
||||
content = content.replace(text_url, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
if(isSomeAttachmentRemoved) {
|
||||
list_warning.add(getString(R.string.attachment_in_draft_is_lost))
|
||||
draft.put(DRAFT_ATTACHMENT_LIST, tmp_attachment_list)
|
||||
draft.put(DRAFT_CONTENT, content)
|
||||
if(isSomeAttachmentRemoved) {
|
||||
list_warning.add(getString(R.string.attachment_in_draft_is_lost))
|
||||
draft.put(DRAFT_ATTACHMENT_LIST, tmp_attachment_list.toJsonArray())
|
||||
draft.put(DRAFT_CONTENT, content)
|
||||
}
|
||||
}
|
||||
} catch(ex : JSONException) {
|
||||
log.trace(ex)
|
||||
@ -2292,21 +2400,14 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
|
||||
if(tmp_attachment_list.length() > 0) {
|
||||
attachment_list.clear()
|
||||
var i = 0
|
||||
val ie = tmp_attachment_list.length()
|
||||
while(i < ie) {
|
||||
val ta = parseItem(
|
||||
::TootAttachment,
|
||||
ServiceType.MASTODON,
|
||||
tmp_attachment_list.optJSONObject(i)
|
||||
)
|
||||
if(ta != null) {
|
||||
val pa = PostAttachment(ta)
|
||||
attachment_list.add(pa)
|
||||
}
|
||||
++ i
|
||||
tmp_attachment_list.forEach {
|
||||
if(it !is JSONObject) return@forEach
|
||||
val pa = PostAttachment(TootAttachment.decodeJson(it))
|
||||
attachment_list.add(pa)
|
||||
}
|
||||
attachment_list.sort()
|
||||
}
|
||||
|
||||
if(reply_id != null) {
|
||||
in_reply_to_id = reply_id
|
||||
in_reply_to_text = reply_text
|
||||
@ -2314,7 +2415,6 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
in_reply_to_url = reply_url
|
||||
}
|
||||
|
||||
|
||||
updateContentWarning()
|
||||
showMediaAttachment()
|
||||
showVisibility()
|
||||
|
@ -205,13 +205,12 @@ inline fun <reified T> parseListOrNull(
|
||||
|
||||
fun <T : TootAttachmentLike> ArrayList<T>.encodeJson() : JSONArray {
|
||||
val a = JSONArray()
|
||||
for(ta in this) {
|
||||
if(ta is TootAttachment) {
|
||||
try {
|
||||
a.put(ta.json)
|
||||
} catch(ex : JSONException) {
|
||||
EntityUtil.log.e(ex, "encode failed.")
|
||||
}
|
||||
forEach { ta->
|
||||
if(ta !is TootAttachment) return@forEach
|
||||
try {
|
||||
a.put(ta.encodeJson())
|
||||
} catch(ex : JSONException) {
|
||||
EntityUtil.log.e(ex, "encode failed.")
|
||||
}
|
||||
}
|
||||
return a
|
||||
|
@ -27,7 +27,7 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
|
||||
|
||||
// host, user ,(instance)
|
||||
internal val reAccountUrl : Pattern =
|
||||
Pattern.compile("\\Ahttps://([A-Za-z0-9._-]+)/@([A-Za-z0-9_]+)(?:@([A-Za-z0-9._-]+))?(?:\\z|[?#])")
|
||||
Pattern.compile("""\Ahttps://([A-Za-z0-9._-]+)/@([A-Za-z0-9_][A-Za-z0-9_-]+)(?:@([A-Za-z0-9._-]+))?(?:\z|[?#])""")
|
||||
|
||||
fun getAcctFromUrl(url : String) : String? {
|
||||
val m = reAccountUrl.matcher(url)
|
||||
|
@ -7,10 +7,11 @@ import org.json.JSONObject
|
||||
import jp.juggler.subwaytooter.Pref
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
import jp.juggler.subwaytooter.util.clipRange
|
||||
import jp.juggler.subwaytooter.util.jsonObject
|
||||
import jp.juggler.subwaytooter.util.parseLong
|
||||
import jp.juggler.subwaytooter.util.parseString
|
||||
|
||||
class TootAttachment(serviceType:ServiceType,src : JSONObject) : TootAttachmentLike {
|
||||
class TootAttachment : TootAttachmentLike {
|
||||
|
||||
companion object {
|
||||
private fun parseFocusValue(parent : JSONObject?, key : String) : Float {
|
||||
@ -19,13 +20,30 @@ class TootAttachment(serviceType:ServiceType,src : JSONObject) : TootAttachmentL
|
||||
if(dv.isFinite()) return clipRange(- 1f, 1f, dv.toFloat())
|
||||
}
|
||||
return 0f
|
||||
|
||||
}
|
||||
|
||||
// 下書きからの復元などに使うパラメータ
|
||||
// 後方互換性的な理由で概ねマストドンに合わせている
|
||||
private const val KEY_IS_STRING_ID = "isStringId"
|
||||
private const val KEY_ID = "id"
|
||||
private const val KEY_TYPE = "type"
|
||||
private const val KEY_URL = "url"
|
||||
private const val KEY_REMOTE_URL = "remote_url"
|
||||
private const val KEY_PREVIEW_URL = "preview_url"
|
||||
private const val KEY_TEXT_URL = "text_url"
|
||||
private const val KEY_DESCRIPTION = "description"
|
||||
private const val KEY_IS_SENSITIVE = "isSensitive"
|
||||
private const val KEY_META = "meta"
|
||||
private const val KEY_FOCUS = "focus"
|
||||
private const val KEY_X = "x"
|
||||
private const val KEY_Y = "y"
|
||||
private const val KEY_TIME_START_UPLOAD = "KEY_TIME_START_UPLOAD"
|
||||
var timeSeed : Long = 0L
|
||||
|
||||
fun decodeJson(src : JSONObject) = TootAttachment(src, decode = true)
|
||||
}
|
||||
|
||||
constructor(parser:TootParser,src:JSONObject):this(parser.serviceType,src)
|
||||
|
||||
val json : JSONObject
|
||||
constructor(parser : TootParser, src : JSONObject) : this(parser.serviceType, src)
|
||||
|
||||
// ID of the attachment
|
||||
val id : EntityId
|
||||
@ -52,10 +70,12 @@ class TootAttachment(serviceType:ServiceType,src : JSONObject) : TootAttachmentL
|
||||
override val focusY : Float
|
||||
|
||||
// 内部フラグ: 再編集で引き継いだ添付メディアなら真
|
||||
var redraft :Boolean = false
|
||||
var redraft : Boolean = false
|
||||
|
||||
// MisskeyはメディアごとにNSFWフラグがある
|
||||
val isSensitive :Boolean
|
||||
val isSensitive : Boolean
|
||||
|
||||
var timeStartUpload : Long = 0L
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
@ -64,39 +84,40 @@ class TootAttachment(serviceType:ServiceType,src : JSONObject) : TootAttachmentL
|
||||
else -> false
|
||||
}
|
||||
|
||||
init {
|
||||
json = src
|
||||
constructor(serviceType : ServiceType, src : JSONObject) {
|
||||
|
||||
when(serviceType) {
|
||||
ServiceType.MISSKEY -> {
|
||||
id = EntityId.mayDefault( src.parseString("id"))
|
||||
id = EntityId.mayDefault(src.parseString("id"))
|
||||
|
||||
val mimeType = src.parseString("type") ?: "?"
|
||||
this.type = when{
|
||||
mimeType.startsWith("image/") -> TootAttachmentLike.TYPE_IMAGE
|
||||
mimeType.startsWith("video/") -> TootAttachmentLike.TYPE_VIDEO
|
||||
mimeType.startsWith("audio/") -> TootAttachmentLike.TYPE_VIDEO
|
||||
else-> TootAttachmentLike.TYPE_UNKNOWN
|
||||
val mimeType = src.parseString("type") ?: "?"
|
||||
this.type = when {
|
||||
mimeType.startsWith("image/") -> TootAttachmentLike.TYPE_IMAGE
|
||||
mimeType.startsWith("video/") -> TootAttachmentLike.TYPE_VIDEO
|
||||
mimeType.startsWith("audio/") -> TootAttachmentLike.TYPE_VIDEO
|
||||
else -> TootAttachmentLike.TYPE_UNKNOWN
|
||||
}
|
||||
|
||||
url = src.parseString("url")
|
||||
preview_url = src.parseString("thumbnailUrl")
|
||||
remote_url = url
|
||||
text_url = url
|
||||
|
||||
|
||||
description = arrayOf(
|
||||
src.parseString("name"),
|
||||
src.parseString("comment")
|
||||
)
|
||||
.filterNotNull()
|
||||
.joinToString(" / ")
|
||||
|
||||
|
||||
focusX = 0f
|
||||
focusY = 0f
|
||||
isSensitive = src.optBoolean("isSensitive",false)
|
||||
isSensitive = src.optBoolean("isSensitive", false)
|
||||
|
||||
}
|
||||
else->{
|
||||
id= EntityId.mayDefault(src.parseLong("id") )
|
||||
|
||||
else -> {
|
||||
id = EntityId.mayDefault(src.parseLong("id"))
|
||||
type = src.parseString("type")
|
||||
url = src.parseString("url")
|
||||
remote_url = src.parseString("remote_url")
|
||||
@ -110,6 +131,10 @@ class TootAttachment(serviceType:ServiceType,src : JSONObject) : TootAttachmentL
|
||||
focusY = parseFocusValue(focus, "y")
|
||||
}
|
||||
}
|
||||
|
||||
// internal use
|
||||
// muse be > 0
|
||||
this.timeStartUpload = src.parseLong(KEY_TIME_START_UPLOAD) ?: ++ timeSeed
|
||||
}
|
||||
|
||||
override val urlForThumbnail : String?
|
||||
@ -139,6 +164,52 @@ class TootAttachment(serviceType:ServiceType,src : JSONObject) : TootAttachmentL
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun encodeJson() = jsonObject {
|
||||
put(KEY_IS_STRING_ID, id is EntityIdString)
|
||||
put(KEY_ID, id.toString())
|
||||
put(KEY_TYPE, type)
|
||||
put(KEY_URL, url)
|
||||
put(KEY_REMOTE_URL, remote_url)
|
||||
put(KEY_PREVIEW_URL, preview_url)
|
||||
put(KEY_TEXT_URL, text_url)
|
||||
put(KEY_DESCRIPTION, description)
|
||||
put(KEY_IS_SENSITIVE, isSensitive)
|
||||
put(KEY_TIME_START_UPLOAD, timeStartUpload)
|
||||
|
||||
if(focusX != 0f || focusY != 0f) {
|
||||
put(KEY_META, jsonObject {
|
||||
put(KEY_FOCUS, jsonObject {
|
||||
put(KEY_X, focusX)
|
||||
put(KEY_Y, focusY)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
constructor(src : JSONObject, decode : Boolean) {
|
||||
|
||||
id = if( src.optBoolean(KEY_IS_STRING_ID) ) {
|
||||
EntityId.mayDefault(src.parseString(KEY_ID))
|
||||
} else {
|
||||
EntityId.mayDefault(src.parseLong(KEY_ID))
|
||||
}
|
||||
|
||||
type = src.parseString(KEY_TYPE)
|
||||
url = src.parseString(KEY_URL)
|
||||
remote_url = src.parseString(KEY_REMOTE_URL)
|
||||
preview_url = src.parseString(KEY_PREVIEW_URL)
|
||||
text_url = src.parseString(KEY_TEXT_URL)
|
||||
description = src.parseString(KEY_DESCRIPTION)
|
||||
isSensitive = src.optBoolean(KEY_IS_SENSITIVE)
|
||||
|
||||
val focus = src.optJSONObject(KEY_META)?.optJSONObject(KEY_FOCUS)
|
||||
focusX = parseFocusValue(focus, KEY_X)
|
||||
focusY = parseFocusValue(focus, KEY_Y)
|
||||
|
||||
timeStartUpload = src.parseLong(KEY_TIME_START_UPLOAD) ?: ++ timeSeed
|
||||
}
|
||||
}
|
||||
|
||||
// v1.3 から 添付ファイルの画像のピクセルサイズが取得できるようになった
|
||||
|
@ -56,11 +56,11 @@ fun createResizedBitmap(
|
||||
}
|
||||
|
||||
val resize_to = Math.min(size, resizeToArg)
|
||||
|
||||
|
||||
// inSampleSizeを計算
|
||||
var bits = 0
|
||||
var x = size
|
||||
while(x > resize_to * 2) {
|
||||
while(resize_to > 0 && x > resize_to * 2) {
|
||||
++ bits
|
||||
x = x shr 1
|
||||
}
|
||||
@ -121,7 +121,6 @@ fun createResizedBitmap(
|
||||
|
||||
6 -> {
|
||||
tmp = dst_width
|
||||
|
||||
dst_width = dst_height
|
||||
dst_height = tmp
|
||||
matrix.postRotate(90f)
|
||||
|
@ -2,8 +2,8 @@ package jp.juggler.subwaytooter.util
|
||||
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment
|
||||
|
||||
class PostAttachment {
|
||||
|
||||
class PostAttachment : Comparable<PostAttachment>{
|
||||
|
||||
companion object {
|
||||
const val STATUS_UPLOADING = 1
|
||||
const val STATUS_UPLOADED = 2
|
||||
@ -16,10 +16,9 @@ class PostAttachment {
|
||||
|
||||
var status : Int
|
||||
var attachment : TootAttachment? = null
|
||||
|
||||
var callback : Callback? = null
|
||||
|
||||
|
||||
|
||||
|
||||
constructor(callback : Callback) {
|
||||
this.status = STATUS_UPLOADING
|
||||
this.callback = callback
|
||||
@ -30,4 +29,13 @@ class PostAttachment {
|
||||
this.attachment = a
|
||||
}
|
||||
|
||||
override fun compareTo(other : PostAttachment) : Int {
|
||||
val ta = this.attachment
|
||||
val tb = other.attachment
|
||||
return if(ta != null ){
|
||||
if( tb == null) 1 else ta.id.compareTo(tb.id)
|
||||
}else{
|
||||
if( tb == null) 0 else -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -444,6 +444,8 @@ class PostHelper(
|
||||
val a = pa.attachment ?: continue
|
||||
if(a.redraft && ! instance.versionGE(TootInstance.VERSION_2_4_1)) continue
|
||||
array.put(a.id)
|
||||
|
||||
log.d("media_ids id=${a.id} time=${pa.timeStartUpload}")
|
||||
}
|
||||
json.put("media_ids", array)
|
||||
}
|
||||
|
@ -349,14 +349,14 @@ fun ByteArray.digestSHA256() : ByteArray {
|
||||
}
|
||||
|
||||
fun ByteArray.startWith(
|
||||
key:ByteArray,
|
||||
thisOffset :Int =0,
|
||||
keyOffset:Int=0,
|
||||
length:Int = key.size-keyOffset
|
||||
):Boolean{
|
||||
if( this.size -thisOffset >= length && key.size -keyOffset >=length ){
|
||||
for( i in 0 until length ){
|
||||
if( this[i+thisOffset] != key[i+keyOffset]) return false
|
||||
key : ByteArray,
|
||||
thisOffset : Int = 0,
|
||||
keyOffset : Int = 0,
|
||||
length : Int = key.size - keyOffset
|
||||
) : Boolean {
|
||||
if(this.size - thisOffset >= length && key.size - keyOffset >= length) {
|
||||
for(i in 0 until length) {
|
||||
if(this[i + thisOffset] != key[i + keyOffset]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -364,24 +364,23 @@ fun ByteArray.startWith(
|
||||
}
|
||||
|
||||
// 各要素の下位8ビットを使ってバイト配列を作る
|
||||
fun IntArray.toByteArray():ByteArray{
|
||||
fun IntArray.toByteArray() : ByteArray {
|
||||
val dst = ByteArray(this.size)
|
||||
for(i in 0 until this.size){
|
||||
for(i in 0 until this.size) {
|
||||
dst[i] = this[i].toByte()
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// 各要素の下位8ビットを使ってバイト配列を作る
|
||||
fun CharArray.toByteArray():ByteArray{
|
||||
fun CharArray.toByteArray() : ByteArray {
|
||||
val dst = ByteArray(this.size)
|
||||
for(i in 0 until this.size){
|
||||
for(i in 0 until this.size) {
|
||||
dst[i] = this[i].toByte()
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
|
||||
//// MD5ハッシュの作成
|
||||
//@Suppress("unused")
|
||||
//fun String.digestMD5() : String {
|
||||
@ -398,9 +397,9 @@ fun String.digestSHA256Base64Url() : String {
|
||||
return this.encodeUTF8().digestSHA256().encodeBase64Url()
|
||||
}
|
||||
|
||||
fun String.toUri():Uri = Uri.parse(this)
|
||||
fun String.toUri() : Uri = Uri.parse(this)
|
||||
|
||||
fun String.unescapeUri():String = Uri.decode(this)
|
||||
fun String.unescapeUri() : String = Uri.decode(this)
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// CharSequence
|
||||
@ -485,10 +484,10 @@ fun String?.optInt() : Int? {
|
||||
}
|
||||
}
|
||||
|
||||
fun String?.filterNotEmpty() :String? = when{
|
||||
this==null -> null
|
||||
fun String?.filterNotEmpty() : String? = when {
|
||||
this == null -> null
|
||||
this.isEmpty() -> null
|
||||
else->this
|
||||
else -> this
|
||||
}
|
||||
|
||||
//fun String.ellipsize(max : Int) = if(this.length > max) this.substring(0, max - 1) + "…" else this
|
||||
@ -675,6 +674,24 @@ inline fun JSONArray.downForEachIndexed(block : (i : Int, v : Any?) -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
fun JSONArray.toAnyList() : ArrayList<Any> {
|
||||
val dst_list = ArrayList<Any>(length())
|
||||
forEach { if(it != null) dst_list.add(it) }
|
||||
return dst_list
|
||||
}
|
||||
|
||||
fun JSONArray.toObjectList() : ArrayList<JSONObject> {
|
||||
val dst_list = ArrayList<JSONObject>(length())
|
||||
forEach { if(it is JSONObject) dst_list.add(it) }
|
||||
return dst_list
|
||||
}
|
||||
|
||||
fun List<JSONObject>.toJsonArray() : JSONArray {
|
||||
val dst_list = JSONArray()
|
||||
forEach { dst_list.put(it) }
|
||||
return dst_list
|
||||
}
|
||||
|
||||
fun JSONArray.toStringArrayList() : ArrayList<String> {
|
||||
val dst_list = ArrayList<String>(length())
|
||||
forEach { o ->
|
||||
@ -706,6 +723,7 @@ fun JSONObject.parseFloatArrayList(name : String) : ArrayList<Float>? {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun String.toJsonObject() = JSONObject(this)
|
||||
fun String.toJsonArray() = JSONArray(this)
|
||||
|
||||
@ -782,6 +800,12 @@ fun JSONObject.parseInt(key : String) : Int? {
|
||||
fun JSONObject.toPostRequestBuilder() : Request.Builder =
|
||||
Request.Builder().post(RequestBody.create(TootApiClient.MEDIA_TYPE_JSON, this.toString()))
|
||||
|
||||
fun jsonObject(initializer : JSONObject.() -> Unit) : JSONObject {
|
||||
val dst = JSONObject()
|
||||
dst.initializer()
|
||||
return dst
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Bundle
|
||||
|
||||
@ -881,20 +905,19 @@ fun vg(v : View, visible : Boolean) {
|
||||
v.visibility = if(visible) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
fun Float.abs() :Float = Math.abs(this)
|
||||
fun Float.abs() : Float = Math.abs(this)
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// context
|
||||
|
||||
fun Context.loadRawResource( resId:Int):ByteArray{
|
||||
resources.openRawResource(resId).use{ inStream->
|
||||
val bao = ByteArrayOutputStream( inStream.available() )
|
||||
IOUtils.copy(inStream,bao)
|
||||
fun Context.loadRawResource(resId : Int) : ByteArray {
|
||||
resources.openRawResource(resId).use { inStream ->
|
||||
val bao = ByteArrayOutputStream(inStream.available())
|
||||
IOUtils.copy(inStream, bao)
|
||||
return bao.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// file
|
||||
|
||||
@ -936,17 +959,16 @@ fun showToast(context : Context, ex : Throwable, string_id : Int, vararg args :
|
||||
Utils.showToastImpl(context, true, ex.withCaption(context.resources, string_id, *args))
|
||||
}
|
||||
|
||||
|
||||
fun Cursor.getInt(key:String) =
|
||||
fun Cursor.getInt(key : String) =
|
||||
getInt(getColumnIndex(key))
|
||||
|
||||
fun Cursor.getIntOrNull(idx:Int) =
|
||||
fun Cursor.getIntOrNull(idx : Int) =
|
||||
if(isNull(idx)) null else getInt(idx)
|
||||
|
||||
fun Cursor.getIntOrNull(key:String) =
|
||||
fun Cursor.getIntOrNull(key : String) =
|
||||
getIntOrNull(getColumnIndex(key))
|
||||
|
||||
fun Cursor.getLong(key:String) =
|
||||
fun Cursor.getLong(key : String) =
|
||||
getLong(getColumnIndex(key))
|
||||
|
||||
//fun Cursor.getLongOrNull(idx:Int) =
|
||||
@ -955,18 +977,16 @@ fun Cursor.getLong(key:String) =
|
||||
//fun Cursor.getLongOrNull(key:String) =
|
||||
// getLongOrNull(getColumnIndex(key))
|
||||
|
||||
fun Cursor.getString(key:String) :String =
|
||||
fun Cursor.getString(key : String) : String =
|
||||
getString(getColumnIndex(key))
|
||||
|
||||
fun Cursor.getStringOrNull(keyIdx:Int) =
|
||||
fun Cursor.getStringOrNull(keyIdx : Int) =
|
||||
if(isNull(keyIdx)) null else getString(keyIdx)
|
||||
|
||||
fun Cursor.getStringOrNull(key:String) =
|
||||
fun Cursor.getStringOrNull(key : String) =
|
||||
getStringOrNull(getColumnIndex(key))
|
||||
|
||||
|
||||
|
||||
fun getDocumentName(contentResolver:ContentResolver,uri : Uri) : String {
|
||||
fun getDocumentName(contentResolver : ContentResolver, uri : Uri) : String {
|
||||
val errorName = "no_name"
|
||||
return contentResolver.query(uri, null, null, null, null, null)
|
||||
?.use { cursor ->
|
||||
@ -995,7 +1015,7 @@ fun getStreamSize(bClose : Boolean, inStream : InputStream) : Long {
|
||||
}
|
||||
}
|
||||
|
||||
fun intentOpenDocument(mimeType : String):Intent{
|
||||
fun intentOpenDocument(mimeType : String) : Intent {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
intent.type = mimeType // "image/*"
|
||||
@ -1018,63 +1038,69 @@ fun intentGetContent(
|
||||
// EXTRA_MIME_TYPES は API 19以降。ACTION_GET_CONTENT でも ACTION_OPEN_DOCUMENT でも指定できる
|
||||
intent.putExtra("android.intent.extra.MIME_TYPES", mimeTypes)
|
||||
|
||||
intent.type =when {
|
||||
mimeTypes.size == 1 -> mimeTypes[0]
|
||||
|
||||
intent.type = when {
|
||||
mimeTypes.size == 1 -> mimeTypes[0]
|
||||
|
||||
// On Android 6.0 and above using "video/* image/" or "image/ video/*" type doesn't work
|
||||
// it only recognizes the first filter you specify.
|
||||
Build.VERSION.SDK_INT >= 23 -> "*/*"
|
||||
|
||||
|
||||
else -> mimeTypes.joinToString(" ")
|
||||
}
|
||||
|
||||
return Intent.createChooser(intent, caption)
|
||||
}
|
||||
|
||||
data class GetContentResultEntry(
|
||||
val uri:Uri,
|
||||
val mimeType:String? =null,
|
||||
var time : Long? =null
|
||||
)
|
||||
|
||||
// returns list of pair of uri and mime-type.
|
||||
fun Intent.handleGetContentResult(contentResolver : ContentResolver) : ArrayList<Pair<Uri, String?>> {
|
||||
val urlList = ArrayList<Pair<Uri, String?>>()
|
||||
fun Intent.handleGetContentResult(contentResolver : ContentResolver) : ArrayList<GetContentResultEntry> {
|
||||
val urlList = ArrayList<GetContentResultEntry>()
|
||||
// 単一選択
|
||||
this.data?.let {
|
||||
urlList.add(Pair(it, this.type))
|
||||
urlList.add(GetContentResultEntry(it, this.type))
|
||||
}
|
||||
// 複数選択
|
||||
val cd = this.clipData
|
||||
if(cd != null) {
|
||||
for(i in 0 until cd.itemCount) {
|
||||
cd.getItemAt(i)?.uri?.let { uri ->
|
||||
if(null == urlList.find { it.first == uri }) {
|
||||
urlList.add(Pair(uri, null as String?))
|
||||
if(null == urlList.find { it.uri == uri }) {
|
||||
urlList.add(GetContentResultEntry(uri ))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
urlList.forEach {
|
||||
try{
|
||||
try {
|
||||
contentResolver.takePersistableUriPermission(
|
||||
it.first,
|
||||
it.uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
}catch(_:Throwable){
|
||||
} catch(_ : Throwable) {
|
||||
}
|
||||
}
|
||||
return urlList
|
||||
}
|
||||
|
||||
fun Matcher.groupOrNull( g:Int):String? =
|
||||
if( groupCount() >= g ){
|
||||
fun Matcher.groupOrNull(g : Int) : String? =
|
||||
if(groupCount() >= g) {
|
||||
group(g)
|
||||
}else {
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// colorARGB.applyAlphaMultiplier(0.5f) でalpha値が半分になったARGB値を得る
|
||||
fun Int.applyAlphaMultiplier(alphaMultiplier: Float? = null):Int{
|
||||
return if( alphaMultiplier ==null ){
|
||||
fun Int.applyAlphaMultiplier(alphaMultiplier : Float? = null) : Int {
|
||||
return if(alphaMultiplier == null) {
|
||||
this
|
||||
}else{
|
||||
} else {
|
||||
val rgb = (this and 0xffffff)
|
||||
val alpha = clipRange(0,255,((this ushr 24).toFloat() * alphaMultiplier +0.5f ).toInt())
|
||||
val alpha = clipRange(0, 255, ((this ushr 24).toFloat() * alphaMultiplier + 0.5f).toInt())
|
||||
return rgb or (alpha shl 24)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user