Merge branch '121-new-available-forums-ui' into 'master'

Modernize AvailableForumsActivity

Turn list of available forums into a `BriarRecyclerView` with XML layout.
Allow to respond to forum invitations from the list of available forums.
The user can either accept or decline an invitation.

![device-2016-05-03-141150](/uploads/0998877d47fb7a3eafe3a90f917d8716/device-2016-05-03-141150.png)

See merge request !172
This commit is contained in:
akwizgran
2016-05-11 08:57:56 +00:00
7 changed files with 237 additions and 93 deletions

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<org.briarproject.android.util.BriarRecyclerView
android:id="@+id/availableForumsView"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:paddingTop="@dimen/listitem_horizontal_margin"
android:background="?attr/selectableItemBackground">
<TextView
android:id="@+id/forumNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:textColor="@android:color/primary_text_light"
android:textSize="@dimen/text_size_medium"
tools:text="This is a name of a forum that is available"/>
<TextView
android:id="@+id/sharedByView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/forumNameView"
android:layout_marginBottom="-8dp"
android:paddingTop="@dimen/margin_medium"
android:textColor="@android:color/secondary_text_light"
android:textSize="@dimen/text_size_small"
tools:text="Shared by Megalox"/>
<Button
android:id="@+id/acceptButton"
style="@style/BriarButtonFlat.Positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_button_accept"
android:layout_below="@+id/sharedByView"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"/>
<Button
android:id="@+id/declineButton"
style="@style/BriarButtonFlat.Negative"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_button_decline"
android:layout_below="@+id/sharedByView"
android:layout_toLeftOf="@+id/acceptButton"
android:layout_toStartOf="@+id/acceptButton"/>
<View style="@style/Divider.ForumList"
android:layout_below="@+id/acceptButton"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"/>
</RelativeLayout>

View File

@@ -102,6 +102,7 @@
<string name="forum_post_hint">Type forum post</string>
<string name="available_forums_title">Available Forums</string>
<string name="forum_joined_toast">Joined Forum</string>
<string name="forum_declined_toast">Forum Invitation Declined</string>
<string name="shared_by_format">Shared by %s</string>
<string name="no_contacts_prompt">You don\'t have any contacts. Add a contact now?</string>
<string name="add_button">Add</string>

View File

