Clean up after migrating blog controllers to view model

This commit is contained in:
Torsten Grote
2021-01-12 14:54:17 -03:00
parent 6860a04e8b
commit 95104d3383
19 changed files with 246 additions and 359 deletions

View File

@@ -13,7 +13,6 @@ import org.briarproject.briar.android.blog.BlogActivity;
import org.briarproject.briar.android.blog.BlogFragment;
import org.briarproject.briar.android.blog.BlogPostFragment;
import org.briarproject.briar.android.blog.FeedFragment;
import org.briarproject.briar.android.blog.FeedPostFragment;
import org.briarproject.briar.android.blog.ReblogActivity;
import org.briarproject.briar.android.blog.ReblogFragment;
import org.briarproject.briar.android.blog.RssFeedImportActivity;
@@ -150,8 +149,6 @@ public interface ActivityComponent {
void inject(BlogPostFragment fragment);
void inject(FeedPostFragment fragment);
void inject(ReblogFragment fragment);
void inject(ReblogActivity activity);

View File

@@ -1,37 +0,0 @@
package org.briarproject.briar.android.blog;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.controller.DbControllerImpl;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.blog.BlogManager;
import java.util.concurrent.Executor;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
abstract class BaseControllerImpl extends DbControllerImpl
implements EventListener {
protected final EventBus eventBus;
protected final AndroidNotificationManager notificationManager;
protected final IdentityManager identityManager;
protected final BlogManager blogManager;
BaseControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, EventBus eventBus,
AndroidNotificationManager notificationManager,
IdentityManager identityManager, BlogManager blogManager) {
super(dbExecutor, lifecycleManager);
this.eventBus = eventBus;
this.notificationManager = notificationManager;
this.identityManager = identityManager;
this.blogManager = blogManager;
}
}

View File

@@ -1,128 +0,0 @@
package org.briarproject.briar.android.blog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.widget.LinkDialogFragment;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import androidx.annotation.CallSuper;
import androidx.annotation.UiThread;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
@UiThread
@MethodsNotNullByDefault
@ParametersNotNullByDefault
abstract class BasePostFragment extends BaseFragment {
static final String POST_ID = "briar.POST_ID";
private static final Logger LOG =
getLogger(BasePostFragment.class.getName());
private final Handler handler = new Handler(Looper.getMainLooper());
protected MessageId postId;
private ProgressBar progressBar;
private BlogPostViewHolder ui;
private BlogPostItem post;
private Runnable refresher;
@CallSuper
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// retrieve MessageId of blog post from arguments
byte[] p = requireArguments().getByteArray(POST_ID);
if (p == null) throw new IllegalStateException("No post ID in args");
postId = new MessageId(p);
View view = inflater.inflate(R.layout.fragment_blog_post, container,
false);
progressBar = view.findViewById(R.id.progressBar);
progressBar.setVisibility(VISIBLE);
ui = new BlogPostViewHolder(view, true, new OnBlogPostClickListener() {
@Override
public void onBlogPostClick(BlogPostItem post) {
// We're already there
}
@Override
public void onAuthorClick(BlogPostItem post) {
if (getContext() == null) return;
Intent i = new Intent(getContext(), BlogActivity.class);
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
getContext().startActivity(i);
}
@Override
public void onLinkClick(String url) {
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
f.show(getParentFragmentManager(), f.getUniqueTag());
}
});
return view;
}
@CallSuper
@Override
public void onStart() {
super.onStart();
startPeriodicUpdate();
}
@CallSuper
@Override
public void onStop() {
super.onStop();
stopPeriodicUpdate();
}
@UiThread
protected void onBlogPostLoaded(BlogPostItem post) {
progressBar.setVisibility(INVISIBLE);
this.post = post;
ui.bindItem(post);
}
private void startPeriodicUpdate() {
refresher = () -> {
LOG.info("Updating Content...");
ui.updateDate(post.getTimestamp());
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
};
LOG.info("Adding Handler Callback");
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
}
private void stopPeriodicUpdate() {
if (refresher != null) {
LOG.info("Removing Handler Callback");
handler.removeCallbacks(refresher);
}
}
}

View File

