Add sharing information to toolbar subtitle of blogs

The toolbar subtitle shows information about how many contacts the
current blog is shared with and how many of those are online.
This commit is contained in:
Torsten Grote
2016-12-05 16:39:34 -02:00
parent 7df6abbcbe
commit d04dda1566
13 changed files with 411 additions and 13 deletions

View File

@@ -2,38 +2,60 @@ package org.briarproject.briar.android.blog;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.View;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.blog.BlogPostAdapter.OnBlogPostClickListener;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.sharing.BlogSharingStatusActivity;
import javax.inject.Inject;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BlogActivity extends BriarActivity implements
OnBlogPostClickListener, BaseFragmentListener {
static final int REQUEST_WRITE_POST = 1;
static final int REQUEST_SHARE = 2;
static final int REQUEST_WRITE_POST = 2;
static final int REQUEST_SHARE = 3;
@Inject
BlogController blogController;
@Override
public void onCreate(Bundle state) {
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
// GroupId from Intent
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException("No group ID in intent");
GroupId groupId = new GroupId(b);
final GroupId groupId = new GroupId(b);
blogController.setGroupId(groupId);
setContentView(R.layout.activity_fragment_container);
// Open Sharing Status on ActionBar click
View actionBar = findViewById(R.id.action_bar);
if (actionBar != null) {
actionBar.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(BlogActivity.this,
BlogSharingStatusActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
startActivity(i);
}
});
}
if (state == null) {
BlogFragment f = BlogFragment.newInstance(groupId);
getSupportFragmentManager().beginTransaction()
@@ -59,4 +81,5 @@ public class BlogActivity extends BriarActivity implements
@Override
public void onFragmentCreated(String tag) {
}
}

View File

@@ -1,5 +1,9 @@
package org.briarproject.briar.android.blog;
import android.support.annotation.UiThread;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
@@ -13,6 +17,8 @@ public interface BlogController extends BaseController {
void setGroupId(GroupId g);
void setBlogSharingListener(BlogSharingListener listener);
void loadBlogPosts(
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);
@@ -23,4 +29,15 @@ public interface BlogController extends BaseController {
void deleteBlog(ResultExceptionHandler<Void, DbException> handler);
void loadSharingContacts(
ResultExceptionHandler<Collection<ContactId>, DbException> handler);
interface BlogSharingListener extends BlogListener {
@UiThread
void onBlogInvitationAccepted(ContactId c);
@UiThread
void onBlogLeft(ContactId c);
}
}

View File

@@ -2,6 +2,8 @@ package org.briarproject.briar.android.blog;
import android.app.Activity;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
@@ -20,8 +22,13 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.blog.Blog;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.blog.event.BlogInvitationResponseReceivedEvent;
import org.briarproject.briar.api.blog.event.BlogPostAddedEvent;
import org.briarproject.briar.api.sharing.InvitationResponse;
import org.briarproject.briar.api.sharing.event.ShareableLeftEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -39,15 +46,19 @@ class BlogControllerImpl extends BaseControllerImpl
private static final Logger LOG =
Logger.getLogger(BlogControllerImpl.class.getName());
private final BlogSharingManager blogSharingManager;
private volatile GroupId groupId = null;
private volatile BlogSharingListener listener;
@Inject
BlogControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, EventBus eventBus,
AndroidNotificationManager notificationManager,
IdentityManager identityManager, BlogManager blogManager) {
IdentityManager identityManager, BlogManager blogManager,
BlogSharingManager blogSharingManager) {
super(dbExecutor, lifecycleManager, eventBus, notificationManager,
identityManager, blogManager);
this.blogSharingManager = blogSharingManager;
}
@Override
@@ -76,6 +87,12 @@ class BlogControllerImpl extends BaseControllerImpl
groupId = g;
}
@Override
public void setBlogSharingListener(BlogSharingListener listener) {
super.setBlogListener(listener);
this.listener = listener;
}
@Override
public void eventOccurred(Event e) {
if (groupId == null) throw new IllegalStateException();
@@ -85,6 +102,20 @@ class BlogControllerImpl extends BaseControllerImpl
LOG.info("Blog post added");
onBlogPostAdded(b.getHeader(), b.isLocal());
}
} else if (e instanceof BlogInvitationResponseReceivedEvent) {
BlogInvitationResponseReceivedEvent b =
(BlogInvitationResponseReceivedEvent) e;
InvitationResponse r = b.getResponse();
if (r.getGroupId().equals(groupId) && r.wasAccepted()) {
LOG.info("Blog invitation accepted");
onBlogInvitationAccepted(b.getContactId());
}
} else if (e instanceof ShareableLeftEvent) {
ShareableLeftEvent s = (ShareableLeftEvent) e;
if (s.getGroupId().equals(groupId)) {
LOG.info("Blog left");
onBlogLeft(s.getContactId());
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent g = (GroupRemovedEvent) e;
if (g.getGroup().getId().equals(groupId)) {
@@ -94,6 +125,24 @@ class BlogControllerImpl extends BaseControllerImpl
}
}
private void onBlogInvitationAccepted(final ContactId c) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {
listener.onBlogInvitationAccepted(c);
}
});
}
private void onBlogLeft(final ContactId c) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {
listener.onBlogLeft(c);
}
});
}
@Override
public void loadBlogPosts(
final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {
@@ -159,4 +208,27 @@ class BlogControllerImpl extends BaseControllerImpl
});
}
@Override
public void loadSharingContacts(
final ResultExceptionHandler<Collection<ContactId>, DbException> handler) {
if (groupId == null) throw new IllegalStateException();
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Collection<Contact> contacts =
blogSharingManager.getSharedWith(groupId);
Collection<ContactId> contactIds =
new ArrayList<>(contacts.size());
for (Contact c : contacts) contactIds.add(c.getId());
handler.onResult(contactIds);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
handler.onException(e);
}
}
});
}
}

