This commit adds badges to the forum list that indicate unread posts.

It does so by extending the compound view `TextAvatarView` and provides
convienient setters that take care of the required UI changes.
The new badge can also be used to indicate a problem with the forum.

Closes #408
This commit is contained in:
Torsten Grote
2016-06-01 13:39:57 -03:00
parent bba7083660
commit 5d06f42000
12 changed files with 142 additions and 58 deletions

View File

@@ -8,7 +8,8 @@
<padding <padding
android:left="@dimen/unread_bubble_padding_horizontal" android:left="@dimen/unread_bubble_padding_horizontal"
android:right="@dimen/unread_bubble_padding_horizontal"/> android:right="@dimen/unread_bubble_padding_horizontal"
android:bottom="1px"/>
<solid <solid
android:color="@color/briar_primary"/> android:color="@color/briar_primary"/>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:radius="@dimen/unread_bubble_size"/>
<padding
android:left="@dimen/unread_bubble_padding_horizontal"
android:right="@dimen/unread_bubble_padding_horizontal"/>
<solid
android:color="@color/briar_gold"/>
<stroke
android:color="@color/briar_primary"
android:width="@dimen/avatar_border_width"/>
</shape>

View File

@@ -11,8 +11,8 @@
<org.briarproject.android.util.TextAvatarView <org.briarproject.android.util.TextAvatarView
android:id="@+id/avatarView" android:id="@+id/avatarView"
android:layout_width="@dimen/avatar_forum_size" android:layout_width="@dimen/listitem_picture_frame_size"
android:layout_height="@dimen/avatar_forum_size" android:layout_height="@dimen/listitem_picture_frame_size"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_marginRight="@dimen/listitem_horizontal_margin" android:layout_marginRight="@dimen/listitem_horizontal_margin"

View File

@@ -1,68 +1,68 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/listitem_horizontal_margin" android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin" android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:paddingTop="@dimen/listitem_horizontal_margin"
android:background="?attr/selectableItemBackground"> android:background="?attr/selectableItemBackground">
<org.briarproject.android.util.TextAvatarView <org.briarproject.android.util.TextAvatarView
android:id="@+id/avatarView" android:id="@+id/avatarView"
android:layout_height="@dimen/avatar_forum_size" android:layout_width="@dimen/listitem_picture_frame_size"
android:layout_width="@dimen/avatar_forum_size" android:layout_height="@dimen/listitem_picture_frame_size"
android:layout_marginRight="@dimen/listitem_horizontal_margin"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
/> android:layout_centerVertical="true"
android:layout_marginRight="@dimen/listitem_horizontal_margin"/>
<TextView <TextView
android:id="@+id/forumNameView" android:id="@+id/forumNameView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginTop="@dimen/listitem_horizontal_margin"
android:layout_toEndOf="@+id/avatarView"
android:layout_toRightOf="@+id/avatarView"
android:maxLines="2" android:maxLines="2"
android:textColor="@color/briar_text_primary" android:textColor="@color/briar_text_primary"
android:textSize="@dimen/text_size_medium" android:textSize="@dimen/text_size_medium"
tools:text="This is a name of a forum" tools:text="This is a name of a forum"/>
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/avatarView"
android:layout_toEndOf="@+id/avatarView"/>
<TextView <TextView
android:id="@+id/unreadView" android:id="@+id/unreadView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/margin_medium" android:layout_below="@+id/forumNameView"
android:layout_toEndOf="@+id/avatarView"
android:layout_toRightOf="@+id/avatarView"
android:paddingBottom="@dimen/listitem_horizontal_margin" android:paddingBottom="@dimen/listitem_horizontal_margin"
android:paddingTop="@dimen/margin_medium"
android:textColor="@color/briar_text_secondary" android:textColor="@color/briar_text_secondary"
android:textSize="@dimen/text_size_small" android:textSize="@dimen/text_size_small"
android:text="@string/no_unread_posts" tools:text="1337 posts"/>
android:layout_below="@+id/forumNameView"
android:layout_toRightOf="@+id/avatarView"
android:layout_toEndOf="@+id/avatarView"/>
<TextView <TextView
android:id="@+id/dateView" android:id="@+id/dateView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/forumNameView" android:layout_below="@+id/forumNameView"
android:paddingTop="@dimen/margin_medium"
android:paddingBottom="@dimen/listitem_horizontal_margin"
android:layout_marginRight="@dimen/listitem_horizontal_margin"
android:layout_marginEnd="@dimen/listitem_horizontal_margin" android:layout_marginEnd="@dimen/listitem_horizontal_margin"
android:layout_marginRight="@dimen/listitem_horizontal_margin"
android:paddingBottom="@dimen/listitem_horizontal_margin"
android:paddingTop="@dimen/margin_medium"
android:textColor="@color/briar_text_secondary" android:textColor="@color/briar_text_secondary"
android:textSize="@dimen/text_size_small" android:textSize="@dimen/text_size_small"
tools:text="Dec 24"/> tools:text="Dec 24"/>
<View style="@style/Divider.ForumList" <View
android:layout_below="@+id/unreadView" style="@style/Divider.ForumList"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"/> android:layout_alignParentStart="true"
android:layout_below="@+id/unreadView"/>
</RelativeLayout> </RelativeLayout>