@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
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;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.IdentityManager;
@@ -28,13 +27,10 @@ import org.briarproject.briar.util.HtmlUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData;
@@ -48,27 +44,23 @@ import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.util.HtmlUtils.ARTICLE;
@NotNullByDefault
public class BaseViewModel extends DbViewModel implements EventListener {
abstract class BaseViewModel extends DbViewModel implements EventListener {
private static final Logger LOG = getLogger(BaseViewModel.class.getName());
protected final TransactionManager db;
private final EventBus eventBus;
protected final TransactionManager db;
protected final IdentityManager identityManager;
protected final AndroidNotificationManager notificationManager;
protected final BlogManager blogManager;
private final MutableLiveData<LiveResult<List<BlogPostItem>>> blogPosts =
protected final MutableLiveData<LiveResult<List<BlogPostItem>>> blogPosts =
new MutableLiveData<>();
// TODO do we still need those caches?
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
private final Map<MessageId, BlogPostHeader> headerCache =
new ConcurrentHashMap<>();
// UI thread
@Nullable
private Boolean postAddedWasLocal = null;
@Inject
BaseViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
@@ -84,7 +76,6 @@ public class BaseViewModel extends DbViewModel implements EventListener {
this.identityManager = identityManager;
this.notificationManager = notificationManager;
this.blogManager = blogManager;
eventBus.addListener(this);
}
@@ -94,10 +85,6 @@ public class BaseViewModel extends DbViewModel implements EventListener {
eventBus.removeListener(this);
}
@Override
public void eventOccurred(Event e) {
}
@DatabaseExecutor
protected List<BlogPostItem> loadBlogPosts(Transaction txn, GroupId groupId)
throws DbException {
@@ -108,7 +95,6 @@ public class BaseViewModel extends DbViewModel implements EventListener {
List<BlogPostItem> items = new ArrayList<>(headers.size());
start = now();
for (BlogPostHeader h : headers) {
headerCache.put(h.getId(), h);
BlogPostItem item = getItem(txn, h);
items.add(item);
}
@@ -135,12 +121,7 @@ public class BaseViewModel extends DbViewModel implements EventListener {
@DatabaseExecutor
private String getPostText(Transaction txn, MessageId m)
throws DbException {
String text = textCache.get(m);
if (text == null) {
text = HtmlUtils.clean(blogManager.getPostText(txn, m), ARTICLE);
textCache.put(m, text);
}
return text;
return HtmlUtils.clean(blogManager.getPostText(txn, m), ARTICLE);
}
LiveData<LiveResult<BlogPostItem>> loadBlogPost(GroupId g, MessageId m) {
@@ -149,7 +130,7 @@ public class BaseViewModel extends DbViewModel implements EventListener {
runOnDbThread(() -> {
try {
long start = now();
BlogPostHeader header = getPostHeader(g, m);
BlogPostHeader header = blogManager.getPostHeader(g, m);
BlogPostItem item = db.transactionWithResult(true, txn ->
getItem(txn, header)
);
@@ -163,24 +144,7 @@ public class BaseViewModel extends DbViewModel implements EventListener {
return result;
}
@DatabaseExecutor
private BlogPostHeader getPostHeader(GroupId g, MessageId m)
throws DbException {
BlogPostHeader header = headerCache.get(m);
if (header == null) {
header = blogManager.getPostHeader(g, m);
headerCache.put(m, header);
}
return header;
}
@UiThread
protected void updateBlogPosts(LiveResult<List<BlogPostItem>> posts) {
blogPosts.setValue(posts);
}
protected void onBlogPostAdded(BlogPostHeader header, boolean local) {
postAddedWasLocal = local;
runOnDbThread(() -> {
try {
db.transaction(true, txn -> {
@@ -189,6 +153,7 @@ public class BaseViewModel extends DbViewModel implements EventListener {
List<BlogPostItem> items = addListItem(blogPosts, item);
if (items != null) {
Collections.sort(items);
postAddedWasLocal = local;
blogPosts.setValue(new LiveResult<>(items));
}
});

View File

@@ -8,7 +8,9 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
// This class is not thread-safe
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
class BlogCommentItem extends BlogPostItem {
private static final BlogCommentComparator COMPARATOR =

View File

@@ -166,7 +166,6 @@ public class BlogFragment extends BaseFragment
if (request == REQUEST_WRITE_BLOG_POST && result == RESULT_OK) {
displaySnackbar(R.string.blogs_blog_post_created, true);
viewModel.loadBlogPosts(groupId);
} else if (request == REQUEST_SHARE_BLOG && result == RESULT_OK) {
displaySnackbar(R.string.blogs_sharing_snackbar, false);
}
@@ -194,7 +193,8 @@ public class BlogFragment extends BaseFragment
@Override
public void onBlogPostClick(BlogPostItem post) {
BlogPostFragment f = BlogPostFragment.newInstance(post.getId());
BlogPostFragment f =
BlogPostFragment.newInstance(groupId, post.getId());
showNextFragment(f);
}

View File

@@ -13,11 +13,11 @@ public interface BlogModule {
@Binds
@IntoMap
@ViewModelKey(FeedViewModel.class)
abstract ViewModel bindFeedViewModel(FeedViewModel feedViewModel);
ViewModel bindFeedViewModel(FeedViewModel feedViewModel);
@Binds
@IntoMap
@ViewModelKey(BlogViewModel.class)
abstract ViewModel bindBlogViewModel(BlogViewModel blogViewModel);
ViewModel bindBlogViewModel(BlogViewModel blogViewModel);
}

View File

@@ -1,39 +1,68 @@
package org.briarproject.briar.android.blog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.widget.LinkDialogFragment;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.annotation.CallSuper;
import androidx.annotation.UiThread;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
@UiThread
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BlogPostFragment extends BasePostFragment {
public class BlogPostFragment extends BaseFragment
implements OnBlogPostClickListener {
private static final String TAG = BlogPostFragment.class.getName();
private static final Logger LOG = getLogger(TAG);
static final String POST_ID = "briar.POST_ID";
protected BlogViewModel viewModel;
private final Handler handler = new Handler(Looper.getMainLooper());
private ProgressBar progressBar;
private BlogPostViewHolder ui;
private BlogPostItem post;
private Runnable refresher;
@Inject
ViewModelProvider.Factory viewModelFactory;
private BlogViewModel viewModel;
static BlogPostFragment newInstance(MessageId postId) {
static BlogPostFragment newInstance(GroupId blogId, MessageId postId) {
BlogPostFragment f = new BlogPostFragment();
Bundle bundle = new Bundle();
bundle.putByteArray(GROUP_ID, blogId.getBytes());
bundle.putByteArray(POST_ID, postId.getBytes());
f.setArguments(bundle);
return f;
}
@@ -45,22 +74,89 @@ public class BlogPostFragment extends BasePostFragment {
.get(BlogViewModel.class);
}
@Override
public String getUniqueTag() {
return TAG;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View v = super.onCreateView(inflater, container, savedInstanceState);
viewModel.loadBlogPost(postId).observe(getViewLifecycleOwner(), res ->
res.onError(this::handleException)
Bundle args = requireArguments();
GroupId groupId =
new GroupId(requireNonNull(args.getByteArray(GROUP_ID)));
MessageId postId = new MessageId(args.getByteArray(POST_ID));
View view = inflater.inflate(R.layout.fragment_blog_post, container,
false);
progressBar = view.findViewById(R.id.progressBar);
progressBar.setVisibility(VISIBLE);
ui = new BlogPostViewHolder(view, true, this);
LifecycleOwner owner = getViewLifecycleOwner();
viewModel.loadBlogPost(groupId, postId).observe(owner, result ->
result.onError(this::handleException)
.onSuccess(this::onBlogPostLoaded)
);
return v;
return view;
}
@CallSuper
@Override
public void onStart() {
super.onStart();
startPeriodicUpdate();
}
@CallSuper
@Override
public void onStop() {
super.onStop();
stopPeriodicUpdate();
}
@UiThread
private void onBlogPostLoaded(BlogPostItem post) {
progressBar.setVisibility(INVISIBLE);
this.post = post;
ui.bindItem(post);
}
@Override
public void onBlogPostClick(BlogPostItem post) {
// We're already there
}
@Override
public void onAuthorClick(BlogPostItem post) {
Intent i = new Intent(requireContext(), BlogActivity.class);
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
getContext().startActivity(i);
}
@Override
public void onLinkClick(String url) {
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
f.show(getParentFragmentManager(), f.getUniqueTag());
}
private void startPeriodicUpdate() {
refresher = () -> {
LOG.info("Updating Content...");
ui.updateDate(post.getTimestamp());
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
};
LOG.info("Adding Handler Callback");
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
}
private void stopPeriodicUpdate() {
if (refresher != null) {
LOG.info("Removing Handler Callback");
handler.removeCallbacks(refresher);
}
}
@Override
public String getUniqueTag() {
return TAG;
}
}

View File

@@ -17,7 +17,7 @@ public class BlogPostItem implements Comparable<BlogPostItem> {
private final BlogPostHeader header;
@Nullable
protected String text;
private boolean read;
private final boolean read;
BlogPostItem(BlogPostHeader header, @Nullable String text) {
this.header = header;

View File

@@ -25,11 +25,14 @@ import androidx.recyclerview.widget.RecyclerView;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
import static org.briarproject.briar.android.blog.BlogPostFragment.POST_ID;
import static org.briarproject.briar.android.util.UiUtils.TEASER_LENGTH;
import static org.briarproject.briar.android.util.UiUtils.getSpanned;
import static org.briarproject.briar.android.util.UiUtils.getTeaser;
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
import static org.briarproject.briar.android.view.AuthorView.COMMENTER;
import static org.briarproject.briar.android.view.AuthorView.REBLOGGER;
import static org.briarproject.briar.android.view.AuthorView.RSS_FEED_REBLOGGED;
import static org.briarproject.briar.api.blog.MessageType.POST;
@UiThread
@@ -138,17 +141,16 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
reblogger.setAuthorClickable(v -> listener.onAuthorClick(item));
}
reblogger.setVisibility(VISIBLE);
reblogger.setPersona(AuthorView.REBLOGGER);
reblogger.setPersona(REBLOGGER);
author.setPersona(item.getHeader().getRootPost().isRssFeed() ?
AuthorView.RSS_FEED_REBLOGGED :
AuthorView.COMMENTER);
RSS_FEED_REBLOGGED : COMMENTER);
// comments
// TODO use nested RecyclerView instead like we do for Image Attachments
for (BlogCommentHeader c : item.getComments()) {
View v = LayoutInflater.from(ctx)
.inflate(R.layout.list_item_blog_comment,
commentContainer, false);
View v = LayoutInflater.from(ctx).inflate(
R.layout.list_item_blog_comment, commentContainer, false);
AuthorView author = v.findViewById(R.id.authorView);
TextView text = v.findViewById(R.id.textView);

View File

@@ -148,8 +148,8 @@ class BlogViewModel extends BaseViewModel {
notificationManager.unblockNotification(groupId);
}
void loadBlogPosts(GroupId groupId) {
loadList(txn -> loadBlogPosts(txn, groupId), this::updateBlogPosts);
private void loadBlogPosts(GroupId groupId) {
loadList(txn -> loadBlogPosts(txn, groupId), blogPosts::setValue);
}
private void loadSharingContacts(GroupId groupId) {

View File

@@ -62,9 +62,6 @@ public class FeedFragment extends BaseFragment
component.inject(this);
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(FeedViewModel.class);
// TODO ideally we only do this once when the ViewModel gets created
viewModel.loadPersonalBlog();
viewModel.loadAllBlogPosts();
}
@Nullable
@@ -154,8 +151,8 @@ public class FeedFragment extends BaseFragment
@Override
public void onBlogPostClick(BlogPostItem post) {
FeedPostFragment f =
FeedPostFragment.newInstance(post.getGroupId(), post.getId());
BaseFragment f =
BlogPostFragment.newInstance(post.getGroupId(), post.getId());
showNextFragment(f);
}

View File

@@ -1,73 +0,0 @@
package org.briarproject.briar.android.blog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.activity.ActivityComponent;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.annotation.UiThread;
import androidx.lifecycle.ViewModelProvider;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
@UiThread
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class FeedPostFragment extends BasePostFragment {
private static final String TAG = FeedPostFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private FeedViewModel viewModel;
static FeedPostFragment newInstance(GroupId blogId, MessageId postId) {
FeedPostFragment f = new FeedPostFragment();
Bundle bundle = new Bundle();
bundle.putByteArray(GROUP_ID, blogId.getBytes());
bundle.putByteArray(POST_ID, postId.getBytes());
f.setArguments(bundle);
return f;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(FeedViewModel.class);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Bundle args = requireArguments();
GroupId groupId =
new GroupId(requireNonNull(args.getByteArray(GROUP_ID)));
MessageId postId =
new MessageId(requireNonNull(args.getByteArray(POST_ID)));
viewModel.loadBlogPost(groupId, postId).observe(getViewLifecycleOwner(),
result -> result.onError(this::handleException)
.onSuccess(this::onBlogPostLoaded)
);
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public String getUniqueTag() {
return TAG;
}
}

View File

@@ -17,6 +17,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.android.viewmodel.LiveResult;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.blog.Blog;
import org.briarproject.briar.api.blog.BlogManager;
@@ -59,6 +60,8 @@ class FeedViewModel extends BaseViewModel {
BlogManager blogManager) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor,
eventBus, identityManager, notificationManager, blogManager);
loadPersonalBlog();
loadAllBlogPosts();
}
@Override
@@ -71,13 +74,15 @@ class FeedViewModel extends BaseViewModel {
GroupAddedEvent g = (GroupAddedEvent) e;
if (g.getGroup().getClientId().equals(CLIENT_ID)) {
LOG.info("Blog added");
loadAllBlogPosts();
// TODO how can this even happen?
// added RSS feeds should trigger BlogPostAddedEvent, no?
onBlogAdded(g.getGroup().getId());
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent g = (GroupRemovedEvent) e;
if (g.getGroup().getClientId().equals(CLIENT_ID)) {
LOG.info("Blog removed");
loadAllBlogPosts();
onBlogRemoved(g.getGroup().getId());
}
}
}
@@ -94,7 +99,7 @@ class FeedViewModel extends BaseViewModel {
notificationManager.unblockAllBlogPostNotifications();
}
void loadPersonalBlog() {
private void loadPersonalBlog() {
runOnDbThread(() -> {
try {
long start = now();
@@ -112,8 +117,8 @@ class FeedViewModel extends BaseViewModel {
return personalBlog;
}
void loadAllBlogPosts() {
loadList(this::loadAllBlogPosts, this::updateBlogPosts);
private void loadAllBlogPosts() {
loadList(this::loadAllBlogPosts, blogPosts::setValue);
}
@DatabaseExecutor
@@ -133,4 +138,30 @@ class FeedViewModel extends BaseViewModel {
return posts;
}
private void onBlogAdded(GroupId g) {
runOnDbThread(() -> {
try {
db.transaction(true, txn -> {
List<BlogPostItem> posts = loadBlogPosts(txn, g);
txn.attach(() -> {
List<BlogPostItem> items =
addListItems(blogPosts, posts);
if (items != null) {
Collections.sort(items);
blogPosts.setValue(new LiveResult<>(items));
}
});
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void onBlogRemoved(GroupId g) {
removeAndUpdateListItems(blogPosts, item ->
item.getGroupId().equals(g)
);
}
}

View File

@@ -11,7 +11,7 @@ import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
import static org.briarproject.briar.android.blog.BlogPostFragment.POST_ID;
public class ReblogActivity extends BriarActivity implements
BaseFragmentListener {
@@ -39,13 +39,11 @@ public class ReblogActivity extends BriarActivity implements
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override

View File

@@ -33,7 +33,7 @@ import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
import static org.briarproject.briar.android.blog.BlogPostFragment.POST_ID;
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH;
@MethodsNotNullByDefault
@@ -45,7 +45,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
@Inject
ViewModelProvider.Factory viewModelFactory;
private FeedViewModel viewModel;
private BlogViewModel viewModel;
private ViewHolder ui;
private BlogPostItem item;
@@ -69,7 +69,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
public void injectFragment(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(FeedViewModel.class);
.get(BlogViewModel.class);
}
@Override
@@ -131,7 +131,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
ui.input.setVisibility(VISIBLE);
}
private class ViewHolder {
private class ViewHolder implements OnBlogPostClickListener {
private final ScrollView scrollView;
private final ProgressBar progressBar;
@@ -142,24 +142,25 @@ public class ReblogFragment extends BaseFragment implements SendListener {
scrollView = v.findViewById(R.id.scrollView);
progressBar = v.findViewById(R.id.progressBar);
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout),
true, new OnBlogPostClickListener() {
@Override
public void onBlogPostClick(BlogPostItem post) {
// do nothing
}
@Override
public void onAuthorClick(BlogPostItem post) {
// probably don't want to allow author clicks here
}
@Override
public void onLinkClick(String url) {
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
f.show(getParentFragmentManager(), f.getUniqueTag());
}
});
true, this);
input = v.findViewById(R.id.inputText);
}
@Override
public void onBlogPostClick(BlogPostItem post) {
// do nothing
}
@Override
public void onAuthorClick(BlogPostItem post) {
// probably don't want to allow author clicks here
}
@Override
public void onLinkClick(String url) {
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
f.show(getParentFragmentManager(), f.getUniqueTag());
}
}
}

View File

@@ -156,11 +156,9 @@ class ForumListViewModel extends DbViewModel implements EventListener {
@UiThread
private void onGroupRemoved(GroupId groupId) {
List<ForumListItem> list = removeListItems(forumItems, i ->
removeAndUpdateListItems(forumItems, i ->
i.getForum().getId().equals(groupId)
);
if (list == null) return;
forumItems.setValue(new LiveResult<>(list));
}
void loadForumInvitations() {

View File

@@ -193,10 +193,7 @@ class GroupListViewModel extends DbViewModel implements EventListener {
@UiThread
private void onGroupRemoved(GroupId groupId) {
List<GroupItem> list =
removeListItems(groupItems, i -> i.getId().equals(groupId));
if (list == null) return;
groupItems.setValue(new LiveResult<>(list));
removeAndUpdateListItems(groupItems, i -> i.getId().equals(groupId));
}
void removeGroup(GroupId g) {

View File

@@ -13,6 +13,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.AndroidExecutor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.Executor;
@@ -27,6 +28,7 @@ import androidx.arch.core.util.Function;
import androidx.core.util.Consumer;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.recyclerview.widget.RecyclerView;
import static java.util.logging.Level.WARNING;
@@ -153,13 +155,33 @@ public abstract class DbViewModel extends AndroidViewModel {
* </ul>
*/
@Nullable
protected <T> List<T> addListItem(LiveData<LiveResult<List<T>>> liveData, T item) {
protected <T> List<T> addListItem(LiveData<LiveResult<List<T>>> liveData,
T item) {
List<T> items = getListCopy(liveData);
if (items == null) return null;
items.add(item);
return items;
}
/**
* Creates a copy of the list available in the given LiveData
* and adds the given items to the copy.
*
* @return a copy of the list in the LiveData with items added or null when
* <ul>
* <li> LiveData does not have a value
* <li> LiveResult in the LiveData has an error
* </ul>
*/
@Nullable
protected <T> List<T> addListItems(LiveData<LiveResult<List<T>>> liveData,
Collection<T> items) {
List<T> copiedItems = getListCopy(liveData);
if (copiedItems == null) return null;
copiedItems.addAll(items);
return copiedItems;
}
/**
* Creates a copy of the list available in the given LiveData
* and replaces items where the given test function returns true.
@@ -221,6 +243,25 @@ public abstract class DbViewModel extends AndroidViewModel {
return changed ? items : null;
}
/**
* Updates the given LiveData with a copy of its list
* with the items removed where the given test function returns true.
* <p>
* Nothing is updated, if the
* <ul>
* <li> LiveData does not have a value
* <li> LiveResult in the LiveData has an error
* <li> test function did return false for all items in the list
* </ul>
*/
@UiThread
protected <T> void removeAndUpdateListItems(
MutableLiveData<LiveResult<List<T>>> liveData,
Function<T, Boolean> test) {
List<T> list = removeListItems(liveData, test);
if (list != null) liveData.setValue(new LiveResult<>(list));
}
/**
* Retrieves a copy of the list of items from the given LiveData
* or null if it is not available.