View File

@@ -7,6 +7,7 @@ import android.support.annotation.UiThread;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.view.LayoutInflater;
@@ -17,6 +18,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
@@ -24,8 +26,10 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.blog.BaseController.BlogListener;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.blog.BlogController.BlogSharingListener;
import org.briarproject.briar.android.blog.BlogPostAdapter.OnBlogPostClickListener;
import org.briarproject.briar.android.controller.SharingController;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.sharing.BlogSharingStatusActivity;
@@ -46,17 +50,20 @@ import static android.widget.Toast.LENGTH_SHORT;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.blog.BlogActivity.REQUEST_SHARE;
import static org.briarproject.briar.android.blog.BlogActivity.REQUEST_WRITE_POST;
import static org.briarproject.briar.android.controller.SharingController.SharingListener;
@UiThread
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BlogFragment extends BaseFragment implements
BlogListener {
public class BlogFragment extends BaseFragment
implements BlogSharingListener, SharingListener {
private final static String TAG = BlogFragment.class.getName();
@Inject
BlogController blogController;
@Inject
SharingController sharingController;
private GroupId groupId;
private BlogPostAdapter adapter;
@@ -101,13 +108,16 @@ public class BlogFragment extends BaseFragment implements
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
blogController.setBlogListener(this);
blogController.setBlogSharingListener(this);
sharingController.setSharingListener(this);
}
@Override
public void onStart() {
super.onStart();
sharingController.onStart();
loadBlog();
loadSharedContacts();
loadBlogPosts(false);
list.startPeriodicUpdate();
}
@@ -115,6 +125,7 @@ public class BlogFragment extends BaseFragment implements
@Override
public void onStop() {
super.onStop();
sharingController.onStop();
list.stopPeriodicUpdate();
}
@@ -255,6 +266,52 @@ public class BlogFragment extends BaseFragment implements
getActivity().setTitle(title);
}
private void loadSharedContacts() {
blogController.loadSharingContacts(
new UiResultExceptionHandler<Collection<ContactId>, DbException>(this) {
@Override
public void onResultUi(Collection<ContactId> contacts) {
sharingController.addAll(contacts);
int online = sharingController.getOnlineCount();
setToolbarSubTitle(contacts.size(), online);
}
@Override
public void onExceptionUi(DbException exception) {
// TODO: Decide how to handle errors in the UI
finish();
}
});
}
@Override
public void onBlogInvitationAccepted(ContactId c) {
sharingController.add(c);
setToolbarSubTitle(sharingController.getTotalCount(),
sharingController.getOnlineCount());
}
@Override
public void onBlogLeft(ContactId c) {
sharingController.remove(c);
setToolbarSubTitle(sharingController.getTotalCount(),
sharingController.getOnlineCount());
}
@Override
public void onSharingInfoUpdated(int total, int online) {
setToolbarSubTitle(total, online);
}
private void setToolbarSubTitle(int total, int online) {
ActionBar actionBar =
((BriarActivity) getActivity()).getSupportActionBar();
if (actionBar != null) {
actionBar.setSubtitle(
getString(R.string.shared_with, total, online));
}
}
private void showWriteButton() {
isMyBlog = true;
if (writeButton != null)
@@ -327,4 +384,5 @@ public class BlogFragment extends BaseFragment implements
public void onBlogRemoved() {
finish();
}
}

