feat: ОЛО СЫН ЕСТ КАМНИ
This commit is contained in:
@@ -55,6 +55,18 @@ object FkmChannel {
|
|||||||
|
|
||||||
"showCall" -> result.success(deliver(ctx, call, "showCall"))
|
"showCall" -> result.success(deliver(ctx, call, "showCall"))
|
||||||
|
|
||||||
|
"editMessage" -> result.success(
|
||||||
|
update(ctx, call, "editMessage") { notifier, data ->
|
||||||
|
notifier.editMessage(data)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
"removeMessage" -> result.success(
|
||||||
|
update(ctx, call, "removeMessage") { notifier, data ->
|
||||||
|
notifier.removeMessage(data)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
"hasNotificationPermission" ->
|
"hasNotificationPermission" ->
|
||||||
result.success(NotificationManagerCompat.from(ctx).areNotificationsEnabled())
|
result.success(NotificationManagerCompat.from(ctx).areNotificationsEnabled())
|
||||||
|
|
||||||
@@ -115,6 +127,24 @@ object FkmChannel {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Правка и удаление ничего не «доставляют» — счётчик они не трогают.
|
||||||
|
private fun update(
|
||||||
|
ctx: Context,
|
||||||
|
call: MethodCall,
|
||||||
|
tag: String,
|
||||||
|
action: (KometNotifier, Map<String, String>) -> Unit,
|
||||||
|
): Boolean {
|
||||||
|
val data = call.argument<Map<String, String>>("data") ?: return false
|
||||||
|
worker.execute {
|
||||||
|
try {
|
||||||
|
action(KometNotifier(ctx), data)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w("Fkm", "$tag failed: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
fun detach() {
|
fun detach() {
|
||||||
channel?.setMethodCallHandler(null)
|
channel?.setMethodCallHandler(null)
|
||||||
channel = null
|
channel = null
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import android.graphics.Typeface
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
|
import android.text.style.StrikethroughSpan
|
||||||
import android.text.style.StyleSpan
|
import android.text.style.StyleSpan
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
@@ -57,7 +58,18 @@ class KometNotifier(private val ctx: Context) {
|
|||||||
private const val ACCENT = 0xFF7C6BF0.toInt()
|
private const val ACCENT = 0xFF7C6BF0.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class Hist(val text: String, val key: String, val name: String, val ts: Long)
|
private data class Hist(
|
||||||
|
val text: String,
|
||||||
|
val key: String,
|
||||||
|
val name: String,
|
||||||
|
val ts: Long,
|
||||||
|
val mid: String,
|
||||||
|
val deleted: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Что нужно для перерисовки чата, когда нового пуша нет: правка и удаление
|
||||||
|
// приходят без заголовка, аккаунта и признака группы.
|
||||||
|
private data class ChatMeta(val title: String, val account: Int, val group: Boolean)
|
||||||
|
|
||||||
fun handle(data: Map<String, String>) {
|
fun handle(data: Map<String, String>) {
|
||||||
when (data["type"]) {
|
when (data["type"]) {
|
||||||
@@ -79,13 +91,15 @@ class KometNotifier(private val ctx: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun notifIdOf(chatId: Long): Int = (chatId and 0x7fffffff).toInt()
|
||||||
|
|
||||||
private fun showMessage(data: Map<String, String>) {
|
private fun showMessage(data: Map<String, String>) {
|
||||||
val chatId = data["mc"]?.toLongOrNull() ?: return
|
val chatId = data["mc"]?.toLongOrNull() ?: return
|
||||||
val notifId = (chatId and 0x7fffffff).toInt()
|
val notifId = notifIdOf(chatId)
|
||||||
if (ChatNotifications.isDisplayed(chatId)) {
|
if (ChatNotifications.isDisplayed(chatId)) {
|
||||||
manager().cancel(notifId)
|
manager().cancel(notifId)
|
||||||
clearHistory(chatId)
|
clearChat(chatId)
|
||||||
rebuildSummary(notifId)
|
syncSummary(notifId, null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val senderId = data["suid"] ?: ""
|
val senderId = data["suid"] ?: ""
|
||||||
@@ -94,14 +108,90 @@ class KometNotifier(private val ctx: Context) {
|
|||||||
val text = data["msg"] ?: data["body"] ?: data["text"] ?: "Новое сообщение"
|
val text = data["msg"] ?: data["body"] ?: data["text"] ?: "Новое сообщение"
|
||||||
val ts = data["ctime"]?.toLongOrNull() ?: data["ttime"]?.toLongOrNull()
|
val ts = data["ctime"]?.toLongOrNull() ?: data["ttime"]?.toLongOrNull()
|
||||||
?: System.currentTimeMillis()
|
?: System.currentTimeMillis()
|
||||||
val isGroup = chatTitle != senderName
|
|
||||||
|
|
||||||
ensureChannel()
|
ensureChannel()
|
||||||
|
|
||||||
val active = activeIds()
|
val active = activeIds()
|
||||||
if (!active.contains(notifId)) clearHistory(chatId)
|
if (!active.contains(notifId)) clearChat(chatId)
|
||||||
val history = appendHistory(chatId, Hist(text, senderId, senderName, ts))
|
val meta = ChatMeta(chatTitle, data["c"]?.toIntOrNull() ?: 0, chatTitle != senderName)
|
||||||
|
saveMeta(chatId, meta)
|
||||||
|
val history = appendHistory(
|
||||||
|
chatId,
|
||||||
|
Hist(text, senderId, senderName, ts, data["msgid"] ?: "", false),
|
||||||
|
)
|
||||||
|
|
||||||
|
render(chatId, notifId, meta, history, alertOnce = false)
|
||||||
|
updateSummary(notifId, senderName, text, ts, active)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сообщение отредактировали — правим текст в уже висящем уведомлении.
|
||||||
|
fun editMessage(data: Map<String, String>) {
|
||||||
|
val chatId = data["mc"]?.toLongOrNull() ?: return
|
||||||
|
val mid = data["msgid"]?.takeIf { it.isNotEmpty() } ?: return
|
||||||
|
val text = data["msg"]?.takeIf { it.isNotEmpty() } ?: return
|
||||||
|
|
||||||
|
val history = loadHistory(chatId)
|
||||||
|
val index = history.indexOfLast { it.mid == mid }
|
||||||
|
if (index < 0) return
|
||||||
|
val old = history[index]
|
||||||
|
if (old.deleted || old.text == text) return
|
||||||
|
|
||||||
|
val updated = history.toMutableList()
|
||||||
|
updated[index] = old.copy(text = text)
|
||||||
|
saveHistory(chatId, updated)
|
||||||
|
|
||||||
|
val notifId = notifIdOf(chatId)
|
||||||
|
if (!activeIds().contains(notifId)) return
|
||||||
|
val meta = loadMeta(chatId) ?: return
|
||||||
|
render(chatId, notifId, meta, updated, alertOnce = true)
|
||||||
|
syncSummary(notifId, updated.last())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сообщение удалили. keep — включено «показывать удалённые сообщения»:
|
||||||
|
// тогда строка остаётся в шторке, но зачёркнутой и с пометкой.
|
||||||
|
fun removeMessage(data: Map<String, String>) {
|
||||||
|
val chatId = data["mc"]?.toLongOrNull() ?: return
|
||||||
|
val mid = data["msgid"]?.takeIf { it.isNotEmpty() } ?: return
|
||||||
|
val keep = data["keep"] == "true"
|
||||||
|
|
||||||
|
val history = loadHistory(chatId)
|
||||||
|
val index = history.indexOfLast { it.mid == mid }
|
||||||
|
if (index < 0) return
|
||||||
|
if (keep && history[index].deleted) return
|
||||||
|
|
||||||
|
val updated = history.toMutableList()
|
||||||
|
if (keep) {
|
||||||
|
updated[index] = updated[index].copy(deleted = true)
|
||||||
|
} else {
|
||||||
|
updated.removeAt(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
val notifId = notifIdOf(chatId)
|
||||||
|
if (updated.isEmpty()) {
|
||||||
|
clearChat(chatId)
|
||||||
|
manager().cancel(notifId)
|
||||||
|
syncSummary(notifId, null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
saveHistory(chatId, updated)
|
||||||
|
if (!activeIds().contains(notifId)) return
|
||||||
|
val meta = loadMeta(chatId) ?: return
|
||||||
|
render(chatId, notifId, meta, updated, alertOnce = true)
|
||||||
|
syncSummary(notifId, updated.last())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun render(
|
||||||
|
chatId: Long,
|
||||||
|
notifId: Int,
|
||||||
|
meta: ChatMeta,
|
||||||
|
history: List<Hist>,
|
||||||
|
alertOnce: Boolean,
|
||||||
|
) {
|
||||||
|
if (history.isEmpty()) return
|
||||||
|
ensureChannel()
|
||||||
|
|
||||||
|
val newest = history.last()
|
||||||
val avatarCache = HashMap<String, Bitmap>()
|
val avatarCache = HashMap<String, Bitmap>()
|
||||||
val personCache = HashMap<String, Person>()
|
val personCache = HashMap<String, Person>()
|
||||||
fun avatarFor(key: String, name: String): Bitmap =
|
fun avatarFor(key: String, name: String): Bitmap =
|
||||||
@@ -115,17 +205,16 @@ class KometNotifier(private val ctx: Context) {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
val senderPerson = personFor(senderId, senderName)
|
|
||||||
val shortcutId = "chat_$chatId"
|
val shortcutId = "chat_$chatId"
|
||||||
publishShortcut(shortcutId, chatId, chatTitle, senderPerson)
|
publishShortcut(shortcutId, chatId, meta.title, personFor(newest.key, newest.name))
|
||||||
|
|
||||||
val style = NotificationCompat.MessagingStyle(Person.Builder().setName("Вы").build())
|
val style = NotificationCompat.MessagingStyle(Person.Builder().setName("Вы").build())
|
||||||
if (isGroup) {
|
if (meta.group) {
|
||||||
style.conversationTitle = chatTitle
|
style.conversationTitle = meta.title
|
||||||
style.isGroupConversation = true
|
style.isGroupConversation = true
|
||||||
}
|
}
|
||||||
for (h in history) {
|
for (h in history) {
|
||||||
style.addMessage(h.text, h.ts, personFor(h.key, h.name))
|
style.addMessage(displayText(h), h.ts, personFor(h.key, h.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
val builder = NotificationCompat.Builder(ctx, CHANNEL_ID)
|
val builder = NotificationCompat.Builder(ctx, CHANNEL_ID)
|
||||||
@@ -134,23 +223,36 @@ class KometNotifier(private val ctx: Context) {
|
|||||||
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setGroup(GROUP_KEY)
|
.setGroup(GROUP_KEY)
|
||||||
.setWhen(ts)
|
.setWhen(newest.ts)
|
||||||
.setShowWhen(true)
|
.setShowWhen(true)
|
||||||
|
.setOnlyAlertOnce(alertOnce)
|
||||||
.setContentIntent(openIntent(notifId, chatId))
|
.setContentIntent(openIntent(notifId, chatId))
|
||||||
.setShortcutId(shortcutId)
|
.setShortcutId(shortcutId)
|
||||||
.setLocusId(LocusIdCompat(shortcutId))
|
.setLocusId(LocusIdCompat(shortcutId))
|
||||||
.setStyle(style)
|
.setStyle(style)
|
||||||
.setLargeIcon(avatarFor(senderId, senderName))
|
.setLargeIcon(avatarFor(newest.key, newest.name))
|
||||||
|
|
||||||
val account = data["c"]?.toIntOrNull() ?: 0
|
if (meta.account != 0) {
|
||||||
if (account != 0) {
|
val replyTo = history.lastOrNull { !it.deleted }?.mid?.toLongOrNull()
|
||||||
builder.addAction(replyAction(notifId, account, chatId, data["msgid"]?.toLongOrNull()))
|
builder.addAction(replyAction(notifId, meta.account, chatId, replyTo))
|
||||||
}
|
}
|
||||||
|
|
||||||
manager().notify(notifId, builder.build())
|
manager().notify(notifId, builder.build())
|
||||||
updateSummary(notifId, senderName, text, ts, active)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun displayText(h: Hist): CharSequence {
|
||||||
|
if (!h.deleted) return h.text
|
||||||
|
val sb = SpannableStringBuilder(ctx.getString(R.string.notif_deleted_prefix))
|
||||||
|
sb.append(' ')
|
||||||
|
val start = sb.length
|
||||||
|
sb.append(h.text)
|
||||||
|
sb.setSpan(StrikethroughSpan(), start, sb.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
return sb
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun plainText(h: Hist): String =
|
||||||
|
if (h.deleted) "${ctx.getString(R.string.notif_deleted_prefix)} ${h.text}" else h.text
|
||||||
|
|
||||||
private fun updateSummary(
|
private fun updateSummary(
|
||||||
notifId: Int,
|
notifId: Int,
|
||||||
senderName: String,
|
senderName: String,
|
||||||
@@ -176,7 +278,8 @@ class KometNotifier(private val ctx: Context) {
|
|||||||
publishSummary(entriesOf(kept))
|
publishSummary(entriesOf(kept))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun rebuildSummary(dismissedId: Int) {
|
// `newest` == null — уведомление чата ушло из шторки.
|
||||||
|
private fun syncSummary(notifId: Int, newest: Hist?) {
|
||||||
val active = activeIds()
|
val active = activeIds()
|
||||||
val reg = loadRegistry()
|
val reg = loadRegistry()
|
||||||
val kept = JSONObject()
|
val kept = JSONObject()
|
||||||
@@ -184,10 +287,19 @@ class KometNotifier(private val ctx: Context) {
|
|||||||
while (keys.hasNext()) {
|
while (keys.hasNext()) {
|
||||||
val k = keys.next()
|
val k = keys.next()
|
||||||
val id = k.toIntOrNull() ?: continue
|
val id = k.toIntOrNull() ?: continue
|
||||||
if (id == dismissedId) continue
|
if (id == notifId) continue
|
||||||
val entry = reg.optJSONObject(k) ?: continue
|
val entry = reg.optJSONObject(k) ?: continue
|
||||||
if (active.contains(id)) kept.put(k, entry)
|
if (active.contains(id)) kept.put(k, entry)
|
||||||
}
|
}
|
||||||
|
if (newest != null && active.contains(notifId)) {
|
||||||
|
kept.put(
|
||||||
|
notifId.toString(),
|
||||||
|
JSONObject()
|
||||||
|
.put("n", newest.name)
|
||||||
|
.put("t", plainText(newest))
|
||||||
|
.put("ts", newest.ts),
|
||||||
|
)
|
||||||
|
}
|
||||||
saveRegistry(kept)
|
saveRegistry(kept)
|
||||||
publishSummary(entriesOf(kept))
|
publishSummary(entriesOf(kept))
|
||||||
}
|
}
|
||||||
@@ -321,30 +433,74 @@ class KometNotifier(private val ctx: Context) {
|
|||||||
|
|
||||||
private fun pushPrefs() = ctx.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
private fun pushPrefs() = ctx.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
private fun appendHistory(chatId: Long, item: Hist): List<Hist> {
|
private fun loadHistory(chatId: Long): List<Hist> {
|
||||||
val prefs = pushPrefs()
|
|
||||||
val key = "hist_$chatId"
|
|
||||||
val arr = try {
|
val arr = try {
|
||||||
JSONArray(prefs.getString(key, "[]"))
|
JSONArray(pushPrefs().getString("hist_$chatId", "[]"))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
JSONArray()
|
JSONArray()
|
||||||
}
|
}
|
||||||
arr.put(
|
|
||||||
JSONObject().put("t", item.text).put("k", item.key)
|
|
||||||
.put("n", item.name).put("ts", item.ts),
|
|
||||||
)
|
|
||||||
while (arr.length() > HISTORY_LIMIT) arr.remove(0)
|
|
||||||
prefs.edit().putString(key, arr.toString()).apply()
|
|
||||||
val out = ArrayList<Hist>(arr.length())
|
val out = ArrayList<Hist>(arr.length())
|
||||||
for (i in 0 until arr.length()) {
|
for (i in 0 until arr.length()) {
|
||||||
val o = arr.getJSONObject(i)
|
val o = arr.optJSONObject(i) ?: continue
|
||||||
out.add(Hist(o.optString("t"), o.optString("k"), o.optString("n"), o.optLong("ts")))
|
out.add(
|
||||||
|
Hist(
|
||||||
|
o.optString("t"),
|
||||||
|
o.optString("k"),
|
||||||
|
o.optString("n"),
|
||||||
|
o.optLong("ts"),
|
||||||
|
o.optString("m"),
|
||||||
|
o.optBoolean("d"),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearHistory(chatId: Long) {
|
private fun saveHistory(chatId: Long, items: List<Hist>) {
|
||||||
pushPrefs().edit().remove("hist_$chatId").apply()
|
val arr = JSONArray()
|
||||||
|
for (h in items) {
|
||||||
|
arr.put(
|
||||||
|
JSONObject()
|
||||||
|
.put("t", h.text)
|
||||||
|
.put("k", h.key)
|
||||||
|
.put("n", h.name)
|
||||||
|
.put("ts", h.ts)
|
||||||
|
.put("m", h.mid)
|
||||||
|
.put("d", h.deleted),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pushPrefs().edit().putString("hist_$chatId", arr.toString()).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun appendHistory(chatId: Long, item: Hist): List<Hist> {
|
||||||
|
val items = ArrayList(loadHistory(chatId))
|
||||||
|
items.add(item)
|
||||||
|
while (items.size > HISTORY_LIMIT) items.removeAt(0)
|
||||||
|
saveHistory(chatId, items)
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearChat(chatId: Long) {
|
||||||
|
pushPrefs().edit().remove("hist_$chatId").remove("meta_$chatId").apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveMeta(chatId: Long, meta: ChatMeta) {
|
||||||
|
val json = JSONObject()
|
||||||
|
.put("t", meta.title)
|
||||||
|
.put("a", meta.account)
|
||||||
|
.put("g", meta.group)
|
||||||
|
.toString()
|
||||||
|
pushPrefs().edit().putString("meta_$chatId", json).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadMeta(chatId: Long): ChatMeta? {
|
||||||
|
val raw = pushPrefs().getString("meta_$chatId", null) ?: return null
|
||||||
|
return try {
|
||||||
|
val o = JSONObject(raw)
|
||||||
|
ChatMeta(o.optString("t"), o.optInt("a"), o.optBoolean("g"))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadRegistry(): JSONObject = try {
|
private fun loadRegistry(): JSONObject = try {
|
||||||
|
|||||||
@@ -8,4 +8,5 @@
|
|||||||
<string name="fkm_status_line">%1$s · принято %2$d</string>
|
<string name="fkm_status_line">%1$s · принято %2$d</string>
|
||||||
<string name="fkm_explain">Это уведомление держит фоновое соединение с сервером, чтобы сообщения приходили без гугловых пушей. Убрать его можно, выключив FKM — кнопкой ниже или в Настройки → Уведомления → FKM.</string>
|
<string name="fkm_explain">Это уведомление держит фоновое соединение с сервером, чтобы сообщения приходили без гугловых пушей. Убрать его можно, выключив FKM — кнопкой ниже или в Настройки → Уведомления → FKM.</string>
|
||||||
<string name="fkm_disable">Выключить</string>
|
<string name="fkm_disable">Выключить</string>
|
||||||
|
<string name="notif_deleted_prefix">Удалено:</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -10,4 +10,5 @@
|
|||||||
<string name="fkm_status_line">%1$s · %2$d delivered</string>
|
<string name="fkm_status_line">%1$s · %2$d delivered</string>
|
||||||
<string name="fkm_explain">This notification is what keeps a background connection to the server, so messages arrive without Google push. To get rid of it, turn FKM off — with the button below, or in Settings → Notifications → FKM.</string>
|
<string name="fkm_explain">This notification is what keeps a background connection to the server, so messages arrive without Google push. To get rid of it, turn FKM off — with the button below, or in Settings → Notifications → FKM.</string>
|
||||||
<string name="fkm_disable">Turn off</string>
|
<string name="fkm_disable">Turn off</string>
|
||||||
|
<string name="notif_deleted_prefix">Deleted:</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ class FkmBridge {
|
|||||||
Future<void> showCall(Map<String, String> data) =>
|
Future<void> showCall(Map<String, String> data) =>
|
||||||
_invoke<void>('showCall', {'data': data});
|
_invoke<void>('showCall', {'data': data});
|
||||||
|
|
||||||
|
Future<void> editMessage(Map<String, String> data) =>
|
||||||
|
_invoke<void>('editMessage', {'data': data});
|
||||||
|
|
||||||
|
Future<void> removeMessage(Map<String, String> data) =>
|
||||||
|
_invoke<void>('removeMessage', {'data': data});
|
||||||
|
|
||||||
Future<bool> hasNotificationPermission() async {
|
Future<bool> hasNotificationPermission() async {
|
||||||
if (!isSupported) return false;
|
if (!isSupported) return false;
|
||||||
return await _invoke<bool>('hasNotificationPermission') ?? false;
|
return await _invoke<bool>('hasNotificationPermission') ?? false;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import '../../core/protocol/opcode_map.dart';
|
|||||||
import '../../core/protocol/packet.dart';
|
import '../../core/protocol/packet.dart';
|
||||||
import '../../core/storage/app_database.dart';
|
import '../../core/storage/app_database.dart';
|
||||||
import '../../core/storage/token_storage.dart';
|
import '../../core/storage/token_storage.dart';
|
||||||
|
import '../config/komet_settings.dart';
|
||||||
import '../utils/logger.dart';
|
import '../utils/logger.dart';
|
||||||
import 'fkm_bridge.dart';
|
import 'fkm_bridge.dart';
|
||||||
import 'push_service.dart';
|
import 'push_service.dart';
|
||||||
@@ -44,8 +45,12 @@ class FkmController {
|
|||||||
enabled.value = await FkmBridge.instance.isEnabled();
|
enabled.value = await FkmBridge.instance.isEnabled();
|
||||||
|
|
||||||
_pushSub = api.pushStream
|
_pushSub = api.pushStream
|
||||||
.where((packet) => packet.opcode == Opcode.notifMessage)
|
.where(
|
||||||
.listen(_onMessagePush);
|
(packet) =>
|
||||||
|
packet.opcode == Opcode.notifMessage ||
|
||||||
|
packet.opcode == Opcode.notifMsgDelete,
|
||||||
|
)
|
||||||
|
.listen(_onPush);
|
||||||
_stateSub = api.stateStream.listen(_onSessionState);
|
_stateSub = api.stateStream.listen(_onSessionState);
|
||||||
|
|
||||||
if (enabled.value) {
|
if (enabled.value) {
|
||||||
@@ -127,31 +132,102 @@ class FkmController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onMessagePush(Packet packet) async {
|
Future<void> _onPush(Packet packet) async {
|
||||||
if (!enabled.value) return;
|
if (!enabled.value) return;
|
||||||
try {
|
try {
|
||||||
final data = await _buildNotification(packet);
|
if (packet.opcode == Opcode.notifMsgDelete) {
|
||||||
if (data != null) await FkmBridge.instance.showMessage(data);
|
await _onDeletePush(packet);
|
||||||
|
} else {
|
||||||
|
await _onMessagePush(packet);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.w('FKM: не удалось показать уведомление: $e');
|
logger.w('FKM: не удалось обновить уведомления: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, String>?> _buildNotification(Packet packet) async {
|
Future<void> _onMessagePush(Packet packet) async {
|
||||||
final payload = packet.payload;
|
final payload = packet.payload;
|
||||||
if (payload is! Map) return null;
|
if (payload is! Map) return;
|
||||||
|
|
||||||
final chatId = payload['chatId'];
|
final chatId = payload['chatId'];
|
||||||
if (chatId is! int) return null;
|
if (chatId is! int) return;
|
||||||
|
|
||||||
final msg = payload['message'];
|
final msg = payload['message'];
|
||||||
if (msg is! Map) return null;
|
if (msg is! Map) return;
|
||||||
|
|
||||||
if (payload['postId'] != null || msg['postId'] != null) return null;
|
if (payload['postId'] != null || msg['postId'] != null) return;
|
||||||
|
|
||||||
final status = msg['status']?.toString();
|
final msgId = msg['id']?.toString();
|
||||||
if (status == 'REMOVED' || status == 'EDITED') return null;
|
switch (msg['status']?.toString()) {
|
||||||
|
case 'REMOVED':
|
||||||
|
if (msgId != null) await _removeNotification(chatId, msgId);
|
||||||
|
return;
|
||||||
|
case 'EDITED':
|
||||||
|
if (msgId != null) await _editNotification(chatId, msgId, msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final data = await _buildNotification(chatId, msg);
|
||||||
|
if (data != null) await FkmBridge.instance.showMessage(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onDeletePush(Packet packet) async {
|
||||||
|
final payload = packet.payload;
|
||||||
|
if (payload is! Map) return;
|
||||||
|
|
||||||
|
final chat = payload['chat'];
|
||||||
|
final chatId = (chat is Map && chat['id'] is int)
|
||||||
|
? chat['id'] as int
|
||||||
|
: payload['chatId'];
|
||||||
|
if (chatId is! int) return;
|
||||||
|
|
||||||
|
final ids = payload['messageIds'];
|
||||||
|
if (ids is! List) return;
|
||||||
|
for (final raw in ids) {
|
||||||
|
final id = raw?.toString();
|
||||||
|
if (id == null || id.isEmpty) continue;
|
||||||
|
await _removeNotification(chatId, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Удалённое сообщение уезжает из шторки, а при включённом «показывать
|
||||||
|
/// удалённые сообщения» остаётся в ней зачёркнутым.
|
||||||
|
Future<void> _removeNotification(int chatId, String msgId) =>
|
||||||
|
FkmBridge.instance.removeMessage({
|
||||||
|
'mc': '$chatId',
|
||||||
|
'msgid': msgId,
|
||||||
|
'keep': KometSettings.viewDeleted.value ? 'true' : 'false',
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> _editNotification(
|
||||||
|
int chatId,
|
||||||
|
String msgId,
|
||||||
|
Map<dynamic, dynamic> msg,
|
||||||
|
) async {
|
||||||
|
final accountId = await TokenStorage.getActiveAccountId();
|
||||||
|
if (accountId == null) return;
|
||||||
|
|
||||||
|
final rawConfig = await AppDatabase.getPrivacyConfig(accountId);
|
||||||
|
if (rawConfig != null) {
|
||||||
|
final config = PrivacyConfig.fromJson(rawConfig);
|
||||||
|
if (config.chatsPushNotification != 'ON') return;
|
||||||
|
if (!config.pushDetails) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final text = _previewText(msg);
|
||||||
|
if (text == _hiddenPreview) return;
|
||||||
|
|
||||||
|
await FkmBridge.instance.editMessage({
|
||||||
|
'mc': '$chatId',
|
||||||
|
'msgid': msgId,
|
||||||
|
'msg': text,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>?> _buildNotification(
|
||||||
|
int chatId,
|
||||||
|
Map<dynamic, dynamic> msg,
|
||||||
|
) async {
|
||||||
final senderId = msg['sender'];
|
final senderId = msg['sender'];
|
||||||
if (senderId is! int) return null;
|
if (senderId is! int) return null;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user