fix can't restore reply from drafts

This commit is contained in:
tateisu 2021-06-27 19:05:04 +09:00
parent 3eccf01edd
commit 3b7b424119
24 changed files with 433 additions and 501 deletions

View File

@ -344,15 +344,15 @@ class ActMain : AppCompatActivity(),
requestWindowFeature(Window.FEATURE_NO_TITLE) requestWindowFeature(Window.FEATURE_NO_TITLE)
App1.setActivityTheme(this, noActionBar = true) App1.setActivityTheme(this, noActionBar = true)
handler = App1.getAppState(this).handler
appState = App1.getAppState(this) appState = App1.getAppState(this)
pref = App1.pref handler = appState.handler
pref = appState.pref
density = appState.density
completionHelper = CompletionHelper(this, pref, appState.handler)
EmojiDecoder.handleUnicodeEmoji = PrefB.bpInAppUnicodeEmoji(pref) EmojiDecoder.handleUnicodeEmoji = PrefB.bpInAppUnicodeEmoji(pref)
density = appState.density
acctPadLr = (0.5f + 4f * density).toInt() acctPadLr = (0.5f + 4f * density).toInt()
timelineFontSizeSp = PrefF.fpTimelineFontSize(pref).clipFontSize() timelineFontSizeSp = PrefF.fpTimelineFontSize(pref).clipFontSize()
acctFontSizeSp = PrefF.fpAcctFontSize(pref).clipFontSize() acctFontSizeSp = PrefF.fpAcctFontSize(pref).clipFontSize()
notificationTlFontSizeSp = PrefF.fpNotificationTlFontSize(pref).clipFontSize() notificationTlFontSizeSp = PrefF.fpNotificationTlFontSize(pref).clipFontSize()
@ -925,7 +925,6 @@ class ActMain : AppCompatActivity(),
initUIQuickToot() initUIQuickToot()
svColumnStrip.isHorizontalFadingEdgeEnabled = true svColumnStrip.isHorizontalFadingEdgeEnabled = true
completionHelper = CompletionHelper(this, pref, appState.handler)
val dm = resources.displayMetrics val dm = resources.displayMetrics
val density = dm.density val density = dm.density

View File

@ -4,7 +4,6 @@ import android.text.SpannableStringBuilder
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.annotation.RawRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import jp.juggler.subwaytooter.api.entity.TootStatus import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.util.* import jp.juggler.util.*
@ -101,35 +100,22 @@ fun ActMain.checkPrivacyPolicy() {
// 既に表示中かもしれない // 既に表示中かもしれない
if (dlgPrivacyPolicy?.get()?.isShowing == true) return if (dlgPrivacyPolicy?.get()?.isShowing == true) return
@RawRes val resId = when (getString(R.string.language_code)) { val checker = PrivacyPolicyChecker(this, pref)
"ja" -> R.raw.privacy_policy_ja
"fr" -> R.raw.privacy_policy_fr
else -> R.raw.privacy_policy_en
}
// プライバシーポリシーデータの読み込み
val bytes = loadRawResource(resId)
if (bytes.isEmpty()) return
// 同意ずみなら表示しない // 同意ずみなら表示しない
val digest = bytes.digestSHA256().encodeBase64Url() if (checker.agreed) return
if (digest == PrefS.spAgreedPrivacyPolicyDigest(pref)) return
val dialog = AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle(R.string.privacy_policy) .setTitle(R.string.privacy_policy)
.setMessage(bytes.decodeUTF8()) .setMessage(checker.text)
.setNegativeButton(R.string.cancel) { _, _ -> .setOnCancelListener { finish() }
finish() .setNegativeButton(R.string.cancel) { _, _ -> finish() }
}
.setOnCancelListener {
finish()
}
.setPositiveButton(R.string.agree) { _, _ -> .setPositiveButton(R.string.agree) { _, _ ->
pref.edit().put(PrefS.spAgreedPrivacyPolicyDigest, digest).apply() pref.edit().put(PrefS.spAgreedPrivacyPolicyDigest, checker.digest).apply()
} }
.create() .create()
dlgPrivacyPolicy = WeakReference(dialog) .also { dlgPrivacyPolicy = WeakReference(it) }
dialog.show() .show()
} }
fun ActMain.closeListItemPopup() { fun ActMain.closeListItemPopup() {

View File

@ -29,11 +29,14 @@ fun ActMain.handleIntentUri(uri: Uri) {
log.d("handleIntentUri $uri") log.d("handleIntentUri $uri")
when (uri.scheme) { when (uri.scheme) {
"subwaytooter", "misskeyclientproto" -> return try { "subwaytooter", "misskeyclientproto" -> {
handleCustomSchemaUri(uri) try {
} catch (ex: Throwable) { handleCustomSchemaUri(uri)
log.trace(ex) } catch (ex: Throwable) {
showToast(ex, "handleCustomSchemaUri failed.") log.trace(ex)
showToast(ex, "handleCustomSchemaUri failed.")
}
return
} }
} }

View File

@ -41,7 +41,7 @@ class ActPost : AppCompatActivity(),
MyClickableSpanHandler, AttachmentPicker.Callback { MyClickableSpanHandler, AttachmentPicker.Callback {
companion object { companion object {
internal val log = LogCategory("ActPost") private val log = LogCategory("ActPost")
var refActPost: WeakReference<ActPost>? = null var refActPost: WeakReference<ActPost>? = null

View File

@ -7,6 +7,8 @@ import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.util.* import jp.juggler.util.*
import org.jetbrains.anko.textColor import org.jetbrains.anko.textColor
private val log = LogCategory("ActPostAccount")
fun ActPost.selectAccount(a: SavedAccount?) { fun ActPost.selectAccount(a: SavedAccount?) {
this.account = a this.account = a
@ -95,7 +97,7 @@ internal fun ActPost.setAccountWithVisibilityConversion(a: SavedAccount) {
states.visibility = a.visibility states.visibility = a.visibility
} }
} catch (ex: Throwable) { } catch (ex: Throwable) {
ActPost.log.trace(ex) log.trace(ex)
} }
showVisibility() showVisibility()
showQuotedRenote() showQuotedRenote()

View File

@ -30,7 +30,7 @@ fun ActPost.decodeAttachments(sv: String) {
try { try {
attachmentList.add(PostAttachment(TootAttachment.decodeJson(it))) attachmentList.add(PostAttachment(TootAttachment.decodeJson(it)))
} catch (ex: Throwable) { } catch (ex: Throwable) {
ActPost.log.trace(ex) log.trace(ex)
} }
} }
} catch (ex: Throwable) { } catch (ex: Throwable) {
@ -126,22 +126,22 @@ fun ActPost.onPostAttachmentCompleteImpl(pa: PostAttachment) {
when (pa.status) { when (pa.status) {
PostAttachment.Status.Error -> { PostAttachment.Status.Error -> {
ActPost.log.w("onPostAttachmentComplete: upload failed.") log.w("onPostAttachmentComplete: upload failed.")
attachmentList.remove(pa) attachmentList.remove(pa)
showMediaAttachment() showMediaAttachment()
} }
PostAttachment.Status.Progress -> { PostAttachment.Status.Progress -> {
// アップロード中…? // アップロード中…?
ActPost.log.w("onPostAttachmentComplete: ?? status=${pa.status}") log.w("onPostAttachmentComplete: ?? status=${pa.status}")
} }
PostAttachment.Status.Ok -> { PostAttachment.Status.Ok -> {
when (val a = pa.attachment) { when (val a = pa.attachment) {
null -> ActPost.log.e("onPostAttachmentComplete: upload complete, but missing attachment entity.") null -> log.e("onPostAttachmentComplete: upload complete, but missing attachment entity.")
else -> { else -> {
// アップロード完了 // アップロード完了
ActPost.log.i("onPostAttachmentComplete: upload complete.") log.i("onPostAttachmentComplete: upload complete.")
// 投稿欄の末尾に追記する // 投稿欄の末尾に追記する
if (PrefB.bpAppendAttachmentUrlToContent(pref)) { if (PrefB.bpAppendAttachmentUrlToContent(pref)) {

View File

@ -11,6 +11,8 @@ import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.* import jp.juggler.subwaytooter.util.*
import jp.juggler.util.* import jp.juggler.util.*
private val log = LogCategory("ActPostExtra")
fun ActPost.appendContentText( fun ActPost.appendContentText(
src: String?, src: String?,
selectBefore: Boolean = false, selectBefore: Boolean = false,
@ -246,7 +248,7 @@ fun ActPost.initializeFromSharedIntent(sharedIntent: Intent) {
appendContentText(sharedIntent) appendContentText(sharedIntent)
} }
} catch (ex: Throwable) { } catch (ex: Throwable) {
ActPost.log.trace(ex) log.trace(ex)
} }
} }

View File

@ -10,9 +10,12 @@ import androidx.annotation.RawRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import jp.juggler.subwaytooter.util.DecodeOptions import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.LinkHelper import jp.juggler.subwaytooter.util.LinkHelper
import jp.juggler.util.LogCategory
import jp.juggler.util.decodeUTF8 import jp.juggler.util.decodeUTF8
import jp.juggler.util.loadRawResource import jp.juggler.util.loadRawResource
private val log = LogCategory("ActPostMushroom")
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
fun ActPost.showRecommendedPlugin(title: String?) { fun ActPost.showRecommendedPlugin(title: String?) {
@ -89,7 +92,7 @@ fun ActPost.openMushroom() {
arMushroom.launch(chooser) arMushroom.launch(chooser)
} catch (ex: Throwable) { } catch (ex: Throwable) {
ActPost.log.trace(ex) log.trace(ex)
showRecommendedPlugin(getString(R.string.plugin_not_installed)) showRecommendedPlugin(getString(R.string.plugin_not_installed))
} }
} }

View File

@ -15,6 +15,8 @@ import jp.juggler.subwaytooter.util.PostAttachment
import jp.juggler.util.* import jp.juggler.util.*
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
private val log = LogCategory("ActPostRedraft")
// DlgDraftPickerから参照される // DlgDraftPickerから参照される
const val DRAFT_CONTENT = "content" const val DRAFT_CONTENT = "content"
const val DRAFT_CONTENT_WARNING = "content_warning" const val DRAFT_CONTENT_WARNING = "content_warning"
@ -60,7 +62,7 @@ fun ActPost.saveDraft() {
} }
if (!hasContent) { if (!hasContent) {
ActPost.log.d("saveDraft: dont save empty content") log.d("saveDraft: dont save empty content")
return return
} }
@ -74,32 +76,29 @@ fun ActPost.saveDraft() {
json[DRAFT_CONTENT_WARNING] = contentWarning json[DRAFT_CONTENT_WARNING] = contentWarning
json[DRAFT_CONTENT_WARNING_CHECK] = cbContentWarning.isChecked json[DRAFT_CONTENT_WARNING_CHECK] = cbContentWarning.isChecked
json[DRAFT_NSFW_CHECK] = cbNSFW.isChecked json[DRAFT_NSFW_CHECK] = cbNSFW.isChecked
states.visibility?.let { json.put(DRAFT_VISIBILITY, it.id.toString()) }
json[DRAFT_ACCOUNT_DB_ID] = account?.db_id ?: -1L json[DRAFT_ACCOUNT_DB_ID] = account?.db_id ?: -1L
json[DRAFT_ATTACHMENT_LIST] = tmpAttachmentList json[DRAFT_ATTACHMENT_LIST] = tmpAttachmentList
states.inReplyToId?.putTo(json, DRAFT_REPLY_ID)
json[DRAFT_REPLY_TEXT] = states.inReplyToText json[DRAFT_REPLY_TEXT] = states.inReplyToText
json[DRAFT_REPLY_IMAGE] = states.inReplyToImage json[DRAFT_REPLY_IMAGE] = states.inReplyToImage
json[DRAFT_REPLY_URL] = states.inReplyToUrl json[DRAFT_REPLY_URL] = states.inReplyToUrl
json[DRAFT_QUOTE] = cbQuote.isChecked json[DRAFT_QUOTE] = cbQuote.isChecked
// deprecated. but still used in old draft.
// json.put(DRAFT_IS_ENQUETE, isEnquete)
json[DRAFT_POLL_TYPE] = spPollType.selectedItemPosition.toPollTypeString() json[DRAFT_POLL_TYPE] = spPollType.selectedItemPosition.toPollTypeString()
json[DRAFT_POLL_MULTIPLE] = cbMultipleChoice.isChecked json[DRAFT_POLL_MULTIPLE] = cbMultipleChoice.isChecked
json[DRAFT_POLL_HIDE_TOTALS] = cbHideTotals.isChecked json[DRAFT_POLL_HIDE_TOTALS] = cbHideTotals.isChecked
json[DRAFT_POLL_EXPIRE_DAY] = etExpireDays.text.toString() json[DRAFT_POLL_EXPIRE_DAY] = etExpireDays.text.toString()
json[DRAFT_POLL_EXPIRE_HOUR] = etExpireHours.text.toString() json[DRAFT_POLL_EXPIRE_HOUR] = etExpireHours.text.toString()
json[DRAFT_POLL_EXPIRE_MINUTE] = etExpireMinutes.text.toString() json[DRAFT_POLL_EXPIRE_MINUTE] = etExpireMinutes.text.toString()
json[DRAFT_ENQUETE_ITEMS] = strChoice.toJsonArray() json[DRAFT_ENQUETE_ITEMS] = strChoice.toJsonArray()
states.visibility?.id?.toString()?.let { json.put(DRAFT_VISIBILITY, it) }
states.inReplyToId?.putTo(json, DRAFT_REPLY_ID)
// deprecated. but still used in old draft.
// json.put(DRAFT_IS_ENQUETE, isEnquete)
PostDraft.save(System.currentTimeMillis(), json) PostDraft.save(System.currentTimeMillis(), json)
} catch (ex: Throwable) { } catch (ex: Throwable) {
ActPost.log.trace(ex) log.trace(ex)
} }
} }
@ -111,98 +110,94 @@ fun ActPost.restoreDraft(draft: JsonObject) {
launchMain { launchMain {
val listWarning = ArrayList<String>() val listWarning = ArrayList<String>()
var targetAccount: SavedAccount? = null var targetAccount: SavedAccount? = null
runWithProgress( runWithProgress("restore from draft", doInBackground = { progress ->
"restore from draft",
doInBackground = { progress ->
fun isTaskCancelled() = !this.coroutineContext.isActive
var content = draft.string(DRAFT_CONTENT) ?: "" fun isTaskCancelled() = !this.coroutineContext.isActive
val accountDbId = draft.long(DRAFT_ACCOUNT_DB_ID) ?: -1L
val tmpAttachmentList =
draft.jsonArray(DRAFT_ATTACHMENT_LIST)?.objectList()?.toMutableList()
val account = SavedAccount.loadAccount(this@restoreDraft, accountDbId) var content = draft.string(DRAFT_CONTENT) ?: ""
if (account == null) { val tmpAttachmentList = draft.jsonArray(DRAFT_ATTACHMENT_LIST)?.objectList()?.toMutableList()
listWarning.add(getString(R.string.account_in_draft_is_lost))
try {
if (tmpAttachmentList != null) {
// 本文からURLを除去する
tmpAttachmentList.forEach {
val textUrl = TootAttachment.decodeJson(it).text_url
if (textUrl?.isNotEmpty() == true) {
content = content.replace(textUrl, "")
}
}
tmpAttachmentList.clear()
draft[DRAFT_ATTACHMENT_LIST] = tmpAttachmentList.toJsonArray()
draft[DRAFT_CONTENT] = content
draft.remove(DRAFT_REPLY_ID)
draft.remove(DRAFT_REPLY_TEXT)
draft.remove(DRAFT_REPLY_IMAGE)
draft.remove(DRAFT_REPLY_URL)
}
} catch (ignored: JsonException) {
}
return@runWithProgress "OK"
}
targetAccount = account
// アカウントがあるなら基本的にはすべての情報を復元できるはずだが、いくつか確認が必要だ
val apiClient = TootApiClient(this@restoreDraft, callback = object : TootApiCallback {
override val isApiCancelled: Boolean
get() = isTaskCancelled()
override suspend fun publishApiProgress(s: String) {
progress.setMessageEx(s)
}
})
apiClient.account = account
states.inReplyToId?.let { inReplyToId ->
val result = apiClient.request("/api/v1/statuses/$inReplyToId")
if (isTaskCancelled()) return@runWithProgress null
val jsonObject = result?.jsonObject
if (jsonObject == null) {
listWarning.add(getString(R.string.reply_to_in_draft_is_lost))
draft.remove(DRAFT_REPLY_ID)
draft.remove(DRAFT_REPLY_TEXT)
draft.remove(DRAFT_REPLY_IMAGE)
}
}
val accountDbId = draft.long(DRAFT_ACCOUNT_DB_ID) ?: -1L
val account = SavedAccount.loadAccount(this@restoreDraft, accountDbId)
if (account == null) {
listWarning.add(getString(R.string.account_in_draft_is_lost))
try { try {
if (tmpAttachmentList != null) { if (tmpAttachmentList != null) {
// 添付メディアの存在確認 // 本文からURLを除去する
var isSomeAttachmentRemoved = false tmpAttachmentList.forEach {
val it = tmpAttachmentList.iterator() val textUrl = TootAttachment.decodeJson(it).text_url
while (it.hasNext()) {
if (isTaskCancelled()) return@runWithProgress null
val ta = TootAttachment.decodeJson(it.next())
if (ActPost.checkExist(ta.url)) continue
it.remove()
isSomeAttachmentRemoved = true
// 本文からURLを除去する
val textUrl = ta.text_url
if (textUrl?.isNotEmpty() == true) { if (textUrl?.isNotEmpty() == true) {
content = content.replace(textUrl, "") content = content.replace(textUrl, "")
} }
} }
if (isSomeAttachmentRemoved) { tmpAttachmentList.clear()
listWarning.add(getString(R.string.attachment_in_draft_is_lost)) draft[DRAFT_ATTACHMENT_LIST] = tmpAttachmentList.toJsonArray()
draft[DRAFT_ATTACHMENT_LIST] = tmpAttachmentList.toJsonArray() draft[DRAFT_CONTENT] = content
draft[DRAFT_CONTENT] = content draft.remove(DRAFT_REPLY_ID)
} draft.remove(DRAFT_REPLY_TEXT)
draft.remove(DRAFT_REPLY_IMAGE)
draft.remove(DRAFT_REPLY_URL)
} }
} catch (ex: JsonException) { } catch (ignored: JsonException) {
ActPost.log.trace(ex)
} }
"OK" return@runWithProgress "OK"
}, }
targetAccount = account
// アカウントがあるなら基本的にはすべての情報を復元できるはずだが、いくつか確認が必要だ
val apiClient = TootApiClient(this@restoreDraft, callback = object : TootApiCallback {
override val isApiCancelled: Boolean
get() = isTaskCancelled()
override suspend fun publishApiProgress(s: String) {
progress.setMessageEx(s)
}
})
apiClient.account = account
// 返信ステータスが存在するかどうか
EntityId.from(draft, DRAFT_REPLY_ID)?.let { inReplyToId ->
val result = apiClient.request("/api/v1/statuses/$inReplyToId")
if (isTaskCancelled()) return@runWithProgress null
if (result?.jsonObject == null) {
listWarning.add(getString(R.string.reply_to_in_draft_is_lost))
draft.remove(DRAFT_REPLY_ID)
draft.remove(DRAFT_REPLY_TEXT)
draft.remove(DRAFT_REPLY_IMAGE)
}
}
try {
if (tmpAttachmentList != null) {
// 添付メディアの存在確認
var isSomeAttachmentRemoved = false
val it = tmpAttachmentList.iterator()
while (it.hasNext()) {
if (isTaskCancelled()) return@runWithProgress null
val ta = TootAttachment.decodeJson(it.next())
if (ActPost.checkExist(ta.url)) continue
it.remove()
isSomeAttachmentRemoved = true
// 本文からURLを除去する
val textUrl = ta.text_url
if (textUrl?.isNotEmpty() == true) {
content = content.replace(textUrl, "")
}
}
if (isSomeAttachmentRemoved) {
listWarning.add(getString(R.string.attachment_in_draft_is_lost))
draft[DRAFT_ATTACHMENT_LIST] = tmpAttachmentList.toJsonArray()
draft[DRAFT_CONTENT] = content
}
}
} catch (ex: JsonException) {
log.trace(ex)
}
"OK"
},
afterProc = { result -> afterProc = { result ->
// cancelled. // cancelled.
if (result == null) return@runWithProgress if (result == null) return@runWithProgress
@ -213,16 +208,12 @@ fun ActPost.restoreDraft(draft: JsonObject) {
val nsfwChecked = draft.optBoolean(DRAFT_NSFW_CHECK) val nsfwChecked = draft.optBoolean(DRAFT_NSFW_CHECK)
val tmpAttachmentList = draft.jsonArray(DRAFT_ATTACHMENT_LIST) val tmpAttachmentList = draft.jsonArray(DRAFT_ATTACHMENT_LIST)
val replyId = EntityId.from(draft, DRAFT_REPLY_ID) val replyId = EntityId.from(draft, DRAFT_REPLY_ID)
val replyText = draft.string(DRAFT_REPLY_TEXT)
val replyImage = draft.string(DRAFT_REPLY_IMAGE)
val replyUrl = draft.string(DRAFT_REPLY_URL)
val draftVisibility = TootVisibility
.parseSavedVisibility(draft.string(DRAFT_VISIBILITY))
val evEmoji = DecodeOptions( val draftVisibility = TootVisibility.parseSavedVisibility(draft.string(DRAFT_VISIBILITY))
this@restoreDraft,
decodeEmoji = true val evEmoji = DecodeOptions(this@restoreDraft, decodeEmoji = true)
).decodeEmoji(content) .decodeEmoji(content)
etContent.setText(evEmoji) etContent.setText(evEmoji)
etContent.setSelection(evEmoji.length) etContent.setSelection(evEmoji.length)
etContentWarning.setText(contentWarning) etContentWarning.setText(contentWarning)
@ -274,9 +265,9 @@ fun ActPost.restoreDraft(draft: JsonObject) {
if (replyId != null) { if (replyId != null) {
states.inReplyToId = replyId states.inReplyToId = replyId
states.inReplyToText = replyText states.inReplyToText = draft.string(DRAFT_REPLY_TEXT)
states.inReplyToImage = replyImage states.inReplyToImage = draft.string(DRAFT_REPLY_IMAGE)
states.inReplyToUrl = replyUrl states.inReplyToUrl = draft.string(DRAFT_REPLY_URL)
} }
showContentWarningEnabled() showContentWarningEnabled()
@ -327,7 +318,7 @@ fun ActPost.initializeFromRedraftStatus(account: SavedAccount, jsonText: String)
} }
} }
} catch (ex: Throwable) { } catch (ex: Throwable) {
ActPost.log.trace(ex) log.trace(ex)
} }
} }
@ -400,6 +391,6 @@ fun ActPost.initializeFromRedraftStatus(account: SavedAccount, jsonText: String)
} }
} }
} catch (ex: Throwable) { } catch (ex: Throwable) {
ActPost.log.trace(ex) log.trace(ex)
} }
} }

View File

@ -11,10 +11,13 @@ import jp.juggler.subwaytooter.api.runApiTask
import jp.juggler.subwaytooter.api.syncStatus import jp.juggler.subwaytooter.api.syncStatus
import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.DecodeOptions import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.util.LogCategory
import jp.juggler.util.decodeJsonObject import jp.juggler.util.decodeJsonObject
import jp.juggler.util.launchMain import jp.juggler.util.launchMain
import jp.juggler.util.showToast import jp.juggler.util.showToast
private val log = LogCategory("ActPostReply")
fun ActPost.showQuotedRenote() { fun ActPost.showQuotedRenote() {
cbQuote.visibility = if (states.inReplyToId != null) View.VISIBLE else View.GONE cbQuote.visibility = if (states.inReplyToId != null) View.VISIBLE else View.GONE
} }
@ -102,6 +105,7 @@ fun ActPost.initializeFromReplyStatus(account: SavedAccount, jsonText: String) {
} }
// リプライ表示をつける // リプライ表示をつける
states.inReplyToId = replyStatus.id
states.inReplyToText = replyStatus.content states.inReplyToText = replyStatus.content
states.inReplyToImage = replyStatus.account.avatar_static states.inReplyToImage = replyStatus.account.avatar_static
states.inReplyToUrl = replyStatus.url states.inReplyToUrl = replyStatus.url
@ -127,18 +131,15 @@ fun ActPost.initializeFromReplyStatus(account: SavedAccount, jsonText: String) {
if (TootVisibility.WebSetting == states.visibility) { if (TootVisibility.WebSetting == states.visibility) {
// 「Web設定に合わせる」だった場合は無条件にリプライ元の公開範囲に変更する // 「Web設定に合わせる」だった場合は無条件にリプライ元の公開範囲に変更する
states.visibility = sample states.visibility = sample
} else if (TootVisibility.isVisibilitySpoilRequired( } else if (TootVisibility.isVisibilitySpoilRequired(states.visibility, sample)) {
states.visibility, sample
)
) {
// デフォルトの方が公開範囲が大きい場合、リプライ元に合わせて公開範囲を狭める // デフォルトの方が公開範囲が大きい場合、リプライ元に合わせて公開範囲を狭める
states.visibility = sample states.visibility = sample
} }
} catch (ex: Throwable) { } catch (ex: Throwable) {
ActPost.log.trace(ex) log.trace(ex)
} }
} catch (ex: Throwable) { } catch (ex: Throwable) {
ActPost.log.trace(ex) log.trace(ex)
} }
} }

