Skip to content

Commit 4f9f568

Browse files
committed
feat: Implement deleted messages recovery and display functionality with new database storage, UI, and Xposed hooks.
1 parent 9f90413 commit 4f9f568

27 files changed

Lines changed: 1300 additions & 9 deletions

app/src/main/AndroidManifest.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@
9393
android:parentActivityName=".activities.MainActivity"
9494
android:windowSoftInputMode="adjustResize" />
9595

96+
<activity
97+
android:name=".activities.DeletedMessagesActivity"
98+
android:theme="@style/AppTheme"
99+
android:parentActivityName=".activities.MainActivity" />
100+
101+
<activity
102+
android:name=".activities.MessageListActivity"
103+
android:theme="@style/AppTheme"
104+
android:parentActivityName=".activities.DeletedMessagesActivity" />
105+
96106
<activity
97107
android:name=".activities.ForceStartActivity"
98108
android:excludeFromRecents="true"
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.wmods.wppenhacer.activities;
2+
3+
import android.os.Bundle;
4+
import android.view.MenuItem;
5+
6+
import com.wmods.wppenhacer.R;
7+
import com.wmods.wppenhacer.activities.base.BaseActivity;
8+
import com.wmods.wppenhacer.databinding.ActivityDeletedMessagesBinding;
9+
import com.wmods.wppenhacer.ui.fragments.DeletedMessagesFragment;
10+
11+
public class DeletedMessagesActivity extends BaseActivity {
12+
13+
private ActivityDeletedMessagesBinding binding;
14+
15+
@Override
16+
protected void onCreate(Bundle savedInstanceState) {
17+
super.onCreate(savedInstanceState);
18+
binding = ActivityDeletedMessagesBinding.inflate(getLayoutInflater());
19+
setContentView(binding.getRoot());
20+
21+
setSupportActionBar(binding.toolbar);
22+
if (getSupportActionBar() != null) {
23+
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
24+
}
25+
26+
if (savedInstanceState == null) {
27+
getSupportFragmentManager().beginTransaction()
28+
.replace(R.id.fragment_container, new DeletedMessagesFragment())
29+
.commit();
30+
}
31+
}
32+
33+
@Override
34+
public boolean onOptionsItemSelected(MenuItem item) {
35+
if (item.getItemId() == android.R.id.home) {
36+
onBackPressed();
37+
return true;
38+
}
39+
return super.onOptionsItemSelected(item);
40+
}
41+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.wmods.wppenhacer.activities;
2+
3+
import android.os.Bundle;
4+
import android.view.MenuItem;
5+
import android.view.View;
6+
import android.widget.Toast;
7+
8+
import androidx.recyclerview.widget.LinearLayoutManager;
9+
10+
import com.wmods.wppenhacer.R;
11+
import com.wmods.wppenhacer.activities.base.BaseActivity;
12+
import com.wmods.wppenhacer.adapter.MessageListAdapter;
13+
import com.wmods.wppenhacer.databinding.ActivityMessageListBinding;
14+
import com.wmods.wppenhacer.xposed.core.WppCore;
15+
import com.wmods.wppenhacer.xposed.core.db.DelMessageStore;
16+
import com.wmods.wppenhacer.xposed.core.db.DeletedMessage;
17+
18+
import java.util.List;
19+
20+
public class MessageListActivity extends BaseActivity implements MessageListAdapter.OnRestoreClickListener {
21+
22+
private ActivityMessageListBinding binding;
23+
private MessageListAdapter adapter;
24+
private DelMessageStore delMessageStore;
25+
private String chatJid;
26+
27+
@Override
28+
protected void onCreate(Bundle savedInstanceState) {
29+
super.onCreate(savedInstanceState);
30+
binding = ActivityMessageListBinding.inflate(getLayoutInflater());
31+
setContentView(binding.getRoot());
32+
33+
chatJid = getIntent().getStringExtra("chat_jid");
34+
if (chatJid == null) {
35+
finish();
36+
return;
37+
}
38+
39+
setSupportActionBar(binding.toolbar);
40+
if (getSupportActionBar() != null) {
41+
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
42+
String title = WppCore.stripJID(chatJid);
43+
getSupportActionBar().setTitle(title);
44+
}
45+
46+
delMessageStore = DelMessageStore.getInstance(this);
47+
adapter = new MessageListAdapter(this);
48+
49+
binding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
50+
binding.recyclerView.setAdapter(adapter);
51+
52+
loadMessages();
53+
}
54+
55+
private void loadMessages() {
56+
new Thread(() -> {
57+
List<DeletedMessage> messages = delMessageStore.getDeletedMessagesByChat(chatJid);
58+
runOnUiThread(() -> {
59+
if (messages.isEmpty()) {
60+
binding.emptyView.setVisibility(View.VISIBLE);
61+
binding.recyclerView.setVisibility(View.GONE);
62+
} else {
63+
binding.emptyView.setVisibility(View.GONE);
64+
binding.recyclerView.setVisibility(View.VISIBLE);
65+
adapter.setMessages(messages);
66+
}
67+
});
68+
}).start();
69+
}
70+
71+
@Override
72+
public boolean onOptionsItemSelected(MenuItem item) {
73+
if (item.getItemId() == android.R.id.home) {
74+
onBackPressed();
75+
return true;
76+
}
77+
return super.onOptionsItemSelected(item);
78+
}
79+
80+
@Override
81+
public void onRestoreClick(DeletedMessage message) {
82+
com.wmods.wppenhacer.xposed.features.general.RecoverDeleteForMe.restoreMessage(this, message);
83+
}
84+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.wmods.wppenhacer.adapter;
2+
3+
import android.view.LayoutInflater;
4+
import android.view.View;
5+
import android.view.ViewGroup;
6+
import android.widget.ImageView;
7+
import android.widget.TextView;
8+
9+
import androidx.annotation.NonNull;
10+
import androidx.recyclerview.widget.RecyclerView;
11+
12+
import com.wmods.wppenhacer.R;
13+
import com.wmods.wppenhacer.xposed.core.WppCore;
14+
import com.wmods.wppenhacer.xposed.core.db.DeletedMessage;
15+
import com.wmods.wppenhacer.xposed.utils.Utils;
16+
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
20+
public class DeletedMessagesAdapter extends RecyclerView.Adapter<DeletedMessagesAdapter.ViewHolder> {
21+
22+
private List<DeletedMessage> messages = new ArrayList<>();
23+
private final OnItemClickListener listener;
24+
25+
public interface OnItemClickListener {
26+
void onItemClick(DeletedMessage message);
27+
}
28+
29+
public DeletedMessagesAdapter(OnItemClickListener listener) {
30+
this.listener = listener;
31+
}
32+
33+
public void setMessages(List<DeletedMessage> messages) {
34+
this.messages = messages;
35+
notifyDataSetChanged();
36+
}
37+
38+
@NonNull
39+
@Override
40+
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
41+
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_deleted_message_contact, parent, false);
42+
return new ViewHolder(view);
43+
}
44+
45+
@Override
46+
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
47+
DeletedMessage message = messages.get(position);
48+
49+
// Contact Name
50+
String contactName = WppCore.getContactName(new com.wmods.wppenhacer.xposed.core.components.FMessageWpp.UserJid(message.getChatJid()));
51+
holder.contactName.setText(contactName != null ? contactName : message.getChatJid());
52+
53+
// Timestamp
54+
holder.timestamp.setText(Utils.getDateTimeFromMillis(message.getTimestamp()));
55+
56+
// Last Message Content
57+
String content = message.getTextContent();
58+
if (content == null || content.isEmpty()) {
59+
if (message.getMediaType() != -1) {
60+
content = "Media (" + message.getMediaType() + ")";
61+
if (message.getMediaCaption() != null && !message.getMediaCaption().isEmpty()) {
62+
content += ": " + message.getMediaCaption();
63+
}
64+
} else {
65+
content = "Deleted Message";
66+
}
67+
}
68+
holder.lastMessage.setText(content);
69+
70+
holder.itemView.setOnClickListener(v -> listener.onItemClick(message));
71+
}
72+
73+
@Override
74+
public int getItemCount() {
75+
return messages.size();
76+
}
77+
78+
static class ViewHolder extends RecyclerView.ViewHolder {
79+
TextView contactName;
80+
TextView timestamp;
81+
TextView lastMessage;
82+
ImageView icon;
83+
84+
ViewHolder(View itemView) {
85+
super(itemView);
86+
contactName = itemView.findViewById(R.id.contact_name);
87+
timestamp = itemView.findViewById(R.id.timestamp);
88+
lastMessage = itemView.findViewById(R.id.last_message);
89+
icon = itemView.findViewById(R.id.icon);
90+
}
91+
}
92+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package com.wmods.wppenhacer.adapter;
2+
3+
import android.net.Uri;
4+
import android.view.LayoutInflater;
5+
import android.view.View;
6+
import android.view.ViewGroup;
7+
import android.widget.ImageView;
8+
import android.widget.TextView;
9+
10+
import androidx.annotation.NonNull;
11+
import androidx.recyclerview.widget.RecyclerView;
12+
13+
import com.google.android.material.button.MaterialButton;
14+
import com.wmods.wppenhacer.R;
15+
import com.wmods.wppenhacer.xposed.core.db.DeletedMessage;
16+
import com.wmods.wppenhacer.xposed.utils.Utils;
17+
18+
import java.io.File;
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
public class MessageListAdapter extends RecyclerView.Adapter<MessageListAdapter.ViewHolder> {
23+
24+
private List<DeletedMessage> messages = new ArrayList<>();
25+
private final OnRestoreClickListener listener;
26+
27+
public interface OnRestoreClickListener {
28+
void onRestoreClick(DeletedMessage message);
29+
}
30+
31+
public MessageListAdapter(OnRestoreClickListener listener) {
32+
this.listener = listener;
33+
}
34+
35+
public void setMessages(List<DeletedMessage> messages) {
36+
this.messages = messages;
37+
notifyDataSetChanged();
38+
}
39+
40+
@NonNull
41+
@Override
42+
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
43+
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_deleted_message, parent, false);
44+
return new ViewHolder(view);
45+
}
46+
47+
@Override
48+
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
49+
DeletedMessage message = messages.get(position);
50+
51+
holder.senderName.setText(message.getSenderJid());
52+
holder.timestamp.setText(Utils.getDateTimeFromMillis(message.getTimestamp()));
53+
54+
if (message.getTextContent() != null && !message.getTextContent().isEmpty()) {
55+
holder.messageContent.setText(message.getTextContent());
56+
holder.messageContent.setVisibility(View.VISIBLE);
57+
} else {
58+
holder.messageContent.setVisibility(View.GONE);
59+
}
60+
61+
if (message.getMediaPath() != null) {
62+
holder.mediaContainer.setVisibility(View.VISIBLE);
63+
File mediaFile = new File(message.getMediaPath());
64+
if (mediaFile.exists()) {
65+
holder.mediaPreview.setImageURI(Uri.fromFile(mediaFile));
66+
} else {
67+
holder.mediaPreview.setImageResource(android.R.drawable.ic_menu_report_image);
68+
}
69+
70+
if (message.getMediaCaption() != null && !message.getMediaCaption().isEmpty()) {
71+
holder.mediaCaption.setText(message.getMediaCaption());
72+
holder.mediaCaption.setVisibility(View.VISIBLE);
73+
} else {
74+
holder.mediaCaption.setVisibility(View.GONE);
75+
}
76+
} else {
77+
holder.mediaContainer.setVisibility(View.GONE);
78+
}
79+
80+
holder.btnRestore.setOnClickListener(v -> listener.onRestoreClick(message));
81+
}
82+
83+
@Override
84+
public int getItemCount() {
85+
return messages.size();
86+
}
87+
88+
static class ViewHolder extends RecyclerView.ViewHolder {
89+
TextView senderName;
90+
TextView timestamp;
91+
TextView messageContent;
92+
View mediaContainer;
93+
ImageView mediaPreview;
94+
TextView mediaCaption;
95+
MaterialButton btnRestore;
96+
97+
ViewHolder(View itemView) {
98+
super(itemView);
99+
senderName = itemView.findViewById(R.id.sender_name);
100+
timestamp = itemView.findViewById(R.id.timestamp);
101+
messageContent = itemView.findViewById(R.id.message_content);
102+
mediaContainer = itemView.findViewById(R.id.media_container);
103+
mediaPreview = itemView.findViewById(R.id.media_preview);
104+
mediaCaption = itemView.findViewById(R.id.media_caption);
105+
btnRestore = itemView.findViewById(R.id.btn_restore);
106+
}
107+
}
108+
}

0 commit comments

Comments
 (0)