From 3b7b4241197bb526c0e9194d761aca0f78938e16 Mon Sep 17 00:00:00 2001 From: tateisu Date: Sun, 27 Jun 2021 19:05:04 +0900 Subject: [PATCH] fix can't restore reply from drafts --- .../java/jp/juggler/subwaytooter/ActMain.kt | 9 +- .../jp/juggler/subwaytooter/ActMainExtra.kt | 32 +- .../jp/juggler/subwaytooter/ActMainIntent.kt | 13 +- .../java/jp/juggler/subwaytooter/ActPost.kt | 2 +- .../jp/juggler/subwaytooter/ActPostAccount.kt | 4 +- .../juggler/subwaytooter/ActPostAttachment.kt | 10 +- .../jp/juggler/subwaytooter/ActPostExtra.kt | 4 +- .../juggler/subwaytooter/ActPostMushroom.kt | 5 +- .../jp/juggler/subwaytooter/ActPostRedraft.kt | 201 +++++---- .../jp/juggler/subwaytooter/ActPostReply.kt | 13 +- .../juggler/subwaytooter/ActPostSchedule.kt | 7 +- .../java/jp/juggler/subwaytooter/ActText.kt | 2 +- .../main/java/jp/juggler/subwaytooter/App1.kt | 20 +- .../jp/juggler/subwaytooter/DlgContextMenu.kt | 389 ++++++++---------- .../subwaytooter/PrivacyPolicyChecker.kt | 35 ++ .../subwaytooter/action/Action_OpenPost.kt | 18 +- .../subwaytooter/action/Action_Status.kt | 1 - .../subwaytooter/api/entity/TootVisibility.kt | 10 +- .../notification/PollingWorker.kt | 47 +-- .../subwaytooter/util/AttachmentUploader.kt | 5 +- .../subwaytooter/util/CompletionHelper.kt | 63 +-- .../subwaytooter/view/MyNetworkImageView.kt | 3 +- .../main/java/jp/juggler/util/ToastUtils.kt | 15 +- app/src/main/res/layout/dlg_context_menu.xml | 26 +- 24 files changed, 433 insertions(+), 501 deletions(-) create mode 100644 app/src/main/java/jp/juggler/subwaytooter/PrivacyPolicyChecker.kt diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt index bbc6fbeb..f1bc6a72 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMainExtra.kt b/app/src/main/java/jp/juggler/subwaytooter/ActMainExtra.kt index 62c86dec..11798def 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMainExtra.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMainExtra.kt @@ -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() { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMainIntent.kt b/app/src/main/java/jp/juggler/subwaytooter/ActMainIntent.kt index 892ec249..6b81a7b0 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMainIntent.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMainIntent.kt @@ -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 } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt index e78f3825..2af90177 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt @@ -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? = null diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPostAccount.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPostAccount.kt index af9adca3..d0f9427d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPostAccount.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPostAccount.kt @@ -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() diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPostAttachment.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPostAttachment.kt index 2a990432..e20d0632 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPostAttachment.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPostAttachment.kt @@ -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)) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPostExtra.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPostExtra.kt index e956b810..d628c900 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPostExtra.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPostExtra.kt @@ -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) } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPostMushroom.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPostMushroom.kt index b8d453df..f3f8d0e4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPostMushroom.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPostMushroom.kt @@ -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)) } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPostRedraft.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPostRedraft.kt index e7f0e64c..16e4dee5 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPostRedraft.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPostRedraft.kt @@ -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() 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) } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPostReply.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPostReply.kt index 9eff4cd7..352654bc 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPostReply.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPostReply.kt @@ -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) } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPostSchedule.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPostSchedule.kt index d5ed8d61..88341639 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPostSchedule.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPostSchedule.kt @@ -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) } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActText.kt b/app/src/main/java/jp/juggler/subwaytooter/ActText.kt index cfae45e6..d661d7a8 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActText.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActText.kt @@ -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) diff --git a/app/src/main/java/jp/juggler/subwaytooter/App1.kt b/app/src/main/java/jp/juggler/subwaytooter/App1.kt index a2fc6e56..401d782c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/App1.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/App1.kt @@ -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 } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt b/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt index b329df9e..0dd3b055 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt @@ -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 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(R.id.btnProfilePin) - val btnProfileUnpin = viewRoot.findViewById(R.id.btnProfileUnpin) - val btnBoostedBy = viewRoot.findViewById(R.id.btnBoostedBy) - val btnFavouritedBy = viewRoot.findViewById(R.id.btnFavouritedBy) - - val btnDomainTimeline = viewRoot.findViewById(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 } diff --git a/app/src/main/java/jp/juggler/subwaytooter/PrivacyPolicyChecker.kt b/app/src/main/java/jp/juggler/subwaytooter/PrivacyPolicyChecker.kt new file mode 100644 index 00000000..712b59bc --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/PrivacyPolicyChecker.kt @@ -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) + } +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_OpenPost.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_OpenPost.kt index 7c06c245..1d6d3c68 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_OpenPost.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_OpenPost.kt @@ -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) } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Status.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Status.kt index f7875b20..7217582a 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Status.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Status.kt @@ -302,7 +302,6 @@ fun ActMain.bookmark( ) ) { bookmark( - accessInfo, statusArg, crossAccountMode, diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootVisibility.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootVisibility.kt index 83b29b39..f30fd054 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootVisibility.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootVisibility.kt @@ -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 } diff --git a/app/src/main/java/jp/juggler/subwaytooter/notification/PollingWorker.kt b/app/src/main/java/jp/juggler/subwaytooter/notification/PollingWorker.kt index cdff0c3b..f07a5a62 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/notification/PollingWorker.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/notification/PollingWorker.kt @@ -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 } ////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/AttachmentUploader.kt b/app/src/main/java/jp/juggler/subwaytooter/util/AttachmentUploader.kt index cd7dfc12..5e7590ae 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/AttachmentUploader.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/AttachmentUploader.kt @@ -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.") } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/CompletionHelper.kt b/app/src/main/java/jp/juggler/subwaytooter/util/CompletionHelper.kt index 6ea6ee4f..166124f0 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/CompletionHelper.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/CompletionHelper.kt @@ -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? = null -// var enqueteItems: ArrayList? = null -// var pollType: TootPollsType? = null -// var pollExpireSeconds = 0 -// var pollHideTotals = false -// var pollMultipleChoice = false -// -// var emojiMapCustom: HashMap? = 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) -> Unit = - { - val popup = this@CompletionHelper.popup - if (popup?.isShowing == true) procTextChanged.run() - } + private val onEmojiListLoad: (list: ArrayList) -> 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() { diff --git a/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.kt b/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.kt index 687ce105..9f3063a4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.kt @@ -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) diff --git a/app/src/main/java/jp/juggler/util/ToastUtils.kt b/app/src/main/java/jp/juggler/util/ToastUtils.kt index ee68747a..02dad7c7 100644 --- a/app/src/main/java/jp/juggler/util/ToastUtils.kt +++ b/app/src/main/java/jp/juggler/util/ToastUtils.kt @@ -10,7 +10,7 @@ object ToastUtils { private val log = LogCategory("ToastUtils") private var refToast: WeakReference? = 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)) -} diff --git a/app/src/main/res/layout/dlg_context_menu.xml b/app/src/main/res/layout/dlg_context_menu.xml index ba54b6bd..7fe9586d 100644 --- a/app/src/main/res/layout/dlg_context_menu.xml +++ b/app/src/main/res/layout/dlg_context_menu.xml @@ -123,7 +123,7 @@