diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt index 3025eab9..49d98d69 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt @@ -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 } - ) + } } // アカウント設定 diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_List.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_List.kt index bbc8dcfb..bac9b042 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_List.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_List.kt @@ -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 + } + } + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/actpost/ActPostAttachment.kt b/app/src/main/java/jp/juggler/subwaytooter/actpost/ActPostAttachment.kt index 819e75ba..7434e36e 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/actpost/ActPostAttachment.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/actpost/ActPostAttachment.kt @@ -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) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/columnviewholder/ViewHolderHeaderProfile.kt b/app/src/main/java/jp/juggler/subwaytooter/columnviewholder/ViewHolderHeaderProfile.kt index 29314cde..a941cd2d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/columnviewholder/ViewHolderHeaderProfile.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/columnviewholder/ViewHolderHeaderProfile.kt @@ -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() diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgFocusPoint.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgFocusPoint.kt index a4f42299..53fd9da2 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgFocusPoint.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgFocusPoint.kt @@ -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(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() } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgListMember.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgListMember.kt index bbadcaac..86468ac9 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgListMember.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgListMember.kt @@ -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) diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgTextInput.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgTextInput.kt index 1fc9cac5..22b30671 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgTextInput.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgTextInput.kt @@ -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(R.id.etInput) - val btnOk = view.findViewById(R.id.btnOk) - val tvCaption = view.findViewById(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(R.id.btnCancel).setOnClickListener { dialog.cancel() } - - dialog.window?.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT) + cont.invokeOnCancellation { dialog.dismissSafe() } dialog.show() } } diff --git a/app/src/main/res/layout/dlg_text_input.xml b/app/src/main/res/layout/dlg_text_input.xml index 5d0bde15..7c28ccf2 100644 --- a/app/src/main/res/layout/dlg_text_input.xml +++ b/app/src/main/res/layout/dlg_text_input.xml @@ -1,29 +1,56 @@ - + android:fadeScrollbars="false" + android:scrollbarStyle="outsideOverlay" + app:maxHeight="240dp"> - + + + + + + + + +