通知のアクセント色をリソースXMLに移す

This commit is contained in:
tateisu 2023-02-06 19:26:02 +09:00
parent d520f9ddfd
commit 60bacaac64
6 changed files with 525 additions and 526 deletions

View File

@ -21,8 +21,8 @@ import jp.juggler.subwaytooter.databinding.ActPushMessageListBinding
import jp.juggler.subwaytooter.databinding.LvPushMessageBinding import jp.juggler.subwaytooter.databinding.LvPushMessageBinding
import jp.juggler.subwaytooter.dialog.actionsDialog import jp.juggler.subwaytooter.dialog.actionsDialog
import jp.juggler.subwaytooter.dialog.runInProgress import jp.juggler.subwaytooter.dialog.runInProgress
import jp.juggler.subwaytooter.notification.NotificationIconAndColor import jp.juggler.subwaytooter.notification.PushMessageIconColor
import jp.juggler.subwaytooter.notification.notificationIconAndColor import jp.juggler.subwaytooter.notification.iconColor
import jp.juggler.subwaytooter.push.pushRepo import jp.juggler.subwaytooter.push.pushRepo
import jp.juggler.subwaytooter.table.PushMessage import jp.juggler.subwaytooter.table.PushMessage
import jp.juggler.subwaytooter.table.daoAccountNotificationStatus import jp.juggler.subwaytooter.table.daoAccountNotificationStatus
@ -165,11 +165,13 @@ class ActPushMessageList : AppCompatActivity() {
} }
private val tintIconMap = HashMap<String, Drawable>() private val tintIconMap = HashMap<String, Drawable>()
fun tintIcon(ic: NotificationIconAndColor) =
fun tintIcon(ic: PushMessageIconColor) =
tintIconMap.getOrPut(ic.name) { tintIconMap.getOrPut(ic.name) {
val src = ContextCompat.getDrawable(this@ActPushMessageList, ic.iconId)!! val context = this
val src = ContextCompat.getDrawable(context, ic.iconId)!!
DrawableCompat.wrap(src).also { DrawableCompat.wrap(src).also {
DrawableCompat.setTint(it, ic.color) DrawableCompat.setTint(it, ContextCompat.getColor(context, ic.colorRes))
} }
} }
@ -190,7 +192,7 @@ class ActPushMessageList : AppCompatActivity() {
pm ?: return pm ?: return
lastItem = pm lastItem = pm
val iconAndColor = pm.notificationIconAndColor() val iconAndColor = pm.iconColor()
Glide.with(views.ivSmall) Glide.with(views.ivSmall)
.load(pm.iconSmall) .load(pm.iconSmall)
.error(tintIcon(iconAndColor)) .error(tintIcon(iconAndColor))

View File

@ -25,7 +25,6 @@ import jp.juggler.subwaytooter.column.startLoading
import jp.juggler.subwaytooter.dialog.actionsDialog import jp.juggler.subwaytooter.dialog.actionsDialog
import jp.juggler.subwaytooter.dialog.pickAccount import jp.juggler.subwaytooter.dialog.pickAccount
import jp.juggler.subwaytooter.dialog.runInProgress import jp.juggler.subwaytooter.dialog.runInProgress
import jp.juggler.subwaytooter.notification.PushSubscriptionHelper
import jp.juggler.subwaytooter.notification.checkNotificationImmediate import jp.juggler.subwaytooter.notification.checkNotificationImmediate
import jp.juggler.subwaytooter.notification.checkNotificationImmediateAll import jp.juggler.subwaytooter.notification.checkNotificationImmediateAll
import jp.juggler.subwaytooter.notification.recycleClickedNotification import jp.juggler.subwaytooter.notification.recycleClickedNotification
@ -47,7 +46,6 @@ import jp.juggler.util.queryIntentActivitiesCompat
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.unifiedpush.android.connector.UnifiedPush import org.unifiedpush.android.connector.UnifiedPush
import java.util.ArrayList
private val log = LogCategory("ActMainIntent") private val log = LogCategory("ActMainIntent")
@ -351,7 +349,7 @@ fun ActMain.handleSharedIntent(intent: Intent) {
} }
// アカウントを追加/更新したらappServerHashの取得をやりなおす // アカウントを追加/更新したらappServerHashの取得をやりなおす
fun ActMain.updatePushDistributer(){ fun ActMain.updatePushDistributer() {
when { when {
fcmHandler.noFcm && prefDevice.pushDistributor.isNullOrEmpty() -> { fcmHandler.noFcm && prefDevice.pushDistributor.isNullOrEmpty() -> {
try { try {

View File

@ -1,488 +1,488 @@
package jp.juggler.subwaytooter.notification package jp.juggler.subwaytooter.notification
//
import android.content.Context //import android.content.Context
import jp.juggler.subwaytooter.R //import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.TootApiClient //import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootApiResult //import jp.juggler.subwaytooter.api.TootApiResult
import jp.juggler.subwaytooter.api.entity.* //import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.table.SavedAccount //import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.SubscriptionServerKey //import jp.juggler.subwaytooter.table.SubscriptionServerKey
import jp.juggler.subwaytooter.table.appDatabase //import jp.juggler.subwaytooter.table.appDatabase
import jp.juggler.util.* //import jp.juggler.util.*
import jp.juggler.util.data.* //import jp.juggler.util.data.*
import jp.juggler.util.log.* //import jp.juggler.util.log.*
import jp.juggler.util.network.toPostRequestBuilder //import jp.juggler.util.network.toPostRequestBuilder
import jp.juggler.util.ui.* //import jp.juggler.util.ui.*
import okhttp3.Request //import okhttp3.Request
//
class PushSubscriptionHelper( //class PushSubscriptionHelper(
val context: Context, // val context: Context,
val account: SavedAccount, // val account: SavedAccount,
private val verbose: Boolean = false, // private val verbose: Boolean = false,
private val daoSubscriptionServerKey: SubscriptionServerKey.Access = // private val daoSubscriptionServerKey: SubscriptionServerKey.Access =
SubscriptionServerKey.Access(appDatabase), // SubscriptionServerKey.Access(appDatabase),
) { //) {
companion object { // companion object {
private val log = LogCategory("PushSubscriptionHelper") // private val log = LogCategory("PushSubscriptionHelper")
} // }
//
private val logBuffer = StringBuilder() // private val logBuffer = StringBuilder()
//
val logString: String // val logString: String
get() = logBuffer.toString() // get() = logBuffer.toString()
//
private var subscribed: Boolean = false // private var subscribed: Boolean = false
//
val flags = account.notificationFlags() // val flags = account.notificationFlags()
//
private fun addLog(s: String?) { // private fun addLog(s: String?) {
if (s?.isNotEmpty() == true) { // if (s?.isNotEmpty() == true) {
if (logBuffer.isNotEmpty()) logBuffer.append('\n') // if (logBuffer.isNotEmpty()) logBuffer.append('\n')
logBuffer.append(s) // logBuffer.append(s)
} // }
} // }
//
// アプリサーバにendpoint URLの変更を伝える // // アプリサーバにendpoint URLの変更を伝える
private suspend fun registerEndpoint( // private suspend fun registerEndpoint(
client: TootApiClient,
deviceId: String,
endpoint: String,
): TootApiResult {
// deprecated
// if (account.last_push_endpoint == endpoint) return TootApiResult()
return client.http(
buildJsonObject {
put("acct", account.acct.ascii)
put("deviceId", deviceId)
put("endpoint", endpoint)
}
.toPostRequestBuilder()
.url("$APP_SERVER/webpushendpoint")
.build()
).also { result ->
result.response?.let { res ->
when (res.code.also { res.close() }) {
in 200 until 300 -> {
// deprecated
// account.updateLastPushEndpoint(endpoint)
}
else -> {
result.caption = "(SubwayTooter App server)"
client.readBodyString(result)
}
}
}
}
}
// suspend fun updateSubscription(
// client: TootApiClient, // client: TootApiClient,
// force: Boolean = false, // deviceId: String,
// progress: suspend (SavedAccount, PollingState) -> Unit = { _, _ -> }, // endpoint: String,
// ): TootApiResult? = try { // ): TootApiResult {
// when {
// account.isPseudo ->
// TootApiResult(context.getString(R.string.pseudo_account_not_supported))
// //
// else -> { // // deprecated
// progress(account, PollingState.CheckPushSubscription) // // if (account.last_push_endpoint == endpoint) return TootApiResult()
// when {
// account.isMisskey -> updateSubscriptionMisskey(client)
// else -> updateSubscriptionMastodon(client, force)
// }
// }
// }
// } catch (ex: Throwable) {
// TootApiResult(ex.withCaption("error."))
// }?.apply {
// //
// if (error != null) addLog("$error $requestInfo".trimEnd()) // return client.http(
// // buildJsonObject {
// // update error text on account table // put("acct", account.acct.ascii)
// val log = logString // put("deviceId", deviceId)
// when {
// log.contains(ERROR_PREVENT_FREQUENTLY_CHECK) -> {
// // don't update if check was skipped.
// }
//
// subscribed || log.isEmpty() -> Unit
// // clear error text if succeeded or no error log
//// if (account.last_subscription_error != null) {
//// account.updateSubscriptionError(null)
//// }
//
// else -> Unit
// // record error text
//// account.updateSubscriptionError(log)
// }
// }
// private suspend fun updateSubscriptionMisskey(client: TootApiClient): TootApiResult? {
//
// // 現在の購読状態を取得できないので、毎回購読の更新を行う
// // FCMのデバイスIDを取得
// val deviceId = try {
// loadFirebaseMessagingToken(context)
// } catch (ex: Throwable) {
// log.e(ex, "loadFirebaseMessagingToken failed.")
// return when (ex) {
// is CancellationException -> null
// else -> TootApiResult(error = context.getString(R.string.missing_fcm_device_id))
// }
// }
//
// // アクセストークン
// val accessToken = account.misskeyApiToken
// ?: return TootApiResult(error = "missing misskeyApiToken.")
//
// // インストールIDを取得
// val installId = try {
// loadInstallId(
// context,
// account,
// deviceId
// ) { a, s -> log.i("[${a.acct.pretty}]${s.desc}") }
// } catch (ex: Throwable) {
// log.e(ex, "loadInstallId failed.")
// return when (ex) {
// is CancellationException -> null
// else -> TootApiResult(error = context.getString(R.string.missing_install_id))
// }
// }
//
// // クライアント識別子
// val clientIdentifier = "$accessToken$installId".digestSHA256Base64Url()
//
// // 購読が不要な場合
// // アプリサーバが410を返せるように状態を通知する
// if (flags == 0) return registerEndpoint(client, deviceId, "none").also {
// if (it.error == null && verbose) addLog(context.getString(R.string.push_subscription_updated))
// }
//
// /*
// https://github.com/syuilo/misskey/blob/master/src/services/create-notification.ts#L46
// Misskeyは通知に既読の概念があり、イベント発生後2秒たっても未読の時だけプッシュ通知が発生する。
// STでプッシュ通知を試すにはSTの画面を非表示にする必要があるのでWebUIを使って投稿していたが、
// WebUIを開いていると通知はすぐ既読になるのでプッシュ通知は発生しない。
// プッシュ通知のテスト時はST2台を使い、片方をプッシュ通知の受信チェック、もう片方を投稿などの作業に使うことになる。
// */
//
// // https://github.com/syuilo/misskey/issues/2541
// // https://github.com/syuilo/misskey/commit/4c6fb60dd25d7e2865fc7c4d97728593ffc3c902
// // 2018/9/1 の上記コミット以降、Misskeyでもサーバ公開鍵を得られるようになった
//
// val endpoint =
// "$APP_SERVER/webpushcallback/${deviceId.encodePercent()}/${account.acct.ascii.encodePercent()}/$flags/$clientIdentifier/misskey"
//
// // アプリサーバが過去のendpoint urlに410を返せるよう、状態を通知する
// val r = registerEndpoint(client, deviceId, endpoint.toUri().encodedPath!!)
// if (r.error != null) return r
//
// // 購読
// @Suppress("SpellCheckingInspection")
// return client.request(
// "/api/sw/register",
// account.putMisskeyApiToken().apply {
// put("endpoint", endpoint) // put("endpoint", endpoint)
// put("auth", "iRdmDrOS6eK6xvG1H6KshQ")
// put(
// "publickey",
// "BBEUVi7Ehdzzpe_ZvlzzkQnhujNJuBKH1R0xYg7XdAKNFKQG9Gpm0TSGRGSuaU7LUFKX-uz8YW0hAshifDCkPuE"
// )
// } // }
// .toPostRequestBuilder() // .toPostRequestBuilder()
// )?.also { result -> // .url("$APP_SERVER/webpushendpoint")
// val jsonObject = result.jsonObject // .build()
// if (jsonObject == null) { // ).also { result ->
// addLog("API error.") // result.response?.let { res ->
// } else { // when (res.code.also { res.close() }) {
// if (verbose) addLog(context.getString(R.string.push_subscription_updated)) // in 200 until 300 -> {
// subscribed = true // // deprecated
// return updateServerKey( // // account.updateLastPushEndpoint(endpoint)
// client, // }
// clientIdentifier, // else -> {
// jsonObject.string("key") ?: "3q2+rw" // result.caption = "(SubwayTooter App server)"
// ) // client.readBodyString(result)
// } // }
// } // }
// } // }
// }
// private suspend fun updateSubscriptionMastodon( // }
// client: TootApiClient, //
// force: Boolean, //// suspend fun updateSubscription(
//// client: TootApiClient,
//// force: Boolean = false,
//// progress: suspend (SavedAccount, PollingState) -> Unit = { _, _ -> },
//// ): TootApiResult? = try {
//// when {
//// account.isPseudo ->
//// TootApiResult(context.getString(R.string.pseudo_account_not_supported))
////
//// else -> {
//// progress(account, PollingState.CheckPushSubscription)
//// when {
//// account.isMisskey -> updateSubscriptionMisskey(client)
//// else -> updateSubscriptionMastodon(client, force)
//// }
//// }
//// }
//// } catch (ex: Throwable) {
//// TootApiResult(ex.withCaption("error."))
//// }?.apply {
////
//// if (error != null) addLog("$error $requestInfo".trimEnd())
////
//// // update error text on account table
//// val log = logString
//// when {
//// log.contains(ERROR_PREVENT_FREQUENTLY_CHECK) -> {
//// // don't update if check was skipped.
//// }
////
//// subscribed || log.isEmpty() -> Unit
//// // clear error text if succeeded or no error log
////// if (account.last_subscription_error != null) {
////// account.updateSubscriptionError(null)
////// }
////
//// else -> Unit
//// // record error text
////// account.updateSubscriptionError(log)
//// }
//// }
//
//// private suspend fun updateSubscriptionMisskey(client: TootApiClient): TootApiResult? {
////
//// // 現在の購読状態を取得できないので、毎回購読の更新を行う
//// // FCMのデバイスIDを取得
//// val deviceId = try {
//// loadFirebaseMessagingToken(context)
//// } catch (ex: Throwable) {
//// log.e(ex, "loadFirebaseMessagingToken failed.")
//// return when (ex) {
//// is CancellationException -> null
//// else -> TootApiResult(error = context.getString(R.string.missing_fcm_device_id))
//// }
//// }
////
//// // アクセストークン
//// val accessToken = account.misskeyApiToken
//// ?: return TootApiResult(error = "missing misskeyApiToken.")
////
//// // インストールIDを取得
//// val installId = try {
//// loadInstallId(
//// context,
//// account,
//// deviceId
//// ) { a, s -> log.i("[${a.acct.pretty}]${s.desc}") }
//// } catch (ex: Throwable) {
//// log.e(ex, "loadInstallId failed.")
//// return when (ex) {
//// is CancellationException -> null
//// else -> TootApiResult(error = context.getString(R.string.missing_install_id))
//// }
//// }
////
//// // クライアント識別子
//// val clientIdentifier = "$accessToken$installId".digestSHA256Base64Url()
////
//// // 購読が不要な場合
//// // アプリサーバが410を返せるように状態を通知する
//// if (flags == 0) return registerEndpoint(client, deviceId, "none").also {
//// if (it.error == null && verbose) addLog(context.getString(R.string.push_subscription_updated))
//// }
////
//// /*
//// https://github.com/syuilo/misskey/blob/master/src/services/create-notification.ts#L46
//// Misskeyは通知に既読の概念があり、イベント発生後2秒たっても未読の時だけプッシュ通知が発生する。
//// STでプッシュ通知を試すにはSTの画面を非表示にする必要があるのでWebUIを使って投稿していたが、
//// WebUIを開いていると通知はすぐ既読になるのでプッシュ通知は発生しない。
//// プッシュ通知のテスト時はST2台を使い、片方をプッシュ通知の受信チェック、もう片方を投稿などの作業に使うことになる。
//// */
////
//// // https://github.com/syuilo/misskey/issues/2541
//// // https://github.com/syuilo/misskey/commit/4c6fb60dd25d7e2865fc7c4d97728593ffc3c902
//// // 2018/9/1 の上記コミット以降、Misskeyでもサーバ公開鍵を得られるようになった
////
//// val endpoint =
//// "$APP_SERVER/webpushcallback/${deviceId.encodePercent()}/${account.acct.ascii.encodePercent()}/$flags/$clientIdentifier/misskey"
////
//// // アプリサーバが過去のendpoint urlに410を返せるよう、状態を通知する
//// val r = registerEndpoint(client, deviceId, endpoint.toUri().encodedPath!!)
//// if (r.error != null) return r
////
//// // 購読
//// @Suppress("SpellCheckingInspection")
//// return client.request(
//// "/api/sw/register",
//// account.putMisskeyApiToken().apply {
//// put("endpoint", endpoint)
//// put("auth", "iRdmDrOS6eK6xvG1H6KshQ")
//// put(
//// "publickey",
//// "BBEUVi7Ehdzzpe_ZvlzzkQnhujNJuBKH1R0xYg7XdAKNFKQG9Gpm0TSGRGSuaU7LUFKX-uz8YW0hAshifDCkPuE"
//// )
//// }
//// .toPostRequestBuilder()
//// )?.also { result ->
//// val jsonObject = result.jsonObject
//// if (jsonObject == null) {
//// addLog("API error.")
//// } else {
//// if (verbose) addLog(context.getString(R.string.push_subscription_updated))
//// subscribed = true
//// return updateServerKey(
//// client,
//// clientIdentifier,
//// jsonObject.string("key") ?: "3q2+rw"
//// )
//// }
//// }
//// }
//
//// private suspend fun updateSubscriptionMastodon(
//// client: TootApiClient,
//// force: Boolean,
//// ): TootApiResult? {
////
//// // 現在の購読状態を取得
//// // https://github.com/tootsuite/mastodon/pull/7471
//// // https://github.com/tootsuite/mastodon/pull/7472
////
//// val subscription404: Boolean
//// val oldSubscription: TootPushSubscription?
//// checkCurrentSubscription(client).let {
//// if (it.failed) return it.result
//// subscription404 = it.is404
//// oldSubscription = parseItem(::TootPushSubscription, it.result?.jsonObject)
//// }
////
//// if (oldSubscription == null) {
//// log.i("${account.acct}: oldSubscription is null")
//// val (ti, result) = TootInstance.get(client)
//// ti ?: return result
//// checkInstanceVersionMastodon(ti, subscription404)?.let { return it }
//// }
////
//// // FCMのデバイスIDを取得
//// val deviceId = try {
//// loadFirebaseMessagingToken(context)
//// } catch (ex: Throwable) {
//// log.e(ex, "loadFirebaseMessagingToken failed.")
//// return when (ex) {
//// is CancellationException -> null
//// else -> TootApiResult(error = context.getString(R.string.missing_fcm_device_id))
//// }
//// }
////
//// // インストールIDを取得
//// val installId = try {
//// loadInstallId(
//// context,
//// account,
//// deviceId
//// ) { a, s -> log.i("[${a.acct.pretty}]${s.desc}") }
//// } catch (ex: Throwable) {
//// log.e(ex, "loadInstallId failed.")
//// return when (ex) {
//// is CancellationException -> null
//// else -> TootApiResult(error = context.getString(R.string.missing_install_id))
//// }
//// }
//// // アクセストークン
//// val accessToken = account.bearerAccessToken
//// ?: return TootApiResult(error = "missing access token.")
////
//// // アクセストークンのダイジェスト
//// val tokenDigest = accessToken.digestSHA256Base64Url()
////
//// // クライアント識別子
//// val clientIdentifier = "$accessToken$installId".digestSHA256Base64Url()
////
//// val endpoint =
//// "$APP_SERVER/webpushcallback/${deviceId.encodePercent()}/${account.acct.ascii.encodePercent()}/$flags/$clientIdentifier"
////
//// val newAlerts = JsonObject().apply {
//// put("follow", account.notification_follow)
//// put(TootNotification.TYPE_ADMIN_SIGNUP, account.notification_follow)
//// put("favourite", account.notification_favourite)
//// put("reblog", account.notification_boost)
//// put("mention", account.notification_mention)
//// put("poll", account.notification_vote)
//// put("follow_request", account.notification_follow_request)
//// put("status", account.notification_post)
//// put("update", account.notification_update)
//// put("emoji_reaction", account.notification_reaction) // fedibird拡張
//// }
////
//// if (!force) {
//// canSkipSubscriptionMastodon(
//// client = client,
//// clientIdentifier = clientIdentifier,
//// endpoint = endpoint,
//// oldSubscription = oldSubscription,
//// newAlerts = newAlerts,
//// )?.let { return it }
//// }
////
//// // アクセストークンの優先権を取得
//// checkDeviceHasPriority(
//// client,
//// tokenDigest = tokenDigest,
//// installId = installId,
//// ).let {
//// if (it.failed) return it.result
//// }
////
//// return when (flags) {
//// // 通知設定が全てカラなので、購読を取り消したい
//// 0 -> unsubscribeMastodon(client)
////
//// // 通知設定が空ではないので購読を行いたい
//// else -> subscribeMastodon(
//// client = client,
//// clientIdentifier = clientIdentifier,
//// endpoint = endpoint,
//// newAlerts = newAlerts
//// )
//// }
//// }
//
// // returns null if no error
// private fun checkInstanceVersionMastodon(
// ti: TootInstance,
// subscription404: Boolean,
// ): TootApiResult? { // ): TootApiResult? {
// //
// // 現在の購読状態を取得 // // 2.4.0rc1 未満にはプッシュ購読APIはない
// // https://github.com/tootsuite/mastodon/pull/7471 // if (!ti.versionGE(TootInstance.VERSION_2_4_0_rc1)) {
// // https://github.com/tootsuite/mastodon/pull/7472 // return TootApiResult(
// // context.getString(R.string.instance_does_not_support_push_api, ti.version)
// val subscription404: Boolean
// val oldSubscription: TootPushSubscription?
// checkCurrentSubscription(client).let {
// if (it.failed) return it.result
// subscription404 = it.is404
// oldSubscription = parseItem(::TootPushSubscription, it.result?.jsonObject)
// }
//
// if (oldSubscription == null) {
// log.i("${account.acct}: oldSubscription is null")
// val (ti, result) = TootInstance.get(client)
// ti ?: return result
// checkInstanceVersionMastodon(ti, subscription404)?.let { return it }
// }
//
// // FCMのデバイスIDを取得
// val deviceId = try {
// loadFirebaseMessagingToken(context)
// } catch (ex: Throwable) {
// log.e(ex, "loadFirebaseMessagingToken failed.")
// return when (ex) {
// is CancellationException -> null
// else -> TootApiResult(error = context.getString(R.string.missing_fcm_device_id))
// }
// }
//
// // インストールIDを取得
// val installId = try {
// loadInstallId(
// context,
// account,
// deviceId
// ) { a, s -> log.i("[${a.acct.pretty}]${s.desc}") }
// } catch (ex: Throwable) {
// log.e(ex, "loadInstallId failed.")
// return when (ex) {
// is CancellationException -> null
// else -> TootApiResult(error = context.getString(R.string.missing_install_id))
// }
// }
// // アクセストークン
// val accessToken = account.bearerAccessToken
// ?: return TootApiResult(error = "missing access token.")
//
// // アクセストークンのダイジェスト
// val tokenDigest = accessToken.digestSHA256Base64Url()
//
// // クライアント識別子
// val clientIdentifier = "$accessToken$installId".digestSHA256Base64Url()
//
// val endpoint =
// "$APP_SERVER/webpushcallback/${deviceId.encodePercent()}/${account.acct.ascii.encodePercent()}/$flags/$clientIdentifier"
//
// val newAlerts = JsonObject().apply {
// put("follow", account.notification_follow)
// put(TootNotification.TYPE_ADMIN_SIGNUP, account.notification_follow)
// put("favourite", account.notification_favourite)
// put("reblog", account.notification_boost)
// put("mention", account.notification_mention)
// put("poll", account.notification_vote)
// put("follow_request", account.notification_follow_request)
// put("status", account.notification_post)
// put("update", account.notification_update)
// put("emoji_reaction", account.notification_reaction) // fedibird拡張
// }
//
// if (!force) {
// canSkipSubscriptionMastodon(
// client = client,
// clientIdentifier = clientIdentifier,
// endpoint = endpoint,
// oldSubscription = oldSubscription,
// newAlerts = newAlerts,
// )?.let { return it }
// }
//
// // アクセストークンの優先権を取得
// checkDeviceHasPriority(
// client,
// tokenDigest = tokenDigest,
// installId = installId,
// ).let {
// if (it.failed) return it.result
// }
//
// return when (flags) {
// // 通知設定が全てカラなので、購読を取り消したい
// 0 -> unsubscribeMastodon(client)
//
// // 通知設定が空ではないので購読を行いたい
// else -> subscribeMastodon(
// client = client,
// clientIdentifier = clientIdentifier,
// endpoint = endpoint,
// newAlerts = newAlerts
// ) // )
// } // }
//
// if (subscription404 && flags == 0) {
// when {
// ti.versionGE(TootInstance.VERSION_2_4_0_rc2) -> {
// // 購読が不要で現在の状況が404だった場合
// // 2.4.0rc2以降では「購読が存在しない」を示すので何もしなくてよい
// if (verbose) addLog(context.getString(R.string.push_subscription_not_exists))
// return TootApiResult()
// } // }
//
// returns null if no error // else -> {
private fun checkInstanceVersionMastodon( // // 2.4.0rc1では「APIが存在しない」と「購読が存在しない」を判別できない
ti: TootInstance, // }
subscription404: Boolean, // }
): TootApiResult? { // }
// return null
// 2.4.0rc1 未満にはプッシュ購読APIはない // }
if (!ti.versionGE(TootInstance.VERSION_2_4_0_rc1)) { //
return TootApiResult( // private class CheckCurrentSubscriptionResult(
context.getString(R.string.instance_does_not_support_push_api, ti.version) // val result: TootApiResult?,
) // val failed: Boolean,
} // val is404: Boolean,
// )
if (subscription404 && flags == 0) { //
when { // @Suppress("BooleanLiteralArgument")
ti.versionGE(TootInstance.VERSION_2_4_0_rc2) -> { // private suspend fun checkCurrentSubscription(client: TootApiClient): CheckCurrentSubscriptionResult {
// 購読が不要で現在の状況が404だった場合 // val r = client.request("/api/v1/push/subscription")
// 2.4.0rc2以降では「購読が存在しない」を示すので何もしなくてよい // fun rvError() = CheckCurrentSubscriptionResult(r, true, false)
if (verbose) addLog(context.getString(R.string.push_subscription_not_exists)) // fun rvOk() = CheckCurrentSubscriptionResult(r, false, false)
return TootApiResult() // fun rv404() = CheckCurrentSubscriptionResult(r, false, true)
} // val res = r?.response ?: return rvError() // cancelled or missing response
//
else -> { // if (res.code != 200) log.i("${account.acct}: check existing subscription: code=${res.code}")
// 2.4.0rc1では「APIが存在しない」と「購読が存在しない」を判別できない //
} // return when (res.code) {
} // 200 -> {
} // if (r.error?.isNotEmpty() == true && r.jsonObject == null) {
return null // // Pleromaが200応答でもエラーHTMLを返す場合がある
} // addLog(context.getString(R.string.instance_does_not_support_push_api_pleroma))
// rvError()
private class CheckCurrentSubscriptionResult( // } else {
val result: TootApiResult?, // // たぶん購読が存在する
val failed: Boolean, // rvOk()
val is404: Boolean, // }
) // }
//
@Suppress("BooleanLiteralArgument") // // この時点では存在しないのが購読なのかAPIなのか分からない
private suspend fun checkCurrentSubscription(client: TootApiClient): CheckCurrentSubscriptionResult { // 404 -> rv404()
val r = client.request("/api/v1/push/subscription") //
fun rvError() = CheckCurrentSubscriptionResult(r, true, false) // 403 -> {
fun rvOk() = CheckCurrentSubscriptionResult(r, false, false) // // アクセストークンにpushスコープがない
fun rv404() = CheckCurrentSubscriptionResult(r, false, true) // if (flags != 0 || verbose) addLog(context.getString(R.string.missing_push_scope))
val res = r?.response ?: return rvError() // cancelled or missing response // rvError()
// }
if (res.code != 200) log.i("${account.acct}: check existing subscription: code=${res.code}") //
// in 400 until 500 -> {
return when (res.code) { // addLog(context.getString(R.string.instance_does_not_support_push_api_pleroma))
200 -> { // rvError()
if (r.error?.isNotEmpty() == true && r.jsonObject == null) { // }
// Pleromaが200応答でもエラーHTMLを返す場合がある //
addLog(context.getString(R.string.instance_does_not_support_push_api_pleroma)) // else -> {
rvError() // addLog("${res.request}")
} else { // addLog("${res.code} ${res.message}")
// たぶん購読が存在する // rvOk() // 後でリトライする
rvOk() // }
} // }
} // }
//
// この時点では存在しないのが購読なのかAPIなのか分からない // private suspend fun unsubscribeMastodon(
404 -> rv404() // client: TootApiClient,
// ): TootApiResult? {
403 -> { //
// アクセストークンにpushスコープがない // val r = client.request("/api/v1/push/subscription", Request.Builder().delete())
if (flags != 0 || verbose) addLog(context.getString(R.string.missing_push_scope)) // val res = r?.response ?: return r
rvError() //
} // return when (res.code) {
// 200 -> {
in 400 until 500 -> { // if (verbose) addLog(context.getString(R.string.push_subscription_deleted))
addLog(context.getString(R.string.instance_does_not_support_push_api_pleroma)) // TootApiResult()
rvError() // }
} //
// 404 -> {
else -> { // if (verbose) {
addLog("${res.request}") // addLog(context.getString(R.string.missing_push_api))
addLog("${res.code} ${res.message}") // r
rvOk() // 後でリトライする // } else {
} // TootApiResult()
} // }
} // }
//
private suspend fun unsubscribeMastodon( // 403 -> {
client: TootApiClient, // addLog(context.getString(R.string.missing_push_scope))
): TootApiResult? { // r
// }
val r = client.request("/api/v1/push/subscription", Request.Builder().delete()) //
val res = r?.response ?: return r // else -> {
// addLog("${res.request}")
return when (res.code) { // addLog("${res.code} ${res.message}")
200 -> { // r
if (verbose) addLog(context.getString(R.string.push_subscription_deleted)) // }
TootApiResult() // }
} // }
//
404 -> { // private suspend fun subscribeMastodon(
if (verbose) { // client: TootApiClient,
addLog(context.getString(R.string.missing_push_api)) // endpoint: String,
r // newAlerts: JsonObject,
} else { // ): TootApiResult? {
TootApiResult() // @Suppress("SpellCheckingInspection")
} // val params = JsonObject().apply {
} // put("subscription", JsonObject().apply {
// put("endpoint", endpoint)
403 -> { // put("keys", JsonObject().apply {
addLog(context.getString(R.string.missing_push_scope)) // put(
r // "p256dh",
} // "BBEUVi7Ehdzzpe_ZvlzzkQnhujNJuBKH1R0xYg7XdAKNFKQG9Gpm0TSGRGSuaU7LUFKX-uz8YW0hAshifDCkPuE"
// )
else -> { // put("auth", "iRdmDrOS6eK6xvG1H6KshQ")
addLog("${res.request}") // })
addLog("${res.code} ${res.message}") // })
r // put("data", JsonObject().apply {
} // put("alerts", newAlerts)
} // account.pushPolicy?.let { put("policy", it) }
} // })
// }
private suspend fun subscribeMastodon( //
client: TootApiClient, // val r = client.request(
endpoint: String, // "/api/v1/push/subscription",
newAlerts: JsonObject, // params.toPostRequestBuilder()
): TootApiResult? { // ) ?: return null
@Suppress("SpellCheckingInspection") //
val params = JsonObject().apply { // val res = r.response ?: return r
put("subscription", JsonObject().apply { //
put("endpoint", endpoint) // return when (res.code) {
put("keys", JsonObject().apply { // 404 -> {
put( // addLog(context.getString(R.string.missing_push_api))
"p256dh", // r
"BBEUVi7Ehdzzpe_ZvlzzkQnhujNJuBKH1R0xYg7XdAKNFKQG9Gpm0TSGRGSuaU7LUFKX-uz8YW0hAshifDCkPuE" // }
) //
put("auth", "iRdmDrOS6eK6xvG1H6KshQ") // 403 -> {
}) // addLog(context.getString(R.string.missing_push_scope))
}) // r
put("data", JsonObject().apply { // }
put("alerts", newAlerts) //
account.pushPolicy?.let { put("policy", it) } // 200 -> {
}) // subscribed = true
} // if (verbose) addLog(context.getString(R.string.push_subscription_updated))
// return TootApiResult()
val r = client.request( // }
"/api/v1/push/subscription", //
params.toPostRequestBuilder() // else -> {
) ?: return null // addLog(r.jsonObject?.toString())
// r
val res = r.response ?: return r // }
// }
return when (res.code) { // }
404 -> { //}
addLog(context.getString(R.string.missing_push_api))
r
}
403 -> {
addLog(context.getString(R.string.missing_push_scope))
r
}
200 -> {
subscribed = true
if (verbose) addLog(context.getString(R.string.push_subscription_updated))
return TootApiResult()
}
else -> {
addLog(r.jsonObject?.toString())
r
}
}
}
}

View File

@ -1,7 +1,6 @@
package jp.juggler.subwaytooter.notification package jp.juggler.subwaytooter.push
import android.graphics.Color import androidx.annotation.ColorRes
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.table.PushMessage import jp.juggler.subwaytooter.table.PushMessage
@ -9,86 +8,84 @@ import jp.juggler.util.log.LogCategory
private val log = LogCategory("NotificationIconAndColor") private val log = LogCategory("NotificationIconAndColor")
enum class NotificationIconAndColor( enum class PushMessageIconColor(
@ColorInt colorArg: Int, @ColorRes val colorRes: Int,
@DrawableRes val iconId: Int, @DrawableRes val iconId: Int,
val keys: Array<String>, val keys: Array<String>,
) { ) {
Favourite( Favourite(
0xe5e825, R.color.colorNotificationAccentFavourite,
R.drawable.ic_star_outline, R.drawable.ic_star_outline,
arrayOf("favourite"), arrayOf("favourite"),
), ),
Mention( Mention(
0x60f516, R.color.colorNotificationAccentMention,
R.drawable.outline_alternate_email_24, R.drawable.outline_alternate_email_24,
arrayOf("mention"), arrayOf("mention"),
), ),
Reply( Reply(
0xff3dbb, R.color.colorNotificationAccentReply,
R.drawable.ic_reply, R.drawable.ic_reply,
arrayOf("reply") arrayOf("reply")
), ),
Reblog( Reblog(
0x39e3d5, R.color.colorNotificationAccentReblog,
R.drawable.ic_repeat, R.drawable.ic_repeat,
arrayOf("reblog", "renote"), arrayOf("reblog", "renote"),
), ),
Quote( Quote(
0x40a9ff, R.color.colorNotificationAccentQuote,
R.drawable.ic_quote, R.drawable.ic_quote,
arrayOf("quote"), arrayOf("quote"),
), ),
Follow( Follow(
0xf57a33, R.color.colorNotificationAccentFollow,
R.drawable.ic_person_add, R.drawable.ic_person_add,
arrayOf("follow", "followRequestAccepted") arrayOf("follow", "followRequestAccepted")
), ),
Unfollow( Unfollow(
0x9433f5, R.color.colorNotificationAccentUnfollow,
R.drawable.ic_follow_cross, R.drawable.ic_follow_cross,
arrayOf("unfollow") arrayOf("unfollow")
), ),
Reaction( Reaction(
0xf5f233, R.color.colorNotificationAccentReaction,
R.drawable.outline_add_reaction_24, R.drawable.outline_add_reaction_24,
arrayOf("reaction", "emoji_reaction", "pleroma:emoji_reaction") arrayOf("reaction", "emoji_reaction", "pleroma:emoji_reaction")
), ),
FollowRequest( FollowRequest(
0xf53333, R.color.colorNotificationAccentFollowRequest,
R.drawable.ic_follow_wait, R.drawable.ic_follow_wait,
arrayOf("follow_request", "receiveFollowRequest"), arrayOf("follow_request", "receiveFollowRequest"),
), ),
Poll( Poll(
0x33f59b, R.color.colorNotificationAccentPoll,
R.drawable.outline_poll_24, R.drawable.outline_poll_24,
arrayOf("pollVote", "poll_vote", "poll"), arrayOf("pollVote", "poll_vote", "poll"),
), ),
Status( Status(
0x33f597, R.color.colorNotificationAccentStatus,
R.drawable.ic_edit, R.drawable.ic_edit,
arrayOf("status", "update", "status_reference") arrayOf("status", "update", "status_reference")
), ),
SignUp( SignUp(
0xf56a33, R.color.colorNotificationAccentSignUp,
R.drawable.outline_group_add_24, R.drawable.outline_group_add_24,
arrayOf("admin.sign_up"), arrayOf("admin.sign_up"),
), ),
Unknown( Unknown(
0xae1aed, R.color.colorNotificationAccentUnknown,
R.drawable.ic_question, R.drawable.ic_question,
arrayOf("unknown"), arrayOf("unknown", "admin.sign_up"),
) )
; ;
val color = Color.BLACK or colorArg
companion object { companion object {
val map = buildMap { val map = buildMap {
values().forEach { values().forEach {
for (k in it.keys) { for (k in it.keys) {
val old: NotificationIconAndColor? = get(k) val old: PushMessageIconColor? = get(k)
if (old != null) { if (old != null) {
error("NotificationIconAndColor: $k is duplicate: ${it.name} and ${old.name}") error("NotificationIconAndColor: $k is duplicate: ${it.name} and ${old.name}")
} else { } else {
@ -100,20 +97,6 @@ enum class NotificationIconAndColor(
} }
} }
fun String.findNotificationIconAndColor() = fun PushMessage.iconColor() =
NotificationIconAndColor.map[this] notificationType?.let { PushMessageIconColor.map[it] }
?: PushMessageIconColor.Unknown
fun PushMessage.notificationIconAndColor(): NotificationIconAndColor {
// mastodon
messageJson?.string("notification_type")
?.findNotificationIconAndColor()?.let { return it }
// misskey
when (messageJson?.string("type")) {
"notification" ->
messageJson?.jsonObject("body")?.string("type")
?.findNotificationIconAndColor()?.let { return it }
}
return NotificationIconAndColor.Unknown
}

View File

@ -4,11 +4,11 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.await import androidx.work.await
import jp.juggler.anko.BuildConfig
import jp.juggler.crypt.* import jp.juggler.crypt.*
import jp.juggler.subwaytooter.ActCallback import jp.juggler.subwaytooter.ActCallback
import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.R
@ -20,7 +20,7 @@ import jp.juggler.subwaytooter.api.push.ApiPushMisskey
import jp.juggler.subwaytooter.dialog.SuspendProgress import jp.juggler.subwaytooter.dialog.SuspendProgress
import jp.juggler.subwaytooter.notification.NotificationChannels import jp.juggler.subwaytooter.notification.NotificationChannels
import jp.juggler.subwaytooter.notification.NotificationDeleteReceiver.Companion.intentNotificationDelete import jp.juggler.subwaytooter.notification.NotificationDeleteReceiver.Companion.intentNotificationDelete
import jp.juggler.subwaytooter.notification.notificationIconAndColor import jp.juggler.subwaytooter.notification.iconColor
import jp.juggler.subwaytooter.pref.PrefDevice import jp.juggler.subwaytooter.pref.PrefDevice
import jp.juggler.subwaytooter.pref.prefDevice import jp.juggler.subwaytooter.pref.prefDevice
import jp.juggler.subwaytooter.push.* import jp.juggler.subwaytooter.push.*
@ -637,7 +637,7 @@ class PushRepo(
} }
val density = context.resources.displayMetrics.density val density = context.resources.displayMetrics.density
val iconAndColor = pm.notificationIconAndColor() val iconAndColor = pm.iconColor()
suspend fun PushMessage.loadSmallIcon(context: Context): IconCompat { suspend fun PushMessage.loadSmallIcon(context: Context): IconCompat {
iconSmall?.notEmpty() iconSmall?.notEmpty()
@ -687,7 +687,7 @@ class PushRepo(
// val piTap = PendingIntent.getActivity(this, nc.pircTap, iTap, PendingIntent.FLAG_IMMUTABLE) // val piTap = PendingIntent.getActivity(this, nc.pircTap, iTap, PendingIntent.FLAG_IMMUTABLE)
ncPushMessage.notify(context, urlDelete) { ncPushMessage.notify(context, urlDelete) {
color = iconAndColor.color color = ContextCompat.getColor(context,iconAndColor.colorRes)
setSmallIcon(iconSmall) setSmallIcon(iconSmall)
iconBitmapLarge?.let { setLargeIcon(it) } iconBitmapLarge?.let { setLargeIcon(it) }
setContentTitle(pm.loginAcct) setContentTitle(pm.loginAcct)

View File

@ -156,4 +156,20 @@
<!-- 白テーマのナビゲーションバーは白ではない --> <!-- 白テーマのナビゲーションバーは白ではない -->
<color name="colorNavigationBarWorkaround">#707070</color> <color name="colorNavigationBarWorkaround">#707070</color>
<!-- 通知のアクセント色 Pushメッセージ用 -->
<color name="colorNotificationAccentFavourite">#e5e825</color>
<color name="colorNotificationAccentFollow">#f57a33</color>
<color name="colorNotificationAccentFollowRequest">#f53333</color>
<color name="colorNotificationAccentMention">#60f516</color>
<color name="colorNotificationAccentPoll">#33f59b</color>
<color name="colorNotificationAccentQuote">#40a9ff</color>
<color name="colorNotificationAccentReaction">#f5f233</color>
<color name="colorNotificationAccentReblog">#39e3d5</color>
<color name="colorNotificationAccentReply">#ff3dbb</color>
<color name="colorNotificationAccentSignUp">#f56a33</color>
<color name="colorNotificationAccentStatus">#33f597</color>
<color name="colorNotificationAccentUnfollow">#9433f5</color>
<color name="colorNotificationAccentUnknown">#ae1aed</color>
</resources> </resources>