View File

@@ -2,14 +2,15 @@
<merge <merge
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools"
tools:showIn="@layout/list_item_forum">
<de.hdodenhof.circleimageview.CircleImageView <de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarBackground" android:id="@+id/avatarBackground"
style="@style/BriarAvatar" style="@style/BriarAvatar"
android:layout_width="@dimen/avatar_forum_size" android:layout_width="@dimen/avatar_forum_size"
android:layout_height="@dimen/avatar_forum_size" android:layout_height="@dimen/avatar_forum_size"
android:layout_gravity="center" android:layout_gravity="bottom|left"
android:src="@android:color/transparent" android:src="@android:color/transparent"
app:civ_fill_color="@color/briar_button_positive"/> app:civ_fill_color="@color/briar_button_positive"/>
@@ -18,13 +19,28 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginRight="@dimen/listitem_picture_frame_offset"
android:layout_marginTop="@dimen/listitem_picture_frame_offset"
android:maxLength="1" android:maxLength="1"
android:shadowColor="@color/forum_avatar_shadow" android:shadowColor="@color/forum_avatar_shadow"
android:shadowDx="0" android:shadowDx="0"
android:shadowDy="1.5" android:shadowDy="1.5"
android:shadowRadius="1.5" android:shadowRadius="1.5"
android:textColor="@color/briar_text_primary_inverse" android:textColor="@color/briar_text_primary_inverse"
android:textSize="30sp" android:textSize="@dimen/avatar_text_size"
tools:text="T"/> tools:text="T"/>
<TextView
android:id="@+id/unreadCountView"
android:layout_width="wrap_content"
android:layout_height="@dimen/unread_bubble_size"
android:layout_gravity="right|top"
android:background="@drawable/bubble"
android:gravity="center"
android:minWidth="@dimen/unread_bubble_size"
android:textColor="@color/briar_text_primary_inverse"
android:textSize="@dimen/unread_bubble_text_size"
android:textStyle="bold"
tools:text="12"/>
</merge> </merge>

View File

@@ -30,7 +30,7 @@
<color name="briar_text_primary">@color/briar_primary</color> <color name="briar_text_primary">@color/briar_primary</color>
<color name="briar_text_primary_inverse">#ffffff</color> <color name="briar_text_primary_inverse">#ffffff</color>
<color name="briar_text_secondary">#333333</color> <color name="briar_text_secondary">#333333</color>
<color name="briar_text_tertiary">#333333</color> <color name="briar_text_tertiary">#FF78909C</color>
<color name="briar_button_positive">#06b9ff</color> <color name="briar_button_positive">#06b9ff</color>
<color name="briar_button_negative">#ff0000</color> <color name="briar_button_negative">#ff0000</color>

View File

@@ -27,11 +27,13 @@
<dimen name="listitem_height_contact_selector">68dp</dimen> <dimen name="listitem_height_contact_selector">68dp</dimen>
<dimen name="listitem_picture_size">48dp</dimen> <dimen name="listitem_picture_size">48dp</dimen>
<dimen name="listitem_picture_size_small">23dp</dimen> <dimen name="listitem_picture_size_small">23dp</dimen>
<dimen name="listitem_picture_frame_size">50dp</dimen> <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="listitem_selectable_picture_size">40dp</dimen>
<dimen name="dropdown_picture_size">32dp</dimen> <dimen name="dropdown_picture_size">32dp</dimen>
<dimen name="avatar_forum_size">48dp</dimen> <dimen name="avatar_forum_size">48dp</dimen>
<dimen name="avatar_border_width">2dp</dimen> <dimen name="avatar_border_width">2dp</dimen>
<dimen name="avatar_text_size">30sp</dimen>
<dimen name="unread_bubble_text_size">12sp</dimen> <dimen name="unread_bubble_text_size">12sp</dimen>
<dimen name="unread_bubble_border_width">2dp</dimen> <dimen name="unread_bubble_border_width">2dp</dimen>

View File

