diff --git a/briar-android/res/drawable/ic_chat.xml b/briar-android/res/drawable/ic_chat.xml deleted file mode 100644 index 04f9fdcdfd7427decad183a5e0a8ae3434f2b4f8..0000000000000000000000000000000000000000 --- a/briar-android/res/drawable/ic_chat.xml +++ /dev/null @@ -1,10 +0,0 @@ -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:alpha="0.54" - android:viewportHeight="24.0" - android:viewportWidth="24.0"> - <path - android:fillColor="#FF000000" - android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z"/> -</vector> 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 8d57dbc10f814f6014d3965c9a8a268d080bc775..0000000000000000000000000000000000000000 --- a/briar-android/res/drawable/ic_expand_more_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="#FF000000" - android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/> -</vector> diff --git a/briar-android/res/layout/activity_fragment_container.xml b/briar-android/res/layout/activity_fragment_container.xml index e6c20760fb5078de5def86eec38402fc2db9042a..8bf59ee5acc2ae1186ebae445268a3028fbb7bae 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"> + + <ProgressBar + android:id="@+id/progressBar" + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:visibility="invisible"/> + +</FrameLayout> \ 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 1af9c5965ab51dd1452d8c38e80ed89d66b90578..0000000000000000000000000000000000000000 --- a/briar-android/res/layout/dropdown_author.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center_vertical"> - - <de.hdodenhof.circleimageview.CircleImageView - android:id="@+id/avatarView" - style="@style/BriarAvatar" - android:layout_width="@dimen/dropdown_picture_size" - android:layout_height="@dimen/dropdown_picture_size" - android:layout_margin="@dimen/margin_small" - tools:src="@drawable/ic_launcher"/> - - <TextView - android:id="@+id/nameView" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_margin="@dimen/margin_small" - android:layout_weight="1" - android:ellipsize="end" - android:singleLine="true" - android:textSize="@dimen/text_size_medium" - tools:text="This is a name of an author. It can be quite long."/> - -</LinearLayout> \ 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 7a0df0d0b5d964b25bd2ef3a6b5c3cd1630f7b3d..18ec1f417cd1f72ba54f917eabb8913ea2c04631 100644 --- a/briar-android/res/layout/fragment_blog_post.xml +++ b/briar-android/res/layout/fragment_blog_post.xml @@ -6,7 +6,12 @@ <FrameLayout android:layout_width="wrap_content" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:descendantFocusability="beforeDescendants" + android:focusable="true" + android:focusableInTouchMode="true"> + <!-- Above Focusability attributes prevent automatic scroll-down, + because body text is selectable --> <include android:id="@+id/postLayout" @@ -15,6 +20,13 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"/> + <ProgressBar + android:id="@+id/progressBar" + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center"/> + </FrameLayout> </ScrollView> 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 3ff8d409e0e60ee2b749b2e51726b8af8e5cab7c..626dcaf26e21df50e8349d692406ef4f0c4c3a4c 100644 --- a/briar-android/res/layout/activity_blog.xml +++ b/briar-android/res/layout/fragment_blog_post_pager.xml @@ -1,15 +1,14 @@ <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v4.view.ViewPager android:id="@+id/pager" + xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent" - tools:context=".android.blogs.BlogActivity"/> + android:layout_height="match_parent"/> <ProgressBar android:id="@+id/progressBar" diff --git a/briar-android/res/layout/fragment_blogs.xml b/briar-android/res/layout/fragment_blogs.xml deleted file mode 100644 index 2e618a9b1e78a86cde64fc731ffa5d318852a0da..0000000000000000000000000000000000000000 --- a/briar-android/res/layout/fragment_blogs.xml +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <android.support.design.widget.TabLayout - android:id="@+id/tabLayout" - style="@style/BriarTabLayout" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <android.support.design.widget.TabItem - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/blogs_feed"/> - - <android.support.design.widget.TabItem - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/blogs_my_blogs"/> - - <android.support.design.widget.TabItem - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/blogs_blog_list"/> - - <android.support.design.widget.TabItem - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/blogs_available_blogs"/> - - <android.support.design.widget.TabItem - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/blogs_drafts"/> - - </android.support.design.widget.TabLayout> - - <android.support.v4.view.ViewPager - android:id="@+id/pager" - android:layout_width="match_parent" - android:layout_height="match_parent"/> - -</LinearLayout> \ 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 288adfaa3937ea3d0b242b6c7177007b3e994d67..0000000000000000000000000000000000000000 --- a/briar-android/res/layout/fragment_blogs_my.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<org.briarproject.android.util.BriarRecyclerView - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - tools:listitem="@layout/list_item_blog"/> diff --git a/briar-android/res/layout/list_item_blog_comment.xml b/briar-android/res/layout/list_item_blog_comment.xml index bdbe42842d003db9c92fc6e1a8515366da1957ff..1be478ab2fd9f60b3e7235034c1ee8f9e52544d3 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 8b136f2b7d5682623d1adb7a777895fb5a457b8c..d3cd14d489ab70c9b6dced4a313af848eb68807a 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"> <LinearLayout android:layout_width="match_parent" @@ -55,7 +56,6 @@ android:layout_height="wrap_content" android:layout_below="@+id/authorView" android:textColor="@color/briar_text_secondary" - android:textIsSelectable="true" android:textSize="@dimen/text_size_medium" tools:text="This is a body text that shows the content of a blog post.\n\nThis one is not short, but it is also not too long."/> 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 9a3ce5b49a27da3c2484e66fe542ca375b669b2b..0000000000000000000000000000000000000000 --- a/briar-android/res/menu/blogs_my_actions.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<menu - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto"> - - <item - android:id="@+id/action_create_blog" - android:icon="@drawable/ic_add_white" - android:title="@string/blogs_my_blogs_create" - app:showAsAction="ifRoom"/> - -</menu> \ No newline at end of file diff --git a/briar-android/res/values/color.xml b/briar-android/res/values/color.xml index e7cb49b1526b08ad1bae60378005f885793d46a0..0ac3140eeef7b00dadaa8cddaf3302d4d34c22eb 100644 --- a/briar-android/res/values/color.xml +++ b/briar-android/res/values/color.xml @@ -35,7 +35,6 @@ <color name="menu_background">#FFFFFF</color> <color name="spinner_border">#61000000</color> <!-- 38% Black --> - <color name="spinner_arrow">@color/briar_blue_dark</color> <color name="forum_discussion_nested_line">#cfd2d4</color> <color name="forum_cell_highlight">#ffffff</color> </resources> \ No newline at end of file diff --git a/briar-android/res/values/dimens.xml b/briar-android/res/values/dimens.xml index 9f62ff0944d8491f42245eeecb7b4a858524a423..c8e7eca7d0dfa6c3a8b2a51123547edf60fb748c 100644 --- a/briar-android/res/values/dimens.xml +++ b/briar-android/res/values/dimens.xml @@ -27,7 +27,6 @@ <dimen name="listitem_picture_frame_size">53dp</dimen> <dimen name="listitem_picture_frame_offset">2dp</dimen> <dimen name="listitem_selectable_picture_size">40dp</dimen> - <dimen name="dropdown_picture_size">32dp</dimen> <dimen name="avatar_forum_size">48dp</dimen> <dimen name="avatar_border_width">2dp</dimen> <dimen name="avatar_text_size">30sp</dimen> diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index ba764c356d3880c5a16081cb1d98e02dd48466dd..e866545097665ba4ad001540e73f0e50ce157360 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -73,6 +73,7 @@ <string name="offline">Offline</string> <string name="send">Send</string> <string name="no_data">No data</string> + <string name="ellipsis">…</string> <!-- Contacts and Private Conversations--> <string name="no_contacts">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.</string> @@ -199,18 +200,16 @@ <string name="nobody">Nobody</string> <!-- Blogs --> - <string name="blogs_feed">Feed</string> - <string name="blogs_my_blogs">My Blogs</string> <string name="blogs_my_blogs_create">Create Blog</string> <string name="blogs_my_blogs_label">Add new Blog</string> <string name="blogs_my_blogs_create_hint_title">Blog title (cannot be changed later)</string> <string name="blogs_my_blogs_create_hint_desc">A short description of your new blog</string> <string name="blogs_my_blogs_create_hint_desc_explanation">Potential readers may or may not subscribe to your blog based on the content of the description.</string> - <string name="blogs_my_blogs_empty_state">You don\'t have any blogs.\n\nWhy don\'t you create one now by clicking the plus in the top right screen corner?</string> <string name="blogs_my_blogs_created">Blog created</string> <string name="blogs_blog_is_empty">This blog is empty</string> <string name="blogs_other_blog_empty_state">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.</string> <string name="tag_new">NEW</string> + <string name="read_more">read more</string> <string name="blogs_write_blog_post">Write Blog Post</string> <string name="blogs_write_blog_post_title_hint">Add a title (optional)</string> <string name="blogs_write_blog_post_body_hint">Type your blog post here</string> @@ -231,7 +230,6 @@ <string name="blogs_blog_list">Blog List</string> <string name="blogs_available_blogs">Available Blogs</string> - <string name="blogs_drafts">Drafts</string> <!-- Blog Sharing --> <string name="blogs_sharing_share">Share Blog</string> @@ -312,7 +310,6 @@ <!-- Multiple Identities --> <string name="anonymous">Anonymous</string> - <string name="new_identity_item">New identity\u2026</string> <string name="new_identity_title">New Identity</string> <string name="create_identity_button">Create Identity</string> <string name="identity_created_toast">Identity created</string> diff --git a/briar-android/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java index 2a0d82c4b26811aec01e7b8656e1aa0e169e6dd3..baee29d41e90080be85f317de5c3130415935ff9 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 a9a9ef8ce5063d305c5db2a40287f6c57d3291ba..17c7dc8503367e81337d47f0e984e8c5d0ef0077 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 6381bc9cd1491eefb9b94cde41b0bfca989e12b4..d4bacf55a9517b9b93d33ba542b7fd516ed6e2d1 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 ab513217afa2a58a0dea5220a7b8275e247a3921..3c4f1bc21bbee32c2be22b73ae56f772acb96c5a 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 0000000000000000000000000000000000000000..06153e422f286c8278c811af08c4d1b53e32a9d0 --- /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 0000000000000000000000000000000000000000..e27c1470f5fe09d2e401b53189a5a8da3dfea3c9 --- /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<BlogPostItem> 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<BlogPostItem> 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<BlogPostItem> 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 249d0ff7cf0fc741b7fad3bff73c02c6d908c7d0..bb7a5cc8d3ec4f6f3f86ba492e8606591aaa0fe0 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,68 +37,39 @@ 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); - - setContentView(R.layout.activity_blog); + boolean isNew = i.getBooleanExtra(IS_NEW_BLOG, false); - 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); + if (state == null) { + BlogFragment f = BlogFragment.newInstance(groupId, blogName, isNew); + getSupportFragmentManager().beginTransaction() + .replace(R.id.fragmentContainer, f, f.getUniqueTag()) + .commit(); } } @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(); - } + public void injectActivity(ActivityComponent component) { + component.inject(this); } @Override - public void injectActivity(ActivityComponent component) { - component.inject(this); + 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 @@ -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<Collection<BlogPostItem>, DbException>( - this) { - @Override - public void onResultUi(Collection<BlogPostItem> 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<BlogPostItem, DbException>(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<BlogPostItem> 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<BlogPostItem> 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 768d29c14cb059bebe4618c93fa7c1c6156ae03e..4d5c35cbc2d3efc3572cadd6c19983376e671e9b 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<BlogPostItem, DbException> handler); - void isMyBlog(ResultExceptionHandler<Boolean, DbException> handler); - - void canDeleteBlog(ResultExceptionHandler<Boolean, DbException> handler); + void loadBlog(ResultExceptionHandler<BlogItem, DbException> handler); void deleteBlog(ResultExceptionHandler<Void, DbException> handler); diff --git a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java index d70779e34ea17b04f149c318601f736fe6a3597e..ad7b7597d2e40387fb7acb3f0e1f518d68810fa8 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<Boolean, DbException> handler) { + public void loadBlog( + final ResultExceptionHandler<BlogItem, DbException> 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.<BlogPostHeader>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<Boolean, DbException> 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<Void, DbException> handler) { diff --git a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java index b3b87a113c3481a7e7eae63457ad6d3bf0ed5e17..4ff2695e3d8ddfbefbaba41dc4952c683048865a 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<BlogPostItem, DbException>( - 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<Collection<BlogPostItem>, DbException>( - getActivity()) { + listener) { @Override public void onResultUi(Collection<BlogPostItem> 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<Boolean, DbException>( - getActivity()) { + private void loadBlog() { + blogController.loadBlog( + new UiResultExceptionHandler<BlogItem, DbException>(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<Boolean, DbException>( - 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<Void, DbException>(getActivity()) { + new UiResultExceptionHandler<Void, DbException>(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 35c9f532be2a239fecd798a629955afb7d548a95..536d05f241f305b9043441521aed197fd464fce0 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<BlogPostHeader> headers, boolean ours) { + BlogItem(Blog blog, Collection<BlogPostHeader> 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 37f7dd30167d58160b40b35aa53922ea4543b219..b3e55f8482f2c812d82ea76fd228bd0cb294bbfa 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<BlogListAdapter.BlogViewHolder> { - private SortedList<BlogListItem> blogs = new SortedList<>( - BlogListItem.class, new SortedList.Callback<BlogListItem>() { + private SortedList<BlogItem> blogs = new SortedList<>( + BlogItem.class, new SortedList.Callback<BlogItem>() { @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<BlogListItem> items) { + public void addAll(Collection<BlogItem> 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 926ce7f37d951713625081a9b9eecc4ddd4a2bd3..ac7b4d504e440bafdf8254703239bd079bfca530 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); + + return super.onCreateView(inflater, container, savedInstanceState); + } - view = inflater.inflate(R.layout.fragment_blog_post, container, - false); - ui = new BlogPostViewHolder(view); - return view; + @Override + public String getUniqueTag() { + return TAG; } @Override @@ -72,65 +62,15 @@ public class BlogPostFragment extends BaseFragment { super.onStart(); blogController.loadBlogPost(postId, new UiResultExceptionHandler<BlogPostItem, DbException>( - 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 0000000000000000000000000000000000000000..f0a534b84714e8616dc77b07d671167ce76ebb12 --- /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<Collection<BlogPostItem>, DbException>( + listener) { + @Override + public void onResultUi(Collection<BlogPostItem> posts) { + onBlogPostsLoaded(select, posts); + } + + @Override + public void onExceptionUi(DbException exception) { + onBlogPostsLoadedException(exception); + } + }); + } + + void loadBlogPost(BlogPostHeader header) { + blogController.loadBlogPost(header, + new UiResultExceptionHandler<BlogPostItem, DbException>( + 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 9cc23d0797e69049f7c36d80808d9cf15c902d5c..13a1b3f177215d6b94b9bf23f689bf3891cd4316 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 9e6f5774877c5a43f99aef2f21780f02dba2d446..41791e962cd69bbca1106f91f53a2f99c51cdf97 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<Collection<BlogPostItem>, DbException>( - getActivity()) { + listener) { @Override public void onResultUi(Collection<BlogPostItem> 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<BlogPostItem, DbException>( - 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 0000000000000000000000000000000000000000..442a85ef9877a91c9ee3f94e557b1e76f182e289 --- /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<BlogPostItem, DbException>( + 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 0000000000000000000000000000000000000000..ab9444104c6b3a84496be03d97cbe9d81a26f941 --- /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<Collection<BlogPostItem>, DbException>( + listener) { + @Override + public void onResultUi(Collection<BlogPostItem> posts) { + onBlogPostsLoaded(select, posts); + } + + @Override + public void onExceptionUi(DbException exception) { + onBlogPostsLoadedException(exception); + } + }); + } + + void loadBlogPost(BlogPostHeader header) { + feedController.loadBlogPost(header, + new UiResultExceptionHandler<BlogPostItem, DbException>( + 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 923d520a65053f6d7adfc3c2e7ab03c977acf2f8..6b4afb89bac8121f8dfac031bc6c60b783cf2512 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 1fee6c6473124c6d2893cbcb5bd01d3e33fe6274..305273ba8d78e77c34a2b93398a2436fcf07a0d1 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<BlogPostItem, DbException>( - 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<Void, DbException>(getActivity()) { + new UiResultExceptionHandler<Void, DbException>(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 ba2eab195ade5bd7c2ded6a9445041b025d70aec..1f9cab0937b755488ff56313f564d2aa1553df91 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<R, E extends Exception> implements ResultExceptionHandler<R, E> { - 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 0616b82a2bf2ad263575803c79f1d765a4826315..03c68d7ac6c694882f37e923f764c4ff93f0bba2 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<R> implements ResultHandler<R> { - 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 c19bcf1eed31cafa16714666da3283ab0a1901cb..d6f4ed363a1a125c5e88fe74de44e4f6fc779f2a 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 610e480e51bb684d2a03cef9f590dc92bb484436..66593d0f8281c64d45d9ea9517bf9655840756c1 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 6920ebbf15dd2388c18367e4d3e89b487da32524..45ab5d34741fc519a45c92f2c371b99851b1b004 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,