View File

@@ -2,6 +2,8 @@ package org.briarproject.briar.android.blog;
import org.briarproject.briar.android.activity.ActivityScope;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.controller.SharingController;
import org.briarproject.briar.android.controller.SharingControllerImpl;
import dagger.Module;
import dagger.Provides;
@@ -22,4 +24,12 @@ public class BlogModule {
FeedController provideFeedController(FeedControllerImpl feedController) {
return feedController;
}
@ActivityScope
@Provides
SharingController provideSharingController(
SharingControllerImpl sharingController) {
return sharingController;
}
}

View File

@@ -0,0 +1,71 @@
package org.briarproject.briar.android.controller;
import android.support.annotation.UiThread;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.android.DestroyableContext;
import java.util.Collection;
@NotNullByDefault
public interface SharingController {
/**
* Sets the listener that is called when contacts go on or offline.
*/
@UiThread
void setSharingListener(SharingListener listener);
/**
* Call this when your lifecycle starts,
* so the listener will be called when information changes.
*/
@UiThread
void onStart();
/**
* Call this when your lifecycle stops,
* so that the controller knows it can stops listening to events.
*/
@UiThread
void onStop();
/**
* Adds one contact to be tracked.
*/
@UiThread
void add(ContactId c);
/**
* Adds a collection of contacts to be tracked.
*/
@UiThread
void addAll(Collection<ContactId> contacts);
/**
* Call this when the contact identified by c is no longer sharing
* the given group identified by GroupId g.
*/
@UiThread
void remove(ContactId c);
/**
* Returns the number of online contacts.
*/
@UiThread
int getOnlineCount();
/**
* Returns the total number of contacts that have been added.
*/
@UiThread
int getTotalCount();
interface SharingListener extends DestroyableContext {
@UiThread
void onSharingInfoUpdated(int total, int online);
}
}

View File

@@ -0,0 +1,105 @@
package org.briarproject.briar.android.controller;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.inject.Inject;
@UiThread
@NotNullByDefault
public class SharingControllerImpl implements SharingController, EventListener {
private final EventBus eventBus;
private final ConnectionRegistry connectionRegistry;
@Nullable
private SharingListener listener;
private Set<ContactId> contacts = new HashSet<>();
@Inject
SharingControllerImpl(EventBus eventBus,
ConnectionRegistry connectionRegistry) {
this.eventBus = eventBus;
this.connectionRegistry = connectionRegistry;
}
@Override
public void setSharingListener(SharingListener listener) {
this.listener = listener;
}
@Override
public void onStart() {
eventBus.addListener(this);
}
@Override
public void onStop() {
eventBus.removeListener(this);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactConnectedEvent) {
setConnected(((ContactConnectedEvent) e).getContactId());
} else if (e instanceof ContactDisconnectedEvent) {
setConnected(((ContactDisconnectedEvent) e).getContactId());
}
}
private void setConnected(final ContactId c) {
if (listener == null) return;
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {
if (contacts.contains(c)) {
int online = getOnlineCount();
listener.onSharingInfoUpdated(contacts.size(), online);
}
}
});
}
@Override
public void addAll(Collection<ContactId> c) {
contacts.addAll(c);
}
@Override
public void add(ContactId c) {
contacts.add(c);
}
@Override
public void remove(ContactId c) {
contacts.remove(c);
}
@Override
public int getOnlineCount() {
int online = 0;
for (ContactId c : contacts) {
if (connectionRegistry.isConnected(c)) online++;
}
return online;
}
@Override
public int getTotalCount() {
return contacts.size();
}
}

