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)
App1.setActivityTheme(this, noActionBar = true)
handler = App1.getAppState(this).handler
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)
density = appState.density
acctPadLr = (0.5f + 4f * density).toInt()
timelineFontSizeSp = PrefF.fpTimelineFontSize(pref).clipFontSize()
acctFontSizeSp = PrefF.fpAcctFontSize(pref).clipFontSize()
notificationTlFontSizeSp = PrefF.fpNotificationTlFontSize(pref).clipFontSize()
@ -925,7 +925,6 @@ class ActMain : AppCompatActivity(),
initUIQuickToot()
svColumnStrip.isHorizontalFadingEdgeEnabled = true
completionHelper = CompletionHelper(this, pref, appState.handler)
val dm = resources.displayMetrics
val density = dm.density

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,8 @@ import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.*
import jp.juggler.util.*
private val log = LogCategory("ActPostExtra")
fun ActPost.appendContentText(
src: String?,
selectBefore: Boolean = false,
@ -246,7 +248,7 @@ fun ActPost.initializeFromSharedIntent(sharedIntent: Intent) {
appendContentText(sharedIntent)
}
} 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 jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.LinkHelper
import jp.juggler.util.LogCategory
import jp.juggler.util.decodeUTF8
import jp.juggler.util.loadRawResource
private val log = LogCategory("ActPostMushroom")
@SuppressLint("InflateParams")
fun ActPost.showRecommendedPlugin(title: String?) {
@ -89,7 +92,7 @@ fun ActPost.openMushroom() {
arMushroom.launch(chooser)
} catch (ex: Throwable) {
ActPost.log.trace(ex)
log.trace(ex)
showRecommendedPlugin(getString(R.string.plugin_not_installed))
}
}

View File

