通知のアクセント色をリソース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.dialog.actionsDialog
import jp.juggler.subwaytooter.dialog.runInProgress
import jp.juggler.subwaytooter.notification.NotificationIconAndColor
import jp.juggler.subwaytooter.notification.notificationIconAndColor
import jp.juggler.subwaytooter.notification.PushMessageIconColor
import jp.juggler.subwaytooter.notification.iconColor
import jp.juggler.subwaytooter.push.pushRepo
import jp.juggler.subwaytooter.table.PushMessage
import jp.juggler.subwaytooter.table.daoAccountNotificationStatus
@ -165,11 +165,13 @@ class ActPushMessageList : AppCompatActivity() {
}
private val tintIconMap = HashMap<String, Drawable>()
fun tintIcon(ic: NotificationIconAndColor) =
fun tintIcon(ic: PushMessageIconColor) =
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.setTint(it, ic.color)
DrawableCompat.setTint(it, ContextCompat.getColor(context, ic.colorRes))
}
}
@ -190,7 +192,7 @@ class ActPushMessageList : AppCompatActivity() {
pm ?: return
lastItem = pm
val iconAndColor = pm.notificationIconAndColor()
val iconAndColor = pm.iconColor()
Glide.with(views.ivSmall)
.load(pm.iconSmall)
.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.pickAccount
import jp.juggler.subwaytooter.dialog.runInProgress
import jp.juggler.subwaytooter.notification.PushSubscriptionHelper
import jp.juggler.subwaytooter.notification.checkNotificationImmediate
import jp.juggler.subwaytooter.notification.checkNotificationImmediateAll
import jp.juggler.subwaytooter.notification.recycleClickedNotification
@ -47,7 +46,6 @@ import jp.juggler.util.queryIntentActivitiesCompat
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.withContext
import org.unifiedpush.android.connector.UnifiedPush
import java.util.ArrayList
private val log = LogCategory("ActMainIntent")
@ -351,7 +349,7 @@ fun ActMain.handleSharedIntent(intent: Intent) {
}
// アカウントを追加/更新したらappServerHashの取得をやりなおす
fun ActMain.updatePushDistributer(){
fun ActMain.updatePushDistributer() {
when {
fcmHandler.noFcm && prefDevice.pushDistributor.isNullOrEmpty() -> {
try {
@ -361,7 +359,7 @@ fun ActMain.updatePushDistributer(){
// 選択しなかった場合は購読の更新を行わない
}
}
else -> PushWorker.enqueueRegisterEndpoint(this)
else -> PushWorker.enqueueRegisterEndpoint(this)
}
}

View File

@ -1,488 +1,488 @@
package jp.juggler.subwaytooter.notification
import android.content.Context
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootApiResult
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.SubscriptionServerKey
import jp.juggler.subwaytooter.table.appDatabase
import jp.juggler.util.*
import jp.juggler.util.data.*
import jp.juggler.util.log.*
import jp.juggler.util.network.toPostRequestBuilder
import jp.juggler.util.ui.*
import okhttp3.Request
class PushSubscriptionHelper(
val context: Context,
val account: SavedAccount,
private val verbose: Boolean = false,
private val daoSubscriptionServerKey: SubscriptionServerKey.Access =
SubscriptionServerKey.Access(appDatabase),
) {
companion object {
private val log = LogCategory("PushSubscriptionHelper")
}
private val logBuffer = StringBuilder()
val logString: String
get() = logBuffer.toString()
private var subscribed: Boolean = false
val flags = account.notificationFlags()
private fun addLog(s: String?) {
if (s?.isNotEmpty() == true) {
if (logBuffer.isNotEmpty()) logBuffer.append('\n')
logBuffer.append(s)
}
}
// アプリサーバにendpoint URLの変更を伝える
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,
// 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)
//import android.content.Context
//import jp.juggler.subwaytooter.R
//import jp.juggler.subwaytooter.api.TootApiClient
//import jp.juggler.subwaytooter.api.TootApiResult
//import jp.juggler.subwaytooter.api.entity.*
//import jp.juggler.subwaytooter.table.SavedAccount
//import jp.juggler.subwaytooter.table.SubscriptionServerKey
//import jp.juggler.subwaytooter.table.appDatabase
//import jp.juggler.util.*
//import jp.juggler.util.data.*
//import jp.juggler.util.log.*
//import jp.juggler.util.network.toPostRequestBuilder
//import jp.juggler.util.ui.*
//import okhttp3.Request
//
//class PushSubscriptionHelper(
// val context: Context,
// val account: SavedAccount,
// private val verbose: Boolean = false,
// private val daoSubscriptionServerKey: SubscriptionServerKey.Access =
// SubscriptionServerKey.Access(appDatabase),
//) {
// companion object {
// private val log = LogCategory("PushSubscriptionHelper")
// }
//
// private val logBuffer = StringBuilder()
//
// val logString: String
// get() = logBuffer.toString()
//
// private var subscribed: Boolean = false
//
// val flags = account.notificationFlags()
//
// private fun addLog(s: String?) {
// if (s?.isNotEmpty() == true) {
// if (logBuffer.isNotEmpty()) logBuffer.append('\n')
// logBuffer.append(s)
// }
// }
//
// // アプリサーバにendpoint URLの変更を伝える
// 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)
// }
// }
// }
// }
// } 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)
//// 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)
//// }
//// }
//
// else -> Unit
// // record error text
//// account.updateSubscriptionError(log)
// }
// }
// private suspend fun updateSubscriptionMisskey(client: TootApiClient): TootApiResult? {
//// 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"
//// )
//// }
//// }
//// }
//
// // 現在の購読状態を取得できないので、毎回購読の更新を行う
// // 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))
// }
// }
//// 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
//// )
//// }
//// }
//
// // アクセストークン
// 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,
// // returns null if no error
// private fun checkInstanceVersionMastodon(
// ti: TootInstance,
// subscription404: 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
// // 2.4.0rc1 未満にはプッシュ購読APIはない
// if (!ti.versionGE(TootInstance.VERSION_2_4_0_rc1)) {
// return TootApiResult(
// context.getString(R.string.instance_does_not_support_push_api, ti.version)
// )
// }
//
// 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()
// }
//
// else -> {
// // 2.4.0rc1では「APIが存在しない」と「購読が存在しない」を判別できない
// }
// }
// }
// return null
// }
// returns null if no error
private fun checkInstanceVersionMastodon(
ti: TootInstance,
subscription404: Boolean,
): TootApiResult? {
// 2.4.0rc1 未満にはプッシュ購読APIはない
if (!ti.versionGE(TootInstance.VERSION_2_4_0_rc1)) {
return TootApiResult(
context.getString(R.string.instance_does_not_support_push_api, ti.version)
)
}
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()
}
else -> {
// 2.4.0rc1では「APIが存在しない」と「購読が存在しない」を判別できない
}
}
}
return null
}
private class CheckCurrentSubscriptionResult(
val result: TootApiResult?,
val failed: Boolean,
val is404: Boolean,
)
@Suppress("BooleanLiteralArgument")
private suspend fun checkCurrentSubscription(client: TootApiClient): CheckCurrentSubscriptionResult {
val r = client.request("/api/v1/push/subscription")
fun rvError() = CheckCurrentSubscriptionResult(r, true, false)
fun rvOk() = CheckCurrentSubscriptionResult(r, false, false)
fun rv404() = CheckCurrentSubscriptionResult(r, false, true)
val res = r?.response ?: return rvError() // cancelled or missing response
if (res.code != 200) log.i("${account.acct}: check existing subscription: code=${res.code}")
return when (res.code) {
200 -> {
if (r.error?.isNotEmpty() == true && r.jsonObject == null) {
// Pleromaが200応答でもエラーHTMLを返す場合がある
addLog(context.getString(R.string.instance_does_not_support_push_api_pleroma))
rvError()
} else {
// たぶん購読が存在する
rvOk()
}
}
// この時点では存在しないのが購読なのかAPIなのか分からない
404 -> rv404()
403 -> {
// アクセストークンにpushスコープがない
if (flags != 0 || verbose) addLog(context.getString(R.string.missing_push_scope))
rvError()
}
in 400 until 500 -> {
addLog(context.getString(R.string.instance_does_not_support_push_api_pleroma))
rvError()
}
else -> {
addLog("${res.request}")
addLog("${res.code} ${res.message}")
rvOk() // 後でリトライする
}
}
}
private suspend fun unsubscribeMastodon(
client: TootApiClient,
): TootApiResult? {
val r = client.request("/api/v1/push/subscription", Request.Builder().delete())
val res = r?.response ?: return r
return when (res.code) {
200 -> {
if (verbose) addLog(context.getString(R.string.push_subscription_deleted))
TootApiResult()
}
404 -> {
if (verbose) {
addLog(context.getString(R.string.missing_push_api))
r
} else {
TootApiResult()
}
}
403 -> {
addLog(context.getString(R.string.missing_push_scope))
r
}
else -> {
addLog("${res.request}")
addLog("${res.code} ${res.message}")
r
}
}
}
private suspend fun subscribeMastodon(
client: TootApiClient,
endpoint: String,
newAlerts: JsonObject,
): TootApiResult? {
@Suppress("SpellCheckingInspection")
val params = JsonObject().apply {
put("subscription", JsonObject().apply {
put("endpoint", endpoint)
put("keys", JsonObject().apply {
put(
"p256dh",
"BBEUVi7Ehdzzpe_ZvlzzkQnhujNJuBKH1R0xYg7XdAKNFKQG9Gpm0TSGRGSuaU7LUFKX-uz8YW0hAshifDCkPuE"
)
put("auth", "iRdmDrOS6eK6xvG1H6KshQ")
})
})
put("data", JsonObject().apply {
put("alerts", newAlerts)
account.pushPolicy?.let { put("policy", it) }
})
}
val r = client.request(
"/api/v1/push/subscription",
params.toPostRequestBuilder()
) ?: return null
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
}
}
}
}
//
// private class CheckCurrentSubscriptionResult(
// val result: TootApiResult?,
// val failed: Boolean,
// val is404: Boolean,
// )
//
// @Suppress("BooleanLiteralArgument")
// private suspend fun checkCurrentSubscription(client: TootApiClient): CheckCurrentSubscriptionResult {
// val r = client.request("/api/v1/push/subscription")
// fun rvError() = CheckCurrentSubscriptionResult(r, true, false)
// fun rvOk() = CheckCurrentSubscriptionResult(r, false, false)
// fun rv404() = CheckCurrentSubscriptionResult(r, false, true)
// val res = r?.response ?: return rvError() // cancelled or missing response
//
// if (res.code != 200) log.i("${account.acct}: check existing subscription: code=${res.code}")
//
// return when (res.code) {
// 200 -> {
// if (r.error?.isNotEmpty() == true && r.jsonObject == null) {
// // Pleromaが200応答でもエラーHTMLを返す場合がある
// addLog(context.getString(R.string.instance_does_not_support_push_api_pleroma))
// rvError()
// } else {
// // たぶん購読が存在する
// rvOk()
// }
// }
//
// // この時点では存在しないのが購読なのかAPIなのか分からない
// 404 -> rv404()
//
// 403 -> {
// // アクセストークンにpushスコープがない
// if (flags != 0 || verbose) addLog(context.getString(R.string.missing_push_scope))
// rvError()
// }
//
// in 400 until 500 -> {
// addLog(context.getString(R.string.instance_does_not_support_push_api_pleroma))
// rvError()
// }
//
// else -> {
// addLog("${res.request}")
// addLog("${res.code} ${res.message}")
// rvOk() // 後でリトライする
// }
// }
// }
//
// private suspend fun unsubscribeMastodon(
// client: TootApiClient,
// ): TootApiResult? {
//
// val r = client.request("/api/v1/push/subscription", Request.Builder().delete())
// val res = r?.response ?: return r
//
// return when (res.code) {
// 200 -> {
// if (verbose) addLog(context.getString(R.string.push_subscription_deleted))
// TootApiResult()
// }
//
// 404 -> {
// if (verbose) {
// addLog(context.getString(R.string.missing_push_api))
// r
// } else {
// TootApiResult()
// }
// }
//
// 403 -> {
// addLog(context.getString(R.string.missing_push_scope))
// r
// }
//
// else -> {
// addLog("${res.request}")
// addLog("${res.code} ${res.message}")
// r
// }
// }
// }
//
// private suspend fun subscribeMastodon(
// client: TootApiClient,
// endpoint: String,
// newAlerts: JsonObject,
// ): TootApiResult? {
// @Suppress("SpellCheckingInspection")
// val params = JsonObject().apply {
// put("subscription", JsonObject().apply {
// put("endpoint", endpoint)
// put("keys", JsonObject().apply {
// put(
// "p256dh",
// "BBEUVi7Ehdzzpe_ZvlzzkQnhujNJuBKH1R0xYg7XdAKNFKQG9Gpm0TSGRGSuaU7LUFKX-uz8YW0hAshifDCkPuE"
// )
// put("auth", "iRdmDrOS6eK6xvG1H6KshQ")
// })
// })
// put("data", JsonObject().apply {
// put("alerts", newAlerts)
// account.pushPolicy?.let { put("policy", it) }
// })
// }
//
// val r = client.request(
// "/api/v1/push/subscription",
// params.toPostRequestBuilder()
// ) ?: return null
//
// 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.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.table.PushMessage
@ -9,86 +8,84 @@ import jp.juggler.util.log.LogCategory
private val log = LogCategory("NotificationIconAndColor")
enum class NotificationIconAndColor(
@ColorInt colorArg: Int,
enum class PushMessageIconColor(
@ColorRes val colorRes: Int,
@DrawableRes val iconId: Int,
val keys: Array<String>,
) {
Favourite(
0xe5e825,
R.color.colorNotificationAccentFavourite,
R.drawable.ic_star_outline,
arrayOf("favourite"),
),
Mention(
0x60f516,
R.color.colorNotificationAccentMention,
R.drawable.outline_alternate_email_24,
arrayOf("mention"),
),
Reply(
0xff3dbb,
R.color.colorNotificationAccentReply,
R.drawable.ic_reply,
arrayOf("reply")
),
Reblog(
0x39e3d5,
R.color.colorNotificationAccentReblog,
R.drawable.ic_repeat,
arrayOf("reblog", "renote"),
),
Quote(
0x40a9ff,
R.color.colorNotificationAccentQuote,
R.drawable.ic_quote,
arrayOf("quote"),
),
Follow(
0xf57a33,
R.color.colorNotificationAccentFollow,
R.drawable.ic_person_add,
arrayOf("follow", "followRequestAccepted")
),
Unfollow(
0x9433f5,
R.color.colorNotificationAccentUnfollow,
R.drawable.ic_follow_cross,
arrayOf("unfollow")
),
Reaction(
0xf5f233,
R.color.colorNotificationAccentReaction,
R.drawable.outline_add_reaction_24,
arrayOf("reaction", "emoji_reaction", "pleroma:emoji_reaction")
),
FollowRequest(
0xf53333,
R.color.colorNotificationAccentFollowRequest,
R.drawable.ic_follow_wait,
arrayOf("follow_request", "receiveFollowRequest"),
),
Poll(
0x33f59b,
R.color.colorNotificationAccentPoll,
R.drawable.outline_poll_24,
arrayOf("pollVote", "poll_vote", "poll"),
),
Status(
0x33f597,
R.color.colorNotificationAccentStatus,
R.drawable.ic_edit,
arrayOf("status", "update", "status_reference")
),
SignUp(
0xf56a33,
R.color.colorNotificationAccentSignUp,
R.drawable.outline_group_add_24,
arrayOf("admin.sign_up"),
),
Unknown(
0xae1aed,
R.color.colorNotificationAccentUnknown,
R.drawable.ic_question,
arrayOf("unknown"),
arrayOf("unknown", "admin.sign_up"),
)
;
val color = Color.BLACK or colorArg
companion object {
val map = buildMap {
values().forEach {
for (k in it.keys) {
val old: NotificationIconAndColor? = get(k)
val old: PushMessageIconColor? = get(k)
if (old != null) {
error("NotificationIconAndColor: $k is duplicate: ${it.name} and ${old.name}")
} else {
@ -100,20 +97,6 @@ enum class NotificationIconAndColor(
}
}
fun String.findNotificationIconAndColor() =
NotificationIconAndColor.map[this]
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
}
fun PushMessage.iconColor() =
notificationType?.let { PushMessageIconColor.map[it] }
?: PushMessageIconColor.Unknown

View File

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

View File

@ -156,4 +156,20 @@
<!-- 白テーマのナビゲーションバーは白ではない -->
<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>