View File

@@ -259,6 +259,7 @@
<string name="forum_invitation_response_declined_received">%s declined the forum invitation.</string>
<string name="sharing_status">Sharing Status</string>
<string name="shared_with">Shared with %1$d (%2$d online)</string>
<plurals name="forums_shared">
<item quantity="one">%d forum shared by contacts</item>
<item quantity="other">%d forums shared by contacts</item>

View File

@@ -0,0 +1,31 @@
package org.briarproject.briar.api.sharing.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class ShareableLeftEvent extends Event {
private final GroupId groupId;
private final ContactId contactId;
public ShareableLeftEvent(GroupId groupId, ContactId contactId) {
this.groupId = groupId;
this.contactId = contactId;
}
public GroupId getGroupId() {
return groupId;
}
public ContactId getContactId() {
return contactId;
}
}

View File

@@ -357,7 +357,8 @@ class BlogSharingManagerImpl extends
throw new IllegalStateException("No responseId");
BlogInvitationResponse response =
new BlogInvitationResponse(responseId,
localState.getSessionId(), localState.getGroupId(),
localState.getSessionId(),
localState.getShareableId(),
localState.getContactId(), accept, time, false,
false, false, false);
return new BlogInvitationResponseReceivedEvent(c, response);

View File

@@ -287,8 +287,8 @@ class ForumSharingManagerImpl extends
throw new IllegalStateException("No responseId");
ForumInvitationResponse response = new ForumInvitationResponse(
responseId, localState.getSessionId(),
localState.getGroupId(), localState.getContactId(), accept,
time, false, false, false, false);
localState.getShareableId(), localState.getContactId(),
accept, time, false, false, false, false);
return new ForumInvitationResponseReceivedEvent(name, c, response);
}
}

View File

@@ -3,8 +3,10 @@ package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.sharing.event.InvitationRequestReceivedEvent;
import javax.annotation.Nullable;
@NotNullByDefault
interface InvitationReceivedEventFactory<IS extends InviteeSessionState, IR extends InvitationRequestReceivedEvent> {
IR build(IS localState, long time, String msg);
IR build(IS localState, long time, @Nullable String msg);
}

View File

@@ -39,6 +39,7 @@ import org.briarproject.briar.api.sharing.SharingInvitationItem;
import org.briarproject.briar.api.sharing.SharingManager;
import org.briarproject.briar.api.sharing.event.InvitationRequestReceivedEvent;
import org.briarproject.briar.api.sharing.event.InvitationResponseReceivedEvent;
import org.briarproject.briar.api.sharing.event.ShareableLeftEvent;
import org.briarproject.briar.client.ConversationClientImpl;
import java.io.IOException;
@@ -897,9 +898,15 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS
} else if (task == TASK_UNSHARE_SHAREABLE_SHARED_BY_US) {
db.setGroupVisibility(txn, contactId, f.getId(), INVISIBLE);
removeFromList(txn, groupId, SHARED_BY_US, f);
// broadcast event informing UI that contact has left the group
ShareableLeftEvent e = new ShareableLeftEvent(f.getId(), contactId);
txn.attach(e);
} else if (task == TASK_UNSHARE_SHAREABLE_SHARED_WITH_US) {
db.setGroupVisibility(txn, contactId, f.getId(), INVISIBLE);
removeFromList(txn, groupId, SHARED_WITH_US, f);
// broadcast event informing UI that contact has left the group
ShareableLeftEvent e = new ShareableLeftEvent(f.getId(), contactId);
txn.attach(e);
}
}