diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java index 37a6c720c..1967e2cd0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumActivity.java @@ -4,7 +4,6 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.LayoutRes; import android.support.annotation.StringRes; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; @@ -90,12 +89,6 @@ public class ForumActivity extends setTitle(forum.getName()); } - @Override - @LayoutRes - protected int getLayout() { - return R.layout.activity_threaded_conversation; - } - @Override protected ThreadItemAdapter createAdapter( LinearLayoutManager layoutManager) { @@ -158,12 +151,6 @@ public class ForumActivity extends return R.string.forum_new_entry_posted; } - @Override - @StringRes - protected int getItemReceivedString() { - return R.string.forum_new_entry_received; - } - private void showUnsubscribeDialog() { OnClickListener okListener = new OnClickListener() { @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java index 28e477e6e..8abb679a7 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupActivity.java @@ -4,7 +4,6 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.LayoutRes; import android.support.annotation.StringRes; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; @@ -92,12 +91,6 @@ public class GroupActivity extends setGroupEnabled(false); } - @Override - @LayoutRes - protected int getLayout() { - return R.layout.activity_threaded_conversation; - } - @Override protected GroupMessageAdapter createAdapter( LinearLayoutManager layoutManager) { @@ -205,12 +198,6 @@ public class GroupActivity extends return R.string.groups_message_sent; } - @Override - @StringRes - protected int getItemReceivedString() { - return R.string.groups_message_received; - } - @Override public void onReplyClick(GroupMessageItem item) { if (!isDissolved) super.onReplyClick(item); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/BaseThreadItemViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/BaseThreadItemViewHolder.java index 6bdbb9c77..a01865237 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/BaseThreadItemViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/BaseThreadItemViewHolder.java @@ -11,6 +11,7 @@ import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; import android.widget.TextView; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @@ -19,6 +20,9 @@ import org.briarproject.briar.R; import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener; import org.briarproject.briar.android.view.AuthorView; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; + @UiThread @NotNullByDefault public abstract class BaseThreadItemViewHolder @@ -48,9 +52,9 @@ public abstract class BaseThreadItemViewHolder textView.setText(StringUtils.trim(item.getText())); if (pos == 0) { - topDivider.setVisibility(View.INVISIBLE); + topDivider.setVisibility(INVISIBLE); } else { - topDivider.setVisibility(View.VISIBLE); + topDivider.setVisibility(VISIBLE); } author.setAuthor(item.getAuthor()); @@ -58,16 +62,13 @@ public abstract class BaseThreadItemViewHolder author.setAuthorStatus(item.getStatus()); if (item.equals(adapter.getReplyItem())) { - layout.setBackgroundColor(ContextCompat - .getColor(getContext(), R.color.forum_cell_highlight)); - } else if (item.equals(adapter.getAddedItem())) { - layout.setBackgroundColor(ContextCompat - .getColor(getContext(), R.color.forum_cell_highlight)); - animateFadeOut(adapter, adapter.getAddedItem()); - adapter.clearAddedItem(); + layout.setActivated(true); + } else if (!item.isRead()) { + layout.setActivated(true); + animateFadeOut(adapter, item); + listener.onUnreadItemVisible(item); } else { - layout.setBackgroundColor(ContextCompat - .getColor(getContext(), R.color.window_background)); + layout.setActivated(false); } } @@ -77,31 +78,29 @@ public abstract class BaseThreadItemViewHolder setIsRecyclable(false); ValueAnimator anim = new ValueAnimator(); adapter.addAnimatingItem(addedItem, anim); - ColorDrawable viewColor = (ColorDrawable) layout.getBackground(); + ColorDrawable viewColor = new ColorDrawable(ContextCompat + .getColor(getContext(), R.color.forum_cell_highlight)); anim.setIntValues(viewColor.getColor(), ContextCompat .getColor(getContext(), R.color.window_background)); anim.setEvaluator(new ArgbEvaluator()); + anim.setInterpolator(new AccelerateInterpolator()); anim.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { - } - @Override public void onAnimationEnd(Animator animation) { + layout.setBackgroundResource( + R.drawable.list_item_thread_background); + layout.setActivated(false); setIsRecyclable(true); adapter.removeAnimatingItem(addedItem); } - @Override public void onAnimationCancel(Animator animation) { - setIsRecyclable(true); - adapter.removeAnimatingItem(addedItem); } - @Override public void onAnimationRepeat(Animator animation) { - } }); anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java index a22316a35..f663a219c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java @@ -1,6 +1,7 @@ package org.briarproject.briar.android.threaded; import android.animation.ValueAnimator; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -18,8 +19,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.annotation.Nullable; - import static android.support.v7.widget.RecyclerView.NO_POSITION; public class ThreadItemAdapter @@ -33,10 +32,8 @@ public class ThreadItemAdapter private final ThreadItemListener listener; private final LinearLayoutManager layoutManager; - // highlight not dependant on time + @Nullable private I replyItem; - // temporary highlight - private I addedItem; private volatile int revision = 0; @@ -58,7 +55,6 @@ public class ThreadItemAdapter public void onBindViewHolder(BaseThreadItemViewHolder ui, int position) { I item = getVisibleItem(position); if (item == null) return; - listener.onItemVisible(item); ui.bind(this, listener, item, position); } @@ -72,6 +68,7 @@ public class ThreadItemAdapter return getVisiblePos(null); } + @Nullable I getReplyItem() { return replyItem; } @@ -84,7 +81,6 @@ public class ThreadItemAdapter public void add(I item) { items.add(item); - addedItem = item; if (item.getParentId() == null) { notifyItemInserted(getVisiblePos(item)); } else { @@ -262,10 +258,6 @@ public class ThreadItemAdapter return null; } - boolean isVisible(I item) { - return getVisiblePos(item) != NO_POSITION; - } - /** * Returns the visible position of the given item. * @@ -296,14 +288,6 @@ public class ThreadItemAdapter return item == null ? visibleCounter : NO_POSITION; } - I getAddedItem() { - return addedItem; - } - - void clearAddedItem() { - addedItem = null; - } - void addAnimatingItem(I item, ValueAnimator anim) { animatingItems.put(item, anim); } @@ -323,10 +307,108 @@ public class ThreadItemAdapter revision++; } - public interface ThreadItemListener { - void onItemVisible(I item); + /** + * Gets the number of unread items above and below the current view port. + * + * Attention: Do not call this when the list is still scrolling, + * because then the view port is unknown. + */ + public UnreadCount getUnreadCount() { + final int positionTop = layoutManager.findFirstVisibleItemPosition(); + final int positionBottom = layoutManager.findLastVisibleItemPosition(); + if (positionTop == NO_POSITION && positionBottom == NO_POSITION) + return new UnreadCount(0, 0); + + int unreadCounterTop = 0, unreadCounterBottom = 0; + int visibleCounter = 0; + int levelLimit = UNDEFINED; + for (I i : items) { + if (levelLimit >= 0) { + if (i.getLevel() > levelLimit) { + // skip all the items below a non visible branch + continue; + } + levelLimit = UNDEFINED; + } + if (visibleCounter > positionBottom && !i.isRead()) { + unreadCounterBottom++; + } else if (visibleCounter < positionTop && !i.isRead()) { + unreadCounterTop++; + } else if (!i.isShowingDescendants()) { + levelLimit = i.getLevel(); + } + visibleCounter++; + } + return new UnreadCount(unreadCounterTop, unreadCounterBottom); + } + + /** + * Returns the position of the first unread item below the current viewport + */ + public int getVisibleUnreadPosBottom() { + final int positionBottom = layoutManager.findLastVisibleItemPosition(); + int visibleCounter = 0; + int levelLimit = UNDEFINED; + for (I i : items) { + if (levelLimit >= 0) { + if (i.getLevel() > levelLimit) { + // skip all the items below a non visible branch + continue; + } + levelLimit = UNDEFINED; + } + if (visibleCounter > positionBottom && !i.isRead()) { + return visibleCounter; + } else if (!i.isShowingDescendants()) { + levelLimit = i.getLevel(); + } + visibleCounter++; + } + return NO_POSITION; + } + + /** + * Returns the position of the first unread item above the current viewport + */ + public int getVisibleUnreadPosTop() { + final int positionTop = layoutManager.findFirstVisibleItemPosition(); + int position = NO_POSITION; + int visibleCounter = 0; + int levelLimit = UNDEFINED; + for (I i : items) { + if (levelLimit >= 0) { + if (i.getLevel() > levelLimit) { + // skip all the items below a non visible branch + continue; + } + levelLimit = UNDEFINED; + } + if (visibleCounter < positionTop && !i.isRead()) { + position = visibleCounter; + } if (visibleCounter >= positionTop) { + return position; + } else if (!i.isShowingDescendants()) { + levelLimit = i.getLevel(); + } + visibleCounter++; + } + return NO_POSITION; + } + + static class UnreadCount { + final int top, bottom; + + private UnreadCount(int top, int bottom) { + this.top = top; + this.bottom = bottom; + } + } + + public interface ThreadItemListener { + void onUnreadItemVisible(I item); void onReplyClick(I item); } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java index 0f83203db..c43ce66e0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java @@ -3,13 +3,12 @@ package org.briarproject.briar.android.threaded; import android.content.Intent; import android.os.Bundle; import android.support.annotation.CallSuper; -import android.support.annotation.LayoutRes; import android.support.annotation.StringRes; import android.support.annotation.UiThread; import android.support.design.widget.Snackbar; -import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.view.MenuItem; import android.view.View; @@ -30,6 +29,7 @@ import org.briarproject.briar.android.threaded.ThreadListController.ThreadListLi import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.TextInputView; import org.briarproject.briar.android.view.TextInputView.TextInputListener; +import org.briarproject.briar.android.view.UnreadMessageButton; import org.briarproject.briar.api.client.NamedGroup; import org.briarproject.briar.api.client.PostHeader; @@ -40,8 +40,12 @@ import javax.annotation.Nullable; import javax.inject.Inject; import static android.support.design.widget.Snackbar.make; +import static android.support.v7.widget.RecyclerView.NO_POSITION; +import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE; import static android.view.View.GONE; import static android.view.View.VISIBLE; +import static java.util.logging.Level.INFO; +import static org.briarproject.briar.android.threaded.ThreadItemAdapter.UnreadCount; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -60,6 +64,7 @@ public abstract class ThreadListActivity getController(); @@ -72,7 +77,7 @@ public abstract class ThreadListActivity + + + + + + \ No newline at end of file diff --git a/briar-android/src/main/res/layout/activity_threaded_conversation.xml b/briar-android/src/main/res/layout/activity_threaded_conversation.xml index 913a17560..0252bbec9 100644 --- a/briar-android/src/main/res/layout/activity_threaded_conversation.xml +++ b/briar-android/src/main/res/layout/activity_threaded_conversation.xml @@ -10,7 +10,7 @@ - @@ -27,7 +27,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|right" - app:layout_anchorGravity="top|right" app:direction="up"/> - + @@ -113,6 +114,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@+id/author" + android:layout_alignParentRight="true" android:layout_toLeftOf="@+id/chevron" android:text="@string/btn_reply" android:textSize="@dimen/text_size_tiny"/> @@ -127,7 +129,8 @@ android:clickable="true" android:padding="@dimen/margin_medium" android:scaleType="center" - android:src="@drawable/selector_chevron"/> + android:src="@drawable/selector_chevron" + android:visibility="gone"/> Group invitation has been sent Compose Message Message sent - Message received Member List Invite Members You created the group @@ -218,7 +217,6 @@ New Forum Post Forum entry posted - New forum entry New Entry New Reply Reply