diff --git a/briar-android/res/drawable/ic_chat.xml b/briar-android/res/drawable/ic_chat.xml deleted file mode 100644 index 04f9fdcdf..000000000 --- a/briar-android/res/drawable/ic_chat.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/briar-android/res/drawable/ic_expand_more_black_24dp.xml b/briar-android/res/drawable/ic_expand_more_black_24dp.xml deleted file mode 100644 index 8d57dbc10..000000000 --- a/briar-android/res/drawable/ic_expand_more_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/briar-android/res/layout/activity_fragment_container.xml b/briar-android/res/layout/activity_fragment_container.xml index e6c20760f..8bf59ee5a 100644 --- a/briar-android/res/layout/activity_fragment_container.xml +++ b/briar-android/res/layout/activity_fragment_container.xml @@ -3,4 +3,14 @@ android:id="@+id/fragmentContainer" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent"/> \ No newline at end of file + android:layout_height="match_parent"> + + + + \ No newline at end of file diff --git a/briar-android/res/layout/dropdown_author.xml b/briar-android/res/layout/dropdown_author.xml deleted file mode 100644 index 1af9c5965..000000000 --- a/briar-android/res/layout/dropdown_author.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/briar-android/res/layout/fragment_blog_post.xml b/briar-android/res/layout/fragment_blog_post.xml index 7a0df0d0b..18ec1f417 100644 --- a/briar-android/res/layout/fragment_blog_post.xml +++ b/briar-android/res/layout/fragment_blog_post.xml @@ -6,7 +6,12 @@ + android:layout_height="wrap_content" + android:descendantFocusability="beforeDescendants" + android:focusable="true" + android:focusableInTouchMode="true"> + + + diff --git a/briar-android/res/layout/activity_blog.xml b/briar-android/res/layout/fragment_blog_post_pager.xml similarity index 79% rename from briar-android/res/layout/activity_blog.xml rename to briar-android/res/layout/fragment_blog_post_pager.xml index 3ff8d409e..626dcaf26 100644 --- a/briar-android/res/layout/activity_blog.xml +++ b/briar-android/res/layout/fragment_blog_post_pager.xml @@ -1,15 +1,14 @@ + android:layout_height="match_parent"/> - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/briar-android/res/layout/fragment_blogs_my.xml b/briar-android/res/layout/fragment_blogs_my.xml deleted file mode 100644 index 288adfaa3..000000000 --- a/briar-android/res/layout/fragment_blogs_my.xml +++ /dev/null @@ -1,7 +0,0 @@ - - diff --git a/briar-android/res/layout/list_item_blog_comment.xml b/briar-android/res/layout/list_item_blog_comment.xml index bdbe42842..1be478ab2 100644 --- a/briar-android/res/layout/list_item_blog_comment.xml +++ b/briar-android/res/layout/list_item_blog_comment.xml @@ -29,7 +29,6 @@ android:paddingLeft="@dimen/listitem_vertical_margin" android:paddingRight="@dimen/listitem_vertical_margin" android:textColor="@color/briar_text_secondary" - android:textIsSelectable="true" android:textSize="@dimen/text_size_small" tools:text="This is a comment that appears below a blog post. Usually, it is expected to be rather short. Not much longer than this one."/> diff --git a/briar-android/res/layout/list_item_blog_post.xml b/briar-android/res/layout/list_item_blog_post.xml index 8b136f2b7..d3cd14d48 100644 --- a/briar-android/res/layout/list_item_blog_post.xml +++ b/briar-android/res/layout/list_item_blog_post.xml @@ -6,7 +6,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:foreground="?android:attr/selectableItemBackground"> diff --git a/briar-android/res/menu/blogs_my_actions.xml b/briar-android/res/menu/blogs_my_actions.xml deleted file mode 100644 index 9a3ce5b49..000000000 --- a/briar-android/res/menu/blogs_my_actions.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/briar-android/res/values/color.xml b/briar-android/res/values/color.xml index e7cb49b15..0ac3140ee 100644 --- a/briar-android/res/values/color.xml +++ b/briar-android/res/values/color.xml @@ -35,7 +35,6 @@ #FFFFFF #61000000 - @color/briar_blue_dark #cfd2d4 #ffffff \ No newline at end of file diff --git a/briar-android/res/values/dimens.xml b/briar-android/res/values/dimens.xml index 9f62ff094..c8e7eca7d 100644 --- a/briar-android/res/values/dimens.xml +++ b/briar-android/res/values/dimens.xml @@ -27,7 +27,6 @@ 53dp 2dp 40dp - 32dp 48dp 2dp 30sp diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index ba764c356..e86654509 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -73,6 +73,7 @@ Offline Send No data + It seems that you are new here and have no contacts yet.\n\nTap the + icon at the top and follow the instructions to add some friends to your list.\n\nPlease remember: You can only add new contacts face-to-face to prevent anyone from impersonating you or reading your messages in the future. @@ -199,18 +200,16 @@ Nobody - Feed - My Blogs Create Blog Add new Blog Blog title (cannot be changed later) A short description of your new blog Potential readers may or may not subscribe to your blog based on the content of the description. - You don\'t have any blogs.\n\nWhy don\'t you create one now by clicking the plus in the top right screen corner? Blog created This blog is empty This blog is currently empty.\n\nEither the author hasn\'t written anything yet, or the person who shared this blog with you needs to come online, so posts can be synchronized. NEW + read more Write Blog Post Add a title (optional) Type your blog post here @@ -231,7 +230,6 @@ Blog List Available Blogs - Drafts Share Blog @@ -312,7 +310,6 @@ Anonymous - New identity\u2026 New Identity Create Identity Identity created diff --git a/briar-android/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java index 2a0d82c4b..baee29d41 100644 --- a/briar-android/src/org/briarproject/android/ActivityComponent.java +++ b/briar-android/src/org/briarproject/android/ActivityComponent.java @@ -6,8 +6,11 @@ import org.briarproject.android.blogs.BlogActivity; import org.briarproject.android.blogs.BlogFragment; import org.briarproject.android.blogs.BlogListFragment; import org.briarproject.android.blogs.BlogPostFragment; +import org.briarproject.android.blogs.BlogPostPagerFragment; import org.briarproject.android.blogs.CreateBlogActivity; +import org.briarproject.android.blogs.FeedPostFragment; import org.briarproject.android.blogs.FeedFragment; +import org.briarproject.android.blogs.FeedPostPagerFragment; import org.briarproject.android.blogs.ReblogActivity; import org.briarproject.android.blogs.ReblogFragment; import org.briarproject.android.blogs.RssFeedImportActivity; @@ -92,6 +95,10 @@ public interface ActivityComponent { void inject(BlogFragment fragment); void inject(BlogPostFragment fragment); + void inject(FeedPostFragment fragment); + + void inject(BlogPostPagerFragment fragment); + void inject(FeedPostPagerFragment fragment); void inject(ReblogFragment fragment); diff --git a/briar-android/src/org/briarproject/android/Destroyable.java b/briar-android/src/org/briarproject/android/Destroyable.java index a9a9ef8ce..17c7dc850 100644 --- a/briar-android/src/org/briarproject/android/Destroyable.java +++ b/briar-android/src/org/briarproject/android/Destroyable.java @@ -2,7 +2,7 @@ package org.briarproject.android; import android.support.annotation.UiThread; -interface Destroyable { +public interface Destroyable { @UiThread boolean hasBeenDestroyed(); diff --git a/briar-android/src/org/briarproject/android/NavDrawerActivity.java b/briar-android/src/org/briarproject/android/NavDrawerActivity.java index 6381bc9cd..d4bacf55a 100644 --- a/briar-android/src/org/briarproject/android/NavDrawerActivity.java +++ b/briar-android/src/org/briarproject/android/NavDrawerActivity.java @@ -74,7 +74,9 @@ public class NavDrawerActivity extends BriarFragmentActivity implements super.onNewIntent(intent); exitIfStartupFailed(intent); checkAuthorHandle(intent); - clearBackStack(); + // FIXME why was the stack cleared here? + // This prevents state from being restored properly +// clearBackStack(); if (intent.getBooleanExtra(INTENT_FORUMS, false)) { startFragment(ForumListFragment.newInstance()); } @@ -248,7 +250,6 @@ public class NavDrawerActivity extends BriarFragmentActivity implements @Override public void hideLoadingScreen() { drawerLayout.setDrawerLockMode(LOCK_MODE_UNLOCKED); - CustomAnimations.animateHeight(toolbar, true, 250); progressViewGroup.setVisibility(INVISIBLE); } diff --git a/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java index ab513217a..3c4f1bc21 100644 --- a/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java +++ b/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java @@ -59,6 +59,9 @@ abstract class BaseControllerImpl extends DbControllerImpl @Override @CallSuper public void onStart() { + if (listener == null) + throw new IllegalStateException( + "OnBlogPostAddedListener needs to be attached"); eventBus.addListener(this); } diff --git a/briar-android/src/org/briarproject/android/blogs/BasePostFragment.java b/briar-android/src/org/briarproject/android/blogs/BasePostFragment.java new file mode 100644 index 000000000..06153e422 --- /dev/null +++ b/briar-android/src/org/briarproject/android/blogs/BasePostFragment.java @@ -0,0 +1,109 @@ +package org.briarproject.android.blogs; + +import android.os.Bundle; +import android.support.annotation.CallSuper; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; + +import org.briarproject.R; +import org.briarproject.android.fragment.BaseFragment; +import org.briarproject.api.db.DbException; + +import java.util.logging.Logger; + +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static org.briarproject.android.util.AndroidUtils.MIN_RESOLUTION; + +public abstract class BasePostFragment extends BaseFragment { + + private final Logger LOG = + Logger.getLogger(BasePostFragment.class.getName()); + + private View view; + private ProgressBar progressBar; + private BlogPostViewHolder ui; + private BlogPostItem post; + private Runnable refresher; + + @CallSuper + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + setHasOptionsMenu(true); + + view = inflater.inflate(R.layout.fragment_blog_post, container, + false); + progressBar = (ProgressBar) view.findViewById(R.id.progressBar); + progressBar.setVisibility(VISIBLE); + ui = new BlogPostViewHolder(view); + return view; + } + + @CallSuper + @Override + public void onStart() { + super.onStart(); + startPeriodicUpdate(); + } + + @CallSuper + @Override + public void onStop() { + super.onStop(); + stopPeriodicUpdate(); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + getActivity().onBackPressed(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @UiThread + protected void onBlogPostLoaded(BlogPostItem post) { + progressBar.setVisibility(INVISIBLE); + this.post = post; + ui.bindItem(post); + } + + @UiThread + protected void onBlogPostLoadException(DbException exception) { + // TODO: Decide how to handle errors in the UI + finish(); + } + + private void startPeriodicUpdate() { + refresher = new Runnable() { + @Override + public void run() { + if (ui == null) return; + LOG.info("Updating Content..."); + + ui.updateDate(post.getTimestamp()); + view.postDelayed(refresher, MIN_RESOLUTION); + } + }; + LOG.info("Adding Handler Callback"); + view.postDelayed(refresher, MIN_RESOLUTION); + } + + private void stopPeriodicUpdate() { + if (refresher != null && ui != null) { + LOG.info("Removing Handler Callback"); + view.removeCallbacks(refresher); + } + } + +} diff --git a/briar-android/src/org/briarproject/android/blogs/BasePostPagerFragment.java b/briar-android/src/org/briarproject/android/blogs/BasePostPagerFragment.java new file mode 100644 index 000000000..e27c1470f --- /dev/null +++ b/briar-android/src/org/briarproject/android/blogs/BasePostPagerFragment.java @@ -0,0 +1,177 @@ +package org.briarproject.android.blogs; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.view.ViewPager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; + +import org.briarproject.R; +import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener; +import org.briarproject.android.fragment.BaseFragment; +import org.briarproject.api.blogs.BlogPostHeader; +import org.briarproject.api.db.DbException; +import org.briarproject.api.sync.MessageId; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static org.briarproject.android.blogs.BasePostPagerFragment.BlogPostPagerAdapter.INVALID_POSITION; + +abstract class BasePostPagerFragment extends BaseFragment + implements OnBlogPostAddedListener { + + static final String POST_ID = "briar.POST_ID"; + + private ViewPager pager; + private ProgressBar progressBar; + private BlogPostPagerAdapter postPagerAdapter; + private MessageId postId; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle state) { + + Bundle args; + if (state == null) args = getArguments(); + else args = state; + byte[] p = args.getByteArray(POST_ID); + if (p == null) + throw new IllegalStateException("No post ID in args"); + postId = new MessageId(p); + + View v = inflater.inflate(R.layout.fragment_blog_post_pager, container, + false); + progressBar = (ProgressBar) v.findViewById(R.id.progressBar); + progressBar.setVisibility(VISIBLE); + + pager = (ViewPager) v.findViewById(R.id.pager); + postPagerAdapter = new BlogPostPagerAdapter(getChildFragmentManager()); + + return v; + } + + @Override + public void onStart() { + super.onStart(); + if (postId == null) { + MessageId selected = getSelectedPost(); + if (selected != null) loadBlogPosts(selected); + } else { + loadBlogPosts(postId); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + MessageId selected = getSelectedPost(); + if (selected != null) + outState.putByteArray(POST_ID, selected.getBytes()); + } + + @Override + public void onBlogPostAdded(BlogPostHeader header, boolean local) { + loadBlogPost(header); + } + + abstract void loadBlogPosts(final MessageId select); + + abstract void loadBlogPost(BlogPostHeader header); + + protected void onBlogPostsLoaded(MessageId select, + Collection posts) { + + postId = null; + postPagerAdapter.setPosts(posts); + selectPost(select); + } + + protected void onBlogPostsLoadedException(DbException exception) { + // TODO: Decide how to handle errors in the UI + finish(); + } + + @Nullable + private MessageId getSelectedPost() { + if (postPagerAdapter.getCount() == 0) return null; + int position = pager.getCurrentItem(); + return postPagerAdapter.getPost(position).getId(); + } + + private void selectPost(MessageId m) { + int pos = postPagerAdapter.getPostPosition(m); + if (pos != INVALID_POSITION) { + progressBar.setVisibility(INVISIBLE); + pager.setAdapter(postPagerAdapter); + pager.setCurrentItem(pos); + } + } + + protected void addPost(BlogPostItem post) { + MessageId selected = getSelectedPost(); + postPagerAdapter.addPost(post); + if (selected != null) selectPost(selected); + } + + @UiThread + static class BlogPostPagerAdapter extends FragmentStatePagerAdapter { + + static final int INVALID_POSITION = -1; + private final List posts = new ArrayList<>(); + + private BlogPostPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public int getCount() { + return posts.size(); + } + + @Override + public Fragment getItem(int position) { + BlogPostItem post = posts.get(position); + return FeedPostFragment.newInstance(post.getGroupId(), post.getId()); + } + + private BlogPostItem getPost(int position) { + return posts.get(position); + } + + private void setPosts(Collection posts) { + this.posts.clear(); + this.posts.addAll(posts); + Collections.sort(this.posts); + notifyDataSetChanged(); + } + + private void addPost(BlogPostItem post) { + posts.add(post); + Collections.sort(posts); + notifyDataSetChanged(); + } + + private int getPostPosition(MessageId m) { + int count = getCount(); + for (int i = 0; i < count; i++) { + if (getPost(i).getId().equals(m)) { + return i; + } + } + return INVALID_POSITION; + } + } + +} diff --git a/briar-android/src/org/briarproject/android/blogs/BlogActivity.java b/briar-android/src/org/briarproject/android/blogs/BlogActivity.java index 249d0ff7c..bb7a5cc8d 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogActivity.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogActivity.java @@ -2,56 +2,29 @@ package org.briarproject.android.blogs; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.annotation.UiThread; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentStatePagerAdapter; -import android.support.v4.view.ViewPager; -import android.view.ViewGroup; import android.widget.ProgressBar; import org.briarproject.R; import org.briarproject.android.ActivityComponent; import org.briarproject.android.BriarActivity; -import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener; import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener; -import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener; -import org.briarproject.api.blogs.BlogPostHeader; -import org.briarproject.api.db.DbException; import org.briarproject.api.sync.GroupId; -import org.briarproject.api.sync.MessageId; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; import javax.inject.Inject; -import static android.view.View.GONE; +import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; public class BlogActivity extends BriarActivity implements - OnBlogPostAddedListener, OnBlogPostClickListener, BaseFragmentListener { static final int REQUEST_WRITE_POST = 1; static final int REQUEST_SHARE = 2; - public static final String BLOG_NAME = "briar.BLOG_NAME"; + static final String BLOG_NAME = "briar.BLOG_NAME"; static final String IS_NEW_BLOG = "briar.IS_NEW_BLOG"; - public static final String POST_ID = "briar.POST_ID"; - - private GroupId groupId; private ProgressBar progressBar; - private ViewPager pager; - private BlogPagerAdapter blogPagerAdapter; - private BlogPostPagerAdapter postPagerAdapter; - private String blogName; - private boolean isNew; - private MessageId savedPostId; @Inject BlogController blogController; @@ -64,62 +37,24 @@ public class BlogActivity extends BriarActivity implements Intent i = getIntent(); byte[] b = i.getByteArrayExtra(GROUP_ID); if (b == null) throw new IllegalStateException("No group ID in intent"); - groupId = new GroupId(b); + GroupId groupId = new GroupId(b); blogController.setGroupId(groupId); // Name of the blog - blogName = i.getStringExtra(BLOG_NAME); + String blogName = i.getStringExtra(BLOG_NAME); if (blogName != null) setTitle(blogName); // Was this blog just created? - isNew = i.getBooleanExtra(IS_NEW_BLOG, false); + boolean isNew = i.getBooleanExtra(IS_NEW_BLOG, false); - setContentView(R.layout.activity_blog); - - pager = (ViewPager) findViewById(R.id.pager); + setContentView(R.layout.activity_fragment_container); progressBar = (ProgressBar) findViewById(R.id.progressBar); - blogPagerAdapter = new BlogPagerAdapter(getSupportFragmentManager()); - postPagerAdapter = new BlogPostPagerAdapter( - getSupportFragmentManager()); - - if (state == null || state.getByteArray(POST_ID) == null) { - // The blog fragment has its own progress bar - hideLoadingScreen(); - pager.setAdapter(blogPagerAdapter); - savedPostId = null; - } else { - // Adapter will be set in selectPostInPostPager() - savedPostId = new MessageId(state.getByteArray(POST_ID)); - } - } - - @Override - public void onResume() { - super.onResume(); - if (savedPostId == null) { - MessageId selected = getSelectedPostInPostPager(); - if (selected != null) loadBlogPosts(selected); - } else { - loadBlogPosts(savedPostId); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - MessageId selected = getSelectedPostInPostPager(); - if (selected != null) - outState.putByteArray(POST_ID, selected.getBytes()); - } - - @Override - public void onBackPressed() { - if (pager.getAdapter() == postPagerAdapter) { - pager.setAdapter(blogPagerAdapter); - savedPostId = null; - } else { - super.onBackPressed(); + if (state == null) { + BlogFragment f = BlogFragment.newInstance(groupId, blogName, isNew); + getSupportFragmentManager().beginTransaction() + .replace(R.id.fragmentContainer, f, f.getUniqueTag()) + .commit(); } } @@ -128,6 +63,15 @@ public class BlogActivity extends BriarActivity implements component.inject(this); } + @Override + public void onBlogPostClick(BlogPostItem post) { + BlogPostPagerFragment f = BlogPostPagerFragment.newInstance(post.getId()); + getSupportFragmentManager().beginTransaction() + .replace(R.id.fragmentContainer, f, f.getUniqueTag()) + .addToBackStack(f.getUniqueTag()) + .commit(); + } + @Override public void showLoadingScreen(boolean isBlocking, int stringId) { progressBar.setVisibility(VISIBLE); @@ -135,176 +79,10 @@ public class BlogActivity extends BriarActivity implements @Override public void hideLoadingScreen() { - progressBar.setVisibility(GONE); + progressBar.setVisibility(INVISIBLE); } @Override public void onFragmentCreated(String tag) { - } - - @Override - public void onBlogPostClick(BlogPostItem post) { - loadBlogPosts(post.getId()); - } - - private void loadBlogPosts(final MessageId select) { - blogController.loadBlogPosts( - new UiResultExceptionHandler, DbException>( - this) { - @Override - public void onResultUi(Collection posts) { - hideLoadingScreen(); - savedPostId = null; - postPagerAdapter.setPosts(posts); - selectPostInPostPager(select); - } - - @Override - public void onExceptionUi(DbException exception) { - // TODO: Decide how to handle errors in the UI - finish(); - } - }); - } - - @Override - public void onBlogPostAdded(BlogPostHeader header, boolean local) { - if (pager.getAdapter() == postPagerAdapter) { - loadBlogPost(header); - } else { - BlogFragment f = blogPagerAdapter.getFragment(); - if (f != null && f.isVisible()) f.onBlogPostAdded(header, local); - } - } - - private void loadBlogPost(BlogPostHeader header) { - blogController.loadBlogPost(header, - new UiResultExceptionHandler(this) { - @Override - public void onResultUi(BlogPostItem post) { - addPostToPostPager(post); - } - - @Override - public void onExceptionUi(DbException exception) { - // TODO: Decide how to handle errors in the UI - finish(); - } - }); - } - - @Nullable - private MessageId getSelectedPostInPostPager() { - if (pager.getAdapter() != postPagerAdapter) return null; - if (postPagerAdapter.getCount() == 0) return null; - int position = pager.getCurrentItem(); - return postPagerAdapter.getPost(position).getId(); - } - - private void selectPostInPostPager(MessageId m) { - int count = postPagerAdapter.getCount(); - for (int i = 0; i < count; i++) { - if (postPagerAdapter.getPost(i).getId().equals(m)) { - pager.setAdapter(postPagerAdapter); - pager.setCurrentItem(i); - return; - } - } - } - - private void addPostToPostPager(BlogPostItem post) { - MessageId selected = getSelectedPostInPostPager(); - postPagerAdapter.addPost(post); - if (selected != null) selectPostInPostPager(selected); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, - Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - // The BlogPostAddedEvent arrives when the controller is not listening, - // so we need to manually reload the blog posts :( - if (requestCode == REQUEST_WRITE_POST && resultCode == RESULT_OK) { - if (pager.getAdapter() == postPagerAdapter) { - MessageId selected = getSelectedPostInPostPager(); - if (selected != null) loadBlogPosts(selected); - } else { - BlogFragment f = blogPagerAdapter.getFragment(); - if (f != null && f.isVisible()) f.loadBlogPosts(true); - } - } - } - - @UiThread - private class BlogPagerAdapter extends FragmentStatePagerAdapter { - - private BlogFragment fragment = null; - - private BlogPagerAdapter(FragmentManager fm) { - super(fm); - } - - @Override - public int getCount() { - return 1; - } - - @Override - public Fragment getItem(int position) { - return BlogFragment.newInstance(groupId, blogName, isNew); - } - - @Override - public Object instantiateItem(ViewGroup container, int position) { - // save a reference to the single fragment here for later - fragment = - (BlogFragment) super.instantiateItem(container, position); - return fragment; - } - - private BlogFragment getFragment() { - return fragment; - } - } - - @UiThread - private static class BlogPostPagerAdapter - extends FragmentStatePagerAdapter { - - private final List posts = new ArrayList<>(); - - private BlogPostPagerAdapter(FragmentManager fm) { - super(fm); - } - - @Override - public int getCount() { - return posts.size(); - } - - @Override - public Fragment getItem(int position) { - return BlogPostFragment.newInstance(posts.get(position).getId()); - } - - private BlogPostItem getPost(int position) { - return posts.get(position); - } - - private void setPosts(Collection posts) { - this.posts.clear(); - this.posts.addAll(posts); - Collections.sort(this.posts); - notifyDataSetChanged(); - } - - private void addPost(BlogPostItem post) { - posts.add(post); - Collections.sort(posts); - notifyDataSetChanged(); - } - } - } diff --git a/briar-android/src/org/briarproject/android/blogs/BlogController.java b/briar-android/src/org/briarproject/android/blogs/BlogController.java index 768d29c14..4d5c35cbc 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogController.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogController.java @@ -21,9 +21,7 @@ public interface BlogController extends BaseController { void loadBlogPost(MessageId m, ResultExceptionHandler handler); - void isMyBlog(ResultExceptionHandler handler); - - void canDeleteBlog(ResultExceptionHandler handler); + void loadBlog(ResultExceptionHandler handler); void deleteBlog(ResultExceptionHandler handler); diff --git a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java index d70779e34..ad7b7597d 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java @@ -3,6 +3,7 @@ package org.briarproject.android.blogs; import org.briarproject.android.controller.ActivityLifecycleController; import org.briarproject.android.controller.handler.ResultExceptionHandler; import org.briarproject.api.blogs.Blog; +import org.briarproject.api.blogs.BlogPostHeader; import org.briarproject.api.db.DbException; import org.briarproject.api.event.BlogPostAddedEvent; import org.briarproject.api.event.Event; @@ -13,6 +14,7 @@ import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; import java.util.Collection; +import java.util.Collections; import java.util.logging.Logger; import javax.inject.Inject; @@ -33,13 +35,6 @@ public class BlogControllerImpl extends BaseControllerImpl @Override public void onActivityCreate() { - if (activity instanceof OnBlogPostAddedListener) { - listener = (OnBlogPostAddedListener) activity; - } else { - throw new IllegalStateException( - "An activity that injects the BlogController must " + - "implement the OnBlogPostAddedListener"); - } } @Override @@ -102,8 +97,8 @@ public class BlogControllerImpl extends BaseControllerImpl } @Override - public void isMyBlog( - final ResultExceptionHandler handler) { + public void loadBlog( + final ResultExceptionHandler handler) { if (groupId == null) throw new IllegalStateException(); runOnDbThread(new Runnable() { @Override @@ -111,7 +106,12 @@ public class BlogControllerImpl extends BaseControllerImpl try { LocalAuthor a = identityManager.getLocalAuthor(); Blog b = blogManager.getBlog(groupId); - handler.onResult(b.getAuthor().getId().equals(a.getId())); + boolean ours = a.getId().equals(b.getAuthor().getId()); + boolean removable = blogManager.canBeRemoved(groupId); + BlogItem blog = new BlogItem(b, + Collections.emptyList(), + ours, removable); + handler.onResult(blog); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -122,24 +122,6 @@ public class BlogControllerImpl extends BaseControllerImpl } - @Override - public void canDeleteBlog( - final ResultExceptionHandler handler) { - if (groupId == null) throw new IllegalStateException(); - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - handler.onResult(blogManager.canBeRemoved(groupId)); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - handler.onException(e); - } - } - }); - } - @Override public void deleteBlog( final ResultExceptionHandler handler) { diff --git a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java index b3b87a113..4ff2695e3 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java @@ -5,8 +5,8 @@ import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; -import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityOptionsCompat; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.view.LayoutInflater; @@ -28,6 +28,7 @@ import org.briarproject.android.sharing.SharingStatusBlogActivity; import org.briarproject.android.util.BriarRecyclerView; import org.briarproject.api.blogs.BlogPostHeader; import org.briarproject.api.db.DbException; +import org.briarproject.api.identity.Author; import org.briarproject.api.sync.GroupId; import java.util.Collection; @@ -59,6 +60,7 @@ public class BlogFragment extends BaseFragment implements private BlogPostAdapter adapter; private BriarRecyclerView list; private MenuItem writeButton, deleteButton; + private boolean isMyBlog = false, canDeleteBlog = false; static BlogFragment newInstance(GroupId groupId, String name, boolean isNew) { @@ -114,14 +116,13 @@ public class BlogFragment extends BaseFragment implements @Override public void injectFragment(ActivityComponent component) { component.inject(this); - blogController.setGroupId(groupId); + blogController.setOnBlogPostAddedListener(this); } @Override public void onStart() { super.onStart(); - checkIfThisIsMyBlog(); - checkIfBlogCanBeDeleted(); + loadBlog(); } @Override @@ -141,7 +142,9 @@ public class BlogFragment extends BaseFragment implements public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.blogs_blog_actions, menu); writeButton = menu.findItem(R.id.action_write_blog_post); + if (isMyBlog) writeButton.setVisible(true); deleteButton = menu.findItem(R.id.action_blog_delete); + if (canDeleteBlog) deleteButton.setVisible(true); super.onCreateOptionsMenu(menu, inflater); } @@ -161,8 +164,8 @@ public class BlogFragment extends BaseFragment implements new Intent(getActivity(), WriteBlogPostActivity.class); i.putExtra(GROUP_ID, groupId.getBytes()); i.putExtra(BLOG_NAME, blogName); - ActivityCompat.startActivityForResult(getActivity(), i, - REQUEST_WRITE_POST, options.toBundle()); + startActivityForResult(i, REQUEST_WRITE_POST, + options.toBundle()); return true; case R.id.action_blog_share: Intent i2 = new Intent(getActivity(), ShareBlogActivity.class); @@ -190,9 +193,10 @@ public class BlogFragment extends BaseFragment implements super.onActivityResult(request, result, data); if (request == REQUEST_WRITE_POST && result == RESULT_OK) { - displaySnackbar(R.string.blogs_blog_post_created); + displaySnackbar(R.string.blogs_blog_post_created, true); + loadBlogPosts(true); } else if (request == REQUEST_SHARE && result == RESULT_OK) { - displaySnackbar(R.string.blogs_sharing_snackbar); + displaySnackbar(R.string.blogs_sharing_snackbar, true); } } @@ -205,15 +209,15 @@ public class BlogFragment extends BaseFragment implements public void onBlogPostAdded(BlogPostHeader header, final boolean local) { blogController.loadBlogPost(header, new UiResultExceptionHandler( - getActivity()) { + listener) { @Override public void onResultUi(BlogPostItem post) { adapter.add(post); if (local) { list.scrollToPosition(0); - displaySnackbar(R.string.blogs_blog_post_created); + displaySnackbar(R.string.blogs_blog_post_created, false); } else { - displaySnackbar(R.string.blogs_blog_post_received); + displaySnackbar(R.string.blogs_blog_post_received, true); } } @@ -229,7 +233,7 @@ public class BlogFragment extends BaseFragment implements void loadBlogPosts(final boolean reload) { blogController.loadBlogPosts( new UiResultExceptionHandler, DbException>( - getActivity()) { + listener) { @Override public void onResultUi(Collection posts) { if (posts.size() > 0) { @@ -243,63 +247,68 @@ public class BlogFragment extends BaseFragment implements @Override public void onExceptionUi(DbException exception) { // TODO: Decide how to handle errors in the UI - getActivity().finish(); + finish(); } }); } - private void checkIfThisIsMyBlog() { - blogController.canDeleteBlog( - new UiResultExceptionHandler( - getActivity()) { + private void loadBlog() { + blogController.loadBlog( + new UiResultExceptionHandler(listener) { @Override - public void onResultUi(Boolean isMyBlog) { - if (isMyBlog) { + public void onResultUi(BlogItem blog) { + setToolbarTitle(blog.getBlog().getAuthor()); + if (blog.isOurs()) showWriteButton(); - } + if (blog.canBeRemoved()) + showDeleteButton(); } @Override public void onExceptionUi(DbException exception) { // TODO: Decide how to handle errors in the UI - getActivity().finish(); + finish(); } }); } - private void checkIfBlogCanBeDeleted() { - blogController.canDeleteBlog( - new UiResultExceptionHandler( - getActivity()) { - @Override - public void onResultUi(Boolean canBeDeleted) { - if (canBeDeleted) { - showDeleteButton(); - } - } + private void setToolbarTitle(Author a) { + String title = getString(R.string.blogs_personal_blog, a.getName()); + getActivity().setTitle(title); - @Override - public void onExceptionUi(DbException exception) { - // TODO: Decide how to handle errors in the UI - getActivity().finish(); - } - }); + // safe title in intent, so it can be restored automatically + Intent intent = getActivity().getIntent(); + intent.putExtra(BLOG_NAME, title); } private void showWriteButton() { + isMyBlog = true; if (writeButton != null) writeButton.setVisible(true); } private void showDeleteButton() { + canDeleteBlog = true; if (deleteButton != null) deleteButton.setVisible(true); } - private void displaySnackbar(int stringId) { + private void displaySnackbar(int stringId, boolean scroll) { Snackbar snackbar = - Snackbar.make(list, stringId, Snackbar.LENGTH_SHORT); + Snackbar.make(list, stringId, Snackbar.LENGTH_LONG); snackbar.getView().setBackgroundResource(R.color.briar_primary); + if (scroll) { + View.OnClickListener onClick = new View.OnClickListener() { + @Override + public void onClick(View v) { + list.smoothScrollToPosition(0); + } + }; + snackbar.setActionTextColor(ContextCompat + .getColor(getContext(), + R.color.briar_button_positive)); + snackbar.setAction(R.string.blogs_blog_post_scroll_to, onClick); + } snackbar.show(); } @@ -323,7 +332,7 @@ public class BlogFragment extends BaseFragment implements private void deleteBlog() { blogController.deleteBlog( - new UiResultExceptionHandler(getActivity()) { + new UiResultExceptionHandler(listener) { @Override public void onResultUi(Void result) { Toast.makeText(getActivity(), @@ -335,7 +344,7 @@ public class BlogFragment extends BaseFragment implements @Override public void onExceptionUi(DbException exception) { // TODO: Decide how to handle errors in the UI - getActivity().finish(); + finish(); } }); } diff --git a/briar-android/src/org/briarproject/android/blogs/BlogListItem.java b/briar-android/src/org/briarproject/android/blogs/BlogItem.java similarity index 82% rename from briar-android/src/org/briarproject/android/blogs/BlogListItem.java rename to briar-android/src/org/briarproject/android/blogs/BlogItem.java index 35c9f532b..536d05f24 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogListItem.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogItem.java @@ -5,15 +5,16 @@ import org.briarproject.api.blogs.BlogPostHeader; import java.util.Collection; -class BlogListItem { +class BlogItem { private final Blog blog; private final int postCount; private final long timestamp; private final int unread; - private final boolean ours; + private final boolean ours, removable; - BlogListItem(Blog blog, Collection headers, boolean ours) { + BlogItem(Blog blog, Collection headers, boolean ours, + boolean removable) { this.blog = blog; if (headers.isEmpty()) { postCount = 0; @@ -35,6 +36,7 @@ class BlogListItem { this.unread = unread; } this.ours = ours; + this.removable = removable; } Blog getBlog() { @@ -64,4 +66,8 @@ class BlogListItem { boolean isOurs() { return ours; } + + boolean canBeRemoved() { + return removable; + } } diff --git a/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java b/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java index 37f7dd301..b3e55f848 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java @@ -29,11 +29,11 @@ import static org.briarproject.android.blogs.BlogActivity.BLOG_NAME; class BlogListAdapter extends RecyclerView.Adapter { - private SortedList blogs = new SortedList<>( - BlogListItem.class, new SortedList.Callback() { + private SortedList blogs = new SortedList<>( + BlogItem.class, new SortedList.Callback() { @Override - public int compare(BlogListItem a, BlogListItem b) { + public int compare(BlogItem a, BlogItem b) { if (a == b) return 0; // The blog with the newest message comes first long aTime = a.getTimestamp(), bTime = b.getTimestamp(); @@ -66,14 +66,14 @@ class BlogListAdapter extends } @Override - public boolean areContentsTheSame(BlogListItem a, BlogListItem b) { + public boolean areContentsTheSame(BlogItem a, BlogItem b) { return a.getBlog().equals(b.getBlog()) && a.getTimestamp() == b.getTimestamp() && a.getUnreadCount() == b.getUnreadCount(); } @Override - public boolean areItemsTheSame(BlogListItem a, BlogListItem b) { + public boolean areItemsTheSame(BlogItem a, BlogItem b) { return a.getBlog().equals(b.getBlog()); } }); @@ -93,7 +93,7 @@ class BlogListAdapter extends @Override public void onBindViewHolder(BlogViewHolder ui, int position) { - final BlogListItem item = getItem(position); + final BlogItem item = getItem(position); // Avatar ui.avatar.setText(item.getName().substring(0, 1)); @@ -145,14 +145,14 @@ class BlogListAdapter extends return blogs.size(); } - public BlogListItem getItem(int position) { + public BlogItem getItem(int position) { return blogs.get(position); } @Nullable - public BlogListItem getItem(GroupId g) { + public BlogItem getItem(GroupId g) { for (int i = 0; i < blogs.size(); i++) { - BlogListItem item = blogs.get(i); + BlogItem item = blogs.get(i); if (item.getBlog().getGroup().getId().equals(g)) { return item; } @@ -160,17 +160,17 @@ class BlogListAdapter extends return null; } - public void addAll(Collection items) { + public void addAll(Collection items) { blogs.addAll(items); } - void updateItem(BlogListItem item) { - BlogListItem oldItem = getItem(item.getBlog().getGroup().getId()); + void updateItem(BlogItem item) { + BlogItem oldItem = getItem(item.getBlog().getGroup().getId()); int position = blogs.indexOf(oldItem); blogs.updateItemAt(position, item); } - public void remove(BlogListItem item) { + public void remove(BlogItem item) { blogs.remove(item); } diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java index 926ce7f37..ac7b4d504 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java @@ -3,35 +3,23 @@ package org.briarproject.android.blogs; import android.os.Bundle; import android.support.annotation.Nullable; import android.view.LayoutInflater; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import org.briarproject.R; import org.briarproject.android.ActivityComponent; import org.briarproject.android.controller.handler.UiResultExceptionHandler; -import org.briarproject.android.fragment.BaseFragment; import org.briarproject.api.db.DbException; import org.briarproject.api.sync.MessageId; -import java.util.logging.Logger; - import javax.inject.Inject; -import static org.briarproject.android.util.AndroidUtils.MIN_RESOLUTION; +import static org.briarproject.android.blogs.BasePostPagerFragment.POST_ID; -public class BlogPostFragment extends BaseFragment { +public class BlogPostFragment extends BasePostFragment { public final static String TAG = BlogPostFragment.class.getName(); - private static final Logger LOG = Logger.getLogger(TAG); - private static final String BLOG_POST_ID = "briar.BLOG_POST_ID"; - - private View view; private MessageId postId; - private BlogPostViewHolder ui; - private BlogPostItem post; - private Runnable refresher; @Inject BlogController blogController; @@ -40,7 +28,7 @@ public class BlogPostFragment extends BaseFragment { BlogPostFragment f = new BlogPostFragment(); Bundle bundle = new Bundle(); - bundle.putByteArray(BLOG_POST_ID, postId.getBytes()); + bundle.putByteArray(POST_ID, postId.getBytes()); f.setArguments(bundle); return f; @@ -50,16 +38,18 @@ public class BlogPostFragment extends BaseFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - setHasOptionsMenu(true); - byte[] b = getArguments().getByteArray(BLOG_POST_ID); - if (b == null) throw new IllegalStateException("No post ID in args"); - postId = new MessageId(b); + Bundle args = getArguments(); + byte[] p = args.getByteArray(POST_ID); + if (p == null) throw new IllegalStateException("No post ID in args"); + postId = new MessageId(p); - view = inflater.inflate(R.layout.fragment_blog_post, container, - false); - ui = new BlogPostViewHolder(view); - return view; + return super.onCreateView(inflater, container, savedInstanceState); + } + + @Override + public String getUniqueTag() { + return TAG; } @Override @@ -72,65 +62,15 @@ public class BlogPostFragment extends BaseFragment { super.onStart(); blogController.loadBlogPost(postId, new UiResultExceptionHandler( - getActivity()) { + listener) { @Override public void onResultUi(BlogPostItem post) { - listener.hideLoadingScreen(); - BlogPostFragment.this.post = post; - ui.bindItem(post); - startPeriodicUpdate(); + onBlogPostLoaded(post); } - @Override public void onExceptionUi(DbException exception) { - // TODO: Decide how to handle errors in the UI - finish(); + onBlogPostLoadException(exception); } }); } - - @Override - public void onStop() { - super.onStop(); - stopPeriodicUpdate(); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - getActivity().onBackPressed(); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - @Override - public String getUniqueTag() { - return TAG; - } - - private void startPeriodicUpdate() { - refresher = new Runnable() { - @Override - public void run() { - if (ui == null) return; - LOG.info("Updating Content..."); - - ui.updateDate(post.getTimestamp()); - view.postDelayed(refresher, MIN_RESOLUTION); - } - }; - LOG.info("Adding Handler Callback"); - view.postDelayed(refresher, MIN_RESOLUTION); - } - - private void stopPeriodicUpdate() { - if (refresher != null && ui != null) { - LOG.info("Removing Handler Callback"); - view.removeCallbacks(refresher); - } - } - } diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java new file mode 100644 index 000000000..f0a534b84 --- /dev/null +++ b/briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java @@ -0,0 +1,78 @@ +package org.briarproject.android.blogs; + +import android.os.Bundle; + +import org.briarproject.android.ActivityComponent; +import org.briarproject.android.controller.handler.UiResultExceptionHandler; +import org.briarproject.api.blogs.BlogPostHeader; +import org.briarproject.api.db.DbException; +import org.briarproject.api.sync.MessageId; + +import java.util.Collection; + +import javax.inject.Inject; + + +public class BlogPostPagerFragment extends BasePostPagerFragment { + + public final static String TAG = BlogPostPagerFragment.class.getName(); + + @Inject + BlogController blogController; + + static BlogPostPagerFragment newInstance(MessageId postId) { + BlogPostPagerFragment f = new BlogPostPagerFragment(); + + Bundle args = new Bundle(); + args.putByteArray(POST_ID, postId.getBytes()); + f.setArguments(args); + + return f; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + blogController.setOnBlogPostAddedListener(this); + } + + @Override + public String getUniqueTag() { + return TAG; + } + + + void loadBlogPosts(final MessageId select) { + blogController.loadBlogPosts( + new UiResultExceptionHandler, DbException>( + listener) { + @Override + public void onResultUi(Collection posts) { + onBlogPostsLoaded(select, posts); + } + + @Override + public void onExceptionUi(DbException exception) { + onBlogPostsLoadedException(exception); + } + }); + } + + void loadBlogPost(BlogPostHeader header) { + blogController.loadBlogPost(header, + new UiResultExceptionHandler( + listener) { + @Override + public void onResultUi(BlogPostItem post) { + addPost(post); + } + + @Override + public void onExceptionUi(DbException exception) { + // TODO: Decide how to handle errors in the UI + finish(); + } + }); + } + +} diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java b/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java index 9cc23d079..13a1b3f17 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java @@ -10,6 +10,7 @@ import android.support.v4.view.ViewCompat; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; @@ -26,7 +27,9 @@ import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAn import static android.view.View.GONE; import static android.view.View.VISIBLE; import static org.briarproject.android.BriarActivity.GROUP_ID; -import static org.briarproject.android.blogs.BlogActivity.POST_ID; +import static org.briarproject.android.blogs.BasePostPagerFragment.POST_ID; +import static org.briarproject.android.util.AndroidUtils.TEASER_LENGTH; +import static org.briarproject.android.util.AndroidUtils.getTeaser; import static org.briarproject.api.blogs.MessageType.POST; @UiThread @@ -81,14 +84,15 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder { void bindItem(final BlogPostItem item) { setTransitionName(item.getId()); - layout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (listener != null) { + if (listener != null) { + layout.setClickable(true); + layout.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { listener.onBlogPostClick(item); } - } - }); + }); + } // author and date BlogPostHeader post = item.getPostHeader(); @@ -104,10 +108,18 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder { } // post body - body.setText(item.getBody()); + CharSequence bodyText = item.getBody(); + if (listener == null) { + body.setTextIsSelectable(true); + } else { + body.setTextIsSelectable(false); + if (item.getBody().length() > TEASER_LENGTH) + bodyText = getTeaser(ctx, item.getBody()); + } + body.setText(bodyText); // reblog button - reblogButton.setOnClickListener(new View.OnClickListener() { + reblogButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(ctx, ReblogActivity.class); @@ -154,6 +166,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder { // TODO make author clickable #624 body.setText(c.getComment()); + if (listener == null) body.setTextIsSelectable(true); commentContainer.addView(v); } diff --git a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java index 9e6f57748..41791e962 100644 --- a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java @@ -108,7 +108,7 @@ public class FeedFragment extends BaseFragment implements }); feedController.loadBlogPosts( new UiResultExceptionHandler, DbException>( - getActivity()) { + listener) { @Override public void onResultUi(Collection posts) { if (posts.isEmpty()) { @@ -175,7 +175,7 @@ public class FeedFragment extends BaseFragment implements public void onBlogPostAdded(BlogPostHeader header, final boolean local) { feedController.loadBlogPost(header, new UiResultExceptionHandler( - getActivity()) { + listener) { @Override public void onResultUi(BlogPostItem post) { adapter.add(post); @@ -195,7 +195,12 @@ public class FeedFragment extends BaseFragment implements @Override public void onBlogPostClick(BlogPostItem post) { - // TODO Open detail view of post + FeedPostPagerFragment f = FeedPostPagerFragment + .newInstance(post.getId()); + getActivity().getSupportFragmentManager().beginTransaction() + .replace(R.id.content_fragment, f, f.getUniqueTag()) + .addToBackStack(f.getUniqueTag()) + .commit(); } @Override diff --git a/briar-android/src/org/briarproject/android/blogs/FeedPostFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedPostFragment.java new file mode 100644 index 000000000..442a85ef9 --- /dev/null +++ b/briar-android/src/org/briarproject/android/blogs/FeedPostFragment.java @@ -0,0 +1,85 @@ +package org.briarproject.android.blogs; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.briarproject.android.ActivityComponent; +import org.briarproject.android.controller.handler.UiResultExceptionHandler; +import org.briarproject.api.db.DbException; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; + +import javax.inject.Inject; + +import static org.briarproject.android.BriarActivity.GROUP_ID; +import static org.briarproject.android.blogs.BasePostPagerFragment.POST_ID; + +public class FeedPostFragment extends BasePostFragment { + + public final static String TAG = FeedPostFragment.class.getName(); + + private MessageId postId; + private GroupId blogId; + + @Inject + FeedController feedController; + + 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; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + Bundle args = getArguments(); + byte[] b = args.getByteArray(GROUP_ID); + if (b == null) throw new IllegalStateException("No group ID in args"); + blogId = new GroupId(b); + + byte[] p = args.getByteArray(POST_ID); + if (p == null) throw new IllegalStateException("No post ID in args"); + postId = new MessageId(p); + + return super.onCreateView(inflater, container, savedInstanceState); + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + @Override + public void onStart() { + super.onStart(); + feedController.loadBlogPost(blogId, postId, + new UiResultExceptionHandler( + listener) { + @Override + public void onResultUi(BlogPostItem post) { + onBlogPostLoaded(post); + } + @Override + public void onExceptionUi(DbException exception) { + onBlogPostLoadException(exception); + } + }); + } + + @Override + public String getUniqueTag() { + return TAG; + } + +} diff --git a/briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java new file mode 100644 index 000000000..ab9444104 --- /dev/null +++ b/briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java @@ -0,0 +1,77 @@ +package org.briarproject.android.blogs; + +import android.os.Bundle; + +import org.briarproject.android.ActivityComponent; +import org.briarproject.android.controller.handler.UiResultExceptionHandler; +import org.briarproject.api.blogs.BlogPostHeader; +import org.briarproject.api.db.DbException; +import org.briarproject.api.sync.MessageId; + +import java.util.Collection; + +import javax.inject.Inject; + +public class FeedPostPagerFragment extends BasePostPagerFragment { + + public final static String TAG = FeedPostPagerFragment.class.getName(); + + @Inject + FeedController feedController; + + static FeedPostPagerFragment newInstance(MessageId postId) { + FeedPostPagerFragment f = new FeedPostPagerFragment(); + + Bundle args = new Bundle(); + args.putByteArray(POST_ID, postId.getBytes()); + f.setArguments(args); + + return f; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + feedController.setOnBlogPostAddedListener(this); + } + + @Override + public String getUniqueTag() { + return TAG; + } + + + void loadBlogPosts(final MessageId select) { + feedController.loadBlogPosts( + new UiResultExceptionHandler, DbException>( + listener) { + @Override + public void onResultUi(Collection posts) { + onBlogPostsLoaded(select, posts); + } + + @Override + public void onExceptionUi(DbException exception) { + onBlogPostsLoadedException(exception); + } + }); + } + + void loadBlogPost(BlogPostHeader header) { + feedController.loadBlogPost(header, + new UiResultExceptionHandler( + listener) { + @Override + public void onResultUi(BlogPostItem post) { + addPost(post); + } + + @Override + public void onExceptionUi(DbException exception) { + // TODO: Decide how to handle errors in the UI + finish(); + } + }); + } + +} diff --git a/briar-android/src/org/briarproject/android/blogs/ReblogActivity.java b/briar-android/src/org/briarproject/android/blogs/ReblogActivity.java index 923d520a6..6b4afb89b 100644 --- a/briar-android/src/org/briarproject/android/blogs/ReblogActivity.java +++ b/briar-android/src/org/briarproject/android/blogs/ReblogActivity.java @@ -15,7 +15,7 @@ import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; -import static org.briarproject.android.blogs.BlogActivity.POST_ID; +import static org.briarproject.android.blogs.BasePostPagerFragment.POST_ID; public class ReblogActivity extends BriarActivity implements BaseFragmentListener { diff --git a/briar-android/src/org/briarproject/android/blogs/ReblogFragment.java b/briar-android/src/org/briarproject/android/blogs/ReblogFragment.java index 1fee6c647..305273ba8 100644 --- a/briar-android/src/org/briarproject/android/blogs/ReblogFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/ReblogFragment.java @@ -27,7 +27,7 @@ import static android.view.View.GONE; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static org.briarproject.android.BriarActivity.GROUP_ID; -import static org.briarproject.android.blogs.BlogActivity.POST_ID; +import static org.briarproject.android.blogs.BasePostPagerFragment.POST_ID; public class ReblogFragment extends BaseFragment { @@ -106,7 +106,7 @@ public class ReblogFragment extends BaseFragment { // TODO: Load blog post when fragment is created. #631 feedController.loadBlogPost(blogId, postId, new UiResultExceptionHandler( - getActivity()) { + listener) { @Override public void onResultUi(BlogPostItem result) { item = result; @@ -148,7 +148,7 @@ public class ReblogFragment extends BaseFragment { private void send() { String comment = getComment(); feedController.repeatPost(item, comment, - new UiResultExceptionHandler(getActivity()) { + new UiResultExceptionHandler(listener) { @Override public void onResultUi(Void result) { // do nothing, this fragment is gone already diff --git a/briar-android/src/org/briarproject/android/controller/handler/UiResultExceptionHandler.java b/briar-android/src/org/briarproject/android/controller/handler/UiResultExceptionHandler.java index ba2eab195..1f9cab093 100644 --- a/briar-android/src/org/briarproject/android/controller/handler/UiResultExceptionHandler.java +++ b/briar-android/src/org/briarproject/android/controller/handler/UiResultExceptionHandler.java @@ -1,33 +1,36 @@ package org.briarproject.android.controller.handler; -import android.app.Activity; import android.support.annotation.UiThread; +import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener; + public abstract class UiResultExceptionHandler implements ResultExceptionHandler { - private final Activity activity; + private final BaseFragmentListener listener; - public UiResultExceptionHandler(Activity activity) { - this.activity = activity; + protected UiResultExceptionHandler(BaseFragmentListener listener) { + this.listener = listener; } @Override public void onResult(final R result) { - activity.runOnUiThread(new Runnable() { + listener.runOnUiThread(new Runnable() { @Override public void run() { - onResultUi(result); + if (!listener.hasBeenDestroyed()) + onResultUi(result); } }); } @Override public void onException(final E exception) { - activity.runOnUiThread(new Runnable() { + listener.runOnUiThread(new Runnable() { @Override public void run() { - onExceptionUi(exception); + if (!listener.hasBeenDestroyed()) + onExceptionUi(exception); } }); } diff --git a/briar-android/src/org/briarproject/android/controller/handler/UiResultHandler.java b/briar-android/src/org/briarproject/android/controller/handler/UiResultHandler.java index 0616b82a2..03c68d7ac 100644 --- a/briar-android/src/org/briarproject/android/controller/handler/UiResultHandler.java +++ b/briar-android/src/org/briarproject/android/controller/handler/UiResultHandler.java @@ -1,22 +1,24 @@ package org.briarproject.android.controller.handler; -import android.app.Activity; import android.support.annotation.UiThread; +import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener; + public abstract class UiResultHandler implements ResultHandler { - private final Activity activity; + private final BaseFragmentListener listener; - public UiResultHandler(Activity activity) { - this.activity = activity; + protected UiResultHandler(BaseFragmentListener listener) { + this.listener = listener; } @Override public void onResult(final R result) { - activity.runOnUiThread(new Runnable() { + listener.runOnUiThread(new Runnable() { @Override public void run() { - onResultUi(result); + if (!listener.hasBeenDestroyed()) + onResultUi(result); } }); } diff --git a/briar-android/src/org/briarproject/android/fragment/BaseFragment.java b/briar-android/src/org/briarproject/android/fragment/BaseFragment.java index c19bcf1ee..d6f4ed363 100644 --- a/briar-android/src/org/briarproject/android/fragment/BaseFragment.java +++ b/briar-android/src/org/briarproject/android/fragment/BaseFragment.java @@ -7,6 +7,7 @@ import android.support.annotation.UiThread; import android.support.v4.app.Fragment; import org.briarproject.android.ActivityComponent; +import org.briarproject.android.Destroyable; public abstract class BaseFragment extends Fragment { @@ -45,7 +46,7 @@ public abstract class BaseFragment extends Fragment { getActivity().supportFinishAfterTransition(); } - public interface BaseFragmentListener { + public interface BaseFragmentListener extends Destroyable { @UiThread void showLoadingScreen(boolean isBlocking, int stringId); diff --git a/briar-android/src/org/briarproject/android/util/AndroidUtils.java b/briar-android/src/org/briarproject/android/util/AndroidUtils.java index 610e480e5..66593d0f8 100644 --- a/briar-android/src/org/briarproject/android/util/AndroidUtils.java +++ b/briar-android/src/org/briarproject/android/util/AndroidUtils.java @@ -6,7 +6,12 @@ import android.content.Context; import android.os.Build; import android.provider.Settings; import android.support.design.widget.TextInputLayout; +import android.support.v4.content.ContextCompat; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; import android.text.format.DateUtils; +import android.text.style.ForegroundColorSpan; import org.briarproject.R; import org.briarproject.util.IoUtils; @@ -31,6 +36,7 @@ import static android.text.format.DateUtils.WEEK_IN_MILLIS; public class AndroidUtils { public static final long MIN_RESOLUTION = MINUTE_IN_MILLIS; + public static final int TEASER_LENGTH = 240; // Fake Bluetooth address returned by BluetoothAdapter on API 23 and later private static final String FAKE_BLUETOOTH_ADDRESS = "02:00:00:00:00:00"; @@ -115,4 +121,25 @@ public class AndroidUtils { MIN_RESOLUTION, flags).toString(); } + public static SpannableStringBuilder getTeaser(Context ctx, String body) { + if (body.length() < TEASER_LENGTH) + throw new IllegalArgumentException( + "String is shorter than TEASER_LENGTH"); + + SpannableStringBuilder builder = + new SpannableStringBuilder(body.substring(0, TEASER_LENGTH)); + String ellipsis = ctx.getString(R.string.ellipsis); + builder.append(ellipsis).append(" "); + + Spannable readMore = new SpannableString( + ctx.getString(R.string.read_more) + ellipsis); + ForegroundColorSpan fg = new ForegroundColorSpan( + ContextCompat.getColor(ctx, R.color.briar_text_link)); + readMore.setSpan(fg, 0, readMore.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + builder.append(readMore); + + return builder; + } + } diff --git a/briar-android/src/org/briarproject/android/util/AuthorView.java b/briar-android/src/org/briarproject/android/util/AuthorView.java index 6920ebbf1..45ab5d347 100644 --- a/briar-android/src/org/briarproject/android/util/AuthorView.java +++ b/briar-android/src/org/briarproject/android/util/AuthorView.java @@ -26,6 +26,7 @@ import de.hdodenhof.circleimageview.CircleImageView; import im.delight.android.identicons.IdenticonDrawable; import static android.content.Context.LAYOUT_INFLATER_SERVICE; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.graphics.Typeface.BOLD; import static android.graphics.Typeface.NORMAL; import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation; @@ -106,6 +107,7 @@ public class AuthorView extends RelativeLayout { public void onClick(View v) { Intent i = new Intent(getContext(), BlogActivity.class); i.putExtra(GROUP_ID, groupId.getBytes()); + i.setFlags(FLAG_ACTIVITY_CLEAR_TOP); ActivityOptionsCompat options = makeCustomAnimation(getContext(), android.R.anim.slide_in_left,