From ab1ed0ff5a4b8603ead323681dee84b8b1e0ca27 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 11 Jan 2021 16:36:15 -0300 Subject: [PATCH] Turn FeedController into FeedViewModel --- .../briarproject/briar/android/AppModule.java | 2 + .../briar/android/blog/BaseController.java | 4 +- .../android/blog/BaseControllerImpl.java | 9 +- .../briar/android/blog/BaseViewModel.java | 219 ++++++++++++++++++ .../briar/android/blog/BlogController.java | 3 +- .../android/blog/BlogControllerImpl.java | 3 +- .../briar/android/blog/BlogFragment.java | 12 +- .../briar/android/blog/BlogModule.java | 18 +- .../briar/android/blog/BlogPostAdapter.java | 39 ++-- .../briar/android/blog/BlogPostItem.java | 5 +- .../briar/android/blog/FeedController.java | 32 --- .../android/blog/FeedControllerImpl.java | 143 ------------ .../briar/android/blog/FeedFragment.java | 196 +++++----------- .../briar/android/blog/FeedPostFragment.java | 44 ++-- .../briar/android/blog/FeedViewModel.java | 174 ++++++++++++++ .../briar/android/blog/ReblogFragment.java | 48 ++-- .../briar/android/viewmodel/DbViewModel.java | 18 ++ 17 files changed, 546 insertions(+), 423 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/blog/BaseViewModel.java delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/blog/FeedController.java delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/blog/FeedControllerImpl.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/blog/FeedViewModel.java diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index 0729aa0f2..e0d4f31cc 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -30,6 +30,7 @@ import org.briarproject.bramble.util.StringUtils; import org.briarproject.briar.android.account.DozeHelperModule; import org.briarproject.briar.android.account.LockManagerImpl; import org.briarproject.briar.android.account.SetupModule; +import org.briarproject.briar.android.blog.BlogModule; import org.briarproject.briar.android.contact.ContactListModule; import org.briarproject.briar.android.forum.ForumModule; import org.briarproject.briar.android.introduction.IntroductionModule; @@ -85,6 +86,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; ContactListModule.class, IntroductionModule.class, // below need to be within same scope as ViewModelProvider.Factory + BlogModule.BindsModule.class, ForumModule.class, GroupListModule.class, GroupConversationModule.class, diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseController.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseController.java index ad7bbdc23..2e6115ced 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseController.java @@ -8,7 +8,7 @@ import org.briarproject.briar.android.controller.handler.ExceptionHandler; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.api.blog.BlogPostHeader; -import java.util.Collection; +import java.util.List; import javax.annotation.Nullable; @@ -24,7 +24,7 @@ interface BaseController { void onStop(); void loadBlogPosts(GroupId g, - ResultExceptionHandler, DbException> handler); + ResultExceptionHandler, DbException> handler); void loadBlogPost(BlogPostHeader header, ResultExceptionHandler handler); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseControllerImpl.java index 036fe9ad2..13f5e5193 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseControllerImpl.java @@ -23,6 +23,7 @@ import org.briarproject.briar.util.HtmlUtils; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; @@ -80,10 +81,10 @@ abstract class BaseControllerImpl extends DbControllerImpl @Override public void loadBlogPosts(GroupId groupId, - ResultExceptionHandler, DbException> handler) { + ResultExceptionHandler, DbException> handler) { runOnDbThread(() -> { try { - Collection items = loadItems(groupId); + List items = loadItems(groupId); handler.onResult(items); } catch (DbException e) { logException(LOG, WARNING, e); @@ -92,12 +93,12 @@ abstract class BaseControllerImpl extends DbControllerImpl }); } - Collection loadItems(GroupId groupId) throws DbException { + List loadItems(GroupId groupId) throws DbException { long start = now(); Collection headers = blogManager.getPostHeaders(groupId); logDuration(LOG, "Loading headers", start); - Collection items = new ArrayList<>(headers.size()); + List items = new ArrayList<>(headers.size()); start = now(); for (BlogPostHeader h : headers) { headerCache.put(h.getId(), h); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseViewModel.java new file mode 100644 index 000000000..10e105b46 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/BaseViewModel.java @@ -0,0 +1,219 @@ +package org.briarproject.briar.android.blog; + +import android.app.Application; + +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.NoSuchMessageException; +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; +import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.AndroidExecutor; +import org.briarproject.briar.android.viewmodel.DbViewModel; +import org.briarproject.briar.android.viewmodel.LiveResult; +import org.briarproject.briar.api.blog.Blog; +import org.briarproject.briar.api.blog.BlogCommentHeader; +import org.briarproject.briar.api.blog.BlogManager; +import org.briarproject.briar.api.blog.BlogPostHeader; +import org.briarproject.briar.util.HtmlUtils; + +import java.util.ArrayList; +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.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Transformations; + +import static java.util.Objects.requireNonNull; +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logDuration; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.util.LogUtils.now; +import static org.briarproject.briar.util.HtmlUtils.ARTICLE; + +@NotNullByDefault +public class BaseViewModel extends DbViewModel implements EventListener { + + private static Logger LOG = getLogger(BaseViewModel.class.getName()); + + protected final TransactionManager db; + private final EventBus eventBus; + protected final IdentityManager identityManager; + protected final BlogManager blogManager; + + protected final MutableLiveData>> blogPosts = + new MutableLiveData<>(); + + // TODO do we still need those caches? + private final Map textCache = new ConcurrentHashMap<>(); + private final Map headerCache = + new ConcurrentHashMap<>(); + + @Inject + BaseViewModel(Application application, + @DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, + TransactionManager db, + AndroidExecutor androidExecutor, + EventBus eventBus, + IdentityManager identityManager, + BlogManager blogManager) { + super(application, dbExecutor, lifecycleManager, db, androidExecutor); + this.db = db; + this.eventBus = eventBus; + this.identityManager = identityManager; + this.blogManager = blogManager; + + eventBus.addListener(this); + } + + @Override + protected void onCleared() { + super.onCleared(); + eventBus.removeListener(this); + } + + @Override + public void eventOccurred(Event e) { + } + + void loadItems(GroupId groupId) { + loadList(txn -> loadBlogPosts(txn, groupId), blogPosts::setValue); + } + + @DatabaseExecutor + protected List loadBlogPosts(Transaction txn, GroupId groupId) + throws DbException { + long start = now(); + List headers = + blogManager.getPostHeaders(txn, groupId); + logDuration(LOG, "Loading headers", start); + List items = new ArrayList<>(headers.size()); + start = now(); + for (BlogPostHeader h : headers) { + headerCache.put(h.getId(), h); + BlogPostItem item = getItem(txn, h); + items.add(item); + } + logDuration(LOG, "Loading bodies", start); + return items; + } + + @DatabaseExecutor + protected BlogPostItem getItem(Transaction txn, BlogPostHeader h) + throws DbException { + String text; + if (h instanceof BlogCommentHeader) { + BlogCommentHeader c = (BlogCommentHeader) h; + BlogCommentItem item = new BlogCommentItem(c); + text = getPostText(txn, item.getPostHeader().getId()); + item.setText(text); + return item; + } else { + text = getPostText(txn, h.getId()); + return new BlogPostItem(h, text); + } + } + + @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; + } + + LiveData> loadBlogPost(GroupId g, MessageId m) { + MutableLiveData> result = + new MutableLiveData<>(); + runOnDbThread(() -> { + try { + long start = now(); + BlogPostHeader header1 = getPostHeader(g, m); + BlogPostItem item = db.transactionWithResult(true, txn -> + getItem(txn, header1) + ); + logDuration(LOG, "Loading post", start); + result.postValue(new LiveResult<>(item)); + } catch (DbException e) { + logException(LOG, WARNING, e); + result.postValue(new LiveResult<>(e)); + } + }); + 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; + } + + void repeatPost(BlogPostItem item, @Nullable String comment) { + runOnDbThread(() -> { + try { + LocalAuthor a = identityManager.getLocalAuthor(); + Blog b = blogManager.getPersonalBlog(a); + BlogPostHeader h = item.getHeader(); + blogManager.addLocalComment(a, b.getId(), comment, h); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + LiveData>> getAllBlogPosts() { + return blogPosts; + } + + LiveData>> getBlogPosts(GroupId g) { + return Transformations.map(blogPosts, result -> { + List allPosts = result.getResultOrNull(); + if (allPosts == null) return result; + List groupPosts = new ArrayList<>(); + for (BlogPostItem item : allPosts) { + if (item.getGroupId().equals(g)) groupPosts.add(item); + } + return new LiveResult<>(groupPosts); + }); + } + + LiveData> getBlogPost(MessageId m) { + return Transformations.map(blogPosts, result -> { + List allPosts = result.getResultOrNull(); + if (allPosts == null) { + Exception e = requireNonNull(result.getException()); + return new LiveResult<>(e); + } + for (BlogPostItem item : allPosts) { + if (item.getId().equals(m)) return new LiveResult<>(item); + } + return new LiveResult<>(new NoSuchMessageException()); + }); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogController.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogController.java index 50f5b5b3d..7f4dba0b2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogController.java @@ -8,6 +8,7 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import java.util.Collection; +import java.util.List; import androidx.annotation.UiThread; @@ -23,7 +24,7 @@ public interface BlogController extends BaseController { void unsetBlogSharingListener(BlogSharingListener listener); void loadBlogPosts( - ResultExceptionHandler, DbException> handler); + ResultExceptionHandler, DbException> handler); void loadBlogPost(MessageId m, ResultExceptionHandler handler); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogControllerImpl.java index 08f43d8bc..317a75134 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogControllerImpl.java @@ -30,6 +30,7 @@ import org.briarproject.briar.api.sharing.event.ContactLeftShareableEvent; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -140,7 +141,7 @@ class BlogControllerImpl extends BaseControllerImpl @Override public void loadBlogPosts( - ResultExceptionHandler, DbException> handler) { + ResultExceptionHandler, DbException> handler) { if (groupId == null) throw new IllegalStateException(); loadBlogPosts(groupId, handler); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogFragment.java index e138ec501..34c9034a3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogFragment.java @@ -32,6 +32,7 @@ import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.api.blog.BlogPostHeader; import java.util.Collection; +import java.util.List; import javax.inject.Inject; @@ -103,8 +104,7 @@ public class BlogFragment extends BaseFragment View v = inflater.inflate(R.layout.fragment_blog, container, false); - adapter = new BlogPostAdapter(requireActivity(), this, - getFragmentManager()); + adapter = new BlogPostAdapter(this, getParentFragmentManager()); list = v.findViewById(R.id.postList); layoutManager = new LinearLayoutManager(getActivity()); list.setLayoutManager(layoutManager); @@ -219,7 +219,7 @@ public class BlogFragment extends BaseFragment this) { @Override public void onResultUi(BlogPostItem post) { - adapter.add(post); +// adapter.add(post); if (local) { list.scrollToPosition(0); displaySnackbar(R.string.blogs_blog_post_created, @@ -258,14 +258,14 @@ public class BlogFragment extends BaseFragment private void loadBlogPosts(boolean reload) { blogController.loadBlogPosts( - new UiResultExceptionHandler, + new UiResultExceptionHandler, DbException>(this) { @Override - public void onResultUi(Collection posts) { + public void onResultUi(List posts) { if (posts.isEmpty()) { list.showData(); } else { - adapter.addAll(posts); + adapter.submitList(posts); if (reload || layoutManagerState == null) { list.scrollToPosition(0); } else { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogModule.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogModule.java index e2db1a7cf..9ab9ff681 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogModule.java @@ -4,13 +4,25 @@ import org.briarproject.briar.android.activity.ActivityScope; import org.briarproject.briar.android.activity.BaseActivity; import org.briarproject.briar.android.controller.SharingController; import org.briarproject.briar.android.controller.SharingControllerImpl; +import org.briarproject.briar.android.viewmodel.ViewModelKey; +import androidx.lifecycle.ViewModel; +import dagger.Binds; import dagger.Module; import dagger.Provides; +import dagger.multibindings.IntoMap; @Module public class BlogModule { + @Module + public abstract static class BindsModule { + @Binds + @IntoMap + @ViewModelKey(FeedViewModel.class) + abstract ViewModel bindFeedViewModel(FeedViewModel feedViewModel); + } + @ActivityScope @Provides BlogController provideBlogController(BaseActivity activity, @@ -19,12 +31,6 @@ public class BlogModule { return blogController; } - @ActivityScope - @Provides - FeedController provideFeedController(FeedControllerImpl feedController) { - return feedController; - } - @ActivityScope @Provides SharingController provideSharingController( diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostAdapter.java index edddb09c9..53a8b301f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostAdapter.java @@ -1,6 +1,5 @@ package org.briarproject.briar.android.blog; -import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -8,22 +7,33 @@ import android.view.ViewGroup; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; -import org.briarproject.briar.android.util.BriarAdapter; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentManager; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; @MethodsNotNullByDefault @ParametersNotNullByDefault -class BlogPostAdapter extends BriarAdapter { +class BlogPostAdapter extends ListAdapter { private final OnBlogPostClickListener listener; @Nullable private final FragmentManager fragmentManager; - BlogPostAdapter(Context ctx, OnBlogPostClickListener listener, + BlogPostAdapter(OnBlogPostClickListener listener, @Nullable FragmentManager fragmentManager) { - super(ctx, BlogPostItem.class); + super(new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(BlogPostItem a, BlogPostItem b) { + return a.getId().equals(b.getId()); + } + + @Override + public boolean areContentsTheSame(BlogPostItem a, BlogPostItem b) { + return a.isRead() == b.isRead(); + } + }); this.listener = listener; this.fragmentManager = fragmentManager; } @@ -31,29 +41,14 @@ class BlogPostAdapter extends BriarAdapter { @Override public BlogPostViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View v = LayoutInflater.from(ctx).inflate( + View v = LayoutInflater.from(parent.getContext()).inflate( R.layout.list_item_blog_post, parent, false); return new BlogPostViewHolder(v, false, listener, fragmentManager); } @Override public void onBindViewHolder(BlogPostViewHolder ui, int position) { - ui.bindItem(getItemAt(position)); - } - - @Override - public int compare(BlogPostItem a, BlogPostItem b) { - return a.compareTo(b); - } - - @Override - public boolean areContentsTheSame(BlogPostItem a, BlogPostItem b) { - return a.isRead() == b.isRead(); - } - - @Override - public boolean areItemsTheSame(BlogPostItem a, BlogPostItem b) { - return a.getId().equals(b.getId()); + ui.bindItem(getItem(position)); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostItem.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostItem.java index 7afaa77b4..3247a083c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostItem.java @@ -74,9 +74,6 @@ public class BlogPostItem implements Comparable { protected static int compare(BlogPostHeader h1, BlogPostHeader h2) { // The newest post comes first - long aTime = h1.getTimeReceived(), bTime = h2.getTimeReceived(); - if (aTime > bTime) return -1; - if (aTime < bTime) return 1; - return 0; + return Long.compare(h2.getTimeReceived(), h1.getTimeReceived()); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedController.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedController.java deleted file mode 100644 index e50b3ba17..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedController.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.briarproject.briar.android.blog; - -import org.briarproject.bramble.api.db.DbException; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; -import org.briarproject.briar.api.blog.Blog; - -import java.util.Collection; - -import androidx.annotation.UiThread; - -@NotNullByDefault -public interface FeedController extends BaseController { - - void loadBlogPosts( - ResultExceptionHandler, DbException> handler); - - void loadPersonalBlog(ResultExceptionHandler handler); - - @UiThread - void setFeedListener(FeedListener listener); - - @UiThread - void unsetFeedListener(FeedListener listener); - - @NotNullByDefault - interface FeedListener extends BlogListener { - - @UiThread - void onBlogAdded(); - } -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedControllerImpl.java deleted file mode 100644 index 5a228891a..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedControllerImpl.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.briarproject.briar.android.blog; - -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.NoSuchMessageException; -import org.briarproject.bramble.api.event.Event; -import org.briarproject.bramble.api.event.EventBus; -import org.briarproject.bramble.api.identity.Author; -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.bramble.api.sync.event.GroupAddedEvent; -import org.briarproject.bramble.api.sync.event.GroupRemovedEvent; -import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; -import org.briarproject.briar.api.android.AndroidNotificationManager; -import org.briarproject.briar.api.blog.Blog; -import org.briarproject.briar.api.blog.BlogManager; -import org.briarproject.briar.api.blog.event.BlogPostAddedEvent; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.concurrent.Executor; -import java.util.logging.Logger; - -import javax.inject.Inject; - -import androidx.annotation.Nullable; - -import static java.util.logging.Level.WARNING; -import static org.briarproject.bramble.util.LogUtils.logDuration; -import static org.briarproject.bramble.util.LogUtils.logException; -import static org.briarproject.bramble.util.LogUtils.now; -import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID; - -@MethodsNotNullByDefault -@ParametersNotNullByDefault -class FeedControllerImpl extends BaseControllerImpl implements FeedController { - - private static final Logger LOG = - Logger.getLogger(FeedControllerImpl.class.getName()); - - // UI thread - @Nullable - private FeedListener listener; - - @Inject - FeedControllerImpl(@DatabaseExecutor Executor dbExecutor, - LifecycleManager lifecycleManager, EventBus eventBus, - AndroidNotificationManager notificationManager, - IdentityManager identityManager, BlogManager blogManager) { - super(dbExecutor, lifecycleManager, eventBus, notificationManager, - identityManager, blogManager); - } - - @Override - public void onStart() { - super.onStart(); - if (listener == null) throw new IllegalStateException(); - notificationManager.blockAllBlogPostNotifications(); - notificationManager.clearAllBlogPostNotifications(); - } - - @Override - public void onStop() { - super.onStop(); - notificationManager.unblockAllBlogPostNotifications(); - } - - @Override - public void setFeedListener(FeedListener listener) { - this.listener = listener; - } - - @Override - public void unsetFeedListener(FeedListener listener) { - if (this.listener == listener) this.listener = null; - } - - @Override - public void eventOccurred(Event e) { - if (listener == null) throw new IllegalStateException(); - if (e instanceof BlogPostAddedEvent) { - BlogPostAddedEvent b = (BlogPostAddedEvent) e; - LOG.info("Blog post added"); - listener.onBlogPostAdded(b.getHeader(), b.isLocal()); - } else if (e instanceof GroupAddedEvent) { - GroupAddedEvent g = (GroupAddedEvent) e; - if (g.getGroup().getClientId().equals(CLIENT_ID)) { - LOG.info("Blog added"); - listener.onBlogAdded(); - } - } else if (e instanceof GroupRemovedEvent) { - GroupRemovedEvent g = (GroupRemovedEvent) e; - if (g.getGroup().getClientId().equals(CLIENT_ID)) { - LOG.info("Blog removed"); - listener.onBlogRemoved(); - } - } - } - - @Override - public void loadBlogPosts( - ResultExceptionHandler, DbException> handler) { - runOnDbThread(() -> { - try { - long start = now(); - Collection posts = new ArrayList<>(); - for (Blog b : blogManager.getBlogs()) { - try { - posts.addAll(loadItems(b.getId())); - } catch (NoSuchGroupException | NoSuchMessageException e) { - logException(LOG, WARNING, e); - } - } - logDuration(LOG, "Loading all posts", start); - handler.onResult(posts); - } catch (DbException e) { - logException(LOG, WARNING, e); - handler.onException(e); - } - }); - } - - @Override - public void loadPersonalBlog( - ResultExceptionHandler handler) { - runOnDbThread(() -> { - try { - long start = now(); - Author a = identityManager.getLocalAuthor(); - Blog b = blogManager.getPersonalBlog(a); - logDuration(LOG, "Loading personal blog", start); - handler.onResult(b); - } catch (DbException e) { - logException(LOG, WARNING, e); - handler.onException(e); - } - }); - } - -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedFragment.java index 75026a034..97cca5ca6 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedFragment.java @@ -10,52 +10,44 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; -import org.briarproject.briar.android.blog.FeedController.FeedListener; -import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler; import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.util.BriarSnackbarBuilder; import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.api.blog.Blog; -import org.briarproject.briar.api.blog.BlogPostHeader; -import java.util.Collection; -import java.util.logging.Logger; +import java.util.List; import javax.inject.Inject; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; -import static android.app.Activity.RESULT_OK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG; import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID; -import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST; @UiThread @MethodsNotNullByDefault @ParametersNotNullByDefault -public class FeedFragment extends BaseFragment implements - OnBlogPostClickListener, FeedListener { +public class FeedFragment extends BaseFragment + implements OnBlogPostClickListener { public final static String TAG = FeedFragment.class.getName(); - private static final Logger LOG = Logger.getLogger(TAG); @Inject - FeedController feedController; + ViewModelProvider.Factory viewModelFactory; + private FeedViewModel viewModel; private BlogPostAdapter adapter; private LinearLayoutManager layoutManager; private BriarRecyclerView list; @Nullable - private Blog personalBlog; - @Nullable private Parcelable layoutManagerState; public static FeedFragment newInstance() { @@ -70,7 +62,12 @@ public class FeedFragment extends BaseFragment implements @Override public void injectFragment(ActivityComponent component) { component.inject(this); - feedController.setFeedListener(this); + // TODO don't use NavDrawerActivity scope here + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(FeedViewModel.class); + // TODO ideally we only do this once when the ViewModel gets created + viewModel.loadPersonalBlog(); + viewModel.loadAllBlogPosts(); } @Nullable @@ -82,8 +79,7 @@ public class FeedFragment extends BaseFragment implements View v = inflater.inflate(R.layout.fragment_blog, container, false); - adapter = - new BlogPostAdapter(getActivity(), this, getFragmentManager()); + adapter = new BlogPostAdapter(this, getParentFragmentManager()); layoutManager = new LinearLayoutManager(getActivity()); list = v.findViewById(R.id.postList); @@ -93,6 +89,12 @@ public class FeedFragment extends BaseFragment implements list.setEmptyText(R.string.blogs_feed_empty_state); list.setEmptyAction(R.string.blogs_feed_empty_state_action); + viewModel.getAllBlogPosts().observe(getViewLifecycleOwner(), result -> + result + .onError(this::handleException) + .onSuccess(this::onBlogPostsLoaded) + ); + if (savedInstanceState != null) { layoutManagerState = savedInstanceState.getParcelable("layoutManager"); @@ -101,40 +103,19 @@ public class FeedFragment extends BaseFragment implements return v; } - @Override - public void onActivityResult(int requestCode, int resultCode, - @Nullable Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - // The BlogPostAddedEvent arrives when the controller is not listening - if (requestCode == REQUEST_WRITE_BLOG_POST && resultCode == RESULT_OK) { - showSnackBar(R.string.blogs_blog_post_created); - } - } - @Override public void onStart() { super.onStart(); - feedController.onStart(); + viewModel.blockAllBlogPostNotifications(); + viewModel.clearAllBlogPostNotifications(); list.startPeriodicUpdate(); - loadPersonalBlog(); - loadBlogPosts(false); } @Override public void onStop() { super.onStop(); - feedController.onStop(); - adapter.clear(); - list.showProgressBar(); + viewModel.unblockAllBlogPostNotifications(); list.stopPeriodicUpdate(); - // TODO save list position in database/preferences? - } - - @Override - public void onDestroy() { - super.onDestroy(); - feedController.unsetFeedListener(this); } @Override @@ -146,50 +127,16 @@ public class FeedFragment extends BaseFragment implements } } - private void loadPersonalBlog() { - feedController.loadPersonalBlog( - new UiResultExceptionHandler(this) { - @Override - public void onResultUi(Blog b) { - personalBlog = b; - } - - @Override - public void onExceptionUi(DbException exception) { - handleException(exception); - } - }); - } - - private void loadBlogPosts(boolean clear) { - int revision = adapter.getRevision(); - feedController.loadBlogPosts( - new UiResultExceptionHandler, DbException>( - this) { - @Override - public void onResultUi(Collection posts) { - if (revision == adapter.getRevision()) { - adapter.incrementRevision(); - if (clear) adapter.setItems(posts); - else adapter.addAll(posts); - if (posts.isEmpty()) list.showData(); - if (layoutManagerState == null) { - list.scrollToPosition(0); // Scroll to the top - } else { - layoutManager.onRestoreInstanceState( - layoutManagerState); - } - } else { - LOG.info("Concurrent update, reloading"); - loadBlogPosts(clear); - } - } - - @Override - public void onExceptionUi(DbException exception) { - handleException(exception); - } - }); + private void onBlogPostsLoaded(List items) { + if (items.isEmpty()) list.showData(); + else adapter.submitList(items, () -> { + Boolean wasLocal = viewModel.getPostAddedWasLocalAndReset(); + if (wasLocal != null && wasLocal) { + showSnackBar(R.string.blogs_blog_post_created); + } else if (wasLocal != null) { + showSnackBar(R.string.blogs_blog_post_received); + } + }); } @Override @@ -200,52 +147,27 @@ public class FeedFragment extends BaseFragment implements @Override public boolean onOptionsItemSelected(MenuItem item) { - if (personalBlog == null) return false; - switch (item.getItemId()) { - case R.id.action_write_blog_post: - Intent i1 = - new Intent(getActivity(), WriteBlogPostActivity.class); - i1.putExtra(GROUP_ID, personalBlog.getId().getBytes()); - startActivityForResult(i1, REQUEST_WRITE_BLOG_POST); - return true; - case R.id.action_rss_feeds_import: - Intent i2 = - new Intent(getActivity(), RssFeedImportActivity.class); - startActivity(i2); - return true; - case R.id.action_rss_feeds_manage: - Intent i3 = - new Intent(getActivity(), RssFeedManageActivity.class); - i3.putExtra(GROUP_ID, personalBlog.getId().getBytes()); - startActivity(i3); - return true; - default: - return super.onOptionsItemSelected(item); + int itemId = item.getItemId(); + if (itemId == R.id.action_write_blog_post) { + Blog personalBlog = viewModel.getPersonalBlog().getValue(); + if (personalBlog == null) return false; + Intent i = new Intent(getActivity(), WriteBlogPostActivity.class); + i.putExtra(GROUP_ID, personalBlog.getId().getBytes()); + startActivity(i); + return true; + } else if (itemId == R.id.action_rss_feeds_import) { + Intent i = new Intent(getActivity(), RssFeedImportActivity.class); + startActivity(i); + return true; + } else if (itemId == R.id.action_rss_feeds_manage) { + Blog personalBlog = viewModel.getPersonalBlog().getValue(); + if (personalBlog == null) return false; + Intent i = new Intent(getActivity(), RssFeedManageActivity.class); + i.putExtra(GROUP_ID, personalBlog.getId().getBytes()); + startActivity(i); + return true; } - } - - @Override - public void onBlogPostAdded(BlogPostHeader header, boolean local) { - feedController.loadBlogPost(header, - new UiResultExceptionHandler( - this) { - @Override - public void onResultUi(BlogPostItem post) { - adapter.incrementRevision(); - adapter.add(post); - if (local) { - showSnackBar(R.string.blogs_blog_post_created); - } else { - showSnackBar(R.string.blogs_blog_post_received); - } - } - - @Override - public void onExceptionUi(DbException exception) { - handleException(exception); - } - } - ); + return super.onOptionsItemSelected(item); } @Override @@ -257,10 +179,10 @@ public class FeedFragment extends BaseFragment implements @Override public void onAuthorClick(BlogPostItem post) { - Intent i = new Intent(getContext(), BlogActivity.class); + Intent i = new Intent(requireContext(), BlogActivity.class); i.putExtra(GROUP_ID, post.getGroupId().getBytes()); i.setFlags(FLAG_ACTIVITY_CLEAR_TOP); - getContext().startActivity(i); + requireContext().startActivity(i); } @Override @@ -283,14 +205,4 @@ public class FeedFragment extends BaseFragment implements sb.make(list, stringRes, LENGTH_LONG).show(); } - @Override - public void onBlogAdded() { - loadBlogPosts(false); - } - - @Override - public void onBlogRemoved() { - loadBlogPosts(true); - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedPostFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedPostFragment.java index b9750a5a1..6e90bb6d7 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedPostFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedPostFragment.java @@ -5,19 +5,19 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import org.briarproject.bramble.api.db.DbException; 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 org.briarproject.briar.android.controller.handler.UiResultExceptionHandler; 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 @@ -27,18 +27,16 @@ public class FeedPostFragment extends BasePostFragment { private static final String TAG = FeedPostFragment.class.getName(); - private GroupId blogId; - @Inject - FeedController feedController; + 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; } @@ -46,6 +44,8 @@ public class FeedPostFragment extends BasePostFragment { @Override public void injectFragment(ActivityComponent component) { component.inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(FeedViewModel.class); } @Nullable @@ -54,10 +54,14 @@ public class FeedPostFragment extends BasePostFragment { @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Bundle args = requireArguments(); - byte[] b = args.getByteArray(GROUP_ID); - if (b == null) throw new IllegalStateException("No group ID in args"); - blogId = new GroupId(b); - + 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); } @@ -66,22 +70,4 @@ public class FeedPostFragment extends BasePostFragment { return TAG; } - @Override - public void onStart() { - super.onStart(); - feedController.loadBlogPost(blogId, postId, - new UiResultExceptionHandler( - this) { - @Override - public void onResultUi(BlogPostItem post) { - onBlogPostLoaded(post); - } - - @Override - public void onExceptionUi(DbException exception) { - handleException(exception); - } - }); - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedViewModel.java new file mode 100644 index 000000000..6f5f44f44 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/FeedViewModel.java @@ -0,0 +1,174 @@ +package org.briarproject.briar.android.blog; + +import android.app.Application; + +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.NoSuchMessageException; +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.identity.Author; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +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; +import org.briarproject.briar.api.blog.BlogPostHeader; +import org.briarproject.briar.api.blog.event.BlogPostAddedEvent; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +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; +import androidx.lifecycle.MutableLiveData; + +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logDuration; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.util.LogUtils.now; +import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID; + +@NotNullByDefault +class FeedViewModel extends BaseViewModel { + + private static final Logger LOG = getLogger(FeedViewModel.class.getName()); + + protected final AndroidNotificationManager notificationManager; + + private final MutableLiveData personalBlog = new MutableLiveData<>(); + @Nullable + private Boolean postAddedWasLocal = null; + + @Inject + FeedViewModel(Application application, + @DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, + TransactionManager db, + AndroidExecutor androidExecutor, + EventBus eventBus, + IdentityManager identityManager, + BlogManager blogManager, + AndroidNotificationManager notificationManager) { + super(application, dbExecutor, lifecycleManager, db, androidExecutor, + eventBus, identityManager, blogManager); + this.notificationManager = notificationManager; + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof BlogPostAddedEvent) { + BlogPostAddedEvent b = (BlogPostAddedEvent) e; + LOG.info("Blog post added"); + onBlogPostAdded(b.getHeader(), b.isLocal()); + } else if (e instanceof GroupAddedEvent) { + GroupAddedEvent g = (GroupAddedEvent) e; + if (g.getGroup().getClientId().equals(CLIENT_ID)) { + LOG.info("Blog added"); + loadAllBlogPosts(); + } + } else if (e instanceof GroupRemovedEvent) { + GroupRemovedEvent g = (GroupRemovedEvent) e; + if (g.getGroup().getClientId().equals(CLIENT_ID)) { + LOG.info("Blog removed"); + loadAllBlogPosts(); + } + } + } + + void blockAllBlogPostNotifications() { + notificationManager.blockAllBlogPostNotifications(); + } + + void clearAllBlogPostNotifications() { + notificationManager.clearAllBlogPostNotifications(); + } + + void unblockAllBlogPostNotifications() { + notificationManager.unblockAllBlogPostNotifications(); + } + + void loadPersonalBlog() { + runOnDbThread(() -> { + try { + long start = now(); + Author a = identityManager.getLocalAuthor(); + Blog b = blogManager.getPersonalBlog(a); + logDuration(LOG, "Loading personal blog", start); + personalBlog.postValue(b); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + LiveData getPersonalBlog() { + return personalBlog; + } + + void loadAllBlogPosts() { + loadList(this::loadAllBlogPosts, blogPosts::setValue); + } + + @DatabaseExecutor + private List loadAllBlogPosts(Transaction txn) + throws DbException { + long start = now(); + List posts = new ArrayList<>(); + for (GroupId g : blogManager.getBlogIds(txn)) { + try { + posts.addAll(loadBlogPosts(txn, g)); + } catch (NoSuchMessageException e) { + logException(LOG, WARNING, e); + } + } + Collections.sort(posts); + logDuration(LOG, "Loading all posts", start); + return posts; + } + + private void onBlogPostAdded(BlogPostHeader header, boolean local) { + postAddedWasLocal = local; + runOnDbThread(() -> { + try { + db.transaction(true, txn -> { + BlogPostItem item = getItem(txn, header); + txn.attach(() -> { + List items = addListItem(blogPosts, item); + if (items != null) { + Collections.sort(items); + blogPosts.setValue(new LiveResult<>(items)); + } + }); + }); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + @UiThread + @Nullable + Boolean getPostAddedWasLocalAndReset() { + if (postAddedWasLocal == null) return null; + boolean wasLocal = postAddedWasLocal; + postAddedWasLocal = null; + return wasLocal; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/ReblogFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/ReblogFragment.java index 318bdecab..a4aecd098 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/blog/ReblogFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/ReblogFragment.java @@ -7,15 +7,12 @@ import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.ScrollView; -import org.briarproject.bramble.api.db.DbException; 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.controller.handler.UiExceptionHandler; -import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler; import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.view.TextInputView; import org.briarproject.briar.android.view.TextSendController; @@ -27,6 +24,8 @@ import java.util.List; import javax.annotation.Nullable; import javax.inject.Inject; +import androidx.lifecycle.ViewModelProvider; + import static android.view.View.FOCUS_DOWN; import static android.view.View.GONE; import static android.view.View.INVISIBLE; @@ -42,12 +41,13 @@ public class ReblogFragment extends BaseFragment implements SendListener { public static final String TAG = ReblogFragment.class.getName(); + @Inject + ViewModelProvider.Factory viewModelFactory; + + private FeedViewModel viewModel; private ViewHolder ui; private BlogPostItem item; - @Inject - FeedController feedController; - static ReblogFragment newInstance(GroupId groupId, MessageId messageId) { ReblogFragment f = new ReblogFragment(); @@ -67,6 +67,8 @@ public class ReblogFragment extends BaseFragment implements SendListener { @Override public void injectFragment(ActivityComponent component) { component.inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(FeedViewModel.class); } @Override @@ -90,30 +92,20 @@ public class ReblogFragment extends BaseFragment implements SendListener { ui.input.setMaxTextLength(MAX_BLOG_POST_TEXT_LENGTH); showProgressBar(); - feedController.loadBlogPost(blogId, postId, - new UiResultExceptionHandler( - this) { - @Override - public void onResultUi(BlogPostItem result) { - item = result; - bindViewHolder(); - } - - @Override - public void onExceptionUi(DbException exception) { - handleException(exception); - } - }); + viewModel.loadBlogPost(blogId, postId).observe(getViewLifecycleOwner(), + result -> result.onError(this::handleException) + .onSuccess(this::bindViewHolder) + ); return v; } - private void bindViewHolder() { - if (item == null) return; + private void bindViewHolder(BlogPostItem item) { + this.item = item; hideProgressBar(); - ui.post.bindItem(item); + ui.post.bindItem(this.item); ui.post.hideReblogButton(); ui.input.setReady(true); @@ -124,13 +116,7 @@ public class ReblogFragment extends BaseFragment implements SendListener { public void onSendClick(@Nullable String text, List headers) { ui.input.hideSoftKeyboard(); - feedController.repeatPost(item, text, - new UiExceptionHandler(this) { - @Override - public void onExceptionUi(DbException exception) { - handleException(exception); - } - }); + viewModel.repeatPost(item, text); finish(); } @@ -165,7 +151,7 @@ public class ReblogFragment extends BaseFragment implements SendListener { public void onAuthorClick(BlogPostItem post) { // probably don't want to allow author clicks here } - }, getFragmentManager()); + }, getParentFragmentManager()); input = v.findViewById(R.id.inputText); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java index 7d3875723..a0aa00c8d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java @@ -142,6 +142,24 @@ public abstract class DbViewModel extends AndroidViewModel { void accept(T t); } + /** + * Creates a copy of the list available in the given LiveData + * and adds the given item to the copy. + * + * @return a copy of the list in the LiveData with item added or null when + *
    + *
  • LiveData does not have a value + *
  • LiveResult in the LiveData has an error + *
+ */ + @Nullable + protected List addListItem(LiveData>> liveData, T item) { + List 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 replaces items where the given test function returns true.