11package com .wmods .wppenhacer .xposed .features .customization ;
22
33import android .annotation .SuppressLint ;
4+ import android .content .SharedPreferences ;
45import android .graphics .Color ;
6+ import android .os .Handler ;
7+ import android .os .Looper ;
58import android .view .View ;
69import android .view .ViewGroup ;
710import android .widget .CursorAdapter ;
1720import com .wmods .wppenhacer .xposed .features .listeners .ConversationItemListener ;
1821import 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+
2031import de .robv .android .xposed .XSharedPreferences ;
2132
2233
2334public 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+ }
0 commit comments