mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
12 Commits
alpha-1.5.
...
load-forum
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35d035b19f | ||
|
|
9ecac899fb | ||
|
|
16104b84b2 | ||
|
|
1542be20db | ||
|
|
070a0181d9 | ||
|
|
d83ae3a3b4 | ||
|
|
143f04bf1b | ||
|
|
138fa6f39d | ||
|
|
8e1371acf0 | ||
|
|
29f0b9d3c0 | ||
|
|
eb45ccfe9e | ||
|
|
e98b5a9882 |
@@ -89,11 +89,17 @@ class H2Database extends JdbcDatabase {
|
||||
try {
|
||||
c = createConnection();
|
||||
closeAllConnections();
|
||||
setDirty(c, false);
|
||||
LOG.info("Compacting DB");
|
||||
s = c.createStatement();
|
||||
s.execute("SHUTDOWN COMPACT");
|
||||
LOG.info("Finished compacting DB");
|
||||
s.close();
|
||||
c.close();
|
||||
// Reopen the DB to mark it as clean after compacting
|
||||
c = createConnection();
|
||||
setDirty(c, false);
|
||||
LOG.info("Marked DB as clean");
|
||||
c.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s, LOG, WARNING);
|
||||
tryToClose(c, LOG, WARNING);
|
||||
@@ -126,6 +132,7 @@ class H2Database extends JdbcDatabase {
|
||||
closeAllConnections();
|
||||
s = c.createStatement();
|
||||
s.execute("SHUTDOWN COMPACT");
|
||||
LOG.info("Finished compacting DB");
|
||||
s.close();
|
||||
c.close();
|
||||
} catch (SQLException e) {
|
||||
|
||||
@@ -217,19 +217,14 @@ public class BriarService extends Service {
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
// Hold a wake lock during shutdown
|
||||
wakeLockManager.runWakefully(() -> {
|
||||
super.onDestroy();
|
||||
LOG.info("Destroyed");
|
||||
stopForeground(true);
|
||||
if (receiver != null) {
|
||||
getApplicationContext().unregisterReceiver(receiver);
|
||||
}
|
||||
// Stop the services in a background thread
|
||||
wakeLockManager.executeWakefully(() -> {
|
||||
if (started) lifecycleManager.stopServices();
|
||||
}, "LifecycleShutdown");
|
||||
}, "LifecycleShutdown");
|
||||
super.onDestroy();
|
||||
LOG.info("Destroyed");
|
||||
// Stop the lifecycle, if not already stopped
|
||||
shutdown(false);
|
||||
stopForeground(true);
|
||||
if (receiver != null) {
|
||||
getApplicationContext().unregisterReceiver(receiver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -299,8 +294,8 @@ public class BriarService extends Service {
|
||||
private void shutdownFromBackground() {
|
||||
// Hold a wake lock during shutdown
|
||||
wakeLockManager.runWakefully(() -> {
|
||||
// Stop the service
|
||||
stopSelf();
|
||||
// Begin lifecycle shutdown
|
||||
shutdown(true);
|
||||
// Hide the UI
|
||||
hideUi();
|
||||
// Wait for shutdown to complete, then exit
|
||||
@@ -335,8 +330,18 @@ public class BriarService extends Service {
|
||||
/**
|
||||
* Starts the shutdown process.
|
||||
*/
|
||||
public void shutdown() {
|
||||
stopSelf(); // This will call onDestroy()
|
||||
public void shutdown(boolean stopAndroidService) {
|
||||
// Hold a wake lock during shutdown
|
||||
wakeLockManager.runWakefully(() -> {
|
||||
// Stop the lifecycle services in a background thread,
|
||||
// then stop this Android service if needed
|
||||
wakeLockManager.executeWakefully(() -> {
|
||||
if (started) lifecycleManager.stopServices();
|
||||
if (stopAndroidService) {
|
||||
androidExecutor.runOnUiThread(() -> stopSelf());
|
||||
}
|
||||
}, "LifecycleShutdown");
|
||||
}, "LifecycleShutdown");
|
||||
}
|
||||
|
||||
public class BriarBinder extends Binder {
|
||||
|
||||
@@ -147,7 +147,7 @@ public class BriarControllerImpl implements BriarController {
|
||||
service.waitForStartup();
|
||||
// Shut down the service and wait for it to shut down
|
||||
LOG.info("Shutting down service");
|
||||
service.shutdown();
|
||||
service.shutdown(true);
|
||||
service.waitForShutdown();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting for service");
|
||||
|
||||
@@ -54,7 +54,7 @@ public class ForumActivity extends
|
||||
|
||||
@Override
|
||||
protected ThreadItemAdapter<ForumPostItem> createAdapter() {
|
||||
return new ThreadItemAdapter<>(this);
|
||||
return new ThreadItemAdapter<>(this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,15 +13,18 @@ import androidx.recyclerview.widget.ListAdapter;
|
||||
@NotNullByDefault
|
||||
class ForumListAdapter extends ListAdapter<ForumListItem, ForumViewHolder> {
|
||||
|
||||
ForumListAdapter() {
|
||||
private final ForumListViewModel viewModel;
|
||||
|
||||
ForumListAdapter(ForumListViewModel viewModel) {
|
||||
super(new ForumListCallback());
|
||||
this.viewModel = viewModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForumViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(
|
||||
R.layout.list_item_forum, parent, false);
|
||||
return new ForumViewHolder(v);
|
||||
return new ForumViewHolder(v, viewModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -40,7 +40,7 @@ public class ForumListFragment extends BaseFragment implements
|
||||
private ForumListViewModel viewModel;
|
||||
private BriarRecyclerView list;
|
||||
private Snackbar snackbar;
|
||||
private final ForumListAdapter adapter = new ForumListAdapter();
|
||||
private ForumListAdapter adapter;
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
@@ -54,6 +54,7 @@ public class ForumListFragment extends BaseFragment implements
|
||||
component.inject(this);
|
||||
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||
.get(ForumListViewModel.class);
|
||||
adapter = new ForumListAdapter(viewModel);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.android.forum;
|
||||
|
||||
import android.app.Application;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
@@ -15,6 +16,7 @@ import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.event.GroupAddedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||
import org.briarproject.briar.android.viewmodel.LiveResult;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
@@ -40,6 +42,7 @@ import androidx.annotation.UiThread;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
@@ -180,4 +183,17 @@ class ForumListViewModel extends DbViewModel implements EventListener {
|
||||
return numInvitations;
|
||||
}
|
||||
|
||||
void deleteForum(GroupId groupId) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
Forum f = forumManager.getForum(groupId);
|
||||
forumManager.removeForum(f);
|
||||
androidExecutor.runOnUiThread(() -> Toast
|
||||
.makeText(getApplication(), R.string.forum_left_toast,
|
||||
LENGTH_SHORT).show());
|
||||
} catch (DbException e) {
|
||||
handleException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(), h.getTimestamp(), h.getAuthor(),
|
||||
h.getAuthorInfo(), h.isRead());
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
@@ -20,6 +21,7 @@ import static org.briarproject.briar.android.activity.BriarActivity.GROUP_NAME;
|
||||
|
||||
class ForumViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final ForumListViewModel viewModel;
|
||||
private final Context ctx;
|
||||
private final ViewGroup layout;
|
||||
private final TextAvatarView avatar;
|
||||
@@ -27,8 +29,9 @@ class ForumViewHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView postCount;
|
||||
private final TextView date;
|
||||
|
||||
ForumViewHolder(View v) {
|
||||
ForumViewHolder(View v, ForumListViewModel viewModel) {
|
||||
super(v);
|
||||
this.viewModel = viewModel;
|
||||
ctx = v.getContext();
|
||||
layout = (ViewGroup) v;
|
||||
avatar = v.findViewById(R.id.avatarView);
|
||||
@@ -64,6 +67,21 @@ class ForumViewHolder extends RecyclerView.ViewHolder {
|
||||
date.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
// Open popup menu on long click
|
||||
layout.setOnLongClickListener(v -> {
|
||||
PopupMenu pm = new PopupMenu(ctx, v);
|
||||
pm.getMenuInflater().inflate(R.menu.forum_list_item_actions,
|
||||
pm.getMenu());
|
||||
pm.setOnMenuItemClickListener(it -> {
|
||||
if (it.getItemId() == R.id.action_forum_delete) {
|
||||
viewModel.deleteForum(item.getForum().getId());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
pm.show();
|
||||
return true;
|
||||
});
|
||||
|
||||
// Open Forum on Click
|
||||
layout.setOnClickListener(v -> {
|
||||
Intent i = new Intent(ctx, ForumActivity.class);
|
||||
|
||||
@@ -91,9 +91,7 @@ class ForumViewModel extends ThreadListViewModel<ForumPostItem> {
|
||||
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<ForumPostItem> {
|
||||
List<ForumPostHeader> headers =
|
||||
forumManager.getPostHeaders(txn, groupId);
|
||||
logDuration(LOG, "Loading headers", start);
|
||||
start = now();
|
||||
List<ForumPostItem> 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<ForumPostItem> {
|
||||
@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<ForumPostItem> {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMessageText(Transaction txn, MessageId m)
|
||||
throws DbException {
|
||||
return forumManager.getPostText(txn, m);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<GroupMessageItem> {
|
||||
public class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
|
||||
|
||||
private boolean isCreator = false;
|
||||
|
||||
GroupMessageAdapter(ThreadItemListener<GroupMessageItem> listener) {
|
||||
super(listener);
|
||||
GroupMessageAdapter(LifecycleOwner lifecycleOwner,
|
||||
ThreadItemListener<GroupMessageItem> listener) {
|
||||
super(lifecycleOwner, listener);
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
@@ -30,6 +33,7 @@ class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
|
||||
return item.getLayout();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public BaseThreadItemViewHolder<GroupMessageItem> onCreateViewHolder(
|
||||
ViewGroup parent, int type) {
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
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.privategroup.GroupMessageHeader;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
@@ -16,20 +12,14 @@ import androidx.annotation.UiThread;
|
||||
|
||||
@UiThread
|
||||
@NotThreadSafe
|
||||
class GroupMessageItem extends ThreadItem {
|
||||
public class GroupMessageItem extends ThreadItem {
|
||||
|
||||
private final GroupId groupId;
|
||||
|
||||
private GroupMessageItem(MessageId messageId, GroupId groupId,
|
||||
@Nullable MessageId parentId, String text, long timestamp,
|
||||
Author author, AuthorInfo authorInfo, boolean isRead) {
|
||||
super(messageId, parentId, text, timestamp, author, authorInfo, isRead);
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
GroupMessageItem(GroupMessageHeader h, String text) {
|
||||
this(h.getId(), h.getGroupId(), h.getParentId(), text, h.getTimestamp(),
|
||||
h.getAuthor(), h.getAuthorInfo(), h.isRead());
|
||||
GroupMessageItem(GroupMessageHeader h) {
|
||||
super(h.getId(), h.getParentId(), h.getTimestamp(), h.getAuthor(),
|
||||
h.getAuthorInfo(), h.isRead());
|
||||
this.groupId = h.getGroupId();
|
||||
}
|
||||
|
||||
public GroupId getGroupId() {
|
||||
|
||||
@@ -99,7 +99,7 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
|
||||
// only act on non-local messages in this group
|
||||
if (!g.isLocal() && g.getGroupId().equals(groupId)) {
|
||||
LOG.info("Group message received, adding...");
|
||||
GroupMessageItem item = buildItem(g.getHeader(), g.getText());
|
||||
GroupMessageItem item = buildItem(g.getHeader());
|
||||
addItem(item, false);
|
||||
// In case the join message comes from the creator,
|
||||
// we need to reload the sharing contacts
|
||||
@@ -167,33 +167,19 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
|
||||
List<GroupMessageHeader> headers =
|
||||
privateGroupManager.getHeaders(txn, groupId);
|
||||
logDuration(LOG, "Loading headers", start);
|
||||
start = now();
|
||||
List<GroupMessageItem> items = new ArrayList<>();
|
||||
for (GroupMessageHeader header : headers) {
|
||||
items.add(loadItem(txn, header));
|
||||
items.add(buildItem(header));
|
||||
}
|
||||
logDuration(LOG, "Loading bodies and creating items", start);
|
||||
return items;
|
||||
}, this::setItems);
|
||||
}
|
||||
|
||||
private GroupMessageItem loadItem(Transaction txn,
|
||||
GroupMessageHeader header) throws DbException {
|
||||
String text;
|
||||
private GroupMessageItem buildItem(GroupMessageHeader header) {
|
||||
if (header instanceof JoinMessageHeader) {
|
||||
// will be looked up later
|
||||
text = "";
|
||||
} else {
|
||||
text = privateGroupManager.getMessageText(txn, header.getId());
|
||||
return new JoinMessageItem((JoinMessageHeader) header);
|
||||
}
|
||||
return buildItem(header, text);
|
||||
}
|
||||
|
||||
private GroupMessageItem buildItem(GroupMessageHeader header, String text) {
|
||||
if (header instanceof JoinMessageHeader) {
|
||||
return new JoinMessageItem((JoinMessageHeader) header, text);
|
||||
}
|
||||
return new GroupMessageItem(header, text);
|
||||
return new GroupMessageItem(header);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -221,19 +207,17 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
|
||||
LOG.info("Creating group message...");
|
||||
GroupMessage msg = groupMessageFactory.createGroupMessage(groupId,
|
||||
timestamp, parentId, author, text, previousMsgId);
|
||||
storePost(msg, text);
|
||||
storePost(msg);
|
||||
});
|
||||
}
|
||||
|
||||
private void storePost(GroupMessage msg, String text) {
|
||||
private void storePost(GroupMessage msg) {
|
||||
runOnDbThread(false, txn -> {
|
||||
long start = now();
|
||||
GroupMessageHeader header =
|
||||
privateGroupManager.addLocalMessage(txn, msg);
|
||||
logDuration(LOG, "Storing group message", start);
|
||||
txn.attach(() ->
|
||||
addItem(buildItem(header, text), true)
|
||||
);
|
||||
txn.attach(() -> addItem(buildItem(header), true));
|
||||
}, this::handleException);
|
||||
}
|
||||
|
||||
@@ -284,4 +268,9 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
|
||||
return isDissolved;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMessageText(Transaction txn, MessageId m)
|
||||
throws DbException {
|
||||
return privateGroupManager.getMessageText(txn, m);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ class JoinMessageItem extends GroupMessageItem {
|
||||
|
||||
private final boolean isInitial;
|
||||
|
||||
JoinMessageItem(JoinMessageHeader h, String text) {
|
||||
super(h, text);
|
||||
JoinMessageItem(JoinMessageHeader h) {
|
||||
super(h);
|
||||
isInitial = h.isInitial();
|
||||
}
|
||||
|
||||
|
||||
@@ -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,10 +26,8 @@ class JoinMessageItemViewHolder
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(GroupMessageItem item,
|
||||
protected void setText(GroupMessageItem item, LifecycleOwner lifecycleOwner,
|
||||
ThreadItemListener<GroupMessageItem> listener) {
|
||||
super.bind(item, listener);
|
||||
|
||||
if (isCreator) bindForCreator((JoinMessageItem) item);
|
||||
else bind((JoinMessageItem) item);
|
||||
}
|
||||
|
||||
@@ -4,35 +4,46 @@ import android.animation.Animator;
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
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.lifecycle.Observer;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import static android.text.util.Linkify.WEB_URLS;
|
||||
import static android.text.util.Linkify.addLinks;
|
||||
import static androidx.core.content.ContextCompat.getColor;
|
||||
import static org.briarproject.bramble.util.StringUtils.trim;
|
||||
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
|
||||
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
public abstract class BaseThreadItemViewHolder<I extends ThreadItem>
|
||||
extends RecyclerView.ViewHolder {
|
||||
extends RecyclerView.ViewHolder implements Observer<String> {
|
||||
|
||||
private final static int ANIMATION_DURATION = 5000;
|
||||
|
||||
protected final TextView textView;
|
||||
private final ViewGroup layout;
|
||||
private final AuthorView author;
|
||||
@Nullable
|
||||
private ThreadItemListener<I> listener = null;
|
||||
@Nullable
|
||||
private LiveData<String> textLiveData = null;
|
||||
|
||||
public BaseThreadItemViewHolder(View v) {
|
||||
super(v);
|
||||
@@ -43,10 +54,9 @@ public abstract class BaseThreadItemViewHolder<I extends ThreadItem>
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void bind(I item, ThreadItemListener<I> listener) {
|
||||
textView.setText(StringUtils.trim(item.getText()));
|
||||
Linkify.addLinks(textView, Linkify.WEB_URLS);
|
||||
makeLinksClickable(textView, listener::onLinkClick);
|
||||
public void bind(I item, LifecycleOwner lifecycleOwner,
|
||||
ThreadItemListener<I> listener) {
|
||||
setText(item, lifecycleOwner, listener);
|
||||
|
||||
author.setAuthor(item.getAuthor(), item.getAuthorInfo());
|
||||
author.setDate(item.getTimestamp());
|
||||
@@ -61,6 +71,20 @@ public abstract class BaseThreadItemViewHolder<I extends ThreadItem>
|
||||
}
|
||||
}
|
||||
|
||||
protected void setText(I item, LifecycleOwner lifecycleOwner,
|
||||
ThreadItemListener<I> listener) {
|
||||
// Clear any existing text while we asynchronously load the new text
|
||||
textView.setText(null);
|
||||
// Remember the listener so we can use it to create links later
|
||||
this.listener = listener;
|
||||
// If the view has been re-bound and we're already asynchronously
|
||||
// loading text for another item, stop observing it
|
||||
if (textLiveData != null) textLiveData.removeObserver(this);
|
||||
// Asynchronously load the text for this item and observe the result
|
||||
textLiveData = listener.loadItemText(item.getId());
|
||||
textLiveData.observe(lifecycleOwner, this);
|
||||
}
|
||||
|
||||
private void animateFadeOut() {
|
||||
setIsRecyclable(false);
|
||||
ValueAnimator anim = new ValueAnimator();
|
||||
@@ -73,6 +97,7 @@ public abstract class BaseThreadItemViewHolder<I extends ThreadItem>
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
layout.setBackgroundResource(
|
||||
@@ -80,9 +105,11 @@ public abstract class BaseThreadItemViewHolder<I extends ThreadItem>
|
||||
layout.setActivated(false);
|
||||
setIsRecyclable(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animation) {
|
||||
}
|
||||
@@ -97,4 +124,24 @@ public abstract class BaseThreadItemViewHolder<I extends ThreadItem>
|
||||
return textView.getContext();
|
||||
}
|
||||
|
||||
void onViewRecycled() {
|
||||
textView.setText(null);
|
||||
if (textLiveData != null) {
|
||||
textLiveData.removeObserver(this);
|
||||
textLiveData = null;
|
||||
listener = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(String s) {
|
||||
if (textLiveData != null) {
|
||||
textLiveData.removeObserver(this);
|
||||
textLiveData = null;
|
||||
textView.setText(trim(s));
|
||||
addLinks(textView, WEB_URLS);
|
||||
makeLinksClickable(textView, requireNonNull(listener)::onLinkClick);
|
||||
listener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ public abstract class ThreadItem implements MessageNode {
|
||||
private final MessageId messageId;
|
||||
@Nullable
|
||||
private final MessageId parentId;
|
||||
private final String text;
|
||||
private final long timestamp;
|
||||
private final Author author;
|
||||
private final AuthorInfo authorInfo;
|
||||
@@ -27,11 +26,10 @@ 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,
|
||||
long timestamp, Author author, AuthorInfo authorInfo,
|
||||
boolean isRead) {
|
||||
this.messageId = messageId;
|
||||
this.parentId = parentId;
|
||||
this.text = text;
|
||||
this.timestamp = timestamp;
|
||||
this.author = author;
|
||||
this.authorInfo = authorInfo;
|
||||
@@ -39,10 +37,6 @@ public abstract class ThreadItem implements MessageNode {
|
||||
this.highlighted = false;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
@@ -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<I extends ThreadItem>
|
||||
|
||||
static final int UNDEFINED = -1;
|
||||
|
||||
private final LifecycleOwner lifecycleOwner;
|
||||
private final ThreadItemListener<I> listener;
|
||||
|
||||
public ThreadItemAdapter(ThreadItemListener<I> listener) {
|
||||
public ThreadItemAdapter(LifecycleOwner lifecycleOwner,
|
||||
ThreadItemListener<I> listener) {
|
||||
super(new DiffUtil.ItemCallback<I>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(I a, I b) {
|
||||
@@ -42,6 +46,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
a.isRead() == b.isRead();
|
||||
}
|
||||
});
|
||||
this.lifecycleOwner = lifecycleOwner;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@@ -58,7 +63,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
public void onBindViewHolder(@NonNull BaseThreadItemViewHolder<I> ui,
|
||||
int position) {
|
||||
I item = getItem(position);
|
||||
ui.bind(item, listener);
|
||||
ui.bind(item, lifecycleOwner, listener);
|
||||
}
|
||||
|
||||
int findItemPosition(MessageId id) {
|
||||
@@ -135,9 +140,19 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
return getItem(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRecycled(BaseThreadItemViewHolder<I> viewHolder) {
|
||||
super.onViewRecycled(viewHolder);
|
||||
viewHolder.onViewRecycled();
|
||||
}
|
||||
|
||||
public interface ThreadItemListener<I> {
|
||||
|
||||
void onReplyClick(I item);
|
||||
|
||||
void onLinkClick(String url);
|
||||
|
||||
LiveData<String> loadItemText(MessageId m);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -85,6 +85,9 @@ public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadI
|
||||
scrollListener = new ThreadScrollListener<>(adapter, viewModel,
|
||||
upButton, downButton);
|
||||
list.getRecyclerView().addOnScrollListener(scrollListener);
|
||||
// This is a tradeoff between memory consumption for cached views
|
||||
// and the cost of loading message text from the database
|
||||
list.getRecyclerView().setItemViewCacheSize(20);
|
||||
|
||||
upButton.setOnClickListener(v -> {
|
||||
int position = adapter.getVisibleUnreadPosTop(layoutManager);
|
||||
@@ -257,4 +260,8 @@ public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadI
|
||||
|
||||
protected abstract int getMaxTextLength();
|
||||
|
||||
@Override
|
||||
public LiveData<String> loadItemText(MessageId m) {
|
||||
return getViewModel().loadMessageText(m);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<I extends ThreadItem>
|
||||
return scrollToItem.getAndSet(null);
|
||||
}
|
||||
|
||||
public LiveData<String> loadMessageText(MessageId m) {
|
||||
MutableLiveData<String> 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;
|
||||
}
|
||||
|
||||
@@ -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<I extends ThreadItem>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(I item, ThreadItemListener<I> listener) {
|
||||
super.bind(item, listener);
|
||||
public void bind(I item, LifecycleOwner lifecycleOwner,
|
||||
ThreadItemListener<I> listener) {
|
||||
super.bind(item, lifecycleOwner, listener);
|
||||
|
||||
for (int i = 0; i < lvls.length; i++) {
|
||||
lvls[i].setVisibility(i < item.getLevel() ? VISIBLE : GONE);
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
android:layout_width="@dimen/listitem_picture_frame_size"
|
||||
android:layout_height="@dimen/listitem_picture_frame_size"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
app:layout_constraintBottom_toTopOf="@+id/divider"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
@@ -38,7 +37,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
app:layout_constraintEnd_toStartOf="@+id/dateView"
|
||||
@@ -51,7 +49,6 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/listitem_horizontal_margin"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/postCountView"
|
||||
@@ -63,7 +60,6 @@
|
||||
style="@style/Divider.ThreadItem"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:layout_marginLeft="@dimen/margin_medium"
|
||||
android:layout_marginTop="@dimen/listitem_horizontal_margin"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_forum_delete"
|
||||
android:title="@string/forum_leave" />
|
||||
|
||||
</menu>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,12 @@ public class GroupMessageAddedEvent extends Event {
|
||||
|
||||
private final GroupId groupId;
|
||||
private final GroupMessageHeader header;
|
||||
private final String text;
|
||||
private final boolean local;
|
||||
|
||||
public GroupMessageAddedEvent(GroupId groupId, GroupMessageHeader header,
|
||||
String text, boolean local) {
|
||||
boolean local) {
|
||||
this.groupId = groupId;
|
||||
this.header = header;
|
||||
this.text = text;
|
||||
this.local = local;
|
||||
}
|
||||
|
||||
@@ -36,10 +34,6 @@ public class GroupMessageAddedEvent extends Event {
|
||||
return header;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public boolean isLocal() {
|
||||
return local;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -170,6 +170,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
txn -> getPreviousMsgId(txn, g));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageId getPreviousMsgId(Transaction txn, GroupId g)
|
||||
throws DbException {
|
||||
try {
|
||||
@@ -605,9 +606,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
throws DbException, FormatException {
|
||||
GroupMessageHeader header = getGroupMessageHeader(txn, m.getGroupId(),
|
||||
m.getId(), meta, Collections.emptyMap());
|
||||
String text = getMessageText(clientHelper.toList(m));
|
||||
txn.attach(new GroupMessageAddedEvent(m.getGroupId(), header, text,
|
||||
local));
|
||||
txn.attach(new GroupMessageAddedEvent(m.getGroupId(), header, local));
|
||||
}
|
||||
|
||||
private void attachJoinMessageAddedEvent(Transaction txn, Message m,
|
||||
@@ -615,8 +614,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
throws DbException, FormatException {
|
||||
JoinMessageHeader header = getJoinMessageHeader(txn, m.getGroupId(),
|
||||
m.getId(), meta, Collections.emptyMap());
|
||||
txn.attach(new GroupMessageAddedEvent(m.getGroupId(), header, "",
|
||||
local));
|
||||
txn.attach(new GroupMessageAddedEvent(m.getGroupId(), header, local));
|
||||
}
|
||||
|
||||
private void addMember(Transaction txn, GroupId g, Author a, Visibility v)
|
||||
|
||||
@@ -6,9 +6,9 @@ sourceSets.configureEach { sourceSet ->
|
||||
|
||||
idea {
|
||||
module {
|
||||
sourceDirs += compileJava.options.generatedSourceOutputDirectory
|
||||
generatedSourceDirs += compileJava.options.generatedSourceOutputDirectory
|
||||
testSourceDirs += compileTestJava.options.generatedSourceOutputDirectory
|
||||
generatedSourceDirs += compileTestJava.options.generatedSourceOutputDirectory
|
||||
sourceDirs += compileJava.options.generatedSourceOutputDirectory.get().getAsFile()
|
||||
generatedSourceDirs += compileJava.options.generatedSourceOutputDirectory.get().getAsFile()
|
||||
testSourceDirs += compileTestJava.options.generatedSourceOutputDirectory.get().getAsFile()
|
||||
generatedSourceDirs += compileTestJava.options.generatedSourceOutputDirectory.get().getAsFile()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user