forked from mirror/monocles_chat_clean
update fork #128
8 changed files with 177 additions and 9 deletions
Implement live updates for microblog posts
Introduce listeners to receive and retract microblog posts in real-time. This allows the post view to update automatically when new posts are received or existing ones are retracted, without requiring a manual refresh. Key changes include: - Add `OnPostReceived` and `OnPostRetracted` listeners in `XmppConnectionService`. - Parse incoming microblog posts from both IQ stanzas and pubsub event messages. - Update the `PostsActivity` to reflect new and retracted posts instantly. - Pass the `postId` on post retraction for more specific UI updates. - Announce support for the `urn:xmpp:microblog:0` namespace.
commit
e63f6e9ff7
|
|
@ -116,7 +116,8 @@ public abstract class AbstractGenerator {
|
|||
final ArrayList<String> features = new ArrayList<>(Arrays.asList(STATIC_FEATURES));
|
||||
features.add("http://jabber.org/protocol/xhtml-im");
|
||||
features.add("urn:xmpp:bob");
|
||||
features.add(Namespace.PUBSUB_SOCIAL_FEED);
|
||||
features.add("urn:xmpp:microblog:0");
|
||||
features.add("urn:xmpp:microblog:0+notify");
|
||||
features.add(Namespace.PUBSUB_STORIES);
|
||||
features.add(Namespace.PUBSUB_STORIES + "+notify");
|
||||
if (Config.MESSAGE_DISPLAYED_SYNCHRONIZATION) {
|
||||
|
|
|
|||
|
|
@ -859,9 +859,29 @@ public class IqGenerator extends AbstractGenerator {
|
|||
final String now = AbstractGenerator.getTimestamp(System.currentTimeMillis());
|
||||
entry.addChild("published").setContent(now);
|
||||
entry.addChild("updated").setContent(now);
|
||||
// create a child node for the node
|
||||
pubsub.addChild("create").setAttribute("node", "urn:xmpp:microblog:0");
|
||||
final Element configure = pubsub.addChild("configure");
|
||||
// Correctly use the 'pubsub#node_config' namespace
|
||||
final Data data = Data.create("http://jabber.org/protocol/pubsub#node_config", defaultPostConfiguration());
|
||||
configure.addChild(data);
|
||||
return iq;
|
||||
}
|
||||
|
||||
public static Bundle defaultPostConfiguration() {
|
||||
Bundle options = new Bundle();
|
||||
options.putString("pubsub#node_type", "leaf");
|
||||
options.putString("pubsub#type", "urn:xmpp:microblog:0");
|
||||
options.putString("pubsub#access_model", "roster");
|
||||
options.putString("pubsub#item_expire", "86400");
|
||||
options.putString("pubsub#persist_items", "1");
|
||||
options.putString("pubsub#max_items", "120");
|
||||
options.putString("pubsub#notify_retract", "1");
|
||||
options.putString("pubsub#send_last_published_item", "on_sub");
|
||||
options.putString("pubsub#publish_model", "publishers");
|
||||
return options;
|
||||
}
|
||||
|
||||
public Iq publishComment(final Account account, final String node, final String title, final String inReplyToId) {
|
||||
final Iq iq = new Iq(Iq.Type.SET);
|
||||
iq.setTo(account.getJid().asBareJid());
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import eu.siacs.conversations.Config;
|
|||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Post;
|
||||
import eu.siacs.conversations.entities.Room;
|
||||
import eu.siacs.conversations.entities.Story;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
|
|
@ -456,6 +457,29 @@ public class IqParser extends AbstractParser implements Consumer<Iq> {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if ("urn:xmpp:microblog:0".equals(node)) {
|
||||
for (Element item : items.getChildren()) {
|
||||
if ("item".equals(item.getName())) {
|
||||
Element entry = item.findChild("entry", Namespace.ATOM);
|
||||
if (entry != null) {
|
||||
try {
|
||||
Post post = Post.fromElement(entry);
|
||||
mXmppConnectionService.onPostReceived(post, account);
|
||||
if (mXmppConnectionService.getOnPostReceivedListener() != null) {
|
||||
mXmppConnectionService.getOnPostReceivedListener().onPostReceived(post);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(
|
||||
Config.LOGTAG,
|
||||
"error creating post from pubsub item in iq",
|
||||
e);
|
||||
}
|
||||
}
|
||||
} else if (item.getName().equals("retract")) {
|
||||
final String postId = item.getAttribute("id");
|
||||
mXmppConnectionService.onPostRetracted(postId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ((packet.hasChild("block", Namespace.BLOCKING)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import java.util.function.Consumer;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import eu.siacs.conversations.crypto.OtrService;
|
||||
import eu.siacs.conversations.entities.Post;
|
||||
import eu.siacs.conversations.entities.Presence;
|
||||
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
|
||||
import eu.siacs.conversations.entities.Story;
|
||||
|
|
@ -1756,8 +1757,29 @@ public class MessageParser extends AbstractParser
|
|||
final Element event =
|
||||
original.findChild("event", "http://jabber.org/protocol/pubsub#event");
|
||||
if (event != null && Jid.Invalid.hasValidFrom(original) && original.getFrom().isBareJid()) {
|
||||
if (event.hasChild("items")) {
|
||||
parseEvent(event, original.getFrom(), account);
|
||||
final Element items = event.findChild("items");
|
||||
if (items != null) {
|
||||
final String node = items.getAttribute("node");
|
||||
if ("urn:xmpp:microblog:0".equals(node)) {
|
||||
for (Element item : items.getChildren()) {
|
||||
if ("item".equals(item.getName())) {
|
||||
Element entry = item.findChild("entry", Namespace.ATOM);
|
||||
if (entry != null) {
|
||||
try {
|
||||
Post post = Post.fromElement(entry);
|
||||
mXmppConnectionService.databaseBackend.createPost(post, account);
|
||||
if (mXmppConnectionService.getOnPostReceivedListener() != null) {
|
||||
mXmppConnectionService.getOnPostReceivedListener().onPostReceived(post);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.d(Config.LOGTAG, "error creating post from pubsub item", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parseEvent(event, original.getFrom(), account);
|
||||
}
|
||||
} else if (event.hasChild("delete")) {
|
||||
parseDeleteEvent(event, original.getFrom(), account);
|
||||
} else if (event.hasChild("purge")) {
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
import java.util.function.Consumer;
|
||||
|
||||
import eu.siacs.conversations.Conversations;
|
||||
import eu.siacs.conversations.entities.Post;
|
||||
import eu.siacs.conversations.entities.Story;
|
||||
import eu.siacs.conversations.entities.StubConversation;
|
||||
import eu.siacs.conversations.utils.TranscoderStrategies;
|
||||
|
|
@ -8227,7 +8228,7 @@ public class XmppConnectionService extends Service {
|
|||
sendIqPacket(account, request, response -> {
|
||||
if (response.getType() == Iq.Type.RESULT) {
|
||||
if (callback != null) {
|
||||
callback.onPostRetracted();
|
||||
callback.onPostRetracted(id);
|
||||
}
|
||||
} else {
|
||||
if (callback != null) {
|
||||
|
|
@ -8242,8 +8243,52 @@ public class XmppConnectionService extends Service {
|
|||
void onPostPublishFailed();
|
||||
}
|
||||
|
||||
public interface OnPostReceived {
|
||||
void onPostReceived(Post post);
|
||||
}
|
||||
|
||||
private OnPostReceived mOnPostReceivedListener;
|
||||
|
||||
public void setOnPostReceivedListener(OnPostReceived listener) {
|
||||
this.mOnPostReceivedListener = listener;
|
||||
}
|
||||
|
||||
public OnPostReceived getOnPostReceivedListener() {
|
||||
return this.mOnPostReceivedListener;
|
||||
}
|
||||
|
||||
public void onPostReceived(Post post, Account account) {
|
||||
if (post == null || account == null) {
|
||||
return;
|
||||
}
|
||||
databaseBackend.createPost(post, account);
|
||||
if (mOnPostReceivedListener != null) {
|
||||
mOnPostReceivedListener.onPostReceived(post);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnPostRetracted {
|
||||
void onPostRetracted();
|
||||
void onPostRetracted(String postId);
|
||||
void onPostRetractionFailed();
|
||||
}
|
||||
|
||||
private OnPostRetracted mOnPostRetractedListener;
|
||||
|
||||
public void setOnPostRetractedListener(OnPostRetracted listener) {
|
||||
this.mOnPostRetractedListener = listener;
|
||||
}
|
||||
|
||||
public OnPostRetracted getOnPostRetractedListener() {
|
||||
return this.mOnPostRetractedListener;
|
||||
}
|
||||
|
||||
public void onPostRetracted(String postId) {
|
||||
if (postId == null) {
|
||||
return;
|
||||
}
|
||||
databaseBackend.deletePost(postId);
|
||||
if (mOnPostRetractedListener != null) {
|
||||
mOnPostRetractedListener.onPostRetracted(postId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import eu.siacs.conversations.R;
|
|||
import eu.siacs.conversations.databinding.ActivityCreatePostBinding;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xml.Namespace;
|
||||
|
||||
public class CreatePostActivity extends XmppActivity {
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -34,7 +37,7 @@ import eu.siacs.conversations.xml.Namespace;
|
|||
import eu.siacs.conversations.xml.XmlReader;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
|
||||
public class PostsActivity extends XmppActivity {
|
||||
public class PostsActivity extends XmppActivity implements XmppConnectionService.OnPostReceived, XmppConnectionService.OnPostRetracted {
|
||||
|
||||
private ActivityPostsBinding binding;
|
||||
private PostsAdapter postsAdapter;
|
||||
|
|
@ -44,6 +47,7 @@ public class PostsActivity extends XmppActivity {
|
|||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = DataBindingUtil.setContentView(this, R.layout.activity_posts);
|
||||
Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
|
||||
setSupportActionBar(binding.toolbar);
|
||||
configureActionBar(getSupportActionBar());
|
||||
|
||||
|
|
@ -93,6 +97,10 @@ public class PostsActivity extends XmppActivity {
|
|||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
if (xmppConnectionService != null) {
|
||||
xmppConnectionService.setOnPostReceivedListener(this);
|
||||
xmppConnectionService.setOnPostRetractedListener(this);
|
||||
}
|
||||
if (postList.isEmpty()) {
|
||||
loadPosts();
|
||||
}
|
||||
|
|
@ -106,11 +114,40 @@ public class PostsActivity extends XmppActivity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (xmppConnectionService != null) {
|
||||
xmppConnectionService.setOnPostReceivedListener(null);
|
||||
xmppConnectionService.setOnPostRetractedListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackendConnected() {
|
||||
if (xmppConnectionService != null) {
|
||||
xmppConnectionService.setOnPostReceivedListener(this);
|
||||
xmppConnectionService.setOnPostRetractedListener(this);
|
||||
}
|
||||
refreshUiReal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostReceived(final Post post) {
|
||||
runOnUiThread(() -> {
|
||||
if (post != null) {
|
||||
postList.add(0, post);
|
||||
java.util.Collections.sort(postList, (p1, p2) -> {
|
||||
if (p1.getPublished() == null && p2.getPublished() == null) return 0;
|
||||
if (p1.getPublished() == null) return 1;
|
||||
if (p2.getPublished() == null) return -1;
|
||||
return p2.getPublished().compareTo(p1.getPublished());
|
||||
});
|
||||
postsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refreshUiReal() {
|
||||
if (postList.isEmpty()) {
|
||||
|
|
@ -262,4 +299,21 @@ public class PostsActivity extends XmppActivity {
|
|||
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onPostRetracted(String postId) {
|
||||
runOnUiThread(() -> {
|
||||
for (int i = 0; i < postList.size(); ++i) {
|
||||
if (postList.get(i).getId().equals(postId)) {
|
||||
postList.remove(i);
|
||||
postsAdapter.notifyItemRemoved(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostRetractionFailed() {
|
||||
runOnUiThread(() -> Toast.makeText(this, R.string.error_retract_post, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -258,10 +258,11 @@ public class PostsAdapter extends RecyclerView.Adapter<PostsAdapter.PostViewHold
|
|||
.setPositiveButton(R.string.retract, (dialog, which) -> {
|
||||
mActivity.xmppConnectionService.retractPost(ownAccount, "urn:xmpp:microblog:0", post.getId(),
|
||||
new XmppConnectionService.OnPostRetracted() {
|
||||
|
||||
@Override
|
||||
public void onPostRetracted() {
|
||||
public void onPostRetracted(String postId) {
|
||||
mActivity.runOnUiThread(() -> {
|
||||
mActivity.xmppConnectionService.databaseBackend.deletePost(post.getId());
|
||||
mActivity.xmppConnectionService.databaseBackend.deletePost(postId);
|
||||
int pos = getAdapterPosition();
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
posts.remove(pos);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue