From 1542be20dba15568d7ba5bca39de6f51a64cd142 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 29 Apr 2025 12:20:20 +0100 Subject: [PATCH] Load forum post text lazily. --- .../briar/android/forum/ForumActivity.java | 2 +- .../briar/android/forum/ForumPostItem.java | 6 ++-- .../briar/android/forum/ForumViewModel.java | 31 +++++++------------ .../conversation/GroupActivity.java | 9 +----- .../conversation/GroupMessageAdapter.java | 10 ++++-- .../conversation/GroupMessageItem.java | 4 +-- .../conversation/GroupViewModel.java | 5 +++ .../JoinMessageItemViewHolder.java | 5 +-- .../threaded/BaseThreadItemViewHolder.java | 27 ++++++++++++++-- .../briar/android/threaded/ThreadItem.java | 6 ++-- .../android/threaded/ThreadItemAdapter.java | 13 ++++++-- .../android/threaded/ThreadListActivity.java | 4 +++ .../android/threaded/ThreadListViewModel.java | 11 +++++++ .../threaded/ThreadPostViewHolder.java | 6 ++-- .../forum/event/ForumPostReceivedEvent.java | 9 +----- .../briar/forum/ForumManagerImpl.java | 5 +-- 16 files changed, 93 insertions(+), 60 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java index c601696f4..943064535 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java @@ -54,7 +54,7 @@ public class ForumActivity extends @Override protected ThreadItemAdapter createAdapter() { - return new ThreadItemAdapter<>(this); + return new ThreadItemAdapter<>(this, this); } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumPostItem.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumPostItem.java index 06601ed84..393cb5031 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumPostItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumPostItem.java @@ -6,10 +6,10 @@ import org.briarproject.briar.api.forum.ForumPostHeader; import javax.annotation.concurrent.NotThreadSafe; @NotThreadSafe -class ForumPostItem extends ThreadItem { +public class ForumPostItem extends ThreadItem { - ForumPostItem(ForumPostHeader h, String text) { - super(h.getId(), h.getParentId(), text, h.getTimestamp(), h.getAuthor(), + ForumPostItem(ForumPostHeader h) { + super(h.getId(), h.getParentId(), null, h.getTimestamp(), h.getAuthor(), h.getAuthorInfo(), h.isRead()); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java index f44427300..be918bcef 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumViewModel.java @@ -91,9 +91,7 @@ class ForumViewModel extends ThreadListViewModel { ForumPostReceivedEvent f = (ForumPostReceivedEvent) e; if (f.getGroupId().equals(groupId)) { LOG.info("Forum post received, adding..."); - ForumPostItem item = - new ForumPostItem(f.getHeader(), f.getText()); - addItem(item, false); + addItem(new ForumPostItem(f.getHeader()), false); } } else if (e instanceof ForumInvitationResponseReceivedEvent) { ForumInvitationResponseReceivedEvent f = @@ -139,22 +137,14 @@ class ForumViewModel extends ThreadListViewModel { List headers = forumManager.getPostHeaders(txn, groupId); logDuration(LOG, "Loading headers", start); - start = now(); List items = new ArrayList<>(); for (ForumPostHeader header : headers) { - items.add(loadItem(txn, header)); + items.add(new ForumPostItem(header)); } - logDuration(LOG, "Loading bodies and creating items", start); return items; }, this::setItems); } - private ForumPostItem loadItem(Transaction txn, ForumPostHeader header) - throws DbException { - String text = forumManager.getPostText(txn, header.getId()); - return new ForumPostItem(header, text); - } - @Override public void createAndStoreMessage(String text, @Nullable MessageId parentId) { @@ -175,21 +165,17 @@ class ForumViewModel extends ThreadListViewModel { @Nullable MessageId parentId, LocalAuthor author) { cryptoExecutor.execute(() -> { LOG.info("Creating forum post..."); - ForumPost msg = forumManager.createLocalPost(groupId, text, - timestamp, parentId, author); - storePost(msg, text); + storePost(forumManager.createLocalPost(groupId, text, + timestamp, parentId, author)); }); } - private void storePost(ForumPost msg, String text) { + private void storePost(ForumPost msg) { runOnDbThread(false, txn -> { long start = now(); ForumPostHeader header = forumManager.addLocalPost(txn, msg); logDuration(LOG, "Storing forum post", start); - txn.attach(() -> { - ForumPostItem item = new ForumPostItem(header, text); - addItem(item, true); - }); + txn.attach(() -> addItem(new ForumPostItem(header), true)); }, this::handleException); } @@ -229,4 +215,9 @@ class ForumViewModel extends ThreadListViewModel { }); } + @Override + protected String getMessageText(Transaction txn, MessageId m) + throws DbException { + return forumManager.getPostText(txn, m); + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java index 23269b21c..7601230c4 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java @@ -15,7 +15,6 @@ import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListAct import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity; import org.briarproject.briar.android.threaded.ThreadListActivity; import org.briarproject.briar.android.threaded.ThreadListViewModel; -import org.briarproject.briar.android.widget.LinkDialogFragment; import org.briarproject.nullsafety.MethodsNotNullByDefault; import org.briarproject.nullsafety.ParametersNotNullByDefault; @@ -56,7 +55,7 @@ public class GroupActivity extends @Override protected GroupMessageAdapter createAdapter() { - return new GroupMessageAdapter(this); + return new GroupMessageAdapter(this, this); } @Override @@ -160,12 +159,6 @@ public class GroupActivity extends if (isDissolved != null && !isDissolved) super.onReplyClick(item); } - @Override - public void onLinkClick(String url){ - LinkDialogFragment f = LinkDialogFragment.newInstance(url); - f.show(getSupportFragmentManager(), f.getUniqueTag()); - } - private void setGroupEnabled(boolean enabled) { sendController.setReady(enabled); list.getRecyclerView().setAlpha(enabled ? 1f : 0.5f); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java index 512ecdc6c..c42283c7d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageAdapter.java @@ -11,16 +11,19 @@ import org.briarproject.briar.android.threaded.ThreadPostViewHolder; import org.briarproject.nullsafety.NotNullByDefault; import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; import androidx.annotation.UiThread; +import androidx.lifecycle.LifecycleOwner; @UiThread @NotNullByDefault -class GroupMessageAdapter extends ThreadItemAdapter { +public class GroupMessageAdapter extends ThreadItemAdapter { private boolean isCreator = false; - GroupMessageAdapter(ThreadItemListener listener) { - super(listener); + GroupMessageAdapter(LifecycleOwner lifecycleOwner, + ThreadItemListener listener) { + super(lifecycleOwner, listener); } @LayoutRes @@ -30,6 +33,7 @@ class GroupMessageAdapter extends ThreadItemAdapter { return item.getLayout(); } + @NonNull @Override public BaseThreadItemViewHolder onCreateViewHolder( ViewGroup parent, int type) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageItem.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageItem.java index 308f3d498..5c3aa2ca9 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupMessageItem.java @@ -1,11 +1,11 @@ package org.briarproject.briar.android.privategroup.conversation; import org.briarproject.bramble.api.identity.Author; -import org.briarproject.briar.api.identity.AuthorInfo; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.R; import org.briarproject.briar.android.threaded.ThreadItem; +import org.briarproject.briar.api.identity.AuthorInfo; import org.briarproject.briar.api.privategroup.GroupMessageHeader; import javax.annotation.Nullable; @@ -16,7 +16,7 @@ import androidx.annotation.UiThread; @UiThread @NotThreadSafe -class GroupMessageItem extends ThreadItem { +public class GroupMessageItem extends ThreadItem { private final GroupId groupId; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java index 437d5136d..2fe22f515 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupViewModel.java @@ -284,4 +284,9 @@ class GroupViewModel extends ThreadListViewModel { return isDissolved; } + @Override + protected String getMessageText(Transaction txn, MessageId m) + throws DbException { + return privateGroupManager.getMessageText(txn, m); + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/JoinMessageItemViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/JoinMessageItemViewHolder.java index c95f92297..a27ff7324 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/JoinMessageItemViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/JoinMessageItemViewHolder.java @@ -9,6 +9,7 @@ import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListe import org.briarproject.nullsafety.NotNullByDefault; import androidx.annotation.UiThread; +import androidx.lifecycle.LifecycleOwner; import static org.briarproject.briar.api.identity.AuthorInfo.Status.OURSELVES; @@ -25,9 +26,9 @@ class JoinMessageItemViewHolder } @Override - public void bind(GroupMessageItem item, + public void bind(GroupMessageItem item, LifecycleOwner lifecycleOwner, ThreadItemListener listener) { - super.bind(item, listener); + super.bind(item, lifecycleOwner, listener); if (isCreator) bindForCreator((JoinMessageItem) item); else bind((JoinMessageItem) item); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/BaseThreadItemViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/BaseThreadItemViewHolder.java index fbd784528..589208ea5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/BaseThreadItemViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/BaseThreadItemViewHolder.java @@ -10,17 +10,22 @@ import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.widget.TextView; -import org.briarproject.bramble.util.StringUtils; +import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.R; import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener; import org.briarproject.briar.android.view.AuthorView; import org.briarproject.nullsafety.NotNullByDefault; +import javax.annotation.Nullable; + import androidx.annotation.CallSuper; import androidx.annotation.UiThread; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LiveData; import androidx.recyclerview.widget.RecyclerView; import static androidx.core.content.ContextCompat.getColor; +import static org.briarproject.bramble.util.StringUtils.trim; import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable; @UiThread @@ -33,6 +38,8 @@ public abstract class BaseThreadItemViewHolder protected final TextView textView; private final ViewGroup layout; private final AuthorView author; + @Nullable + private MessageId boundMessageId = null; public BaseThreadItemViewHolder(View v) { super(v); @@ -43,8 +50,22 @@ public abstract class BaseThreadItemViewHolder } @CallSuper - public void bind(I item, ThreadItemListener listener) { - textView.setText(StringUtils.trim(item.getText())); + public void bind(I item, LifecycleOwner lifecycleOwner, + ThreadItemListener listener) { + boundMessageId = item.getId(); + String text = item.getText(); + if (text == null) { + textView.setText(null); + LiveData textLiveData = listener.loadItemText(item.getId()); + textLiveData.observe(lifecycleOwner, t -> { + // Check that the ViewHolder hasn't been re-bound while loading + if (item.getId().equals(boundMessageId)) { + textView.setText(t); + } + }); + } else { + textView.setText(trim(text)); + } Linkify.addLinks(textView, Linkify.WEB_URLS); makeLinksClickable(textView, listener::onLinkClick); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItem.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItem.java index 979fcad9e..7e3c1a724 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItem.java @@ -19,6 +19,7 @@ public abstract class ThreadItem implements MessageNode { private final MessageId messageId; @Nullable private final MessageId parentId; + @Nullable private final String text; private final long timestamp; private final Author author; @@ -27,8 +28,8 @@ public abstract class ThreadItem implements MessageNode { private boolean isRead, highlighted; public ThreadItem(MessageId messageId, @Nullable MessageId parentId, - String text, long timestamp, Author author, AuthorInfo authorInfo, - boolean isRead) { + @Nullable String text, long timestamp, Author author, + AuthorInfo authorInfo, boolean isRead) { this.messageId = messageId; this.parentId = parentId; this.text = text; @@ -39,6 +40,7 @@ public abstract class ThreadItem implements MessageNode { this.highlighted = false; } + @Nullable public String getText() { return text; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java index 2941cdf94..8d78007c5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java @@ -13,6 +13,8 @@ import javax.annotation.Nullable; import androidx.annotation.NonNull; import androidx.annotation.UiThread; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LiveData; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.ListAdapter; @@ -27,9 +29,11 @@ public class ThreadItemAdapter static final int UNDEFINED = -1; + private final LifecycleOwner lifecycleOwner; private final ThreadItemListener listener; - public ThreadItemAdapter(ThreadItemListener listener) { + public ThreadItemAdapter(LifecycleOwner lifecycleOwner, + ThreadItemListener listener) { super(new DiffUtil.ItemCallback() { @Override public boolean areItemsTheSame(I a, I b) { @@ -42,6 +46,7 @@ public class ThreadItemAdapter a.isRead() == b.isRead(); } }); + this.lifecycleOwner = lifecycleOwner; this.listener = listener; } @@ -58,7 +63,7 @@ public class ThreadItemAdapter public void onBindViewHolder(@NonNull BaseThreadItemViewHolder ui, int position) { I item = getItem(position); - ui.bind(item, listener); + ui.bind(item, lifecycleOwner, listener); } int findItemPosition(MessageId id) { @@ -136,8 +141,12 @@ public class ThreadItemAdapter } public interface ThreadItemListener { + void onReplyClick(I item); + void onLinkClick(String url); + + LiveData loadItemText(MessageId m); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java index 1939a5d9b..a5e054efb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java @@ -257,4 +257,8 @@ public abstract class ThreadListActivity loadItemText(MessageId m) { + return getViewModel().loadMessageText(m); + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java index ecd6ffde5..c0c5217a1 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListViewModel.java @@ -6,6 +6,7 @@ import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.NoSuchGroupException; +import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventBus; @@ -260,4 +261,14 @@ public abstract class ThreadListViewModel return scrollToItem.getAndSet(null); } + public LiveData loadMessageText(MessageId m) { + MutableLiveData textLiveData = new MutableLiveData<>(); + runOnDbThread(true, txn -> + textLiveData.postValue(getMessageText(txn, m)), + this::handleException); + return textLiveData; + } + + protected abstract String getMessageText(Transaction txn, MessageId m) + throws DbException; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadPostViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadPostViewHolder.java index eb9be9acb..23eb26972 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadPostViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadPostViewHolder.java @@ -10,6 +10,7 @@ import org.briarproject.nullsafety.NotNullByDefault; import java.util.Locale; import androidx.annotation.UiThread; +import androidx.lifecycle.LifecycleOwner; import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -40,8 +41,9 @@ public class ThreadPostViewHolder } @Override - public void bind(I item, ThreadItemListener listener) { - super.bind(item, listener); + public void bind(I item, LifecycleOwner lifecycleOwner, + ThreadItemListener listener) { + super.bind(item, lifecycleOwner, listener); for (int i = 0; i < lvls.length; i++) { lvls[i].setVisibility(i < item.getLevel() ? VISIBLE : GONE); diff --git a/briar-api/src/main/java/org/briarproject/briar/api/forum/event/ForumPostReceivedEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/forum/event/ForumPostReceivedEvent.java index fb654b055..43e5e8941 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/forum/event/ForumPostReceivedEvent.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/forum/event/ForumPostReceivedEvent.java @@ -16,13 +16,10 @@ public class ForumPostReceivedEvent extends Event { private final GroupId groupId; private final ForumPostHeader header; - private final String text; - public ForumPostReceivedEvent(GroupId groupId, ForumPostHeader header, - String text) { + public ForumPostReceivedEvent(GroupId groupId, ForumPostHeader header) { this.groupId = groupId; this.header = header; - this.text = text; } public GroupId getGroupId() { @@ -32,8 +29,4 @@ public class ForumPostReceivedEvent extends Event { public ForumPostHeader getHeader() { return header; } - - public String getText() { - return text; - } } diff --git a/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java index f15fb9b75..754d89410 100644 --- a/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java @@ -83,10 +83,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { messageTracker.trackIncomingMessage(txn, m); ForumPostHeader header = getForumPostHeader(txn, m.getId(), meta); - String text = getPostText(body); - ForumPostReceivedEvent event = - new ForumPostReceivedEvent(m.getGroupId(), header, text); - txn.attach(event); + txn.attach(new ForumPostReceivedEvent(m.getGroupId(), header)); return ACCEPT_SHARE; }