View File

@ -8,10 +8,13 @@ import jp.juggler.subwaytooter.api.entity.parseItem
import jp.juggler.subwaytooter.dialog.DlgDateTime import jp.juggler.subwaytooter.dialog.DlgDateTime
import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.PostAttachment import jp.juggler.subwaytooter.util.PostAttachment
import jp.juggler.util.LogCategory
import jp.juggler.util.cast import jp.juggler.util.cast
import jp.juggler.util.decodeJsonObject import jp.juggler.util.decodeJsonObject
import jp.juggler.util.notEmpty import jp.juggler.util.notEmpty
private val log = LogCategory("ActPostSchedule")
fun ActPost.showSchedule() { fun ActPost.showSchedule() {
tvSchedule.text = when (states.timeSchedule) { tvSchedule.text = when (states.timeSchedule) {
0L -> getString(R.string.unspecified) 0L -> getString(R.string.unspecified)
@ -37,7 +40,7 @@ fun ActPost.initializeFromScheduledStatus(account: SavedAccount, jsonText: Strin
::TootScheduled, ::TootScheduled,
TootParser(this, account), TootParser(this, account),
jsonText.decodeJsonObject(), jsonText.decodeJsonObject(),
ActPost.log log
) ?: error("initializeFromScheduledStatus: parse failed.") ) ?: error("initializeFromScheduledStatus: parse failed.")
scheduledStatus = item scheduledStatus = item
@ -67,6 +70,6 @@ fun ActPost.initializeFromScheduledStatus(account: SavedAccount, jsonText: Strin
this.attachmentList.addAll(it) this.attachmentList.addAll(it)
} }
} catch (ex: Throwable) { } catch (ex: Throwable) {
ActPost.log.trace(ex) log.trace(ex)
} }
} }

View File

