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