Skip to content

Commit 70396bd

Browse files
committed
Migrate ConversationItemListener to Kotlin and add caching
1 parent 733e679 commit 70396bd

4 files changed

Lines changed: 246 additions & 113 deletions

File tree

app/src/main/java/com/wmods/wppenhacer/xposed/core/db/MessageHistoryStore.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ class MessageHistoryStore private constructor(context: Context) {
7474
private val EMPTY_SEEN_ITEM = MessageSeenItem("", "", false)
7575
private val EMPTY_MESSAGE_LIST = ArrayList<MessageItem>()
7676

77+
@Volatile
78+
private var hideSeenChangeListener: HideSeenChangeListener? = null
79+
7780
@Volatile
7881
private var mInstance: MessageHistoryStore? = null
7982

@@ -84,6 +87,12 @@ class MessageHistoryStore private constructor(context: Context) {
8487
}
8588
}
8689

90+
@JvmStatic
91+
fun setHideSeenChangeListener(listener: HideSeenChangeListener?) {
92+
hideSeenChangeListener = listener
93+
}
94+
95+
8796
}
8897

8998
fun insertMessage(id: Long, message: String, timestamp: Long) {
@@ -165,7 +174,7 @@ class MessageHistoryStore private constructor(context: Context) {
165174
val cacheKey = createSeenMessageCacheKey(jid, messageId, type)
166175
seenMessageCache.remove(cacheKey)
167176
invalidateSeenMessagesListCache(jid, type)
168-
177+
hideSeenChangeListener?.onHideSeenChanged(jid, messageId, type, viewed)
169178
} catch (t: Throwable) {
170179
XposedBridge.log(t)
171180
}
@@ -197,6 +206,7 @@ class MessageHistoryStore private constructor(context: Context) {
197206
}
198207
invalidateSeenMessagesListCache(jid, type)
199208

209+
hideSeenChangeListener?.onHideSeenChanged(jid, messageId, type, viewed)
200210
return true
201211
} catch (t: Throwable) {
202212
XposedBridge.log(t)
Lines changed: 130 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package com.wmods.wppenhacer.xposed.features.customization;
22

33
import android.annotation.SuppressLint;
4+
import android.content.SharedPreferences;
45
import android.graphics.Color;
6+
import android.os.Handler;
7+
import android.os.Looper;
58
import android.view.View;
69
import android.view.ViewGroup;
710
import android.widget.CursorAdapter;
@@ -17,32 +20,54 @@
1720
import com.wmods.wppenhacer.xposed.features.listeners.ConversationItemListener;
1821
import com.wmods.wppenhacer.xposed.utils.Utils;
1922

23+
import java.util.HashMap;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.concurrent.ConcurrentHashMap;
27+
import java.util.concurrent.ExecutorService;
28+
import java.util.concurrent.Executors;
29+
import java.util.concurrent.atomic.AtomicBoolean;
30+
2031
import de.robv.android.xposed.XSharedPreferences;
2132

2233

2334
public class HideSeenView extends Feature {
2435

36+
private static final int JID_CACHE_SIZE = 30;
37+
private static final long REFRESH_DEBOUNCE_MS = 80;
38+
private static final Object CACHE_LOCK = new Object();
39+
private static final android.util.LruCache<String, JidSeenCache> jidCache = new android.util.LruCache<>(JID_CACHE_SIZE) {
40+
@Override
41+
protected void entryRemoved(boolean evicted, String key, JidSeenCache oldValue, JidSeenCache newValue) {
42+
loadedReadStatus.remove(key);
43+
loadedPlayedStatus.remove(key);
44+
}
45+
};
46+
private static final Handler mainHandler = new Handler(Looper.getMainLooper());
47+
private static final AtomicBoolean refreshScheduled = new AtomicBoolean(false);
48+
private static final ExecutorService cacheExecutor = Executors.newFixedThreadPool(2);
49+
private static final ConcurrentHashMap<String, Boolean> loadingReadStatus = new ConcurrentHashMap<>();
50+
private static final ConcurrentHashMap<String, Boolean> loadingPlayedStatus = new ConcurrentHashMap<>();
51+
private static final ConcurrentHashMap<String, Boolean> loadedReadStatus = new ConcurrentHashMap<>();
52+
private static final ConcurrentHashMap<String, Boolean> loadedPlayedStatus = new ConcurrentHashMap<>();
53+
2554
public HideSeenView(ClassLoader loader, XSharedPreferences preferences) {
2655
super(loader, preferences);
2756
}
2857

29-
public static void updateAllBubbleViews() {
30-
var adapter = ConversationItemListener.getAdapter();
31-
if (adapter instanceof CursorAdapter cursorAdapter) {
32-
WppCore.getCurrentActivity().runOnUiThread(cursorAdapter::notifyDataSetChanged);
33-
}
34-
}
3558

3659
@Override
3760
public void doHook() throws Throwable {
3861
if (!prefs.getBoolean("hide_seen_view", false)) return;
3962

63+
MessageHistoryStore.setHideSeenChangeListener(HideSeenView::handleHideSeenChanged);
64+
4065
// Register listener
4166
ConversationItemListener.conversationListeners.add(new ConversationItemListener.OnConversationItemListener() {
4267
@Override
43-
public void onItemBind(FMessageWpp fMessage, ViewGroup view, int position, View convertView) {
68+
public void onItemBind(FMessageWpp fMessage, ViewGroup viewGroup, int position, View convertView) {
4469
if (fMessage.getKey().isFromMe) return;
45-
updateBubbleView(fMessage, view);
70+
updateBubbleView(fMessage, viewGroup);
4671
}
4772
});
4873
}
@@ -52,32 +77,118 @@ private static void updateBubbleView(FMessageWpp fmessage, View viewGroup) {
5277
var userJid = fmessage.getKey().remoteJid;
5378
var messageId = fmessage.getKey().messageID;
5479
if (userJid.isNull()) return;
80+
var jid = userJid.getPhoneRawString();
5581
ImageView view = viewGroup.findViewById(Utils.getID("view_once_control_icon", "id"));
5682
if (view != null) {
57-
var messageOnce = MessageHistoryStore.getInstance().getHideSeenMessage(userJid.getPhoneRawString(), messageId, MessageHistoryStore.ReceiptType.PLAYED);
58-
if (messageOnce != null) {
59-
view.setColorFilter(messageOnce.viewed ? Color.GREEN : Color.RED);
60-
} else {
83+
var played = getCachedStatus(jid, messageId, MessageHistoryStore.ReceiptType.PLAYED);
84+
if (played == null) {
85+
ensureCacheLoaded(jid, MessageHistoryStore.ReceiptType.PLAYED);
6186
view.setColorFilter(null);
87+
} else {
88+
view.setColorFilter(played ? Color.GREEN : Color.RED);
6289
}
6390
}
6491
ViewGroup dateWrapper = viewGroup.findViewById(Utils.getID("date_wrapper", "id"));
6592
if (dateWrapper != null) {
66-
TextView status = dateWrapper.findViewById(0xf7ff2001);
93+
TextView status = dateWrapper.findViewWithTag("seen_view");
6794
if (status == null) {
6895
status = new TextView(viewGroup.getContext());
69-
status.setId(0xf7ff2001);
96+
status.setTag("seen_view");
7097
status.setTextSize(8);
7198
dateWrapper.addView(status);
7299
}
73-
var message = MessageHistoryStore.getInstance().getHideSeenMessage(userJid.getPhoneRawString(), messageId, MessageHistoryStore.ReceiptType.READ);
74-
if (message != null) {
100+
var viewedMessage = getCachedStatus(jid, messageId, MessageHistoryStore.ReceiptType.READ);
101+
if (viewedMessage == null) {
102+
ensureCacheLoaded(jid, MessageHistoryStore.ReceiptType.READ);
103+
status.setVisibility(View.GONE);
104+
} else {
75105
status.setVisibility(View.VISIBLE);
76-
status.setText(message.viewed ? "\uD83D\uDFE2" : "\uD83D\uDD34");
106+
status.setText(viewedMessage ? "\uD83D\uDFE2" : "\uD83D\uDD34");
107+
}
108+
}
109+
}
110+
111+
private static Boolean getCachedStatus(String jid, String messageId, MessageHistoryStore.ReceiptType type) {
112+
synchronized (CACHE_LOCK) {
113+
var cache = jidCache.get(jid);
114+
if (cache == null) return null;
115+
Map<String, Boolean> map = type == MessageHistoryStore.ReceiptType.READ ? cache.readStatus : cache.playedStatus;
116+
return map.get(messageId);
117+
}
118+
}
119+
120+
private static void ensureCacheLoaded(String jid, MessageHistoryStore.ReceiptType type) {
121+
var loadingMap = type == MessageHistoryStore.ReceiptType.READ ? loadingReadStatus : loadingPlayedStatus;
122+
var loadedMap = type == MessageHistoryStore.ReceiptType.READ ? loadedReadStatus : loadedPlayedStatus;
123+
if (loadedMap.containsKey(jid)) return;
124+
if (loadingMap.putIfAbsent(jid, true) != null) return;
125+
cacheExecutor.execute(() -> {
126+
try {
127+
Map<String, Boolean> map = loadStatusMap(jid, type);
128+
synchronized (CACHE_LOCK) {
129+
var cache = jidCache.get(jid);
130+
if (cache == null) {
131+
cache = new JidSeenCache();
132+
jidCache.put(jid, cache);
133+
}
134+
if (type == MessageHistoryStore.ReceiptType.READ) {
135+
cache.readStatus = map;
136+
} else {
137+
cache.playedStatus = map;
138+
}
139+
}
140+
loadedMap.put(jid, true);
141+
requestRefresh();
142+
} finally {
143+
loadingMap.remove(jid);
144+
}
145+
});
146+
}
147+
148+
private static Map<String, Boolean> loadStatusMap(String jid, MessageHistoryStore.ReceiptType type) {
149+
Map<String, Boolean> map = new HashMap<>();
150+
List<MessageHistoryStore.MessageSeenItem> viewed = MessageHistoryStore.getInstance().getHideSeenMessages(jid, type, true);
151+
if (viewed != null) {
152+
for (var item : viewed) {
153+
map.put(item.message, true);
154+
}
155+
}
156+
List<MessageHistoryStore.MessageSeenItem> notViewed = MessageHistoryStore.getInstance().getHideSeenMessages(jid, type, false);
157+
if (notViewed != null) {
158+
for (var item : notViewed) {
159+
map.put(item.message, false);
160+
}
161+
}
162+
return map;
163+
}
164+
165+
private static void handleHideSeenChanged(String jid, String messageId, MessageHistoryStore.ReceiptType type, boolean viewed) {
166+
synchronized (CACHE_LOCK) {
167+
var cache = jidCache.get(jid);
168+
if (cache == null) {
169+
cache = new JidSeenCache();
170+
jidCache.put(jid, cache);
171+
}
172+
if (type == MessageHistoryStore.ReceiptType.READ) {
173+
cache.readStatus.put(messageId, viewed);
77174
} else {
78-
status.setVisibility(View.GONE);
175+
cache.playedStatus.put(messageId, viewed);
79176
}
80177
}
178+
requestRefresh();
179+
}
180+
181+
private static void requestRefresh() {
182+
if (!refreshScheduled.compareAndSet(false, true)) return;
183+
mainHandler.postDelayed(() -> {
184+
refreshScheduled.set(false);
185+
ConversationItemListener.notifyDataSetChanged();
186+
}, REFRESH_DEBOUNCE_MS);
187+
}
188+
189+
private static class JidSeenCache {
190+
Map<String, Boolean> readStatus = new HashMap<>();
191+
Map<String, Boolean> playedStatus = new HashMap<>();
81192
}
82193

83194

@@ -86,4 +197,4 @@ private static void updateBubbleView(FMessageWpp fmessage, View viewGroup) {
86197
public String getPluginName() {
87198
return "Hide Seen View";
88199
}
89-
}
200+
}

app/src/main/java/com/wmods/wppenhacer/xposed/features/general/AntiRevoke.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import com.wmods.wppenhacer.xposed.core.db.MessageStore
1616
import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator
1717
import com.wmods.wppenhacer.xposed.core.devkit.UnobfuscatorCache
1818
import com.wmods.wppenhacer.xposed.features.listeners.ConversationItemListener
19-
import com.wmods.wppenhacer.xposed.features.listeners.MenuStatusListener
2019
import com.wmods.wppenhacer.xposed.utils.ReflectionUtils
2120
import com.wmods.wppenhacer.xposed.utils.Utils
2221
import de.robv.android.xposed.XC_MethodHook
@@ -131,11 +130,11 @@ class AntiRevoke(loader: ClassLoader, preferences: XSharedPreferences) :
131130
ConversationItemListener.OnConversationItemListener() {
132131
override fun onItemBind(
133132
fMessage: FMessageWpp,
134-
viewGroup: ViewGroup,
133+
view: ViewGroup,
135134
position: Int,
136135
convertView: View?
137136
) {
138-
val dateTextView = viewGroup.findViewById<TextView>(Utils.getID("date", "id"))
137+
val dateTextView = view.findViewById<TextView>(Utils.getID("date", "id"))
139138
bindRevokedMessageUI(fMessage, dateTextView, "antirevoke")
140139
}
141140
})

0 commit comments

Comments
 (0)