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.
This commit is contained in:
Torsten Grote
2016-05-02 13:39:05 -03:00
parent 2cc621ed1b
commit bcfdd848a4
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="forum_post_hint">Type forum post</string>
<string name="available_forums_title">Available Forums</string> <string name="available_forums_title">Available Forums</string>
<string name="forum_joined_toast">Joined Forum</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="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="no_contacts_prompt">You don\'t have any contacts. Add a contact now?</string>
<string name="add_button">Add</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> public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.BaseContactHolder>
extends RecyclerView.Adapter<VH> { extends RecyclerView.Adapter<VH> {
protected SortedList<ContactListItem> contacts; protected final SortedList<ContactListItem> contacts;
protected final OnItemClickListener listener; protected final OnItemClickListener listener;
protected Context ctx; protected Context ctx;
public BaseContactListAdapter(Context context, OnItemClickListener listener) { public BaseContactListAdapter(Context context, OnItemClickListener listener) {
this.ctx = context; this.ctx = context;
this.listener = listener; this.listener = listener;
this.contacts = new SortedList<ContactListItem>(ContactListItem.class, this.contacts = new SortedList<>(ContactListItem.class,
new SortedListCallBacks()); new SortedListCallBacks());
} }

View File

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

View File

@@ -1,57 +1,160 @@
package org.briarproject.android.forum; package org.briarproject.android.forum;
import android.content.Context; 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.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.util.StringUtils; import org.briarproject.util.StringUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import static android.text.TextUtils.TruncateAt.END; class AvailableForumsAdapter extends
import static android.widget.LinearLayout.VERTICAL; 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) { this.ctx = ctx;
super(ctx, android.R.layout.simple_expandable_list_item_1, this.listener = listener;
new ArrayList<AvailableForumsItem>());
pad = LayoutUtils.getPadding(ctx);
} }
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public AvailableForumViewHolder onCreateViewHolder(ViewGroup parent,
AvailableForumsItem item = getItem(position); int viewType) {
Context ctx = getContext();
LinearLayout layout = new LinearLayout(ctx); View v = LayoutInflater.from(ctx)
layout.setOrientation(VERTICAL); .inflate(R.layout.list_item_available_forum, parent, false);
return new AvailableForumViewHolder(v);
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;
} }
@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);
}
}