@ -15,6 +15,8 @@ import jp.juggler.subwaytooter.util.PostAttachment
import jp.juggler.util.*
import kotlinx.coroutines.isActive
private val log = LogCategory("ActPostRedraft")
// DlgDraftPickerから参照される
const val DRAFT_CONTENT = "content"
const val DRAFT_CONTENT_WARNING = "content_warning"
@ -60,7 +62,7 @@ fun ActPost.saveDraft() {
}
if (!hasContent) {
ActPost.log.d("saveDraft: dont save empty content")
log.d("saveDraft: dont save empty content")
return
}
@ -74,32 +76,29 @@ fun ActPost.saveDraft() {
json[DRAFT_CONTENT_WARNING] = contentWarning
json[DRAFT_CONTENT_WARNING_CHECK] = cbContentWarning.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_ATTACHMENT_LIST] = tmpAttachmentList
states.inReplyToId?.putTo(json, DRAFT_REPLY_ID)
json[DRAFT_REPLY_TEXT] = states.inReplyToText
json[DRAFT_REPLY_IMAGE] = states.inReplyToImage
json[DRAFT_REPLY_URL] = states.inReplyToUrl
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_MULTIPLE] = cbMultipleChoice.isChecked
json[DRAFT_POLL_HIDE_TOTALS] = cbHideTotals.isChecked
json[DRAFT_POLL_EXPIRE_DAY] = etExpireDays.text.toString()
json[DRAFT_POLL_EXPIRE_HOUR] = etExpireHours.text.toString()
json[DRAFT_POLL_EXPIRE_MINUTE] = etExpireMinutes.text.toString()
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)
} catch (ex: Throwable) {
ActPost.log.trace(ex)
log.trace(ex)
}
}
@ -111,98 +110,94 @@ fun ActPost.restoreDraft(draft: JsonObject) {
launchMain {
val listWarning = ArrayList<String>()
var targetAccount: SavedAccount? = null
runWithProgress(
"restore from draft",
doInBackground = { progress ->
fun isTaskCancelled() = !this.coroutineContext.isActive
runWithProgress("restore from draft", doInBackground = { progress ->
var content = draft.string(DRAFT_CONTENT) ?: ""
val accountDbId = draft.long(DRAFT_ACCOUNT_DB_ID) ?: -1L
val tmpAttachmentList =
draft.jsonArray(DRAFT_ATTACHMENT_LIST)?.objectList()?.toMutableList()
fun isTaskCancelled() = !this.coroutineContext.isActive
val account = SavedAccount.loadAccount(this@restoreDraft, accountDbId)
if (account == null) {
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)
}
}
var content = draft.string(DRAFT_CONTENT) ?: ""
val tmpAttachmentList = draft.jsonArray(DRAFT_ATTACHMENT_LIST)?.objectList()?.toMutableList()
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 {
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
// 本文からURLを除去する
tmpAttachmentList.forEach {
val textUrl = TootAttachment.decodeJson(it).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
}
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 (ex: JsonException) {
ActPost.log.trace(ex)
} catch (ignored: JsonException) {
}
"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 ->
// cancelled.
if (result == null) return@runWithProgress
@ -213,16 +208,12 @@ fun ActPost.restoreDraft(draft: JsonObject) {
val nsfwChecked = draft.optBoolean(DRAFT_NSFW_CHECK)
val tmpAttachmentList = draft.jsonArray(DRAFT_ATTACHMENT_LIST)
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(
this@restoreDraft,
decodeEmoji = true
).decodeEmoji(content)
val draftVisibility = TootVisibility.parseSavedVisibility(draft.string(DRAFT_VISIBILITY))
val evEmoji = DecodeOptions(this@restoreDraft, decodeEmoji = true)
.decodeEmoji(content)
etContent.setText(evEmoji)
etContent.setSelection(evEmoji.length)
etContentWarning.setText(contentWarning)
@ -274,9 +265,9 @@ fun ActPost.restoreDraft(draft: JsonObject) {
if (replyId != null) {
states.inReplyToId = replyId
states.inReplyToText = replyText
states.inReplyToImage = replyImage
states.inReplyToUrl = replyUrl
states.inReplyToText = draft.string(DRAFT_REPLY_TEXT)
states.inReplyToImage = draft.string(DRAFT_REPLY_IMAGE)
states.inReplyToUrl = draft.string(DRAFT_REPLY_URL)
}
showContentWarningEnabled()
@ -327,7 +318,7 @@ fun ActPost.initializeFromRedraftStatus(account: SavedAccount, jsonText: String)
}
}
} catch (ex: Throwable) {
ActPost.log.trace(ex)
log.trace(ex)
}
}
@ -400,6 +391,6 @@ fun ActPost.initializeFromRedraftStatus(account: SavedAccount, jsonText: String)
}
}
} 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.table.SavedAccount
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.util.LogCategory
import jp.juggler.util.decodeJsonObject
import jp.juggler.util.launchMain
import jp.juggler.util.showToast
private val log = LogCategory("ActPostReply")
fun ActPost.showQuotedRenote() {
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.inReplyToImage = replyStatus.account.avatar_static
states.inReplyToUrl = replyStatus.url
@ -127,18 +131,15 @@ fun ActPost.initializeFromReplyStatus(account: SavedAccount, jsonText: String) {
if (TootVisibility.WebSetting == states.visibility) {
// 「Web設定に合わせる」だった場合は無条件にリプライ元の公開範囲に変更する
states.visibility = sample
} else if (TootVisibility.isVisibilitySpoilRequired(
states.visibility, sample
)
) {
} else if (TootVisibility.isVisibilitySpoilRequired(states.visibility, sample)) {
// デフォルトの方が公開範囲が大きい場合、リプライ元に合わせて公開範囲を狭める
states.visibility = sample
}
} catch (ex: Throwable) {
ActPost.log.trace(ex)
log.trace(ex)
}
} 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.table.SavedAccount
import jp.juggler.subwaytooter.util.PostAttachment
import jp.juggler.util.LogCategory
import jp.juggler.util.cast
import jp.juggler.util.decodeJsonObject
import jp.juggler.util.notEmpty
private val log = LogCategory("ActPostSchedule")
fun ActPost.showSchedule() {
tvSchedule.text = when (states.timeSchedule) {
0L -> getString(R.string.unspecified)
@ -37,7 +40,7 @@ fun ActPost.initializeFromScheduledStatus(account: SavedAccount, jsonText: Strin
::TootScheduled,
TootParser(this, account),
jsonText.decodeJsonObject(),
ActPost.log
log
) ?: error("initializeFromScheduledStatus: parse failed.")
scheduledStatus = item
@ -67,6 +70,6 @@ fun ActPost.initializeFromScheduledStatus(account: SavedAccount, jsonText: Strin
this.attachmentList.addAll(it)
}
} catch (ex: Throwable) {
ActPost.log.trace(ex)
log.trace(ex)
}
}

