添付メディアの説明文の入力時にサムネイル表示を行う。複数行入力に対応する。
This commit is contained in:
parent
2ebf63151a
commit
b6f30f0097
|
@ -1,6 +1,5 @@
|
|||
package jp.juggler.subwaytooter.action
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Build
|
||||
import jp.juggler.subwaytooter.*
|
||||
import jp.juggler.subwaytooter.actmain.addColumn
|
||||
|
@ -71,7 +70,7 @@ fun ActMain.accountAdd() {
|
|||
val tootInstance = runApiTask2(apiHost) { TootInstance.getOrThrow(it) }
|
||||
addPseudoAccount(apiHost, tootInstance)?.let { a ->
|
||||
showToast(false, R.string.server_confirmed)
|
||||
addColumn(defaultInsertPosition, a, ColumnType.LOCAL,protect=true)
|
||||
addColumn(defaultInsertPosition, a, ColumnType.LOCAL, protect = true)
|
||||
dialogHost.dismissSafe()
|
||||
}
|
||||
}
|
||||
|
@ -139,56 +138,49 @@ private suspend fun ActMain.createUser(
|
|||
* @param onComplete 非nullならアカウント認証が終わったタイミングで呼ばれる
|
||||
*/
|
||||
// アクセストークンの手動入力(更新)
|
||||
fun ActMain.accessTokenPrompt(
|
||||
suspend fun ActMain.accessTokenPrompt(
|
||||
apiHost: Host,
|
||||
onComplete: (() -> Unit)? = null,
|
||||
) {
|
||||
DlgTextInput.show(
|
||||
this,
|
||||
getString(R.string.access_token_or_api_token),
|
||||
null,
|
||||
callback = object : DlgTextInput.Callback {
|
||||
override fun onEmptyError() {
|
||||
showToast(true, R.string.token_not_specified)
|
||||
showTextInputDialog(
|
||||
title = getString(R.string.access_token_or_api_token),
|
||||
initialText = null,
|
||||
onEmptyText = { showToast(true, R.string.token_not_specified) },
|
||||
) { text ->
|
||||
try {
|
||||
val accessToken = text.trim()
|
||||
val auth2Result = runApiTask2(apiHost) { client ->
|
||||
val ti = TootInstance.getExOrThrow(client, forceAccessToken = accessToken)
|
||||
|
||||
val tokenJson = JsonObject()
|
||||
|
||||
val userJson = client.verifyAccount(
|
||||
accessToken,
|
||||
outTokenInfo = tokenJson, // 更新される
|
||||
misskeyVersion = ti.misskeyVersionMajor
|
||||
)
|
||||
val parser = TootParser(this, linkHelper = LinkHelper.create(ti))
|
||||
|
||||
Auth2Result(
|
||||
tootInstance = ti,
|
||||
tokenJson = tokenJson,
|
||||
accountJson = userJson,
|
||||
tootAccount = parser.account(userJson)
|
||||
?: error("can't parse user information."),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onOK(dialog: Dialog, text: String) {
|
||||
launchMain {
|
||||
try {
|
||||
val accessToken = text.trim()
|
||||
val auth2Result = runApiTask2(apiHost) { client ->
|
||||
val ti =
|
||||
TootInstance.getExOrThrow(client, forceAccessToken = accessToken)
|
||||
|
||||
val tokenJson = JsonObject()
|
||||
|
||||
val userJson = client.verifyAccount(
|
||||
accessToken,
|
||||
outTokenInfo = tokenJson, // 更新される
|
||||
misskeyVersion = ti.misskeyVersionMajor
|
||||
)
|
||||
|
||||
val parser = TootParser(this, linkHelper = LinkHelper.create(ti))
|
||||
|
||||
Auth2Result(
|
||||
tootInstance = ti,
|
||||
tokenJson = tokenJson,
|
||||
accountJson = userJson,
|
||||
tootAccount = parser.account(userJson)
|
||||
?: error("can't parse user information."),
|
||||
)
|
||||
}
|
||||
if (afterAccountVerify(auth2Result)) {
|
||||
dialog.dismissSafe()
|
||||
onComplete?.invoke()
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
showApiError(ex)
|
||||
}
|
||||
when (afterAccountVerify(auth2Result)) {
|
||||
false -> false
|
||||
else -> {
|
||||
onComplete?.invoke()
|
||||
true
|
||||
}
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
showApiError(ex)
|
||||
false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// アカウント設定
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package jp.juggler.subwaytooter.action
|
||||
|
||||
import android.app.Dialog
|
||||
import jp.juggler.subwaytooter.ActMain
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.actmain.addColumn
|
||||
|
@ -14,8 +13,8 @@ import jp.juggler.subwaytooter.column.ColumnType
|
|||
import jp.juggler.subwaytooter.column.onListListUpdated
|
||||
import jp.juggler.subwaytooter.column.onListNameUpdated
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.dialog.DlgTextInput
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.showTextInputDialog
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
|
@ -23,7 +22,6 @@ import jp.juggler.util.data.buildJsonObject
|
|||
import jp.juggler.util.log.showToast
|
||||
import jp.juggler.util.network.toPostRequestBuilder
|
||||
import jp.juggler.util.network.toPutRequestBuilder
|
||||
import jp.juggler.util.ui.dismissSafe
|
||||
import okhttp3.Request
|
||||
|
||||
fun ActMain.clickListTl(pos: Int, accessInfo: SavedAccount, item: TimelineItem?) {
|
||||
|
@ -164,61 +162,54 @@ fun ActMain.listDelete(
|
|||
}
|
||||
}
|
||||
|
||||
fun ActMain.listRename(
|
||||
suspend fun ActMain.listRename(
|
||||
accessInfo: SavedAccount,
|
||||
item: TootList,
|
||||
) {
|
||||
|
||||
DlgTextInput.show(
|
||||
this,
|
||||
getString(R.string.rename),
|
||||
item.title,
|
||||
callback = object : DlgTextInput.Callback {
|
||||
override fun onEmptyError() {
|
||||
showToast(false, R.string.list_name_empty)
|
||||
}
|
||||
|
||||
override fun onOK(dialog: Dialog, text: String) {
|
||||
launchMain {
|
||||
var resultList: TootList? = null
|
||||
runApiTask(accessInfo) { client ->
|
||||
if (accessInfo.isMisskey) {
|
||||
client.request(
|
||||
"/api/users/lists/update",
|
||||
accessInfo.putMisskeyApiToken().apply {
|
||||
put("listId", item.id)
|
||||
put("title", text)
|
||||
}.toPostRequestBuilder()
|
||||
)
|
||||
} else {
|
||||
client.request(
|
||||
"/api/v1/lists/${item.id}",
|
||||
buildJsonObject {
|
||||
put("title", text)
|
||||
}.toPutRequestBuilder()
|
||||
)
|
||||
}?.also { result ->
|
||||
client.publishApiProgress(getString(R.string.parsing_response))
|
||||
resultList = parseItem(result.jsonObject) {
|
||||
TootList(
|
||||
TootParser(this, accessInfo),
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
}?.let { result ->
|
||||
when (val list = resultList) {
|
||||
null -> showToast(false, result.error)
|
||||
else -> {
|
||||
for (column in appState.columnList) {
|
||||
column.onListNameUpdated(accessInfo, list)
|
||||
}
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
}
|
||||
}
|
||||
showTextInputDialog(
|
||||
title = getString(R.string.rename),
|
||||
initialText = item.title,
|
||||
onEmptyText = { showToast(false, R.string.list_name_empty) },
|
||||
) { text ->
|
||||
var resultList: TootList? = null
|
||||
val result = runApiTask(accessInfo) { client ->
|
||||
if (accessInfo.isMisskey) {
|
||||
client.request(
|
||||
"/api/users/lists/update",
|
||||
accessInfo.putMisskeyApiToken().apply {
|
||||
put("listId", item.id)
|
||||
put("title", text)
|
||||
}.toPostRequestBuilder()
|
||||
)
|
||||
} else {
|
||||
client.request(
|
||||
"/api/v1/lists/${item.id}",
|
||||
buildJsonObject {
|
||||
put("title", text)
|
||||
}.toPutRequestBuilder()
|
||||
)
|
||||
}?.also { result ->
|
||||
client.publishApiProgress(getString(R.string.parsing_response))
|
||||
resultList = parseItem(result.jsonObject) {
|
||||
TootList(
|
||||
TootParser(this, accessInfo),
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
result ?: return@showTextInputDialog true
|
||||
when (val list = resultList) {
|
||||
null -> {
|
||||
showToast(false, result.error)
|
||||
false
|
||||
}
|
||||
else -> {
|
||||
for (column in appState.columnList) {
|
||||
column.onListNameUpdated(accessInfo, list)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package jp.juggler.subwaytooter.actpost
|
||||
|
||||
import android.app.Dialog
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.text.InputType
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import jp.juggler.subwaytooter.ActPost
|
||||
import jp.juggler.subwaytooter.R
|
||||
|
@ -13,10 +16,12 @@ import jp.juggler.subwaytooter.api.entity.TootAttachment.Companion.tootAttachmen
|
|||
import jp.juggler.subwaytooter.api.entity.TootAttachment.Companion.tootAttachmentJson
|
||||
import jp.juggler.subwaytooter.api.runApiTask
|
||||
import jp.juggler.subwaytooter.calcIconRound
|
||||
import jp.juggler.subwaytooter.databinding.DlgFocusPointBinding
|
||||
import jp.juggler.subwaytooter.defaultColorIcon
|
||||
import jp.juggler.subwaytooter.dialog.DlgFocusPoint
|
||||
import jp.juggler.subwaytooter.dialog.DlgTextInput
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.decodeAttachmentBitmap
|
||||
import jp.juggler.subwaytooter.dialog.focusPointDialog
|
||||
import jp.juggler.subwaytooter.dialog.showTextInputDialog
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.util.AttachmentRequest
|
||||
import jp.juggler.subwaytooter.util.PostAttachment
|
||||
|
@ -29,7 +34,10 @@ import jp.juggler.util.log.showToast
|
|||
import jp.juggler.util.log.withCaption
|
||||
import jp.juggler.util.network.toPutRequestBuilder
|
||||
import jp.juggler.util.ui.dismissSafe
|
||||
import jp.juggler.util.ui.isLiveActivity
|
||||
import jp.juggler.util.ui.vg
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
private val log = LogCategory("ActPostAttachment")
|
||||
|
||||
|
@ -212,13 +220,10 @@ private fun ActPost.appendArrachmentUrl(a: TootAttachment) {
|
|||
|
||||
// 添付した画像をタップ
|
||||
fun ActPost.performAttachmentClick(idx: Int) {
|
||||
val pa = try {
|
||||
attachmentList[idx]
|
||||
} catch (ex: Throwable) {
|
||||
showToast(false, ex.withCaption("can't get attachment item[$idx]."))
|
||||
return
|
||||
}
|
||||
launchAndShowError {
|
||||
val pa = attachmentList.elementAtOrNull(idx)
|
||||
?: error("can't get attachment item[$idx].")
|
||||
|
||||
actionsDialog(getString(R.string.media_attachment)) {
|
||||
action(getString(R.string.set_description)) {
|
||||
editAttachmentDescription(pa)
|
||||
|
@ -265,11 +270,12 @@ fun ActPost.deleteAttachment(pa: PostAttachment) {
|
|||
.show()
|
||||
}
|
||||
|
||||
fun ActPost.openFocusPoint(pa: PostAttachment) {
|
||||
suspend fun ActPost.openFocusPoint(pa: PostAttachment) {
|
||||
val attachment = pa.attachment ?: return
|
||||
DlgFocusPoint(this, attachment)
|
||||
.setCallback { x, y -> sendFocusPoint(pa, attachment, x, y) }
|
||||
.show()
|
||||
focusPointDialog(
|
||||
attachment = attachment,
|
||||
callback = { x, y -> sendFocusPoint(pa, attachment, x, y) }
|
||||
)
|
||||
}
|
||||
|
||||
fun ActPost.sendFocusPoint(pa: PostAttachment, attachment: TootAttachment, x: Float, y: Float) {
|
||||
|
@ -300,42 +306,65 @@ fun ActPost.sendFocusPoint(pa: PostAttachment, attachment: TootAttachment, x: Fl
|
|||
}
|
||||
}
|
||||
|
||||
fun ActPost.editAttachmentDescription(pa: PostAttachment) {
|
||||
suspend fun ActPost.editAttachmentDescription(pa: PostAttachment) {
|
||||
val a = pa.attachment
|
||||
if (a == null) {
|
||||
showToast(true, R.string.attachment_description_cant_edit_while_uploading)
|
||||
return
|
||||
}
|
||||
|
||||
DlgTextInput.show(
|
||||
this,
|
||||
getString(R.string.attachment_description),
|
||||
a.description,
|
||||
callback = object : DlgTextInput.Callback {
|
||||
override fun onEmptyError() {
|
||||
showToast(true, R.string.description_empty)
|
||||
}
|
||||
|
||||
override fun onOK(dialog: Dialog, text: String) {
|
||||
val attachmentId = pa.attachment?.id ?: return
|
||||
val account = this@editAttachmentDescription.account ?: return
|
||||
launchMain {
|
||||
val (result, newAttachment) = attachmentUploader.setAttachmentDescription(
|
||||
account,
|
||||
attachmentId,
|
||||
text
|
||||
)
|
||||
when (newAttachment) {
|
||||
null -> result?.error?.let { showToast(true, it) }
|
||||
else -> {
|
||||
pa.attachment = newAttachment
|
||||
showMediaAttachment()
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
val attachmentId = pa.attachment?.id ?: return
|
||||
val account = this.account ?: return
|
||||
var bitmap: Bitmap? = null
|
||||
try {
|
||||
val url = a.preview_url
|
||||
if (url != null) {
|
||||
val result = runApiTask { client ->
|
||||
try {
|
||||
val (result, data) = client.getHttpBytes(url)
|
||||
data?.let {
|
||||
bitmap = decodeAttachmentBitmap(it, 1024)
|
||||
?: return@runApiTask TootApiResult("image decode failed.")
|
||||
}
|
||||
result
|
||||
} catch (ex: Throwable) {
|
||||
TootApiResult(ex.withCaption("preview loading failed."))
|
||||
}
|
||||
}
|
||||
})
|
||||
result ?: return
|
||||
if (!isLiveActivity) return
|
||||
result.error?.let{
|
||||
showToast(true, result.error ?: "error")
|
||||
// not exit
|
||||
}
|
||||
}
|
||||
showTextInputDialog(
|
||||
title = getString(R.string.attachment_description),
|
||||
bitmap = bitmap,
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE,
|
||||
initialText = a.description,
|
||||
onEmptyText = { showToast(true, R.string.description_empty) },
|
||||
) { text ->
|
||||
val (result, newAttachment) = attachmentUploader.setAttachmentDescription(
|
||||
account,
|
||||
attachmentId,
|
||||
text
|
||||
)
|
||||
result ?: return@showTextInputDialog true
|
||||
when (newAttachment) {
|
||||
null -> {
|
||||
result.error?.let { showToast(true, it) }
|
||||
false
|
||||
}
|
||||
else -> {
|
||||
pa.attachment = newAttachment
|
||||
showMediaAttachment()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
bitmap?.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
fun ActPost.onPickCustomThumbnailImpl(pa: PostAttachment, src: GetContentResultEntry) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package jp.juggler.subwaytooter.columnviewholder
|
||||
|
||||
import android.app.Dialog
|
||||
import android.graphics.Color
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableStringBuilder
|
||||
|
@ -22,7 +21,7 @@ import jp.juggler.subwaytooter.api.entity.TootStatus
|
|||
import jp.juggler.subwaytooter.api.runApiTask
|
||||
import jp.juggler.subwaytooter.column.*
|
||||
import jp.juggler.subwaytooter.databinding.LvHeaderProfileBinding
|
||||
import jp.juggler.subwaytooter.dialog.DlgTextInput
|
||||
import jp.juggler.subwaytooter.dialog.showTextInputDialog
|
||||
import jp.juggler.subwaytooter.emoji.EmojiMap
|
||||
import jp.juggler.subwaytooter.itemviewholder.DlgContextMenu
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
|
@ -41,7 +40,7 @@ import jp.juggler.subwaytooter.util.openCustomTab
|
|||
import jp.juggler.subwaytooter.util.startMargin
|
||||
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
|
||||
import jp.juggler.subwaytooter.view.MyTextView
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.buildJsonObject
|
||||
import jp.juggler.util.data.intoStringResource
|
||||
import jp.juggler.util.data.notEmpty
|
||||
|
@ -49,7 +48,6 @@ import jp.juggler.util.data.notZero
|
|||
import jp.juggler.util.log.showToast
|
||||
import jp.juggler.util.network.toPostRequestBuilder
|
||||
import jp.juggler.util.ui.attrColor
|
||||
import jp.juggler.util.ui.dismissSafe
|
||||
import jp.juggler.util.ui.setIconDrawableId
|
||||
import jp.juggler.util.ui.vg
|
||||
import org.jetbrains.anko.textColor
|
||||
|
@ -444,49 +442,46 @@ internal class ViewHolderHeaderProfile(
|
|||
val who = whoRef.get()
|
||||
val relation = this.relation
|
||||
val lastColumn = column
|
||||
DlgTextInput.show(
|
||||
activity,
|
||||
daoAcctColor.getStringWithNickname(
|
||||
activity,
|
||||
R.string.personal_notes_of,
|
||||
who.acct
|
||||
),
|
||||
relation?.note ?: "",
|
||||
allowEmpty = true,
|
||||
callback = object : DlgTextInput.Callback {
|
||||
override fun onEmptyError() {
|
||||
activity.launchAndShowError {
|
||||
activity.showTextInputDialog(
|
||||
title = daoAcctColor.getStringWithNickname(
|
||||
activity,
|
||||
R.string.personal_notes_of,
|
||||
who.acct
|
||||
),
|
||||
initialText = relation?.note ?: "",
|
||||
allowEmpty = true,
|
||||
onEmptyText = {},
|
||||
) { text ->
|
||||
val result = activity.runApiTask(column.accessInfo) { client ->
|
||||
when {
|
||||
accessInfo.isPseudo ->
|
||||
TootApiResult("Personal notes is not supported on pseudo account.")
|
||||
accessInfo.isMisskey ->
|
||||
TootApiResult("Personal notes is not supported on Misskey account.")
|
||||
else ->
|
||||
client.request(
|
||||
"/api/v1/accounts/${who.id}/note",
|
||||
buildJsonObject {
|
||||
put("comment", text)
|
||||
}.toPostRequestBuilder()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOK(dialog: Dialog, text: String) {
|
||||
launchMain {
|
||||
activity.runApiTask(column.accessInfo) { client ->
|
||||
when {
|
||||
accessInfo.isPseudo ->
|
||||
TootApiResult("Personal notes is not supported on pseudo account.")
|
||||
accessInfo.isMisskey ->
|
||||
TootApiResult("Personal notes is not supported on Misskey account.")
|
||||
else ->
|
||||
client.request(
|
||||
"/api/v1/accounts/${who.id}/note",
|
||||
buildJsonObject {
|
||||
put("comment", text)
|
||||
}.toPostRequestBuilder()
|
||||
)
|
||||
}
|
||||
}?.let { result ->
|
||||
when (val error = result.error) {
|
||||
null -> {
|
||||
relation?.note = text
|
||||
dialog.dismissSafe()
|
||||
if (lastColumn == column) bindData(column)
|
||||
}
|
||||
else -> activity.showToast(true, error)
|
||||
}
|
||||
}
|
||||
result ?: return@showTextInputDialog true
|
||||
when (val error = result.error) {
|
||||
null -> {
|
||||
relation?.note = text
|
||||
if (lastColumn == column) bindData(column)
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
activity.showToast(true, error)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -496,7 +491,6 @@ internal class ViewHolderHeaderProfile(
|
|||
|
||||
R.id.btnFollow -> {
|
||||
activity.followFromAnotherAccount(
|
||||
|
||||
activity.nextPosition(column),
|
||||
accessInfo,
|
||||
whoRef?.get()
|
||||
|
@ -506,7 +500,6 @@ internal class ViewHolderHeaderProfile(
|
|||
|
||||
R.id.btnMoved -> {
|
||||
activity.followFromAnotherAccount(
|
||||
|
||||
activity.nextPosition(column),
|
||||
accessInfo,
|
||||
movedRef?.get()
|
||||
|
|
|
@ -1,129 +1,101 @@
|
|||
package jp.juggler.subwaytooter.dialog
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment
|
||||
import jp.juggler.subwaytooter.view.FocusPointView
|
||||
import jp.juggler.subwaytooter.databinding.DlgFocusPointBinding
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.log.*
|
||||
import jp.juggler.util.ui.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
class DlgFocusPoint(
|
||||
val activity: AppCompatActivity,
|
||||
val attachment: TootAttachment,
|
||||
) : View.OnClickListener {
|
||||
private val log = LogCategory("DlgFocusPoint")
|
||||
|
||||
companion object {
|
||||
|
||||
val log = LogCategory("DlgFocusPoint")
|
||||
fun decodeAttachmentBitmap(
|
||||
data: ByteArray,
|
||||
@Suppress("SameParameterValue") pixelMax: Int,
|
||||
): Bitmap? {
|
||||
val options = BitmapFactory.Options()
|
||||
options.inJustDecodeBounds = true
|
||||
options.inScaled = false
|
||||
options.outWidth = 0
|
||||
options.outHeight = 0
|
||||
BitmapFactory.decodeByteArray(data, 0, data.size, options)
|
||||
var w = options.outWidth
|
||||
var h = options.outHeight
|
||||
if (w <= 0 || h <= 0) {
|
||||
log.e("can't decode bounds.")
|
||||
return null
|
||||
}
|
||||
var bits = 0
|
||||
while (w > pixelMax || h > pixelMax) {
|
||||
++bits
|
||||
w = w shr 1
|
||||
h = h shr 1
|
||||
}
|
||||
options.inJustDecodeBounds = false
|
||||
options.inSampleSize = 1 shl bits
|
||||
return BitmapFactory.decodeByteArray(data, 0, data.size, options)
|
||||
}
|
||||
|
||||
val dialog: Dialog
|
||||
private val focusPointView: FocusPointView
|
||||
suspend fun AppCompatActivity.focusPointDialog(
|
||||
attachment: TootAttachment,
|
||||
callback: (x: Float, y: Float) -> Unit,
|
||||
) {
|
||||
var bitmap: Bitmap? = null
|
||||
|
||||
init {
|
||||
val viewRoot = activity.layoutInflater.inflate(R.layout.dlg_focus_point, null, false)
|
||||
focusPointView = viewRoot.findViewById(R.id.ivFocus)
|
||||
viewRoot.findViewById<View>(R.id.btnClose).setOnClickListener(this)
|
||||
|
||||
this.dialog = Dialog(activity)
|
||||
dialog.setContentView(viewRoot)
|
||||
dialog.setOnDismissListener {
|
||||
bitmap?.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.btnClose -> dialog.dismissSafe()
|
||||
}
|
||||
}
|
||||
|
||||
fun setCallback(callback: (x: Float, y: Float) -> Unit): DlgFocusPoint {
|
||||
focusPointView.callback = callback
|
||||
return this
|
||||
}
|
||||
|
||||
fun show() {
|
||||
try {
|
||||
val url = attachment.preview_url
|
||||
if (url == null) {
|
||||
activity.showToast(false, "missing image url")
|
||||
showToast(false, "missing image url")
|
||||
return
|
||||
}
|
||||
|
||||
val options = BitmapFactory.Options()
|
||||
|
||||
fun decodeBitmap(
|
||||
data: ByteArray,
|
||||
@Suppress("SameParameterValue") pixelMax: Int,
|
||||
): Bitmap? {
|
||||
options.inJustDecodeBounds = true
|
||||
options.inScaled = false
|
||||
options.outWidth = 0
|
||||
options.outHeight = 0
|
||||
BitmapFactory.decodeByteArray(data, 0, data.size, options)
|
||||
var w = options.outWidth
|
||||
var h = options.outHeight
|
||||
if (w <= 0 || h <= 0) {
|
||||
log.e("can't decode bounds.")
|
||||
return null
|
||||
}
|
||||
var bits = 0
|
||||
while (w > pixelMax || h > pixelMax) {
|
||||
++bits
|
||||
w = w shr 1
|
||||
h = h shr 1
|
||||
}
|
||||
options.inJustDecodeBounds = false
|
||||
options.inSampleSize = 1 shl bits
|
||||
return BitmapFactory.decodeByteArray(data, 0, data.size, options)
|
||||
}
|
||||
|
||||
launchMain {
|
||||
var resultBitmap: Bitmap? = null
|
||||
val result = activity.runApiTask { client ->
|
||||
try {
|
||||
val (result, data) = client.getHttpBytes(url)
|
||||
data?.let {
|
||||
resultBitmap = decodeBitmap(it, 1024)
|
||||
?: return@runApiTask TootApiResult("image decode failed.")
|
||||
}
|
||||
result
|
||||
} catch (ex: Throwable) {
|
||||
TootApiResult(ex.withCaption("preview loading failed."))
|
||||
}
|
||||
}
|
||||
val bitmap = resultBitmap
|
||||
when {
|
||||
bitmap == null -> {
|
||||
activity.showToast(true, result?.error ?: "?")
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
activity.isFinishing -> {
|
||||
bitmap.recycle()
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
else -> {
|
||||
this@DlgFocusPoint.bitmap = bitmap
|
||||
focusPointView.setAttachment(attachment, bitmap)
|
||||
dialog.window?.setLayout(
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
dialog.show()
|
||||
val result = runApiTask { client ->
|
||||
try {
|
||||
val (result, data) = client.getHttpBytes(url)
|
||||
data?.let {
|
||||
bitmap = decodeAttachmentBitmap(it, 1024)
|
||||
?: return@runApiTask TootApiResult("image decode failed.")
|
||||
}
|
||||
result
|
||||
} catch (ex: Throwable) {
|
||||
TootApiResult(ex.withCaption("preview loading failed."))
|
||||
}
|
||||
}
|
||||
result ?: return
|
||||
if (bitmap == null) {
|
||||
showToast(true, result.error ?: "error")
|
||||
return
|
||||
} else if (!isLiveActivity) {
|
||||
return
|
||||
}
|
||||
val dialog = Dialog(this)
|
||||
val views = DlgFocusPointBinding.inflate(layoutInflater)
|
||||
dialog.setContentView(views.root)
|
||||
views.ivFocus.setAttachment(attachment, bitmap!!)
|
||||
views.ivFocus.callback = callback
|
||||
dialog.window?.setLayout(
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
suspendCancellableCoroutine { cont ->
|
||||
views.btnClose.setOnClickListener {
|
||||
if (cont.isActive) cont.resume(Unit) {}
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
dialog.setOnDismissListener {
|
||||
if (cont.isActive) cont.resumeWithException(CancellationException())
|
||||
}
|
||||
cont.invokeOnCancellation { dialog.dismissSafe() }
|
||||
dialog.show()
|
||||
}
|
||||
} finally {
|
||||
bitmap?.recycle()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
|
|||
import jp.juggler.subwaytooter.view.MyListView
|
||||
import jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.log.*
|
||||
|
@ -247,30 +248,28 @@ class DlgListMember(
|
|||
}
|
||||
|
||||
private fun openListCreator() {
|
||||
DlgTextInput.show(
|
||||
activity,
|
||||
activity.getString(R.string.list_create),
|
||||
null,
|
||||
callback = object : DlgTextInput.Callback {
|
||||
|
||||
override fun onEmptyError() {
|
||||
activity.launchAndShowError {
|
||||
activity.showTextInputDialog(
|
||||
title = activity.getString(R.string.list_create),
|
||||
initialText = null,
|
||||
onEmptyText = {
|
||||
activity.showToast(false, R.string.list_name_empty)
|
||||
}
|
||||
|
||||
override fun onOK(dialog: Dialog, text: String) {
|
||||
val list_owner = this@DlgListMember.listOwner
|
||||
|
||||
if (list_owner == null) {
|
||||
},
|
||||
) { text ->
|
||||
when (val listOwner1 = this@DlgListMember.listOwner) {
|
||||
null -> {
|
||||
activity.showToast(false, "list owner is not selected.")
|
||||
return
|
||||
false
|
||||
}
|
||||
|
||||
activity.listCreate(list_owner, text) {
|
||||
dialog.dismissSafe()
|
||||
loadLists()
|
||||
else -> {
|
||||
activity.listCreate(listOwner1, text) {
|
||||
loadLists()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ErrorItem(val message: String)
|
||||
|
|
|
@ -1,67 +1,69 @@
|
|||
package jp.juggler.subwaytooter.dialog
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.view.View
|
||||
import android.graphics.Bitmap
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import jp.juggler.subwaytooter.databinding.DlgTextInputBinding
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.ui.dismissSafe
|
||||
import jp.juggler.util.ui.visible
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
import jp.juggler.subwaytooter.R
|
||||
|
||||
object DlgTextInput {
|
||||
|
||||
interface Callback {
|
||||
fun onOK(dialog: Dialog, text: String)
|
||||
|
||||
fun onEmptyError()
|
||||
suspend fun AppCompatActivity.showTextInputDialog(
|
||||
title: CharSequence,
|
||||
initialText: CharSequence?,
|
||||
allowEmpty: Boolean = false,
|
||||
inputType: Int? = null,
|
||||
bitmap: Bitmap? = null,
|
||||
onEmptyText: suspend () -> Unit,
|
||||
// returns true if we can close dialog
|
||||
onOk: suspend (String) -> Boolean,
|
||||
) {
|
||||
val views = DlgTextInputBinding.inflate(layoutInflater)
|
||||
views.tvCaption.text = title
|
||||
initialText?.notEmpty()?.let {
|
||||
views.etInput.setText(it)
|
||||
views.etInput.setSelection(it.length)
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
fun show(
|
||||
activity: Activity,
|
||||
caption: CharSequence,
|
||||
initialText: CharSequence?,
|
||||
allowEmpty: Boolean = false,
|
||||
callback: Callback,
|
||||
) {
|
||||
val view = activity.layoutInflater.inflate(R.layout.dlg_text_input, null, false)
|
||||
val etInput = view.findViewById<EditText>(R.id.etInput)
|
||||
val btnOk = view.findViewById<View>(R.id.btnOk)
|
||||
val tvCaption = view.findViewById<TextView>(R.id.tvCaption)
|
||||
|
||||
tvCaption.text = caption
|
||||
if (initialText != null && initialText.isNotEmpty()) {
|
||||
etInput.setText(initialText)
|
||||
etInput.setSelection(initialText.length)
|
||||
// views.llInput.maxHeight = (100f * resources.displayMetrics.density + 0.5f).toInt()
|
||||
inputType?.let { views.etInput.inputType = it }
|
||||
bitmap?.let { views.ivBitmap.visible().setImageBitmap(it) }
|
||||
views.etInput.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
views.btnOk.performClick()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
etInput.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
btnOk.performClick()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
val dialog = Dialog(this)
|
||||
dialog.setContentView(views.root)
|
||||
dialog.window?.setLayout(
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
suspendCancellableCoroutine { cont ->
|
||||
views.btnOk.setOnClickListener {
|
||||
launchAndShowError {
|
||||
val text = views.etInput.text.toString().trim { it <= ' ' }
|
||||
if (text.isEmpty() && !allowEmpty) {
|
||||
onEmptyText()
|
||||
} else if (onOk(text)) {
|
||||
if (cont.isActive) cont.resume(Unit) {}
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dialog = Dialog(activity)
|
||||
dialog.setContentView(view)
|
||||
btnOk.setOnClickListener {
|
||||
val token = etInput.text.toString().trim { it <= ' ' }
|
||||
|
||||
if (token.isEmpty() && !allowEmpty) {
|
||||
callback.onEmptyError()
|
||||
} else {
|
||||
callback.onOK(dialog, token)
|
||||
}
|
||||
views.btnCancel.setOnClickListener { dialog.cancel() }
|
||||
dialog.setOnDismissListener {
|
||||
if (cont.isActive) cont.resumeWithException(CancellationException())
|
||||
}
|
||||
|
||||
view.findViewById<View>(R.id.btnCancel).setOnClickListener { dialog.cancel() }
|
||||
|
||||
dialog.window?.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT)
|
||||
cont.invokeOnCancellation { dialog.dismissSafe() }
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,56 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCaption"
|
||||
<jp.juggler.subwaytooter.view.MaxHeightScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:labelFor="@+id/etInput"
|
||||
tools:ignore="LabelFor" />
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbarStyle="outsideOverlay"
|
||||
app:maxHeight="240dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:imeOptions="actionDone"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingVertical="6dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivBitmap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:scaleType="fitCenter"
|
||||
android:visibility="gone"
|
||||
tools:src="@drawable/ic_face"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCaption"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@+id/etInput"
|
||||
tools:ignore="LabelFor"
|
||||
tools:text="title title title title title title title title title title title title title title title title title title title title title title title title title title title title title title title title title title title " />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionDone"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
tools:inputType="textMultiLine"
|
||||
tools:text="text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text " />
|
||||
</LinearLayout>
|
||||
</jp.juggler.subwaytooter.view.MaxHeightScrollView>
|
||||
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
|
|
Loading…
Reference in New Issue