@@ -81,12 +81,15 @@
<string name="forum_leave">Leave Forum</string> <string name="forum_leave">Leave Forum</string>
<string name="forum_left_toast">Left Forum</string> <string name="forum_left_toast">Left Forum</string>
<string name="forum_sharing_status">Sharing Status</string> <string name="forum_sharing_status">Sharing Status</string>
<string name="no_forum_posts">No posts</string> <string name="forum_no_posts">No posts</string>
<string name="no_unread_posts">no unread posts</string>
<plurals name="unread_posts"> <plurals name="unread_posts">
<item quantity="one">%d unread post</item> <item quantity="one">%d unread post</item>
<item quantity="other">%d unread posts</item> <item quantity="other">%d unread posts</item>
</plurals> </plurals>
<plurals name="forum_posts">
<item quantity="one">%d post</item>
<item quantity="other">%d posts</item>
</plurals>
<string name="create_forum_title">New Forum</string> <string name="create_forum_title">New Forum</string>
<string name="choose_forum_name">Choose a name for your forum:</string> <string name="choose_forum_name">Choose a name for your forum:</string>
<string name="create_forum_button">Create Forum</string> <string name="create_forum_button">Create Forum</string>

View File

@@ -19,10 +19,12 @@ import org.briarproject.api.sync.GroupId;
import java.util.Collection; import java.util.Collection;
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.BriarActivity.GROUP_ID;
import static org.briarproject.android.forum.ForumActivity.FORUM_NAME; import static org.briarproject.android.forum.ForumActivity.FORUM_NAME;
public class ForumListAdapter extends class ForumListAdapter extends
RecyclerView.Adapter<ForumListAdapter.ForumViewHolder> { RecyclerView.Adapter<ForumListAdapter.ForumViewHolder> {
private SortedList<ForumListItem> forums = new SortedList<>( private SortedList<ForumListItem> forums = new SortedList<>(
@@ -76,7 +78,7 @@ public class ForumListAdapter extends
private final Context ctx; private final Context ctx;
public ForumListAdapter(Context ctx) { ForumListAdapter(Context ctx) {
this.ctx = ctx; this.ctx = ctx;
} }
@@ -94,31 +96,36 @@ public class ForumListAdapter extends
// Avatar // Avatar
ui.avatar.setText(item.getForum().getName().substring(0, 1)); ui.avatar.setText(item.getForum().getName().substring(0, 1));
ui.avatar.setBackgroundBytes(item.getForum().getId().getBytes()); ui.avatar.setBackgroundBytes(item.getForum().getId().getBytes());
ui.avatar.setUnreadCount(item.getUnreadCount());
// Forum Name // Forum Name
ui.name.setText(item.getForum().getName()); ui.name.setText(item.getForum().getName());
// Unread Count // Post Count
int unread = item.getUnreadCount(); int postCount = item.getPostCount();
if (unread > 0) { if (postCount > 0) {
ui.unread.setText(ctx.getResources() ui.unread.setText(ctx.getResources()
.getQuantityString(R.plurals.unread_posts, unread, unread)); .getQuantityString(R.plurals.forum_posts, postCount,
postCount));
ui.unread.setTextColor( ui.unread.setTextColor(
ContextCompat.getColor(ctx, R.color.briar_button_positive)); ContextCompat
.getColor(ctx, R.color.briar_text_secondary));
} else { } else {
ui.unread.setText(ctx.getString(R.string.no_unread_posts)); ui.avatar.setProblem(true);
ui.unread.setText(ctx.getString(R.string.forum_no_posts));
ui.unread.setTextColor( ui.unread.setTextColor(
ContextCompat.getColor(ctx, R.color.briar_text_secondary)); ContextCompat
.getColor(ctx, R.color.briar_text_tertiary));
} }
// Date or "No Posts" // Date
if (item.isEmpty()) { if (item.isEmpty()) {
ui.date.setVisibility(View.GONE); ui.date.setVisibility(GONE);
} else { } else {
long timestamp = item.getTimestamp(); long timestamp = item.getTimestamp();
ui.date.setText( ui.date.setText(
DateUtils.getRelativeTimeSpanString(ctx, timestamp)); DateUtils.getRelativeTimeSpanString(ctx, timestamp));
ui.date.setVisibility(View.VISIBLE); ui.date.setVisibility(VISIBLE);
} }
// Open Forum on Click // Open Forum on Click
@@ -158,7 +165,7 @@ public class ForumListAdapter extends
forums.addAll(items); forums.addAll(items);
} }
public void updateItem(ForumListItem item) { void updateItem(ForumListItem item) {
ForumListItem oldItem = getItem(item.getForum().getGroup().getId()); ForumListItem oldItem = getItem(item.getForum().getGroup().getId());
int position = forums.indexOf(oldItem); int position = forums.indexOf(oldItem);
forums.updateItemAt(position, item); forums.updateItemAt(position, item);
@@ -176,7 +183,7 @@ public class ForumListAdapter extends
return forums.size() == 0; return forums.size() == 0;
} }
protected static class ForumViewHolder extends RecyclerView.ViewHolder { static class ForumViewHolder extends RecyclerView.ViewHolder {
private final ViewGroup layout; private final ViewGroup layout;
private final TextAvatarView avatar; private final TextAvatarView avatar;
@@ -184,7 +191,7 @@ public class ForumListAdapter extends
private final TextView unread; private final TextView unread;
private final TextView date; private final TextView date;
public ForumViewHolder(View v) { ForumViewHolder(View v) {
super(v); super(v);
layout = (ViewGroup) v; layout = (ViewGroup) v;

View File

@@ -14,8 +14,6 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.AndroidComponent;
import org.briarproject.android.fragment.BaseEventFragment; import org.briarproject.android.fragment.BaseEventFragment;
import org.briarproject.android.util.BriarRecyclerView; import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;

View File

@@ -9,6 +9,7 @@ class ForumListItem {
private final Forum forum; private final Forum forum;
private final boolean empty; private final boolean empty;
private final int postCount;
private final long timestamp; private final long timestamp;
private final int unread; private final int unread;
@@ -16,6 +17,7 @@ class ForumListItem {
this.forum = forum; this.forum = forum;
empty = headers.isEmpty(); empty = headers.isEmpty();
if (empty) { if (empty) {
postCount = 0;
timestamp = 0; timestamp = 0;
unread = 0; unread = 0;
} else { } else {
@@ -29,6 +31,7 @@ class ForumListItem {
} }
if (!h.isRead()) unread++; if (!h.isRead()) unread++;
} }
this.postCount = headers.size();
this.timestamp = newest.getTimestamp(); this.timestamp = newest.getTimestamp();
this.unread = unread; this.unread = unread;
} }
@@ -42,6 +45,10 @@ class ForumListItem {
return empty; return empty;
} }
int getPostCount() {
return postCount;
}
long getTimestamp() { long getTimestamp() {
return timestamp; return timestamp;
} }

View File

@@ -1,13 +1,13 @@
package org.briarproject.android.util; package org.briarproject.android.util;
import android.content.Context; import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.PorterDuff; import android.support.v4.content.ContextCompat;
import android.support.v7.widget.AppCompatTextView; import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.TextView;
import org.briarproject.R; import org.briarproject.R;
@@ -15,8 +15,10 @@ import de.hdodenhof.circleimageview.CircleImageView;
public class TextAvatarView extends FrameLayout { public class TextAvatarView extends FrameLayout {
final private AppCompatTextView textView; final private AppCompatTextView character;
final private CircleImageView backgroundView; final private CircleImageView background;
final private TextView badge;
private int unreadCount;
public TextAvatarView(Context context, AttributeSet attrs) { public TextAvatarView(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
@@ -25,8 +27,10 @@ public class TextAvatarView extends FrameLayout {
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater inflater
.inflate(R.layout.text_avatar_view, this, true); .inflate(R.layout.text_avatar_view, this, true);
textView = (AppCompatTextView) findViewById(R.id.textAvatarView); character = (AppCompatTextView) findViewById(R.id.textAvatarView);
backgroundView = (CircleImageView) findViewById(R.id.avatarBackground); background = (CircleImageView) findViewById(R.id.avatarBackground);
badge = (TextView) findViewById(R.id.unreadCountView);
badge.setVisibility(INVISIBLE);
} }
public TextAvatarView(Context context) { public TextAvatarView(Context context) {
@@ -34,7 +38,32 @@ public class TextAvatarView extends FrameLayout {
} }
public void setText(String text) { public void setText(String text) {
textView.setText(text); character.setText(text);
}
public void setUnreadCount(int count) {
if (count > 0) {
this.unreadCount = count;
badge.setBackgroundResource(R.drawable.bubble);
badge.setText(String.valueOf(count));
badge.setTextColor(ContextCompat.getColor(getContext(), R.color.briar_text_primary_inverse));
badge.setVisibility(VISIBLE);
} else {
badge.setVisibility(INVISIBLE);
}
}
public void setProblem(boolean problem) {
if (problem) {
badge.setBackgroundResource(R.drawable.bubble_problem);
badge.setText("!");
badge.setTextColor(ContextCompat.getColor(getContext(), R.color.briar_primary));
badge.setVisibility(VISIBLE);
} else if (unreadCount > 0) {
setUnreadCount(unreadCount);
} else {
badge.setVisibility(INVISIBLE);
}
} }
public void setBackgroundBytes(byte[] bytes) { public void setBackgroundBytes(byte[] bytes) {
@@ -43,7 +72,7 @@ public class TextAvatarView extends FrameLayout {
int b = getByte(bytes, 2) * 3 / 4 + 96; int b = getByte(bytes, 2) * 3 / 4 + 96;
int color = Color.rgb(r, g, b); int color = Color.rgb(r, g, b);
backgroundView.setFillColor(color); background.setFillColor(color);
} }
private byte getByte(byte[] bytes, int index) { private byte getByte(byte[] bytes, int index) {