View File

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

View File

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

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) {
// WindowMetrics#getBounds() the window size including all system bar areas
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())
}
} else {
@ -40,7 +40,7 @@ fun ActPost.saveWindowSize() {
windowManager.defaultDisplay?.let { display ->
val dm = DisplayMetrics()
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)
}
}
@ -265,17 +265,15 @@ fun ActMain.shareText(text: String?) {
}
fun ActMain.clickReply(accessInfo: SavedAccount, status: TootStatus) {
if (!accessInfo.isPseudo) {
reply(accessInfo, status)
} else {
replyFromAnotherAccount(accessInfo, status)
when {
accessInfo.isPseudo -> replyFromAnotherAccount(accessInfo, status)
else -> reply(accessInfo, status)
}
}
fun ActMain.clickQuote(accessInfo: SavedAccount, status: TootStatus) {
if (!accessInfo.isPseudo) {
reply(accessInfo, status, quote = true)
} else {
quoteFromAnotherAccount(accessInfo, status)
when {
accessInfo.isPseudo -> quoteFromAnotherAccount(accessInfo, status)
else -> reply(accessInfo, status, quote = true)
}
}

View File

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

View File

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

View File

@ -42,9 +42,6 @@ class PollingWorker private constructor(contextArg: Context) {
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_ERROR = 3
@ -72,6 +69,7 @@ class PollingWorker private constructor(contextArg: Context) {
suspend fun getFirebaseMessagingToken(context: Context): String? {
val prefDevice = PrefDevice.from(context)
// 設定ファイルに保持されていたらそれを使う
prefDevice
.getString(PrefDevice.KEY_DEVICE_TOKEN, null)
@ -103,20 +101,22 @@ class PollingWorker private constructor(contextArg: Context) {
// インストールIDを生成する前に、各データの通知登録キャッシュをクリアする
// トークンがまだ生成されていない場合、このメソッドは null を返します。
@Suppress("BlockingMethodInNonBlockingContext")
suspend fun prepareInstallId(
context: Context,
job: JobItem? = null,
): String? {
suspend fun prepareInstallId(context: Context, job: JobItem? = null): String? {
if (!PrivacyPolicyChecker(context).agreed) {
log.w("prepareInstallId: PrivacyPolicy not agreed.")
return null
}
val prefDevice = PrefDevice.from(context)
var sv = prefDevice.getString(PrefDevice.KEY_INSTALL_ID, null)
if (sv?.isNotEmpty() == true) return sv
prefDevice.getString(PrefDevice.KEY_INSTALL_ID, null)
?.notEmpty()?.let { return it }
SavedAccount.clearRegistrationCache()
try {
return try {
val device_token = getFirebaseMessagingToken(context)
?: return null
?: error("getFirebaseMessagingToken returns null")
val request = Request.Builder()
.url("$APP_SERVER/counter")
@ -124,28 +124,21 @@ class PollingWorker private constructor(contextArg: Context) {
val call = App1.ok_http_client.newCall(request)
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) {
log.e(
TootApiClient.formatResponse(
response,
"getInstallId: get/counter failed."
)
)
return null
(device_token + UUID.randomUUID() + body).digestSHA256Base64Url()
.also { prefDevice.edit().putString(PrefDevice.KEY_INSTALL_ID, it).apply() }
}
sv = (device_token + UUID.randomUUID() + body).digestSHA256Base64Url()
prefDevice.edit().putString(PrefDevice.KEY_INSTALL_ID, sv).apply()
return sv
} catch (ex: Throwable) {
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.SystemClock
import androidx.annotation.WorkerThread
import jp.juggler.subwaytooter.ActPost
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.TootApiCallback
import jp.juggler.subwaytooter.api.TootApiClient
@ -477,7 +476,7 @@ class AttachmentUploader(
val isJpeg = MIME_TYPE_JPEG == mimeType
val isPng = MIME_TYPE_PNG == mimeType
if (!isJpeg && !isPng) {
ActPost.log.d("createOpener: source is not jpeg or png")
log.d("createOpener: source is not jpeg or png")
break
}
@ -525,7 +524,7 @@ class AttachmentUploader(
}
}
} catch (ex: Throwable) {
ActPost.log.trace(ex)
log.trace(ex)
context.showToast(ex, "Resizing image failed.")
}

View File

@ -26,6 +26,7 @@ import jp.juggler.util.*
import java.util.*
import kotlin.math.min
// 入力補完機能
class CompletionHelper(
private val activity: AppCompatActivity,
private val pref: SharedPreferences,
@ -36,38 +37,14 @@ class CompletionHelper(
private val reCharsNotEmoji = "[^0-9A-Za-z_-]".asciiPattern()
}
///////////////////////////////////////////////////////////////////////////////////
// 投稿機能はPostImplに移動した
// var content: String? = null
// 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
///////////////////////////////////////////////////////////////////////////////////
// 入力補完機能
interface Callback2 {
fun onTextUpdate()
fun canOpenPopup(): Boolean
}
private val pickerCaptionEmoji: String by lazy {
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 et: MyEditText? = null
@ -77,11 +54,9 @@ class CompletionHelper(
private var accessInfo: SavedAccount? = null
private val onEmojiListLoad: (list: ArrayList<CustomEmoji>) -> Unit =
{
val popup = this@CompletionHelper.popup
if (popup?.isShowing == true) procTextChanged.run()
}
private val onEmojiListLoad: (list: ArrayList<CustomEmoji>) -> Unit = {
if (popup?.isShowing == true) procTextChanged.run()
}
private val procTextChanged = object : Runnable {
@ -320,24 +295,10 @@ class CompletionHelper(
return popup
}
interface Callback2 {
fun onTextUpdate()
fun canOpenPopup(): Boolean
}
fun setInstance(accessInfo: SavedAccount?) {
this.accessInfo = accessInfo
if (accessInfo != null) {
App1.custom_emoji_lister.getList(accessInfo, onEmojiListLoad)
}
val popup = this.popup
if (popup?.isShowing == true) {
procTextChanged.run()
}
accessInfo?.let { App1.custom_emoji_lister.getList(it, onEmojiListLoad) }
if (popup?.isShowing == true) procTextChanged.run()
}
fun closeAcctPopup() {
@ -346,9 +307,7 @@ class CompletionHelper(
}
fun onScrollChanged() {
if (popup?.isShowing == true) {
popup?.updatePosition()
}
popup?.takeIf { it.isShowing }?.updatePosition()
}
fun onDestroy() {

View File

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

View File

@ -10,7 +10,7 @@ object ToastUtils {
private val log = LogCategory("ToastUtils")
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 {
// 前回のトーストの表示を終了する
@ -41,21 +41,18 @@ object ToastUtils {
// at android.widget.Toast$TN.handleShow (Toast.java:435)
// 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)")
}
fun Context.showToast(ex: Throwable, caption: String) {
fun Context.showToast(ex: Throwable, caption: String): Boolean =
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))
}
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))
}

View File

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