@@ -21,14 +21,14 @@ import static android.support.v7.util.SortedList.INVALID_POSITION;
public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.BaseContactHolder>
extends RecyclerView.Adapter<VH> {
protected SortedList<ContactListItem> contacts;
protected final SortedList<ContactListItem> contacts;
protected final OnItemClickListener listener;
protected Context ctx;
public BaseContactListAdapter(Context context, OnItemClickListener listener) {
this.ctx = context;
this.listener = listener;
this.contacts = new SortedList<ContactListItem>(ContactListItem.class,
this.contacts = new SortedList<>(ContactListItem.class,
new SortedListCallBacks());
}

View File

@@ -1,34 +1,30 @@
package org.briarproject.android.forum;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.support.v7.widget.LinearLayoutManager;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.AndroidComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.util.ListLoadingProgressBar;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.event.GroupAddedEvent;
import org.briarproject.api.event.GroupRemovedEvent;
import org.briarproject.api.event.MessageValidatedEvent;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.sync.ClientId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import javax.inject.Inject;
@@ -36,16 +32,15 @@ import javax.inject.Inject;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
import static org.briarproject.android.forum.AvailableForumsAdapter.AvailableForumClickListener;
public class AvailableForumsActivity extends BriarActivity
implements EventListener, OnItemClickListener {
implements EventListener, AvailableForumClickListener {
private static final Logger LOG =
Logger.getLogger(AvailableForumsActivity.class.getName());
private AvailableForumsAdapter adapter = null;
private ListView list = null;
private AvailableForumsAdapter adapter;
// Fields that are accessed from background threads must be volatile
@Inject protected volatile ForumManager forumManager;
@@ -56,15 +51,13 @@ implements EventListener, OnItemClickListener {
public void onCreate(Bundle state) {
super.onCreate(state);
adapter = new AvailableForumsAdapter(this);
list = new ListView(this);
list.setLayoutParams(MATCH_MATCH);
list.setAdapter(adapter);
list.setOnItemClickListener(this);
setContentView(R.layout.activity_available_forums);
// Show a progress bar while the list is loading
ListLoadingProgressBar loading = new ListLoadingProgressBar(this);
setContentView(loading);
adapter = new AvailableForumsAdapter(this, this);
BriarRecyclerView list =
(BriarRecyclerView) findViewById(R.id.availableForumsView);
list.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(adapter);
}
@Override
@@ -113,11 +106,12 @@ implements EventListener, OnItemClickListener {
LOG.info("No forums available, finishing");
finish();
} else {
setContentView(list);
adapter.clear();
List<AvailableForumsItem> list =
new ArrayList<>(available.size());
for (ForumContacts f : available)
adapter.add(new AvailableForumsItem(f));
adapter.sort(AvailableForumsItemComparator.INSTANCE);
list.add(new AvailableForumsItem(f));
adapter.addAll(list);
}
}
});
@@ -145,37 +139,33 @@ implements EventListener, OnItemClickListener {
LOG.info("Forum removed, reloading");
loadForums();
}
} else if (e instanceof MessageValidatedEvent) {
MessageValidatedEvent m = (MessageValidatedEvent) e;
ClientId c = m.getClientId();
if (m.isValid() && !m.isLocal()
&& c.equals(forumSharingManager.getClientId())) {
LOG.info("Available forums updated, reloading");
loadForums();
}
} else if (e instanceof ForumInvitationReceivedEvent) {
LOG.info("Available forums updated, reloading");
loadForums();
}
}
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
AvailableForumsItem item = adapter.getItem(position);
Collection<ContactId> shared = new ArrayList<>();
for (Contact c : item.getContacts()) shared.add(c.getId());
subscribe(item.getForum(), shared);
String joined = getString(R.string.forum_joined_toast);
Toast.makeText(this, joined, LENGTH_SHORT).show();
public void onItemClick(AvailableForumsItem item, boolean accept) {
respondToInvitation(item.getForum(), accept);
// show toast
int res = R.string.forum_declined_toast;
if (accept) res = R.string.forum_joined_toast;
Toast.makeText(this, res, LENGTH_SHORT).show();
}
private void subscribe(final Forum f, final Collection<ContactId> shared) {
private void respondToInvitation(final Forum f, final boolean accept) {
runOnDbThread(new Runnable() {
public void run() {
try {
forumManager.addForum(f);
forumSharingManager.respondToInvitation(f, accept);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
loadForums();
}
});
}
}

View File

@@ -1,57 +1,160 @@
package org.briarproject.android.forum;
import android.content.Context;
import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.Button;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.contact.Contact;
import org.briarproject.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import static android.text.TextUtils.TruncateAt.END;
import static android.widget.LinearLayout.VERTICAL;
class AvailableForumsAdapter extends
RecyclerView.Adapter<AvailableForumsAdapter.AvailableForumViewHolder> {
class AvailableForumsAdapter extends ArrayAdapter<AvailableForumsItem> {
private final Context ctx;
private final AvailableForumClickListener listener;
private final SortedList<AvailableForumsItem> forums =
new SortedList<>(AvailableForumsItem.class,
new SortedListCallBacks());
private final int pad;
AvailableForumsAdapter(Context ctx,
AvailableForumClickListener listener) {
AvailableForumsAdapter(Context ctx) {
super(ctx, android.R.layout.simple_expandable_list_item_1,
new ArrayList<AvailableForumsItem>());
pad = LayoutUtils.getPadding(ctx);
this.ctx = ctx;
this.listener = listener;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
AvailableForumsItem item = getItem(position);
Context ctx = getContext();
public AvailableForumViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
LinearLayout layout = new LinearLayout(ctx);
layout.setOrientation(VERTICAL);
TextView name = new TextView(ctx);
name.setTextSize(18);
name.setSingleLine();
name.setEllipsize(END);
name.setPadding(pad, pad, pad, pad);
name.setText(item.getForum().getName());
layout.addView(name);
TextView status = new TextView(ctx);
status.setPadding(pad, 0, pad, pad);
Collection<String> names = new ArrayList<String>();
for (Contact c : item.getContacts()) names.add(c.getAuthor().getName());
String format = ctx.getString(R.string.shared_by_format);
status.setText(String.format(format, StringUtils.join(names, ", ")));
layout.addView(status);
return layout;
View v = LayoutInflater.from(ctx)
.inflate(R.layout.list_item_available_forum, parent, false);
return new AvailableForumViewHolder(v);
}
@Override
public void onBindViewHolder(AvailableForumViewHolder ui, int position) {
final AvailableForumsItem item = getItem(position);
ui.name.setText(item.getForum().getName());
Collection<String> names = new ArrayList<>();
for (Contact c : item.getContacts()) names.add(c.getAuthor().getName());
ui.sharedBy.setText(ctx.getString(R.string.shared_by_format,
StringUtils.join(names, ", ")));
ui.accept.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onItemClick(item, true);
}
});
ui.decline.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onItemClick(item, false);
}
});
}
@Override
public int getItemCount() {
return forums.size();
}
public AvailableForumsItem getItem(int position) {
return forums.get(position);
}
public void add(AvailableForumsItem item) {
forums.add(item);
}
public void addAll(Collection<AvailableForumsItem> list) {
forums.addAll(list);
}
public void clear() {
forums.clear();
}
protected static class AvailableForumViewHolder
extends RecyclerView.ViewHolder {
public final ViewGroup layout;
public final TextView name;
public final TextView sharedBy;
public final Button accept;
public final Button decline;
public AvailableForumViewHolder(View v) {
super(v);
layout = (ViewGroup) v;
name = (TextView) v.findViewById(R.id.forumNameView);
sharedBy = (TextView) v.findViewById(R.id.sharedByView);
accept = (Button) v.findViewById(R.id.acceptButton);
decline = (Button) v.findViewById(R.id.declineButton);
}
}
private class SortedListCallBacks
extends SortedList.Callback<AvailableForumsItem> {
@Override
public int compare(AvailableForumsItem o1,
AvailableForumsItem o2) {
return String.CASE_INSENSITIVE_ORDER
.compare(o1.getForum().getName(),
o2.getForum().getName());
}
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count) {
notifyItemRangeChanged(position, count);
}
@Override
public boolean areContentsTheSame(AvailableForumsItem oldItem,
AvailableForumsItem newItem) {
return oldItem.getForum().equals(newItem.getForum()) &&
oldItem.getContacts().equals(newItem.getContacts());
}
@Override
public boolean areItemsTheSame(AvailableForumsItem oldItem,
AvailableForumsItem newItem) {
return oldItem.getForum().equals(newItem.getForum());
}
}
interface AvailableForumClickListener {
void onItemClick(AvailableForumsItem item, boolean accept);
}
}

View File

@@ -1,16 +0,0 @@
package org.briarproject.android.forum;
import java.util.Comparator;
class AvailableForumsItemComparator implements Comparator<AvailableForumsItem> {
static final AvailableForumsItemComparator INSTANCE =
new AvailableForumsItemComparator();
public int compare(AvailableForumsItem a, AvailableForumsItem b) {
if (a == b) return 0;
String aName = a.getForum().getName();
String bName = b.getForum().getName();
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
}
}