@ -159,7 +159,7 @@ class ActText : AppCompatActivity() {
} }
private fun search() { private fun search() {
selection.trim().notEmpty()?.let { selection.trim().notEmpty()?.also {
try { try {
val intent = Intent(Intent.ACTION_WEB_SEARCH) val intent = Intent(Intent.ACTION_WEB_SEARCH)
intent.putExtra(SearchManager.QUERY, it) intent.putExtra(SearchManager.QUERY, it)

View File

@ -18,14 +18,15 @@ import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
import com.bumptech.glide.load.engine.executor.GlideExecutor import com.bumptech.glide.load.engine.executor.GlideExecutor
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import jp.juggler.subwaytooter.emoji.EmojiMap
import jp.juggler.subwaytooter.api.TootApiClient import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.emoji.EmojiMap
import jp.juggler.subwaytooter.table.* import jp.juggler.subwaytooter.table.*
import jp.juggler.subwaytooter.util.CustomEmojiCache import jp.juggler.subwaytooter.util.CustomEmojiCache
import jp.juggler.subwaytooter.util.CustomEmojiLister import jp.juggler.subwaytooter.util.CustomEmojiLister
import jp.juggler.subwaytooter.util.ProgressResponseBody import jp.juggler.subwaytooter.util.ProgressResponseBody
import jp.juggler.util.* import jp.juggler.util.*
import okhttp3.* import okhttp3.*
import okhttp3.OkHttpClient
import org.conscrypt.Conscrypt import org.conscrypt.Conscrypt
import ru.gildor.coroutines.okhttp.await import ru.gildor.coroutines.okhttp.await
import java.io.File import java.io.File
@ -36,6 +37,8 @@ import java.net.CookiePolicy
import java.security.Security import java.security.Security
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.logging.Level
import java.util.logging.Logger
import kotlin.math.max import kotlin.math.max
class App1 : Application() { class App1 : Application() {
@ -370,6 +373,9 @@ class App1 : Application() {
log.d("create okhttp client") log.d("create okhttp client")
run { run {
Logger.getLogger(OkHttpClient::class.java.name).level = Level.FINE
// API用のHTTP設定はキャッシュを使わない // API用のHTTP設定はキャッシュを使わない
ok_http_client = prepareOkHttp(60, 60) ok_http_client = prepareOkHttp(60, 60)
.build() .build()
@ -540,19 +546,19 @@ class App1 : Application() {
val call = ok_http_client2.newCall(request_builder.build()) val call = ok_http_client2.newCall(request_builder.build())
response = call.await() response = call.await()
} catch (ex: Throwable) { } catch (ex: Throwable) {
log.e(ex, "getHttp network error.") log.e(ex, "getHttp network error. $url")
return null return null
} }
if (!response.isSuccessful) { if (!response.isSuccessful) {
log.e(TootApiClient.formatResponse(response, "getHttp response error.")) log.e(TootApiClient.formatResponse(response, "getHttp response error. $url"))
return null return null
} }
return try { return try {
response.body?.bytes() response.body?.bytes()
} catch (ex: Throwable) { } catch (ex: Throwable) {
log.e(ex, "getHttp content error.") log.e(ex, "getHttp content error. $url")
null null
} }
} }
@ -579,19 +585,19 @@ class App1 : Application() {
val call = ok_http_client2.newCall(request_builder.build()) val call = ok_http_client2.newCall(request_builder.build())
response = call.await() response = call.await()
} catch (ex: Throwable) { } catch (ex: Throwable) {
log.e(ex, "getHttp network error.") log.e(ex, "getHttp network error. $url")
return null return null
} }
if (!response.isSuccessful) { if (!response.isSuccessful) {
log.e(TootApiClient.formatResponse(response, "getHttp response error.")) log.e(TootApiClient.formatResponse(response, "getHttp response error. $url"))
return null return null
} }
return try { return try {
response.body?.string() response.body?.string()
} catch (ex: Throwable) { } catch (ex: Throwable) {
log.e(ex, "getHttp content error.") log.e(ex, "getHttp content error. $url")
null null
} }
} }

View File

@ -8,6 +8,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.widget.* import android.widget.*
import androidx.annotation.IdRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import jp.juggler.subwaytooter.action.* import jp.juggler.subwaytooter.action.*
import jp.juggler.subwaytooter.api.entity.* import jp.juggler.subwaytooter.api.entity.*
@ -43,38 +44,20 @@ internal class DlgContextMenu(
private val dialog: Dialog private val dialog: Dialog
private val viewRoot = activity.layoutInflater.inflate(R.layout.dlg_context_menu, null, false) private val viewRoot = activity.layoutInflater.inflate(R.layout.dlg_context_menu, null, false)
private fun <T : View> fv(@IdRes id: Int): T = viewRoot.findViewById(id)
private val btnCrossAccountActionsForStatus: Button = private val btnGroupStatusCrossAccount: Button = fv(R.id.btnGroupStatusCrossAccount)
viewRoot.findViewById(R.id.btnCrossAccountActionsForStatus) private val llGroupStatusCrossAccount: View = fv(R.id.llGroupStatusCrossAccount)
private val llCrossAccountActionsForStatus: View = private val btnGroupStatusAround: Button = fv(R.id.btnGroupStatusAround)
viewRoot.findViewById(R.id.llCrossAccountActionsForStatus) private val llGroupStatusAround: View = fv(R.id.llGroupStatusAround)
private val btnGroupStatusByMe: Button = fv(R.id.btnGroupStatusByMe)
private val btnCrossAccountActionsForAccount: Button = private val llGroupStatusByMe: View = fv(R.id.llGroupStatusByMe)
viewRoot.findViewById(R.id.btnCrossAccountActionsForAccount) private val btnGroupStatusExtra: Button = fv(R.id.btnGroupStatusExtra)
private val llCrossAccountActionsForAccount: View = private val llGroupStatusExtra: View = fv(R.id.llGroupStatusExtra)
viewRoot.findViewById(R.id.llCrossAccountActionsForAccount) private val btnGroupUserCrossAccount: Button = fv(R.id.btnGroupUserCrossAccount)
private val llGroupUserCrossAccount: View = fv(R.id.llGroupUserCrossAccount)
private val btnAroundThisToot: Button = private val btnGroupUserExtra: Button = fv(R.id.btnGroupUserExtra)
viewRoot.findViewById(R.id.btnAroundThisToot) private val llGroupUserExtra: View = fv(R.id.llGroupUserExtra)
private val llAroundThisToot: View =
viewRoot.findViewById(R.id.llAroundThisToot)
private val btnYourToot: Button =
viewRoot.findViewById(R.id.btnYourToot)
private val llYourToot: View =
viewRoot.findViewById(R.id.llYourToot)
private val btnStatusExtraAction: Button =
viewRoot.findViewById(R.id.btnStatusExtraAction)
private val llStatusExtraAction: View =
viewRoot.findViewById(R.id.llStatusExtraAction)
private val btnAccountExtraAction: Button =
viewRoot.findViewById(R.id.btnAccountExtraAction)
private val llAccountExtraAction: View =
viewRoot.findViewById(R.id.llAccountExtraAction)
private val btnPostNotification: Button = viewRoot.findViewById(R.id.btnPostNotification)
init { init {
val columnType = column.type val columnType = column.type
@ -93,136 +76,121 @@ internal class DlgContextMenu(
dialog.setCancelable(true) dialog.setCancelable(true)
dialog.setCanceledOnTouchOutside(true) dialog.setCanceledOnTouchOutside(true)
val llStatus: View = viewRoot.findViewById(R.id.llStatus) val btnAccountWebPage: View = fv(R.id.btnAccountWebPage)
val btnStatusWebPage: View = viewRoot.findViewById(R.id.btnStatusWebPage) val btnAroundAccountTL: View = fv(R.id.btnAroundAccountTL)
val btnText: View = viewRoot.findViewById(R.id.btnText) val btnAroundFTL: View = fv(R.id.btnAroundFTL)
val btnFavouriteAnotherAccount: View = val btnAroundLTL: View = fv(R.id.btnAroundLTL)
viewRoot.findViewById(R.id.btnFavouriteAnotherAccount) val btnAvatarImage: View = fv(R.id.btnAvatarImage)
val btnBookmarkAnotherAccount: View = val btnBlock: ImageView = fv(R.id.btnBlock)
viewRoot.findViewById(R.id.btnBookmarkAnotherAccount) val btnBookmarkAnotherAccount: View = fv(R.id.btnBookmarkAnotherAccount)
val btnBoostAnotherAccount: View = viewRoot.findViewById(R.id.btnBoostAnotherAccount) val btnBoostAnotherAccount: View = fv(R.id.btnBoostAnotherAccount)
val btnReactionAnotherAccount: View = viewRoot.findViewById(R.id.btnReactionAnotherAccount) val btnBoostedBy: View = fv(R.id.btnBoostedBy)
val btnReplyAnotherAccount: View = viewRoot.findViewById(R.id.btnReplyAnotherAccount) val btnBoostWithVisibility: Button = fv(R.id.btnBoostWithVisibility)
val btnQuoteAnotherAccount: View = viewRoot.findViewById(R.id.btnQuoteAnotherAccount) val btnConversationAnotherAccount: View = fv(R.id.btnConversationAnotherAccount)
val btnQuoteTootBT: View = viewRoot.findViewById(R.id.btnQuoteTootBT) val btnConversationMute: Button = fv(R.id.btnConversationMute)
val btnDelete: View = viewRoot.findViewById(R.id.btnDelete) val btnCopyAccountId: Button = fv(R.id.btnCopyAccountId)
val btnRedraft: View = viewRoot.findViewById(R.id.btnRedraft) val btnDelete: View = fv(R.id.btnDelete)
val btnDeleteSuggestion: View = fv(R.id.btnDeleteSuggestion)
val btnReportStatus: View = viewRoot.findViewById(R.id.btnReportStatus) val btnDomainBlock: Button = fv(R.id.btnDomainBlock)
val btnReportUser: View = viewRoot.findViewById(R.id.btnReportUser) val btnDomainTimeline: View = fv(R.id.btnDomainTimeline)
val btnMuteApp: Button = viewRoot.findViewById(R.id.btnMuteApp) val btnEndorse: Button = fv(R.id.btnEndorse)
val llAccountActionBar: View = viewRoot.findViewById(R.id.llAccountActionBar) val btnFavouriteAnotherAccount: View = fv(R.id.btnFavouriteAnotherAccount)
val btnFollow: ImageButton = viewRoot.findViewById(R.id.btnFollow) val btnFavouritedBy: View = fv(R.id.btnFavouritedBy)
val btnFollow: ImageButton = fv(R.id.btnFollow)
val btnMute: ImageView = viewRoot.findViewById(R.id.btnMute) val btnFollowFromAnotherAccount: View = fv(R.id.btnFollowFromAnotherAccount)
val btnBlock: ImageView = viewRoot.findViewById(R.id.btnBlock) val btnFollowRequestNG: View = fv(R.id.btnFollowRequestNG)
val btnProfile: View = viewRoot.findViewById(R.id.btnProfile) val btnFollowRequestOK: View = fv(R.id.btnFollowRequestOK)
val btnSendMessage: View = viewRoot.findViewById(R.id.btnSendMessage) val btnHideBoost: View = fv(R.id.btnHideBoost)
val btnAccountWebPage: View = viewRoot.findViewById(R.id.btnAccountWebPage) val btnHideFavourite: View = fv(R.id.btnHideFavourite)
val btnFollowRequestOK: View = viewRoot.findViewById(R.id.btnFollowRequestOK) val btnInstanceInformation: Button = fv(R.id.btnInstanceInformation)
val btnFollowRequestNG: View = viewRoot.findViewById(R.id.btnFollowRequestNG) val btnListMemberAddRemove: View = fv(R.id.btnListMemberAddRemove)
val btnDeleteSuggestion: View = viewRoot.findViewById(R.id.btnDeleteSuggestion) val btnMute: ImageView = fv(R.id.btnMute)
val btnFollowFromAnotherAccount: View = val btnMuteApp: Button = fv(R.id.btnMuteApp)
viewRoot.findViewById(R.id.btnFollowFromAnotherAccount) val btnNotificationDelete: View = fv(R.id.btnNotificationDelete)
val btnSendMessageFromAnotherAccount: View = val btnNotificationFrom: Button = fv(R.id.btnNotificationFrom)
viewRoot.findViewById(R.id.btnSendMessageFromAnotherAccount) val btnOpenAccountInAdminWebUi: Button = fv(R.id.btnOpenAccountInAdminWebUi)
val btnOpenProfileFromAnotherAccount: View = val btnOpenInstanceInAdminWebUi: Button = fv(R.id.btnOpenInstanceInAdminWebUi)
viewRoot.findViewById(R.id.btnOpenProfileFromAnotherAccount) val btnOpenProfileFromAnotherAccount: View = fv(R.id.btnOpenProfileFromAnotherAccount)
val btnDomainBlock: Button = viewRoot.findViewById(R.id.btnDomainBlock) val btnOpenTimeline: Button = fv(R.id.btnOpenTimeline)
val btnInstanceInformation: Button = viewRoot.findViewById(R.id.btnInstanceInformation) val btnProfile: View = fv(R.id.btnProfile)
val btnProfileDirectory: Button = viewRoot.findViewById(R.id.btnProfileDirectory) val btnProfileDirectory: Button = fv(R.id.btnProfileDirectory)
val ivFollowedBy: ImageView = viewRoot.findViewById(R.id.ivFollowedBy) val btnProfilePin: View = fv(R.id.btnProfilePin)
val btnOpenTimeline: Button = viewRoot.findViewById(R.id.btnOpenTimeline) val btnProfileUnpin: View = fv(R.id.btnProfileUnpin)
val btnConversationAnotherAccount: View = val btnQuoteAnotherAccount: View = fv(R.id.btnQuoteAnotherAccount)
viewRoot.findViewById(R.id.btnConversationAnotherAccount) val btnQuoteTootBT: View = fv(R.id.btnQuoteTootBT)
val btnAvatarImage: View = viewRoot.findViewById(R.id.btnAvatarImage) val btnReactionAnotherAccount: View = fv(R.id.btnReactionAnotherAccount)
val btnRedraft: View = fv(R.id.btnRedraft)
val llNotification: View = viewRoot.findViewById(R.id.llNotification) val btnReplyAnotherAccount: View = fv(R.id.btnReplyAnotherAccount)
val btnNotificationDelete: View = viewRoot.findViewById(R.id.btnNotificationDelete) val btnReportStatus: View = fv(R.id.btnReportStatus)
val btnConversationMute: Button = viewRoot.findViewById(R.id.btnConversationMute) val btnReportUser: View = fv(R.id.btnReportUser)
val btnSendMessage: View = fv(R.id.btnSendMessage)
val btnHideBoost: View = viewRoot.findViewById(R.id.btnHideBoost) val btnSendMessageFromAnotherAccount: View = fv(R.id.btnSendMessageFromAnotherAccount)
val btnShowBoost: View = viewRoot.findViewById(R.id.btnShowBoost) val btnShowBoost: View = fv(R.id.btnShowBoost)
val btnHideFavourite: View = viewRoot.findViewById(R.id.btnHideFavourite) val btnShowFavourite: View = fv(R.id.btnShowFavourite)
val btnShowFavourite: View = viewRoot.findViewById(R.id.btnShowFavourite) val btnStatusWebPage: View = fv(R.id.btnStatusWebPage)
val btnText: View = fv(R.id.btnText)
val btnListMemberAddRemove: View = viewRoot.findViewById(R.id.btnListMemberAddRemove) val ivFollowedBy: ImageView = fv(R.id.ivFollowedBy)
val btnEndorse: Button = viewRoot.findViewById(R.id.btnEndorse) val llAccountActionBar: View = fv(R.id.llAccountActionBar)
val llLinks: LinearLayout = fv(R.id.llLinks)
val btnAroundAccountTL: View = viewRoot.findViewById(R.id.btnAroundAccountTL) val llNotification: View = fv(R.id.llNotification)
val btnAroundLTL: View = viewRoot.findViewById(R.id.btnAroundLTL) val llStatus: View = fv(R.id.llStatus)
val btnAroundFTL: View = viewRoot.findViewById(R.id.btnAroundFTL) val btnStatusNotification: Button = fv(R.id.btnStatusNotification)
val btnCopyAccountId: Button = viewRoot.findViewById(R.id.btnCopyAccountId)
val btnOpenAccountInAdminWebUi: Button =
viewRoot.findViewById(R.id.btnOpenAccountInAdminWebUi)
val btnOpenInstanceInAdminWebUi: Button =
viewRoot.findViewById(R.id.btnOpenInstanceInAdminWebUi)
val btnBoostWithVisibility: Button = viewRoot.findViewById(R.id.btnBoostWithVisibility)
val llLinks: LinearLayout = viewRoot.findViewById(R.id.llLinks)
val btnNotificationFrom: Button = viewRoot.findViewById(R.id.btnNotificationFrom)
val btnProfilePin = viewRoot.findViewById<View>(R.id.btnProfilePin)
val btnProfileUnpin = viewRoot.findViewById<View>(R.id.btnProfileUnpin)
val btnBoostedBy = viewRoot.findViewById<View>(R.id.btnBoostedBy)
val btnFavouritedBy = viewRoot.findViewById<View>(R.id.btnFavouritedBy)
val btnDomainTimeline = viewRoot.findViewById<View>(R.id.btnDomainTimeline)
arrayOf( arrayOf(
btnNotificationFrom, btnAccountWebPage,
btnAroundAccountTL, btnAroundAccountTL,
btnAroundLTL,
btnAroundFTL, btnAroundFTL,
btnStatusWebPage, btnAroundLTL,
btnText, btnAvatarImage,
btnFavouriteAnotherAccount, btnBlock,
btnBookmarkAnotherAccount, btnBookmarkAnotherAccount,
btnBoostAnotherAccount, btnBoostAnotherAccount,
btnReactionAnotherAccount, btnBoostedBy,
btnReplyAnotherAccount, btnBoostWithVisibility,
btnQuoteAnotherAccount,
btnQuoteTootBT,
btnReportStatus,
btnReportUser,
btnMuteApp,
btnDelete,
btnRedraft,
btnFollow,
btnMute,
btnBlock,
btnProfile,
btnSendMessage,
btnAccountWebPage,
btnFollowRequestOK,
btnFollowRequestNG,
btnDeleteSuggestion,
btnFollowFromAnotherAccount,
btnSendMessageFromAnotherAccount,
btnOpenProfileFromAnotherAccount,
btnOpenTimeline,
btnConversationAnotherAccount, btnConversationAnotherAccount,
btnAvatarImage,
btnNotificationDelete,
btnConversationMute, btnConversationMute,
btnHideBoost,
btnShowBoost,
btnHideFavourite,
btnShowFavourite,
btnListMemberAddRemove,
btnInstanceInformation,
btnProfileDirectory,
btnDomainBlock,
btnEndorse,
btnCopyAccountId, btnCopyAccountId,
btnDelete,
btnDeleteSuggestion,
btnDomainBlock,
btnDomainTimeline,
btnEndorse,
btnFavouriteAnotherAccount,
btnFavouritedBy,
btnFollow,
btnFollowFromAnotherAccount,
btnFollowRequestNG,
btnFollowRequestOK,
btnHideBoost,
btnHideFavourite,
btnInstanceInformation,
btnListMemberAddRemove,
btnMute,
btnMuteApp,
btnNotificationDelete,
btnNotificationFrom,
btnOpenAccountInAdminWebUi, btnOpenAccountInAdminWebUi,
btnOpenInstanceInAdminWebUi, btnOpenInstanceInAdminWebUi,
btnBoostWithVisibility, btnOpenProfileFromAnotherAccount,
btnOpenTimeline,
btnProfile,
btnProfileDirectory,
btnProfilePin, btnProfilePin,
btnProfileUnpin, btnProfileUnpin,
btnBoostedBy, btnQuoteAnotherAccount,
btnFavouritedBy, btnQuoteTootBT,
btnDomainTimeline, btnReactionAnotherAccount,
btnPostNotification, btnRedraft,
btnReplyAnotherAccount,
btnReportStatus,
btnReportUser,
btnSendMessage,
btnSendMessageFromAnotherAccount,
btnShowBoost,
btnShowFavourite,
btnStatusNotification,
btnStatusWebPage,
btnText,
viewRoot.findViewById(R.id.btnQuoteUrlStatus), viewRoot.findViewById(R.id.btnQuoteUrlStatus),
viewRoot.findViewById(R.id.btnTranslate), viewRoot.findViewById(R.id.btnTranslate),
@ -231,21 +199,17 @@ internal class DlgContextMenu(
viewRoot.findViewById(R.id.btnShareUrlAccount), viewRoot.findViewById(R.id.btnShareUrlAccount),
viewRoot.findViewById(R.id.btnQuoteName) viewRoot.findViewById(R.id.btnQuoteName)
).forEach { ).forEach { it.setOnClickListener(this) }
it.setOnClickListener(this@DlgContextMenu)
}
arrayOf( arrayOf(
btnFollow,
btnProfile,
btnMute,
btnBlock, btnBlock,
btnSendMessage, btnFollow,
btnMute,
btnProfile,
btnQuoteAnotherAccount, btnQuoteAnotherAccount,
btnQuoteTootBT, btnQuoteTootBT,
).forEach { btnSendMessage,
it.setOnLongClickListener(this) ).forEach { it.setOnLongClickListener(this) }
}
val accountList = SavedAccount.loadAccountList(activity) val accountList = SavedAccount.loadAccountList(activity)
// final ArrayList< SavedAccount > account_list_non_pseudo_same_instance = new ArrayList<>(); // final ArrayList< SavedAccount > account_list_non_pseudo_same_instance = new ArrayList<>();
@ -305,7 +269,7 @@ internal class DlgContextMenu(
} }
llLinks.vg(llLinks.childCount > 1) llLinks.vg(llLinks.childCount > 1)
btnYourToot.vg(statusByMe) btnGroupStatusByMe.vg(statusByMe)
btnQuoteTootBT.vg(status.reblogParent != null) btnQuoteTootBT.vg(status.reblogParent != null)
@ -448,7 +412,7 @@ internal class DlgContextMenu(
btnReportUser.vg(!(accessInfo.isPseudo || accessInfo.isMe(who))) btnReportUser.vg(!(accessInfo.isPseudo || accessInfo.isMe(who)))
btnPostNotification.vg(!accessInfo.isPseudo && accessInfo.isMastodon && relation.following) btnStatusNotification.vg(!accessInfo.isPseudo && accessInfo.isMastodon && relation.following)
?.let { ?.let {
it.text = when (relation.notifying) { it.text = when (relation.notifying) {
true -> activity.getString(R.string.stop_notify_posts_from_this_user) true -> activity.getString(R.string.stop_notify_posts_from_this_user)
@ -522,12 +486,12 @@ internal class DlgContextMenu(
btnListMemberAddRemove.visibility = View.VISIBLE btnListMemberAddRemove.visibility = View.VISIBLE
updateGroup(btnCrossAccountActionsForStatus, llCrossAccountActionsForStatus) updateGroup(btnGroupStatusCrossAccount, llGroupStatusCrossAccount)
updateGroup(btnCrossAccountActionsForAccount, llCrossAccountActionsForAccount) updateGroup(btnGroupUserCrossAccount, llGroupUserCrossAccount)
updateGroup(btnAroundThisToot, llAroundThisToot) updateGroup(btnGroupStatusAround, llGroupStatusAround)
updateGroup(btnYourToot, llYourToot) updateGroup(btnGroupStatusByMe, llGroupStatusByMe)
updateGroup(btnStatusExtraAction, llStatusExtraAction) updateGroup(btnGroupStatusExtra, llGroupStatusExtra)
updateGroup(btnAccountExtraAction, llAccountExtraAction) updateGroup(btnGroupUserExtra, llGroupUserExtra)
} }
fun show() { fun show() {
@ -585,35 +549,35 @@ internal class DlgContextMenu(
} }
fun onClickUpdateGroup(v: View): Boolean = when (v.id) { fun onClickUpdateGroup(v: View): Boolean = when (v.id) {
R.id.btnCrossAccountActionsForStatus -> updateGroup( R.id.btnGroupStatusCrossAccount -> updateGroup(
btnCrossAccountActionsForStatus, btnGroupStatusCrossAccount,
llCrossAccountActionsForStatus, llGroupStatusCrossAccount,
toggle = true toggle = true
) )
R.id.btnCrossAccountActionsForAccount -> updateGroup( R.id.btnGroupUserCrossAccount -> updateGroup(
btnCrossAccountActionsForAccount, btnGroupUserCrossAccount,
llCrossAccountActionsForAccount, llGroupUserCrossAccount,
toggle = true toggle = true
) )
R.id.btnAroundThisToot -> updateGroup( R.id.btnGroupStatusAround -> updateGroup(
btnAroundThisToot, btnGroupStatusAround,
llAroundThisToot, llGroupStatusAround,
toggle = true toggle = true
) )
R.id.btnYourToot -> updateGroup( R.id.btnGroupStatusByMe -> updateGroup(
btnYourToot, btnGroupStatusByMe,
llYourToot, llGroupStatusByMe,
toggle = true toggle = true
) )
R.id.btnStatusExtraAction -> updateGroup( R.id.btnGroupStatusExtra -> updateGroup(
btnStatusExtraAction, btnGroupStatusExtra,
llStatusExtraAction, llGroupStatusExtra,
toggle = true toggle = true
) )
R.id.btnAccountExtraAction -> updateGroup( R.id.btnGroupUserExtra -> updateGroup(
btnAccountExtraAction, btnGroupUserExtra,
llAccountExtraAction, llGroupUserExtra,
toggle = true toggle = true
) )
else -> false else -> false
@ -671,7 +635,7 @@ internal class DlgContextMenu(
R.id.btnOpenAccountInAdminWebUi -> openBrowser("https://${accessInfo.apiHost.ascii}/admin/accounts/${who.id}") R.id.btnOpenAccountInAdminWebUi -> openBrowser("https://${accessInfo.apiHost.ascii}/admin/accounts/${who.id}")
R.id.btnOpenInstanceInAdminWebUi -> openBrowser("https://${accessInfo.apiHost.ascii}/admin/instances/${who.apDomain.ascii}") R.id.btnOpenInstanceInAdminWebUi -> openBrowser("https://${accessInfo.apiHost.ascii}/admin/instances/${who.apDomain.ascii}")
R.id.btnNotificationFrom -> clickNotificationFrom(pos, accessInfo, who) R.id.btnNotificationFrom -> clickNotificationFrom(pos, accessInfo, who)
R.id.btnPostNotification -> clickStatusNotification(accessInfo, who, relation) R.id.btnStatusNotification -> clickStatusNotification(accessInfo, who, relation)
R.id.btnQuoteUrlAccount -> openPost(who.url?.notEmpty()) R.id.btnQuoteUrlAccount -> openPost(who.url?.notEmpty())
R.id.btnShareUrlAccount -> shareText(who.url?.notEmpty()) R.id.btnShareUrlAccount -> shareText(who.url?.notEmpty())
else -> return false else -> return false
@ -740,36 +704,33 @@ internal class DlgContextMenu(
override fun onLongClick(v: View): Boolean { override fun onLongClick(v: View): Boolean {
val whoRef = this.whoRef val whoRef = this.whoRef
val who = whoRef?.get() val who = whoRef?.get()
when (v.id) {
R.id.btnFollow -> { with(activity) {
dialog.dismissSafe() val pos = nextPosition(column)
activity.followFromAnotherAccount(
activity.nextPosition(column), when (v.id) {
accessInfo, // events don't close dialog
who R.id.btnMute -> userMuteFromAnotherAccount(who, accessInfo)
) R.id.btnBlock -> userBlockFromAnotherAccount(who, accessInfo)
R.id.btnQuoteAnotherAccount -> quoteFromAnotherAccount(accessInfo, status)
R.id.btnQuoteTootBT -> quoteFromAnotherAccount(accessInfo, status?.reblogParent)
// events close dialog before action
R.id.btnFollow -> {
dialog.dismissSafe()
followFromAnotherAccount(pos, accessInfo, who)
}
R.id.btnProfile -> {
dialog.dismissSafe()
userProfileFromAnotherAccount(pos, accessInfo, who)
}
R.id.btnSendMessage -> {
dialog.dismissSafe()
mentionFromAnotherAccount(accessInfo, who)
}
else -> return false
} }
R.id.btnProfile -> {
dialog.dismissSafe()
activity.userProfileFromAnotherAccount(
activity.nextPosition(column),
accessInfo,
who
)
}
R.id.btnSendMessage -> {
dialog.dismissSafe()
activity.mentionFromAnotherAccount(accessInfo, who)
}
R.id.btnMute -> activity.userMuteFromAnotherAccount(who, accessInfo)
R.id.btnBlock -> activity.userBlockFromAnotherAccount(who, accessInfo)
R.id.btnQuoteAnotherAccount -> activity.quoteFromAnotherAccount(accessInfo, status)
R.id.btnQuoteTootBT -> activity.quoteFromAnotherAccount(accessInfo, status?.reblogParent)
else -> return false
} }
return true return true
} }

View File

@ -0,0 +1,35 @@
package jp.juggler.subwaytooter
import android.content.Context
import android.content.SharedPreferences
import androidx.annotation.RawRes
import jp.juggler.util.decodeUTF8
import jp.juggler.util.digestSHA256
import jp.juggler.util.encodeBase64Url
import jp.juggler.util.loadRawResource
// 利用規約
// 同意済みかどうか調べる
// 関連データを提供する
class PrivacyPolicyChecker(
val context: Context,
val pref: SharedPreferences = context.pref(),
) {
val bytes by lazy {
@RawRes val resId = when (context.getString(R.string.language_code)) {
"ja" -> R.raw.privacy_policy_ja
"fr" -> R.raw.privacy_policy_fr
else -> R.raw.privacy_policy_en
}
context.loadRawResource(resId)
}
val text by lazy { bytes.decodeUTF8() }
val digest by lazy { bytes.digestSHA256().encodeBase64Url() }
val agreed: Boolean
get() = when {
bytes.isEmpty() -> true
else -> digest == PrefS.spAgreedPrivacyPolicyDigest(pref)
}
}

View File

@ -32,7 +32,7 @@ fun ActPost.saveWindowSize() {
if (Build.VERSION.SDK_INT >= 30) { if (Build.VERSION.SDK_INT >= 30) {
// WindowMetrics#getBounds() the window size including all system bar areas // WindowMetrics#getBounds() the window size including all system bar areas
windowManager?.currentWindowMetrics?.bounds?.let { bounds -> windowManager?.currentWindowMetrics?.bounds?.let { bounds ->
ActPost.log.d("API=${Build.VERSION.SDK_INT}, WindowMetrics#getBounds() $bounds") log.d("API=${Build.VERSION.SDK_INT}, WindowMetrics#getBounds() $bounds")
PrefDevice.savePostWindowBound(this, bounds.width(), bounds.height()) PrefDevice.savePostWindowBound(this, bounds.width(), bounds.height())
} }
} else { } else {
@ -40,7 +40,7 @@ fun ActPost.saveWindowSize() {
windowManager.defaultDisplay?.let { display -> windowManager.defaultDisplay?.let { display ->
val dm = DisplayMetrics() val dm = DisplayMetrics()
display.getMetrics(dm) display.getMetrics(dm)
ActPost.log.d("API=${Build.VERSION.SDK_INT}, displayMetrics=${dm.widthPixels},${dm.heightPixels}") log.d("API=${Build.VERSION.SDK_INT}, displayMetrics=${dm.widthPixels},${dm.heightPixels}")
PrefDevice.savePostWindowBound(this, dm.widthPixels, dm.heightPixels) PrefDevice.savePostWindowBound(this, dm.widthPixels, dm.heightPixels)
} }
} }
@ -265,17 +265,15 @@ fun ActMain.shareText(text: String?) {
} }
fun ActMain.clickReply(accessInfo: SavedAccount, status: TootStatus) { fun ActMain.clickReply(accessInfo: SavedAccount, status: TootStatus) {
if (!accessInfo.isPseudo) { when {
reply(accessInfo, status) accessInfo.isPseudo -> replyFromAnotherAccount(accessInfo, status)
} else { else -> reply(accessInfo, status)
replyFromAnotherAccount(accessInfo, status)
} }
} }
fun ActMain.clickQuote(accessInfo: SavedAccount, status: TootStatus) { fun ActMain.clickQuote(accessInfo: SavedAccount, status: TootStatus) {
if (!accessInfo.isPseudo) { when {
reply(accessInfo, status, quote = true) accessInfo.isPseudo -> quoteFromAnotherAccount(accessInfo, status)
} else { else -> reply(accessInfo, status, quote = true)
quoteFromAnotherAccount(accessInfo, status)
} }
} }

View File

@ -302,7 +302,6 @@ fun ActMain.bookmark(
) )
) { ) {
bookmark( bookmark(
accessInfo, accessInfo,
statusArg, statusArg,
crossAccountMode, crossAccountMode,

View File

@ -142,15 +142,11 @@ enum class TootVisibility(
fun parseSavedVisibility(sv: String?): TootVisibility? { fun parseSavedVisibility(sv: String?): TootVisibility? {
sv ?: return null sv ?: return null
// 新しい方式ではenumのID // 新しい方式ではenumのidの文字列表現
for (v in values()) { values().find { it.id.toString() == sv }?.let { return it }
if (v.id.toString() == sv) return v
}
// 古い方式ではマストドンの公開範囲文字列かweb_setting // 古い方式ではマストドンの公開範囲文字列かweb_setting
for (v in values()) { values().find { it.strMastodon == sv }?.let { return it }
if (v.strMastodon == sv) return v
}
return null return null
} }

View File

@ -42,9 +42,6 @@ class PollingWorker private constructor(contextArg: Context) {
val log = LogCategory("PollingWorker") val log = LogCategory("PollingWorker")
// private const val FCM_SENDER_ID = "433682361381"
// private const val FCM_SCOPE = "FCM"
const val NOTIFICATION_ID = 1 const val NOTIFICATION_ID = 1
const val NOTIFICATION_ID_ERROR = 3 const val NOTIFICATION_ID_ERROR = 3
@ -72,6 +69,7 @@ class PollingWorker private constructor(contextArg: Context) {
suspend fun getFirebaseMessagingToken(context: Context): String? { suspend fun getFirebaseMessagingToken(context: Context): String? {
val prefDevice = PrefDevice.from(context) val prefDevice = PrefDevice.from(context)
// 設定ファイルに保持されていたらそれを使う // 設定ファイルに保持されていたらそれを使う
prefDevice prefDevice
.getString(PrefDevice.KEY_DEVICE_TOKEN, null) .getString(PrefDevice.KEY_DEVICE_TOKEN, null)
@ -103,20 +101,22 @@ class PollingWorker private constructor(contextArg: Context) {
// インストールIDを生成する前に、各データの通知登録キャッシュをクリアする // インストールIDを生成する前に、各データの通知登録キャッシュをクリアする
// トークンがまだ生成されていない場合、このメソッドは null を返します。 // トークンがまだ生成されていない場合、このメソッドは null を返します。
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
suspend fun prepareInstallId( suspend fun prepareInstallId(context: Context, job: JobItem? = null): String? {
context: Context, if (!PrivacyPolicyChecker(context).agreed) {
job: JobItem? = null, log.w("prepareInstallId: PrivacyPolicy not agreed.")
): String? { return null
}
val prefDevice = PrefDevice.from(context) val prefDevice = PrefDevice.from(context)
var sv = prefDevice.getString(PrefDevice.KEY_INSTALL_ID, null) prefDevice.getString(PrefDevice.KEY_INSTALL_ID, null)
if (sv?.isNotEmpty() == true) return sv ?.notEmpty()?.let { return it }
SavedAccount.clearRegistrationCache() SavedAccount.clearRegistrationCache()
try { return try {
val device_token = getFirebaseMessagingToken(context) val device_token = getFirebaseMessagingToken(context)
?: return null ?: error("getFirebaseMessagingToken returns null")
val request = Request.Builder() val request = Request.Builder()
.url("$APP_SERVER/counter") .url("$APP_SERVER/counter")
@ -124,28 +124,21 @@ class PollingWorker private constructor(contextArg: Context) {
val call = App1.ok_http_client.newCall(request) val call = App1.ok_http_client.newCall(request)
job?.currentCall = WeakReference(call) job?.currentCall = WeakReference(call)
val response = call.await() call.await().use { response ->
val body = response.body?.string()
val body = response.body?.string() if (!response.isSuccessful || body?.isEmpty() != false) {
log.e(TootApiClient.formatResponse(response, "getInstallId: get/counter failed."))
return null
}
if (!response.isSuccessful || body?.isEmpty() != false) { (device_token + UUID.randomUUID() + body).digestSHA256Base64Url()
log.e( .also { prefDevice.edit().putString(PrefDevice.KEY_INSTALL_ID, it).apply() }
TootApiClient.formatResponse(
response,
"getInstallId: get/counter failed."
)
)
return null
} }
sv = (device_token + UUID.randomUUID() + body).digestSHA256Base64Url()
prefDevice.edit().putString(PrefDevice.KEY_INSTALL_ID, sv).apply()
return sv
} catch (ex: Throwable) { } catch (ex: Throwable) {
log.trace(ex, "prepareInstallId failed.") log.trace(ex, "prepareInstallId failed.")
null
} }
return null
} }
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////

View File

@ -7,7 +7,6 @@ import android.net.Uri
import android.os.Handler import android.os.Handler
import android.os.SystemClock import android.os.SystemClock
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import jp.juggler.subwaytooter.ActPost
import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.TootApiCallback import jp.juggler.subwaytooter.api.TootApiCallback
import jp.juggler.subwaytooter.api.TootApiClient import jp.juggler.subwaytooter.api.TootApiClient
@ -477,7 +476,7 @@ class AttachmentUploader(
val isJpeg = MIME_TYPE_JPEG == mimeType val isJpeg = MIME_TYPE_JPEG == mimeType
val isPng = MIME_TYPE_PNG == mimeType val isPng = MIME_TYPE_PNG == mimeType
if (!isJpeg && !isPng) { if (!isJpeg && !isPng) {
ActPost.log.d("createOpener: source is not jpeg or png") log.d("createOpener: source is not jpeg or png")
break break
} }
@ -525,7 +524,7 @@ class AttachmentUploader(
} }
} }
} catch (ex: Throwable) { } catch (ex: Throwable) {
ActPost.log.trace(ex) log.trace(ex)
context.showToast(ex, "Resizing image failed.") context.showToast(ex, "Resizing image failed.")
} }

View File

@ -26,6 +26,7 @@ import jp.juggler.util.*
import java.util.* import java.util.*
import kotlin.math.min import kotlin.math.min
// 入力補完機能
class CompletionHelper( class CompletionHelper(
private val activity: AppCompatActivity, private val activity: AppCompatActivity,
private val pref: SharedPreferences, private val pref: SharedPreferences,
@ -36,38 +37,14 @@ class CompletionHelper(
private val reCharsNotEmoji = "[^0-9A-Za-z_-]".asciiPattern() private val reCharsNotEmoji = "[^0-9A-Za-z_-]".asciiPattern()
} }
/////////////////////////////////////////////////////////////////////////////////// interface Callback2 {
// 投稿機能はPostImplに移動した fun onTextUpdate()
// var content: String? = null fun canOpenPopup(): Boolean
// var spoilerText: String? = null }
// var visibility: TootVisibility = TootVisibility.Public
// var bNSFW = false
// var inReplyToId: EntityId? = null
// var attachmentList: ArrayList<PostAttachment>? = null
// var enqueteItems: ArrayList<String>? = null
// var pollType: TootPollsType? = null
// var pollExpireSeconds = 0
// var pollHideTotals = false
// var pollMultipleChoice = false
//
// var emojiMapCustom: HashMap<String, CustomEmoji>? = null
// var redraftStatusId: EntityId? = null
// var useQuoteToot = false
// var scheduledAt = 0L
// var scheduledId: EntityId? = null
///////////////////////////////////////////////////////////////////////////////////
// 入力補完機能
private val pickerCaptionEmoji: String by lazy { private val pickerCaptionEmoji: String by lazy {
activity.getString(R.string.open_picker_emoji) activity.getString(R.string.open_picker_emoji)
} }
// private val picker_caption_tag : String by lazy {
// activity.getString(R.string.open_picker_tag)
// }
// private val picker_caption_mention : String by lazy {
// activity.getString(R.string.open_picker_mention)
// }
private var callback2: Callback2? = null private var callback2: Callback2? = null
private var et: MyEditText? = null private var et: MyEditText? = null
@ -77,11 +54,9 @@ class CompletionHelper(
private var accessInfo: SavedAccount? = null private var accessInfo: SavedAccount? = null
private val onEmojiListLoad: (list: ArrayList<CustomEmoji>) -> Unit = private val onEmojiListLoad: (list: ArrayList<CustomEmoji>) -> Unit = {
{ if (popup?.isShowing == true) procTextChanged.run()
val popup = this@CompletionHelper.popup }
if (popup?.isShowing == true) procTextChanged.run()
}
private val procTextChanged = object : Runnable { private val procTextChanged = object : Runnable {
@ -320,24 +295,10 @@ class CompletionHelper(
return popup return popup
} }
interface Callback2 {
fun onTextUpdate()
fun canOpenPopup(): Boolean
}
fun setInstance(accessInfo: SavedAccount?) { fun setInstance(accessInfo: SavedAccount?) {
this.accessInfo = accessInfo this.accessInfo = accessInfo
accessInfo?.let { App1.custom_emoji_lister.getList(it, onEmojiListLoad) }
if (accessInfo != null) { if (popup?.isShowing == true) procTextChanged.run()
App1.custom_emoji_lister.getList(accessInfo, onEmojiListLoad)
}
val popup = this.popup
if (popup?.isShowing == true) {
procTextChanged.run()
}
} }
fun closeAcctPopup() { fun closeAcctPopup() {
@ -346,9 +307,7 @@ class CompletionHelper(
} }
fun onScrollChanged() { fun onScrollChanged() {
if (popup?.isShowing == true) { popup?.takeIf { it.isShowing }?.updatePosition()
popup?.updatePosition()
}
} }
fun onDestroy() { fun onDestroy() {

View File

@ -30,7 +30,6 @@ import jp.juggler.util.clipRange
class MyNetworkImageView : AppCompatImageView { class MyNetworkImageView : AppCompatImageView {
companion object { companion object {
internal val log = LogCategory("MyNetworkImageView") internal val log = LogCategory("MyNetworkImageView")
} }
@ -264,7 +263,7 @@ class MyNetworkImageView : AppCompatImageView {
} }
// その他のDrawable // その他のDrawable
// たとえばInstanceTickerのアイコンにSVGが使われていたらPictureDrawableになる // たとえばInstanceTickerのアイコンにSVGが使われていたらPictureDrawableになる
log.w("cornerRadius=$mCornerRadius,drawable=$resource,url=$urlLoading") // log.w("cornerRadius=$mCornerRadius,drawable=$resource,url=$urlLoading")
} }
setImageDrawable(resource) setImageDrawable(resource)

View File

@ -10,7 +10,7 @@ object ToastUtils {
private val log = LogCategory("ToastUtils") private val log = LogCategory("ToastUtils")
private var refToast: WeakReference<Toast>? = null private var refToast: WeakReference<Toast>? = null
internal fun showToastImpl(context: Context, bLong: Boolean, message: String) { internal fun showToastImpl(context: Context, bLong: Boolean, message: String): Boolean {
runOnMainLooper { runOnMainLooper {
// 前回のトーストの表示を終了する // 前回のトーストの表示を終了する
@ -41,21 +41,18 @@ object ToastUtils {
// at android.widget.Toast$TN.handleShow (Toast.java:435) // at android.widget.Toast$TN.handleShow (Toast.java:435)
// at android.widget.Toast$TN$2.handleMessage (Toast.java:345) // at android.widget.Toast$TN$2.handleMessage (Toast.java:345)
} }
return false
} }
} }
fun Context.showToast(bLong: Boolean, caption: String?) { fun Context.showToast(bLong: Boolean, caption: String?): Boolean =
ToastUtils.showToastImpl(this, bLong, caption ?: "(null)") ToastUtils.showToastImpl(this, bLong, caption ?: "(null)")
}
fun Context.showToast(ex: Throwable, caption: String) { fun Context.showToast(ex: Throwable, caption: String): Boolean =
ToastUtils.showToastImpl(this, true, ex.withCaption(caption)) ToastUtils.showToastImpl(this, true, ex.withCaption(caption))
}
fun Context.showToast(bLong: Boolean, stringId: Int, vararg args: Any) { fun Context.showToast(bLong: Boolean, stringId: Int, vararg args: Any): Boolean =
ToastUtils.showToastImpl(this, bLong, getString(stringId, *args)) ToastUtils.showToastImpl(this, bLong, getString(stringId, *args))
}
fun Context.showToast(ex: Throwable, stringId: Int, vararg args: Any) { fun Context.showToast(ex: Throwable, stringId: Int, vararg args: Any): Boolean =
ToastUtils.showToastImpl(this, true, ex.withCaption(resources, stringId, *args)) ToastUtils.showToastImpl(this, true, ex.withCaption(resources, stringId, *args))
}

View File

@ -123,7 +123,7 @@
<Button <Button
android:id="@+id/btnCrossAccountActionsForStatus" android:id="@+id/btnGroupStatusCrossAccount"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/btn_bg_transparent_round6dp" android:background="@drawable/btn_bg_transparent_round6dp"
@ -140,7 +140,7 @@
android:textSize="12sp" /> android:textSize="12sp" />
<LinearLayout <LinearLayout
android:id="@+id/llCrossAccountActionsForStatus" android:id="@+id/llGroupStatusCrossAccount"
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"
@ -262,7 +262,7 @@
</LinearLayout> </LinearLayout>
<Button <Button
android:id="@+id/btnAroundThisToot" android:id="@+id/btnGroupStatusAround"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/btn_bg_transparent_round6dp" android:background="@drawable/btn_bg_transparent_round6dp"
@ -279,7 +279,7 @@
android:textSize="12sp" /> android:textSize="12sp" />
<LinearLayout <LinearLayout
android:id="@+id/llAroundThisToot" android:id="@+id/llGroupStatusAround"
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"
@ -332,7 +332,7 @@
<Button <Button
android:id="@+id/btnYourToot" android:id="@+id/btnGroupStatusByMe"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/btn_bg_transparent_round6dp" android:background="@drawable/btn_bg_transparent_round6dp"
@ -349,7 +349,7 @@
android:textSize="12sp" /> android:textSize="12sp" />
<LinearLayout <LinearLayout
android:id="@+id/llYourToot" android:id="@+id/llGroupStatusByMe"
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"
@ -420,7 +420,7 @@
<Button <Button
android:id="@+id/btnStatusExtraAction" android:id="@+id/btnGroupStatusExtra"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/btn_bg_transparent_round6dp" android:background="@drawable/btn_bg_transparent_round6dp"
@ -437,7 +437,7 @@
android:textSize="12sp" /> android:textSize="12sp" />
<LinearLayout <LinearLayout
android:id="@+id/llStatusExtraAction" android:id="@+id/llGroupStatusExtra"
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"
@ -810,7 +810,7 @@
/> />
<Button <Button
android:id="@+id/btnCrossAccountActionsForAccount" android:id="@+id/btnGroupUserCrossAccount"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/btn_bg_transparent_round6dp" android:background="@drawable/btn_bg_transparent_round6dp"
@ -827,7 +827,7 @@
android:textSize="12sp" /> android:textSize="12sp" />
<LinearLayout <LinearLayout
android:id="@+id/llCrossAccountActionsForAccount" android:id="@+id/llGroupUserCrossAccount"
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"
@ -880,7 +880,7 @@
</LinearLayout> </LinearLayout>
<Button <Button
android:id="@+id/btnAccountExtraAction" android:id="@+id/btnGroupUserExtra"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/btn_bg_transparent_round6dp" android:background="@drawable/btn_bg_transparent_round6dp"
@ -897,7 +897,7 @@
android:textSize="12sp" /> android:textSize="12sp" />
<LinearLayout <LinearLayout
android:id="@+id/llAccountExtraAction" android:id="@+id/llGroupUserExtra"
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"
@ -906,7 +906,7 @@
tools:ignore="RtlSymmetry"> tools:ignore="RtlSymmetry">
<Button <Button
android:id="@+id/btnPostNotification" android:id="@+id/btnStatusNotification"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/btn_bg_transparent_round6dp" android:background="@drawable/btn_bg_transparent_round6dp"