Merge branch '498-implement-ui-for-sharing-blogs' into 'master'

UI for sharing blogs

Not posting any screenshots, because the UI is the same as for forums.

This does not yet offer the possibility to unsubscribe from blogs again. Should be done in a different MR as this one is big enough already.

Closes #498,  #497

See merge request !263
This commit is contained in:
Torsten Grote
2016-08-03 21:47:02 +00:00
70 changed files with 2185 additions and 648 deletions

View File

@@ -0,0 +1,658 @@
package org.briarproject;
import net.jodah.concurrentunit.Waiter;
import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogInvitationRequest;
import org.briarproject.api.blogs.BlogInvitationResponse;
import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogPostFactory;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyPair;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DbException;
import org.briarproject.api.event.BlogInvitationReceivedEvent;
import org.briarproject.api.event.BlogInvitationResponseReceivedEvent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.MessageStateChangedEvent;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.sharing.InvitationMessage;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.SyncSession;
import org.briarproject.api.sync.SyncSessionFactory;
import org.briarproject.api.sync.ValidationManager.State;
import org.briarproject.api.system.Clock;
import org.briarproject.blogs.BlogsModule;
import org.briarproject.contact.ContactModule;
import org.briarproject.crypto.CryptoModule;
import org.briarproject.lifecycle.LifecycleModule;
import org.briarproject.properties.PropertiesModule;
import org.briarproject.sharing.SharingModule;
import org.briarproject.sync.SyncModule;
import org.briarproject.transport.TransportModule;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import javax.inject.Inject;
import static org.briarproject.TestPluginsModule.MAX_LATENCY;
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
import static org.briarproject.api.sync.ValidationManager.State.INVALID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class BlogSharingIntegrationTest extends BriarTestCase {
private LifecycleManager lifecycleManager0, lifecycleManager1,
lifecycleManager2;
private SyncSessionFactory sync0, sync1, sync2;
private BlogManager blogManager0, blogManager1;
private ContactManager contactManager0, contactManager1, contactManager2;
private Contact contact1, contact2, contact01, contact02;
private ContactId contactId1, contactId2, contactId01, contactId02;
private IdentityManager identityManager0, identityManager1,
identityManager2;
private LocalAuthor author0, author1, author2;
private Blog blog0, blog1, blog2;
private SharerListener listener0, listener2;
private InviteeListener listener1;
@Inject
Clock clock;
@Inject
AuthorFactory authorFactory;
@Inject
BlogPostFactory blogPostFactory;
@Inject
CryptoComponent cryptoComponent;
// objects accessed from background threads need to be volatile
private volatile BlogSharingManager blogSharingManager0;
private volatile BlogSharingManager blogSharingManager1;
private volatile BlogSharingManager blogSharingManager2;
private volatile Waiter eventWaiter;
private volatile Waiter msgWaiter;
private final File testDir = TestUtils.getTestDirectory();
private final SecretKey master = TestUtils.getSecretKey();
private final int TIMEOUT = 15000;
private final String SHARER = "Sharer";
private final String INVITEE = "Invitee";
private final String CONTACT2 = "Contact2";
private static final Logger LOG =
Logger.getLogger(BlogSharingIntegrationTest.class.getName());
private BlogSharingIntegrationTestComponent t0, t1, t2;
@Rule
public ExpectedException thrown = ExpectedException.none();
@Before
public void setUp() {
BlogSharingIntegrationTestComponent component =
DaggerBlogSharingIntegrationTestComponent.builder().build();
component.inject(this);
injectEagerSingletons(component);
assertTrue(testDir.mkdirs());
File t0Dir = new File(testDir, SHARER);
t0 = DaggerBlogSharingIntegrationTestComponent.builder()
.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
injectEagerSingletons(t0);
File t1Dir = new File(testDir, INVITEE);
t1 = DaggerBlogSharingIntegrationTestComponent.builder()
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
injectEagerSingletons(t1);
File t2Dir = new File(testDir, CONTACT2);
t2 = DaggerBlogSharingIntegrationTestComponent.builder()
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
injectEagerSingletons(t2);
identityManager0 = t0.getIdentityManager();
identityManager1 = t1.getIdentityManager();
identityManager2 = t2.getIdentityManager();
contactManager0 = t0.getContactManager();
contactManager1 = t1.getContactManager();
contactManager2 = t2.getContactManager();
blogManager0 = t0.getBlogManager();
blogManager1 = t1.getBlogManager();
blogSharingManager0 = t0.getBlogSharingManager();
blogSharingManager1 = t1.getBlogSharingManager();
blogSharingManager2 = t2.getBlogSharingManager();
sync0 = t0.getSyncSessionFactory();
sync1 = t1.getSyncSessionFactory();
sync2 = t2.getSyncSessionFactory();
// initialize waiters fresh for each test
eventWaiter = new Waiter();
msgWaiter = new Waiter();
}
@Test
public void testPersonalBlogCannotBeSharedWithOwner() throws Exception {
startLifecycles();
defaultInit(true);
assertFalse(blogSharingManager0.canBeShared(blog1.getId(), contact1));
assertFalse(blogSharingManager0.canBeShared(blog2.getId(), contact2));
assertFalse(blogSharingManager1.canBeShared(blog0.getId(), contact01));
assertFalse(blogSharingManager2.canBeShared(blog0.getId(), contact02));
// create invitation
blogSharingManager0
.sendInvitation(blog1.getId(), contactId1, "Hi!");
// sync invitation
sync0To1();
// make sure the invitee ignored the request for their own blog
assertFalse(listener1.requestReceived);
stopLifecycles();
}
@Test
public void testSuccessfulSharing() throws Exception {
startLifecycles();
// initialize and let invitee accept all requests
defaultInit(true);
// send invitation
blogSharingManager0
.sendInvitation(blog2.getId(), contactId1, "Hi!");
// invitee has own blog and that of the sharer
assertEquals(2, blogManager1.getBlogs().size());
// sync first request message
sync0To1();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// sync response back
sync1To0();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// blog was added successfully
assertEquals(0, blogSharingManager0.getInvited().size());
assertEquals(3, blogManager1.getBlogs().size());
// invitee has one invitation message from sharer
List<InvitationMessage> list =
new ArrayList<>(blogSharingManager1
.getInvitationMessages(contactId01));
assertEquals(2, list.size());
// check other things are alright with the message
for (InvitationMessage m : list) {
if (m instanceof BlogInvitationRequest) {
BlogInvitationRequest invitation =
(BlogInvitationRequest) m;
assertFalse(invitation.isAvailable());
assertEquals(blog2.getAuthor().getName(),
invitation.getBlogAuthorName());
assertEquals(contactId1, invitation.getContactId());
assertEquals("Hi!", invitation.getMessage());
} else {
BlogInvitationResponse response =
(BlogInvitationResponse) m;
assertEquals(contactId01, response.getContactId());
assertTrue(response.wasAccepted());
assertTrue(response.isLocal());
}
}
// sharer has own invitation message and response
assertEquals(2,
blogSharingManager0.getInvitationMessages(contactId1)
.size());
// blog can not be shared again
assertFalse(blogSharingManager0.canBeShared(blog2.getId(), contact1));
assertFalse(blogSharingManager1.canBeShared(blog2.getId(), contact01));
stopLifecycles();
}
@Test
public void testDeclinedSharing() throws Exception {
startLifecycles();
// initialize and let invitee deny all requests
defaultInit(false);
// send invitation
blogSharingManager0
.sendInvitation(blog2.getId(), contactId1, null);
// sync first request message
sync0To1();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// sync response back
sync1To0();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// blog was not added
assertEquals(0, blogSharingManager0.getInvited().size());
assertEquals(2, blogManager1.getBlogs().size());
// blog is no longer available to invitee who declined
assertEquals(0, blogSharingManager1.getInvited().size());
// invitee has one invitation message from sharer and one response
List<InvitationMessage> list =
new ArrayList<>(blogSharingManager1
.getInvitationMessages(contactId01));
assertEquals(2, list.size());
// check things are alright with the message
for (InvitationMessage m : list) {
if (m instanceof BlogInvitationRequest) {
BlogInvitationRequest invitation =
(BlogInvitationRequest) m;
assertFalse(invitation.isAvailable());
assertEquals(blog2.getAuthor().getName(),
invitation.getBlogAuthorName());
assertEquals(contactId1, invitation.getContactId());
assertEquals(null, invitation.getMessage());
} else {
BlogInvitationResponse response =
(BlogInvitationResponse) m;
assertEquals(contactId01, response.getContactId());
assertFalse(response.wasAccepted());
assertTrue(response.isLocal());
}
}
// sharer has own invitation message and response
assertEquals(2,
blogSharingManager0.getInvitationMessages(contactId1)
.size());
// blog can be shared again
assertTrue(blogSharingManager0.canBeShared(blog2.getId(), contact1));
stopLifecycles();
}
@Test
public void testInviteeLeavesAfterFinished() throws Exception {
startLifecycles();
// initialize and let invitee accept all requests
defaultInit(true);
// send invitation
blogSharingManager0
.sendInvitation(blog2.getId(), contactId1, "Hi!");
// sync first request message
sync0To1();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// sync response back
sync1To0();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// blog was added successfully
assertEquals(0, blogSharingManager0.getInvited().size());
assertEquals(3, blogManager1.getBlogs().size());
assertTrue(blogManager1.getBlogs().contains(blog2));
// sharer shares blog with invitee
assertTrue(blogSharingManager0.getSharedWith(blog2.getId())
.contains(contact1));
// invitee gets blog shared by sharer
assertTrue(blogSharingManager1.getSharedBy(blog2.getId())
.contains(contact01));
// invitee un-subscribes from blog
blogManager1.removeBlog(blog2);
// send leave message to sharer
sync1To0();
// blog is gone
assertEquals(0, blogSharingManager0.getInvited().size());
assertEquals(2, blogManager1.getBlogs().size());
// sharer no longer shares blog with invitee
assertFalse(blogSharingManager0.getSharedWith(blog2.getId())
.contains(contact1));
// invitee no longer gets blog shared by sharer
assertFalse(blogSharingManager1.getSharedBy(blog2.getId())
.contains(contact01));
// blog can be shared again
assertTrue(blogSharingManager0.canBeShared(blog2.getId(), contact1));
assertTrue(blogSharingManager1.canBeShared(blog2.getId(), contact01));
stopLifecycles();
}
@Test
public void testInvitationForExistingBlog() throws Exception {
startLifecycles();
// initialize and let invitee accept all requests
defaultInit(true);
// 1 and 2 are adding each other
contactManager1.addContact(author2,
author1.getId(), master, clock.currentTimeMillis(), true,
true
);
contactManager2.addContact(author1,
author2.getId(), master, clock.currentTimeMillis(), true,
true
);
assertEquals(3, blogManager1.getBlogs().size());
// sharer sends invitation for 2's blog to 1
blogSharingManager0
.sendInvitation(blog2.getId(), contactId1, "Hi!");
// sync first request message
sync0To1();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// make sure blog2 is shared by 0
Collection<Contact> contacts =
blogSharingManager1.getSharedBy(blog2.getId());
assertEquals(1, contacts.size());
assertTrue(contacts.contains(contact01));
// make sure 1 knows that they have blog2 already
Collection<InvitationMessage> messages =
blogSharingManager1.getInvitationMessages(contactId01);
assertEquals(2, messages.size());
assertEquals(blog2, blogManager1.getBlog(blog2.getId()));
// sync response back
sync1To0();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// blog was not added, because it was there already
assertEquals(0, blogSharingManager0.getInvited().size());
assertEquals(3, blogManager1.getBlogs().size());
stopLifecycles();
}
@After
public void tearDown() throws InterruptedException {
TestUtils.deleteTestDirectory(testDir);
}
private class SharerListener implements EventListener {
volatile boolean requestReceived = false;
volatile boolean responseReceived = false;
public void eventOccurred(Event e) {
if (e instanceof MessageStateChangedEvent) {
MessageStateChangedEvent event = (MessageStateChangedEvent) e;
State s = event.getState();
ClientId c = event.getClientId();
if ((s == DELIVERED || s == INVALID) &&
c.equals(blogSharingManager0.getClientId()) &&
!event.isLocal()) {
LOG.info("TEST: Sharer received message in group " +
event.getMessage().getGroupId().hashCode());
msgWaiter.resume();
} else if (s == DELIVERED && !event.isLocal() &&
c.equals(blogManager0.getClientId())) {
LOG.info("TEST: Sharer received blog post");
msgWaiter.resume();
}
} else if (e instanceof BlogInvitationResponseReceivedEvent) {
BlogInvitationResponseReceivedEvent event =
(BlogInvitationResponseReceivedEvent) e;
eventWaiter.assertEquals(contactId1, event.getContactId());
responseReceived = true;
eventWaiter.resume();
}
// this is only needed for tests where a blog is re-shared
else if (e instanceof BlogInvitationReceivedEvent) {
BlogInvitationReceivedEvent event =
(BlogInvitationReceivedEvent) e;
eventWaiter.assertEquals(contactId1, event.getContactId());
requestReceived = true;
Blog b = event.getBlog();
try {
Contact c = contactManager0.getContact(contactId1);
blogSharingManager0.respondToInvitation(b, c, true);
} catch (DbException ex) {
eventWaiter.rethrow(ex);
} finally {
eventWaiter.resume();
}
}
}
}
private class InviteeListener implements EventListener {
volatile boolean requestReceived = false;
volatile boolean responseReceived = false;
private final boolean accept, answer;
InviteeListener(boolean accept, boolean answer) {
this.accept = accept;
this.answer = answer;
}
InviteeListener(boolean accept) {
this(accept, true);
}
public void eventOccurred(Event e) {
if (e instanceof MessageStateChangedEvent) {
MessageStateChangedEvent event = (MessageStateChangedEvent) e;
State s = event.getState();
ClientId c = event.getClientId();
if ((s == DELIVERED || s == INVALID) &&
c.equals(blogSharingManager0.getClientId()) &&
!event.isLocal()) {
LOG.info("TEST: Invitee received message in group " +
event.getMessage().getGroupId().hashCode());
msgWaiter.resume();
} else if (s == DELIVERED && !event.isLocal() &&
c.equals(blogManager0.getClientId())) {
LOG.info("TEST: Invitee received blog post");
msgWaiter.resume();
}
} else if (e instanceof BlogInvitationReceivedEvent) {
BlogInvitationReceivedEvent event =
(BlogInvitationReceivedEvent) e;
requestReceived = true;
if (!answer) return;
Blog b = event.getBlog();
try {
eventWaiter.assertEquals(1,
blogSharingManager1.getInvited().size());
Contact c =
contactManager1.getContact(event.getContactId());
blogSharingManager1.respondToInvitation(b, c, accept);
} catch (DbException ex) {
eventWaiter.rethrow(ex);
} finally {
eventWaiter.resume();
}
}
// this is only needed for tests where a blog is re-shared
else if (e instanceof BlogInvitationResponseReceivedEvent) {
BlogInvitationResponseReceivedEvent event =
(BlogInvitationResponseReceivedEvent) e;
eventWaiter.assertEquals(contactId01, event.getContactId());
responseReceived = true;
eventWaiter.resume();
}
}
}
private void startLifecycles() throws InterruptedException {
// Start the lifecycle manager and wait for it to finish
lifecycleManager0 = t0.getLifecycleManager();
lifecycleManager1 = t1.getLifecycleManager();
lifecycleManager2 = t2.getLifecycleManager();
lifecycleManager0.startServices();
lifecycleManager1.startServices();
lifecycleManager2.startServices();
lifecycleManager0.waitForStartup();
lifecycleManager1.waitForStartup();
lifecycleManager2.waitForStartup();
}
private void stopLifecycles() throws InterruptedException {
// Clean up
lifecycleManager0.stopServices();
lifecycleManager1.stopServices();
lifecycleManager2.stopServices();
lifecycleManager0.waitForShutdown();
lifecycleManager1.waitForShutdown();
lifecycleManager2.waitForShutdown();
}
private void defaultInit(boolean accept) throws DbException {
addDefaultIdentities();
addDefaultContacts();
getPersonalBlogOfSharer();
listenToEvents(accept);
}
private void addDefaultIdentities() throws DbException {
KeyPair keyPair = cryptoComponent.generateSignatureKeyPair();
author0 = authorFactory.createLocalAuthor(SHARER,
keyPair.getPublic().getEncoded(),
keyPair.getPrivate().getEncoded());
identityManager0.addLocalAuthor(author0);
keyPair = cryptoComponent.generateSignatureKeyPair();
author1 = authorFactory.createLocalAuthor(INVITEE,
keyPair.getPublic().getEncoded(),
keyPair.getPrivate().getEncoded());
identityManager1.addLocalAuthor(author1);
keyPair = cryptoComponent.generateSignatureKeyPair();
author2 = authorFactory.createLocalAuthor(CONTACT2,
keyPair.getPublic().getEncoded(),
keyPair.getPrivate().getEncoded());
identityManager2.addLocalAuthor(author2);
}
private void addDefaultContacts() throws DbException {
// sharer adds invitee as contact
contactId1 = contactManager0.addContact(author1,
author0.getId(), master, clock.currentTimeMillis(), true,
true
);
contact1 = contactManager0.getContact(contactId1);
// sharer adds second contact
contactId2 = contactManager0.addContact(author2,
author0.getId(), master, clock.currentTimeMillis(), true,
true
);
contact2 = contactManager0.getContact(contactId2);
// contacts add sharer back
contactId01 = contactManager1.addContact(author0,
author1.getId(), master, clock.currentTimeMillis(), true,
true
);
contact01 = contactManager1.getContact(contactId01);
contactId02 = contactManager2.addContact(author0,
author2.getId(), master, clock.currentTimeMillis(), true,
true
);
contact02 = contactManager2.getContact(contactId02);
}
private void getPersonalBlogOfSharer() throws DbException {
blog0 = blogManager0.getPersonalBlog(author0);
blog1 = blogManager0.getPersonalBlog(author1);
blog2 = blogManager0.getPersonalBlog(author2);
}
private void listenToEvents(boolean accept) {
listener0 = new SharerListener();
t0.getEventBus().addListener(listener0);
listener1 = new InviteeListener(accept);
t1.getEventBus().addListener(listener1);
listener2 = new SharerListener();
t2.getEventBus().addListener(listener2);
}
private void sync0To1() throws IOException, TimeoutException {
deliverMessage(sync0, contactId01, sync1, contactId1,
"Sharer to Invitee");
}
private void sync1To0() throws IOException, TimeoutException {
deliverMessage(sync1, contactId1, sync0, contactId01,
"Invitee to Sharer");
}
private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
SyncSessionFactory toSync, ContactId toId, String debug)
throws IOException, TimeoutException {
if (debug != null) LOG.info("TEST: Sending message from " + debug);
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Create an outgoing sync session
SyncSession sessionFrom =
fromSync.createSimplexOutgoingSession(toId, MAX_LATENCY, out);
// Write whatever needs to be written
sessionFrom.run();
out.close();
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
// Create an incoming sync session
SyncSession sessionTo = toSync.createIncomingSession(fromId, in);
// Read whatever needs to be read
sessionTo.run();
in.close();
// wait for message to actually arrive
msgWaiter.await(TIMEOUT, 1);
}
private void injectEagerSingletons(
BlogSharingIntegrationTestComponent component) {
component.inject(new LifecycleModule.EagerSingletons());
component.inject(new BlogsModule.EagerSingletons());
component.inject(new CryptoModule.EagerSingletons());
component.inject(new ContactModule.EagerSingletons());
component.inject(new TransportModule.EagerSingletons());
component.inject(new SharingModule.EagerSingletons());
component.inject(new SyncModule.EagerSingletons());
component.inject(new PropertiesModule.EagerSingletons());
}
}

View File

@@ -0,0 +1,100 @@
package org.briarproject;
import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.MessageQueueManager;
import org.briarproject.api.clients.PrivateGroupFactory;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.sync.SyncSessionFactory;
import org.briarproject.blogs.BlogsModule;
import org.briarproject.clients.ClientsModule;
import org.briarproject.contact.ContactModule;
import org.briarproject.crypto.CryptoModule;
import org.briarproject.data.DataModule;
import org.briarproject.db.DatabaseModule;
import org.briarproject.event.EventModule;
import org.briarproject.forum.ForumModule;
import org.briarproject.identity.IdentityModule;
import org.briarproject.lifecycle.LifecycleModule;
import org.briarproject.properties.PropertiesModule;
import org.briarproject.sharing.SharingModule;
import org.briarproject.sync.SyncModule;
import org.briarproject.system.SystemModule;
import org.briarproject.transport.TransportModule;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
TestDatabaseModule.class,
TestPluginsModule.class,
TestSeedProviderModule.class,
ClientsModule.class,
ContactModule.class,
CryptoModule.class,
DataModule.class,
DatabaseModule.class,
EventModule.class,
BlogsModule.class,
ForumModule.class,
IdentityModule.class,
LifecycleModule.class,
PropertiesModule.class,
SharingModule.class,
SyncModule.class,
SystemModule.class,
TransportModule.class
})
interface BlogSharingIntegrationTestComponent {
void inject(BlogSharingIntegrationTest testCase);
void inject(ContactModule.EagerSingletons init);
void inject(CryptoModule.EagerSingletons init);
void inject(BlogsModule.EagerSingletons init);
void inject(LifecycleModule.EagerSingletons init);
void inject(PropertiesModule.EagerSingletons init);
void inject(SharingModule.EagerSingletons init);
void inject(SyncModule.EagerSingletons init);
void inject(TransportModule.EagerSingletons init);
LifecycleManager getLifecycleManager();
EventBus getEventBus();
IdentityManager getIdentityManager();
ContactManager getContactManager();
BlogSharingManager getBlogSharingManager();
BlogManager getBlogManager();
SyncSessionFactory getSyncSessionFactory();
/* the following methods are only needed to manually construct messages */
DatabaseComponent getDatabaseComponent();
PrivateGroupFactory getPrivateGroupFactory();
ClientHelper getClientHelper();
MessageQueueManager getMessageQueueManager();
}

View File

@@ -7,6 +7,7 @@ import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.sync.SyncSessionFactory;
import org.briarproject.blogs.BlogsModule;
import org.briarproject.clients.ClientsModule;
import org.briarproject.contact.ContactModule;
import org.briarproject.crypto.CryptoModule;
@@ -38,6 +39,7 @@ import dagger.Component;
DatabaseModule.class,
EventModule.class,
ForumModule.class,
BlogsModule.class,
IdentityModule.class,
LifecycleModule.class,
PropertiesModule.class,
@@ -46,7 +48,7 @@ import dagger.Component;
SystemModule.class,
TransportModule.class
})
public interface ForumManagerTestComponent {
interface ForumManagerTestComponent {
void inject(ForumManagerTest testCase);

View File

@@ -11,6 +11,7 @@ import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.sync.SyncSessionFactory;
import org.briarproject.blogs.BlogsModule;
import org.briarproject.clients.ClientsModule;
import org.briarproject.contact.ContactModule;
import org.briarproject.crypto.CryptoModule;
@@ -42,6 +43,7 @@ import dagger.Component;
DatabaseModule.class,
EventModule.class,
ForumModule.class,
BlogsModule.class,
IdentityModule.class,
LifecycleModule.class,
PropertiesModule.class,
@@ -50,7 +52,7 @@ import dagger.Component;
SystemModule.class,
TransportModule.class
})
public interface ForumSharingIntegrationTestComponent {
interface ForumSharingIntegrationTestComponent {
void inject(ForumSharingIntegrationTest testCase);

View File

@@ -102,7 +102,7 @@
</activity>
<activity
android:name=".android.forum.ForumInvitationsActivity"
android:name=".android.sharing.InvitationsForumActivity"
android:label="@string/forum_invitations_title"
android:parentActivityName=".android.NavDrawerActivity">
<meta-data
@@ -111,6 +111,16 @@
/>
</activity>
<activity
android:name=".android.sharing.InvitationsBlogActivity"
android:label="@string/blogs_sharing_invitations_title"
android:parentActivityName=".android.contact.ConversationActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.contact.ConversationActivity"
/>
</activity>
<activity
android:name=".android.forum.CreateForumActivity"
android:label="@string/create_forum_title"
@@ -133,8 +143,8 @@
</activity>
<activity
android:name=".android.forum.ShareForumActivity"
android:label="@string/forums_share_toolbar_header"
android:name=".android.sharing.ShareForumActivity"
android:label="@string/activity_share_toolbar_header"
android:parentActivityName=".android.forum.ForumActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@@ -143,8 +153,18 @@
</activity>
<activity
android:name=".android.forum.ForumSharingStatusActivity"
android:label="@string/forum_sharing_status"
android:name=".android.sharing.ShareBlogActivity"
android:label="@string/activity_share_toolbar_header"
android:parentActivityName=".android.blogs.BlogActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.blogs.BlogActivity"
/>
</activity>
<activity
android:name=".android.sharing.SharingStatusForumActivity"
android:label="@string/sharing_status"
android:parentActivityName=".android.forum.ForumActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@@ -152,6 +172,16 @@
/>
</activity>
<activity
android:name=".android.sharing.SharingStatusBlogActivity"
android:label="@string/sharing_status"
android:parentActivityName=".android.blogs.BlogActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.blogs.BlogActivity"
/>
</activity>
<activity
android:name=".android.blogs.CreateBlogActivity"
android:label="@string/blogs_my_blogs_label"

View File

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

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="@+id/shareForumContainer"
android:id="@+id/shareContainer"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@@ -1,57 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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:orientation="vertical">
<include
android:id="@+id/messageLayout"
layout="@layout/list_item_msg_out"/>
<RelativeLayout
android:id="@+id/introductionLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|end"
android:background="@drawable/notice_out"
android:layout_marginLeft="@dimen/message_bubble_margin_non_tail"
android:layout_marginRight="@dimen/message_bubble_margin_tail">
<TextView
android:id="@+id/introductionText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium"
android:textStyle="italic"
android:textColor="@color/briar_text_secondary"
tools:text="@string/introduction_request_received"/>
<TextView
android:id="@+id/introductionTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/introductionText"
android:textColor="@color/private_message_date"
android:textSize="@dimen/text_size_tiny"
tools:text="Dec 24, 13:37"/>
<ImageView
android:id="@+id/introductionStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/introductionTime"
android:layout_toRightOf="@+id/introductionTime"
android:layout_alignBottom="@+id/introductionTime"
android:layout_marginLeft="@dimen/margin_medium"
tools:ignore="ContentDescription"
tools:src="@drawable/message_delivered"/>
</RelativeLayout>
</LinearLayout>

View File

@@ -37,13 +37,13 @@
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:layout_alignEnd="@+id/introductionText"
android:layout_alignRight="@+id/introductionText"
android:layout_below="@+id/showForumsButton"
android:layout_below="@+id/showInvitationsButton"
android:textColor="@color/private_message_date"
android:textSize="@dimen/text_size_tiny"
tools:text="Dec 24, 13:37"/>
<Button
android:id="@+id/showForumsButton"
android:id="@+id/showInvitationsButton"
style="@style/BriarButtonFlat.Positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -51,7 +51,7 @@
android:layout_alignEnd="@+id/introductionText"
android:layout_alignRight="@+id/introductionText"
android:layout_below="@+id/introductionText"
android:text="@string/forum_show_invitations"/>
tools:text="@string/forum_show_invitations"/>
</RelativeLayout>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_blog_share"
android:icon="@drawable/social_share_white"
android:title="@string/blogs_sharing_share"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_blog_sharing_status"
android:title="@string/sharing_status"
app:showAsAction="never"/>
</menu>

View File

@@ -17,7 +17,7 @@
<item
android:id="@+id/action_forum_sharing_status"
android:title="@string/forum_sharing_status"
android:title="@string/sharing_status"
app:showAsAction="never"/>
<item

View File

@@ -79,7 +79,7 @@
<string name="show_forums">Mostrar</string>
<string name="forum_leave">Sair do fórum</string>
<string name="forum_left_toast">Saiu do fórum</string>
<string name="forum_sharing_status">Status de compartilhamento</string>
<string name="sharing_status">Status de compartilhamento</string>
<string name="no_posts">Sem Posts</string>
<plurals name="unread_posts">
<item quantity="one">%d post não lido</item>

View File

@@ -53,6 +53,7 @@
<string name="now">now</string>
<string name="contact_list_title">Contacts</string>
<string name="no_contacts">It seems that you are new here and have no contacts yet.\n\nTap the + icon at the top and follow the instructions to add some friends to your list.\n\nPlease remember: You can only add new contacts face-to-face to prevent anyone from impersonating you or reading your messages in the future.</string>
<string name="no_contacts_selector">It seems that you are new here and have no contacts yet.\n\nPlease come back here after you added your first contact.</string>
<string name="add_contact_title">Add a Contact</string>
<string name="your_nickname">Choose the identity you want to use:</string>
<string name="face_to_face">You must be face-to-face with the person you want to add as a contact. This will prevent anyone from impersonating you or reading your messages in future.</string>
@@ -95,7 +96,7 @@
<string name="show_forums">Show</string>
<string name="forum_leave">Leave Forum</string>
<string name="forum_left_toast">Left Forum</string>
<string name="forum_sharing_status">Sharing Status</string>
<string name="sharing_status">Sharing Status</string>
<string name="no_posts">No posts</string>
<plurals name="unread_posts">
<item quantity="one">%d unread post</item>
@@ -113,6 +114,7 @@
<string name="forum_share_button">Share Forum</string>
<string name="forum_shared_snackbar">Forum shared with chosen contacts</string>
<string name="forum_share_message">You may compose an optional invitation message that will be sent to the selected contacts.</string>
<string name="forum_share_error">There was an error sharing this forum.</string>
<string name="forum_invitation_received">%1$s has shared the forum \"%2$s\" with you.</string>
<string name="forum_invitation_sent">You have shared the forum \"%1$s\" with %2$s.</string>
<string name="forum_show_invitations">Show Forum Invitations</string>
@@ -265,7 +267,7 @@
<string name="settings_toolbar_header">Settings</string>
<string name="contacts_toolbar_header">Contacts</string>
<string name="forums_toolbar_header">Forums</string>
<string name="forums_share_toolbar_header">Choose Contacts</string>
<string name="activity_share_toolbar_header">Choose Contacts</string>
<!-- Progress titles -->
<string name="progress_title_logout">Signing out of Briar..</string>
<string name="progress_title_please_wait">Please wait..</string>
@@ -304,6 +306,21 @@
<string name="blogs_delete_blog_cancel">Keep</string>
<string name="blogs_blog_deleted">Blog Deleted</string>
<string name="blogs_remove_blog">Remove Blog</string>
<string name="blogs_sharing_share">Share Blog</string>
<string name="blogs_sharing_error">There was an error sharing this blog.</string>
<string name="blogs_sharing_button">Share Blog</string>
<string name="blogs_sharing_snackbar">Blog shared with chosen contacts</string>
<string name="blogs_sharing_response_accepted_sent">You accepted the blog invitation from %s.</string>
<string name="blogs_sharing_response_declined_sent">You declined the blog invitation from %s.</string>
<string name="blogs_sharing_response_accepted_received">%s accepted the blog invitation.</string>
<string name="blogs_sharing_response_declined_received">%s declined the blog invitation.</string>
<string name="blogs_sharing_invitation_received">%1$s has shared the personal blog of %2$s with you.</string>
<string name="blogs_sharing_invitation_sent">You have shared the personal blog of %1$s with %2$s.</string>
<string name="blogs_sharing_show_invitations">Show Blog Invitations</string>
<string name="blogs_sharing_invitations_title">Blog Invitations</string>
<string name="blogs_sharing_exists">You are subscribed to this blog already. Accepting again can lead to faster blog post delivery.</string>
<string name="blogs_sharing_joined_toast">Subscribed to Blog</string>
<string name="blogs_sharing_declined_toast">Blog Invitation Declined</string>
<string name="blogs_blog_list">Blog List</string>
<string name="blogs_available_blogs">Available Blogs</string>

View File

@@ -15,14 +15,9 @@ import org.briarproject.android.blogs.RssFeedManageActivity;
import org.briarproject.android.blogs.WriteBlogPostActivity;
import org.briarproject.android.contact.ContactListFragment;
import org.briarproject.android.contact.ConversationActivity;
import org.briarproject.android.forum.ForumInvitationsActivity;
import org.briarproject.android.forum.ContactSelectorFragment;
import org.briarproject.android.forum.CreateForumActivity;
import org.briarproject.android.forum.ForumActivity;
import org.briarproject.android.forum.ForumListFragment;
import org.briarproject.android.forum.ForumSharingStatusActivity;
import org.briarproject.android.forum.ShareForumActivity;
import org.briarproject.android.forum.ShareForumMessageFragment;
import org.briarproject.android.identity.CreateIdentityActivity;
import org.briarproject.android.introduction.ContactChooserFragment;
import org.briarproject.android.introduction.IntroductionActivity;
@@ -33,6 +28,15 @@ import org.briarproject.android.keyagreement.KeyAgreementActivity;
import org.briarproject.android.keyagreement.ShowQrCodeFragment;
import org.briarproject.android.panic.PanicPreferencesActivity;
import org.briarproject.android.panic.PanicResponderActivity;
import org.briarproject.android.sharing.ContactSelectorFragment;
import org.briarproject.android.sharing.InvitationsBlogActivity;
import org.briarproject.android.sharing.InvitationsForumActivity;
import org.briarproject.android.sharing.ShareBlogActivity;
import org.briarproject.android.sharing.ShareBlogMessageFragment;
import org.briarproject.android.sharing.ShareForumActivity;
import org.briarproject.android.sharing.ShareForumMessageFragment;
import org.briarproject.android.sharing.SharingStatusBlogActivity;
import org.briarproject.android.sharing.SharingStatusForumActivity;
import dagger.Component;
@@ -63,13 +67,19 @@ public interface ActivityComponent {
void inject(CreateIdentityActivity activity);
void inject(ForumInvitationsActivity activity);
void inject(InvitationsForumActivity activity);
void inject(InvitationsBlogActivity activity);
void inject(CreateForumActivity activity);
void inject(ShareForumActivity activity);
void inject(ForumSharingStatusActivity activity);
void inject(ShareBlogActivity activity);
void inject(SharingStatusForumActivity activity);
void inject(SharingStatusBlogActivity activity);
void inject(ForumActivity activity);
@@ -105,6 +115,7 @@ public interface ActivityComponent {
void inject(ContactChooserFragment fragment);
void inject(ContactSelectorFragment fragment);
void inject(ShareForumMessageFragment fragment);
void inject(ShareBlogMessageFragment fragment);
void inject(IntroductionMessageFragment fragment);
}

View File

@@ -8,6 +8,7 @@ import org.briarproject.android.api.ReferenceManager;
import org.briarproject.android.report.BriarReportSender;
import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogPostFactory;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.contact.ContactExchangeTask;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.crypto.CryptoComponent;
@@ -93,6 +94,8 @@ public interface AndroidComponent extends CoreEagerSingletons {
ForumSharingManager forumSharingManager();
BlogSharingManager blogSharingManager();
ForumPostFactory forumPostFactory();
BlogManager blogManager();

View File

@@ -21,11 +21,12 @@ import org.briarproject.api.db.DbException;
import org.briarproject.api.event.BlogPostAddedEvent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.event.ForumPostReceivedEvent;
import org.briarproject.api.event.IntroductionRequestReceivedEvent;
import org.briarproject.api.event.IntroductionResponseReceivedEvent;
import org.briarproject.api.event.IntroductionSucceededEvent;
import org.briarproject.api.event.InvitationReceivedEvent;
import org.briarproject.api.event.InvitationResponseReceivedEvent;
import org.briarproject.api.event.PrivateMessageReceivedEvent;
import org.briarproject.api.event.SettingsUpdatedEvent;
import org.briarproject.api.lifecycle.Service;
@@ -174,8 +175,11 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
} else if (e instanceof IntroductionSucceededEvent) {
Contact c = ((IntroductionSucceededEvent) e).getContact();
showIntroductionSucceededNotification(c);
} else if (e instanceof ForumInvitationReceivedEvent) {
ContactId c = ((ForumInvitationReceivedEvent) e).getContactId();
} else if (e instanceof InvitationReceivedEvent) {
ContactId c = ((InvitationReceivedEvent) e).getContactId();
showNotificationForPrivateConversation(c);
} else if (e instanceof InvitationResponseReceivedEvent) {
ContactId c = ((InvitationResponseReceivedEvent) e).getContactId();
showNotificationForPrivateConversation(c);
}
}

View File

@@ -32,6 +32,7 @@ public class BlogActivity extends BriarActivity implements BlogPostListener,
OnBlogPostClickListener, BaseFragmentListener {
static final int REQUEST_WRITE_POST = 1;
static final int REQUEST_SHARE = 2;
static final String BLOG_NAME = "briar.BLOG_NAME";
static final String IS_MY_BLOG = "briar.IS_MY_BLOG";
static final String IS_NEW_BLOG = "briar.IS_NEW_BLOG";
@@ -185,6 +186,7 @@ public class BlogActivity extends BriarActivity implements BlogPostListener,
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// The BlogPostAddedEvent arrives when the controller is not listening,
// so we need to manually reload the blog posts :(

View File

@@ -23,6 +23,8 @@ import org.briarproject.android.blogs.BlogController.BlogPostListener;
import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener;
import org.briarproject.android.controller.handler.UiResultHandler;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.sharing.ShareBlogActivity;
import org.briarproject.android.sharing.SharingStatusBlogActivity;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.sync.GroupId;
@@ -30,6 +32,9 @@ import java.util.Collection;
import javax.inject.Inject;
import static android.app.Activity.RESULT_OK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static android.support.design.widget.Snackbar.LENGTH_LONG;
import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
import static android.widget.Toast.LENGTH_SHORT;
@@ -37,6 +42,7 @@ import static org.briarproject.android.BriarActivity.GROUP_ID;
import static org.briarproject.android.blogs.BlogActivity.BLOG_NAME;
import static org.briarproject.android.blogs.BlogActivity.IS_MY_BLOG;
import static org.briarproject.android.blogs.BlogActivity.IS_NEW_BLOG;
import static org.briarproject.android.blogs.BlogActivity.REQUEST_SHARE;
import static org.briarproject.android.blogs.BlogActivity.REQUEST_WRITE_POST;
public class BlogFragment extends BaseFragment implements BlogPostListener {
@@ -136,12 +142,18 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (myBlog) {
inflater.inflate(R.menu.blogs_my_blog_actions, menu);
} else {
inflater.inflate(R.menu.blogs_blog_actions, menu);
}
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
ActivityOptionsCompat options =
makeCustomAnimation(getActivity(),
android.R.anim.slide_in_left,
android.R.anim.slide_out_right);
switch (item.getItemId()) {
case android.R.id.home:
getActivity().onBackPressed();
@@ -151,18 +163,36 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
new Intent(getActivity(), WriteBlogPostActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
i.putExtra(BLOG_NAME, blogName);
ActivityOptionsCompat options =
makeCustomAnimation(getActivity(),
android.R.anim.slide_in_left,
android.R.anim.slide_out_right);
ActivityCompat.startActivityForResult(getActivity(), i,
REQUEST_WRITE_POST, options.toBundle());
return true;
case R.id.action_blog_share:
Intent i2 = new Intent(getActivity(), ShareBlogActivity.class);
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i2.putExtra(GROUP_ID, groupId.getBytes());
startActivityForResult(i2, REQUEST_SHARE, options.toBundle());
return true;
case R.id.action_blog_sharing_status:
Intent i3 = new Intent(getActivity(),
SharingStatusBlogActivity.class);
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i3.putExtra(GROUP_ID, groupId.getBytes());
startActivity(i3, options.toBundle());
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActivityResult(int request, int result, Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_SHARE && result == RESULT_OK) {
displaySnackbar(R.string.blogs_sharing_snackbar);
}
}
@Override
public String getUniqueTag() {
return TAG;
@@ -202,6 +232,13 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
loadData(true);
}
private void displaySnackbar(int stringId) {
Snackbar snackbar =
Snackbar.make(list, stringId, Snackbar.LENGTH_SHORT);
snackbar.getView().setBackgroundResource(R.color.briar_primary);
snackbar.show();
}
private void showDeleteDialog() {
DialogInterface.OnClickListener okListener =
new DialogInterface.OnClickListener() {

View File

@@ -20,6 +20,7 @@ import org.briarproject.android.ActivityComponent;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.keyagreement.KeyAgreementActivity;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
@@ -33,13 +34,11 @@ import org.briarproject.api.event.ContactStatusChangedEvent;
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.ForumInvitationResponseReceivedEvent;
import org.briarproject.api.event.IntroductionRequestReceivedEvent;
import org.briarproject.api.event.IntroductionResponseReceivedEvent;
import org.briarproject.api.event.InvitationReceivedEvent;
import org.briarproject.api.event.InvitationResponseReceivedEvent;
import org.briarproject.api.event.PrivateMessageReceivedEvent;
import org.briarproject.api.forum.ForumInvitationRequest;
import org.briarproject.api.forum.ForumInvitationResponse;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
@@ -91,6 +90,8 @@ public class ContactListFragment extends BaseFragment implements EventListener {
protected volatile IntroductionManager introductionManager;
@Inject
protected volatile ForumSharingManager forumSharingManager;
@Inject
protected volatile BlogSharingManager blogSharingManager;
public static ContactListFragment newInstance() {
@@ -277,14 +278,14 @@ public class ContactListFragment extends BaseFragment implements EventListener {
(IntroductionResponseReceivedEvent) e;
IntroductionResponse ir = m.getIntroductionResponse();
updateItem(m.getContactId(), ConversationItem.from(ir));
} else if (e instanceof ForumInvitationReceivedEvent) {
LOG.info("Forum Invitation received, reloading conversation...");
ForumInvitationReceivedEvent m = (ForumInvitationReceivedEvent) e;
} else if (e instanceof InvitationReceivedEvent) {
LOG.info("Invitation received, reloading conversation...");
InvitationReceivedEvent m = (InvitationReceivedEvent) e;
reloadConversation(m.getContactId());
} else if (e instanceof ForumInvitationResponseReceivedEvent) {
LOG.info("Forum Invitation Response received, reloading ...");
ForumInvitationResponseReceivedEvent m =
(ForumInvitationResponseReceivedEvent) e;
} else if (e instanceof InvitationResponseReceivedEvent) {
LOG.info("Invitation Response received, reloading ...");
InvitationResponseReceivedEvent m =
(InvitationResponseReceivedEvent) e;
reloadConversation(m.getContactId());
}
}
@@ -404,14 +405,19 @@ public class ContactListFragment extends BaseFragment implements EventListener {
LOG.info("Loading introduction messages took " + duration + " ms");
now = System.currentTimeMillis();
Collection<InvitationMessage> invitations =
Collection<InvitationMessage> forumInvitations =
forumSharingManager.getInvitationMessages(id);
for (InvitationMessage i : invitations) {
for (InvitationMessage i : forumInvitations) {
messages.add(ConversationItem.from(i));
}
Collection<InvitationMessage> blogInvitations =
blogSharingManager.getInvitationMessages(id);
for (InvitationMessage i : blogInvitations) {
messages.add(ConversationItem.from(i));
}
duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Loading forum invitations took " + duration + " ms");
LOG.info("Loading invitations took " + duration + " ms");
return messages;
}

View File

@@ -30,6 +30,7 @@ import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.introduction.IntroductionActivity;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.FormatException;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
@@ -44,15 +45,13 @@ 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.ForumInvitationResponseReceivedEvent;
import org.briarproject.api.event.IntroductionRequestReceivedEvent;
import org.briarproject.api.event.IntroductionResponseReceivedEvent;
import org.briarproject.api.event.InvitationReceivedEvent;
import org.briarproject.api.event.InvitationResponseReceivedEvent;
import org.briarproject.api.event.MessagesAckedEvent;
import org.briarproject.api.event.MessagesSentEvent;
import org.briarproject.api.event.PrivateMessageReceivedEvent;
import org.briarproject.api.forum.ForumInvitationRequest;
import org.briarproject.api.forum.ForumInvitationResponse;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.introduction.IntroductionManager;
import org.briarproject.api.introduction.IntroductionMessage;
@@ -64,6 +63,8 @@ import org.briarproject.api.messaging.PrivateMessageFactory;
import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.api.plugins.ConnectionRegistry;
import org.briarproject.api.sharing.InvitationMessage;
import org.briarproject.api.sharing.InvitationRequest;
import org.briarproject.api.sharing.InvitationResponse;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
@@ -128,6 +129,8 @@ public class ConversationActivity extends BriarActivity
protected volatile IntroductionManager introductionManager;
@Inject
protected volatile ForumSharingManager forumSharingManager;
@Inject
protected volatile BlogSharingManager blogSharingManager;
private volatile GroupId groupId = null;
private volatile ContactId contactId = null;
@@ -334,9 +337,16 @@ public class ConversationActivity extends BriarActivity
Collection<IntroductionMessage> introductions =
introductionManager
.getIntroductionMessages(contactId);
Collection<InvitationMessage> invitations =
Collection<InvitationMessage> forumInvitations =
forumSharingManager
.getInvitationMessages(contactId);
Collection<InvitationMessage> blogInvitations =
blogSharingManager
.getInvitationMessages(contactId);
List<InvitationMessage> invitations = new ArrayList<>(
forumInvitations.size() + blogInvitations.size());
invitations.addAll(forumInvitations);
invitations.addAll(blogInvitations);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Loading headers took " + duration + " ms");
@@ -388,13 +398,13 @@ public class ConversationActivity extends BriarActivity
items.add(item);
}
for (InvitationMessage i : invitations) {
if (i instanceof ForumInvitationRequest) {
ForumInvitationRequest r =
(ForumInvitationRequest) i;
if (i instanceof InvitationRequest) {
InvitationRequest r =
(InvitationRequest) i;
items.add(ConversationItem.from(r));
} else if (i instanceof ForumInvitationResponse) {
ForumInvitationResponse r =
(ForumInvitationResponse) i;
} else if (i instanceof InvitationResponse) {
InvitationResponse r =
(InvitationResponse) i;
items.add(ConversationItem
.from(ConversationActivity.this,
contactName, r));
@@ -541,6 +551,7 @@ public class ConversationActivity extends BriarActivity
IntroductionRequestReceivedEvent event =
(IntroductionRequestReceivedEvent) e;
if (event.getContactId().equals(contactId)) {
LOG.info("Introduction request received, adding...");
IntroductionRequest ir = event.getIntroductionRequest();
ConversationItem item = new ConversationIntroductionInItem(ir);
addConversationItem(item);
@@ -549,21 +560,24 @@ public class ConversationActivity extends BriarActivity
IntroductionResponseReceivedEvent event =
(IntroductionResponseReceivedEvent) e;
if (event.getContactId().equals(contactId)) {
LOG.info("Introduction response received, adding...");
IntroductionResponse ir = event.getIntroductionResponse();
ConversationItem item =
ConversationItem.from(this, contactName, ir);
addConversationItem(item);
}
} else if (e instanceof ForumInvitationReceivedEvent) {
ForumInvitationReceivedEvent event =
(ForumInvitationReceivedEvent) e;
} else if (e instanceof InvitationReceivedEvent) {
InvitationReceivedEvent event =
(InvitationReceivedEvent) e;
if (event.getContactId().equals(contactId)) {
LOG.info("Invitation received, reloading...");
loadMessages();
}
} else if (e instanceof ForumInvitationResponseReceivedEvent) {
ForumInvitationResponseReceivedEvent event =
(ForumInvitationResponseReceivedEvent) e;
} else if (e instanceof InvitationResponseReceivedEvent) {
InvitationResponseReceivedEvent event =
(InvitationResponseReceivedEvent) e;
if (event.getContactId().equals(contactId)) {
LOG.info("Invitation response received, reloading...");
loadMessages();
}
}

View File

@@ -13,18 +13,25 @@ import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.forum.ForumInvitationsActivity;
import org.briarproject.android.sharing.InvitationsBlogActivity;
import org.briarproject.android.sharing.InvitationsForumActivity;
import org.briarproject.android.util.AndroidUtils;
import org.briarproject.api.blogs.BlogInvitationRequest;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.forum.ForumInvitationRequest;
import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.api.sharing.InvitationRequest;
import org.briarproject.util.StringUtils;
import java.util.List;
import static android.support.v7.util.SortedList.INVALID_POSITION;
import static android.support.v7.widget.RecyclerView.ViewHolder;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.android.contact.ConversationItem.BLOG_INVITATION_IN;
import static org.briarproject.android.contact.ConversationItem.BLOG_INVITATION_OUT;
import static org.briarproject.android.contact.ConversationItem.FORUM_INVITATION_IN;
import static org.briarproject.android.contact.ConversationItem.FORUM_INVITATION_OUT;
import static org.briarproject.android.contact.ConversationItem.INTRODUCTION_IN;
@@ -46,13 +53,13 @@ class ConversationAdapter extends RecyclerView.Adapter {
private IntroductionHandler intro;
private String contactName;
public ConversationAdapter(Context context,
ConversationAdapter(Context context,
IntroductionHandler introductionHandler) {
ctx = context;
intro = introductionHandler;
}
public void setContactName(String contactName) {
void setContactName(String contactName) {
this.contactName = contactName;
notifyDataSetChanged();
}
@@ -77,7 +84,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
return new IntroductionHolder(v, type);
} else if (type == INTRODUCTION_OUT) {
v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_introduction_out, viewGroup, false);
R.layout.list_item_msg_notice_out, viewGroup, false);
return new IntroductionHolder(v, type);
} else if (type == NOTICE_IN) {
v = LayoutInflater.from(viewGroup.getContext()).inflate(
@@ -87,13 +94,15 @@ class ConversationAdapter extends RecyclerView.Adapter {
v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_notice_out, viewGroup, false);
return new NoticeHolder(v, type);
} else if (type == FORUM_INVITATION_IN) {
} else if (type == FORUM_INVITATION_IN || type == BLOG_INVITATION_IN) {
v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_forum_invitation_in, viewGroup, false);
R.layout.list_item_shareable_invitation_in, viewGroup,
false);
return new InvitationHolder(v, type);
} else if (type == FORUM_INVITATION_OUT) {
} else if (type == FORUM_INVITATION_OUT ||
type == BLOG_INVITATION_OUT) {
v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_forum_invitation_out, viewGroup, false);
R.layout.list_item_msg_notice_out, viewGroup, false);
return new InvitationHolder(v, type);
}
// incoming message (non-local)
@@ -119,12 +128,12 @@ class ConversationAdapter extends RecyclerView.Adapter {
bindNotice((NoticeHolder) ui, (ConversationNoticeOutItem) item);
} else if (item instanceof ConversationNoticeInItem) {
bindNotice((NoticeHolder) ui, (ConversationNoticeInItem) item);
} else if (item instanceof ConversationForumInvitationOutItem) {
} else if (item instanceof ConversationShareableInvitationOutItem) {
bindInvitation((InvitationHolder) ui,
(ConversationForumInvitationOutItem) item);
} else if (item instanceof ConversationForumInvitationInItem) {
(ConversationShareableInvitationOutItem) item);
} else if (item instanceof ConversationShareableInvitationInItem) {
bindInvitation((InvitationHolder) ui,
(ConversationForumInvitationInItem) item);
(ConversationShareableInvitationInItem) item);
} else {
throw new IllegalArgumentException("Unhandled Conversation Item");
}
@@ -180,9 +189,9 @@ class ConversationAdapter extends RecyclerView.Adapter {
String message = ir.getMessage();
if (StringUtils.isNullOrEmpty(message)) {
ui.messageLayout.setVisibility(View.GONE);
ui.messageLayout.setVisibility(GONE);
} else {
ui.messageLayout.setVisibility(View.VISIBLE);
ui.messageLayout.setVisibility(VISIBLE);
ui.message.body.setText(StringUtils.trim(message));
ui.message.date
.setText(AndroidUtils.formatDate(ctx, item.getTime()));
@@ -213,8 +222,8 @@ class ConversationAdapter extends RecyclerView.Adapter {
ui.text.setText(ctx.getString(
R.string.introduction_request_answered_received,
contactName, ir.getName()));
ui.acceptButton.setVisibility(View.GONE);
ui.declineButton.setVisibility(View.GONE);
ui.acceptButton.setVisibility(GONE);
ui.declineButton.setVisibility(GONE);
}
// Incoming Introduction Request (Not Answered)
else {
@@ -230,12 +239,12 @@ class ConversationAdapter extends RecyclerView.Adapter {
if (item.getIntroductionRequest().doesIntroduceOtherIdentity()) {
// don't allow accept when one of our identities is introduced
ui.acceptButton.setVisibility(View.GONE);
ui.acceptButton.setVisibility(GONE);
ui.text.setText(ctx.getString(
R.string.introduction_request_for_our_identity_received,
contactName, ir.getName()));
} else {
ui.acceptButton.setVisibility(View.VISIBLE);
ui.acceptButton.setVisibility(VISIBLE);
ui.acceptButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -245,7 +254,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
}
});
}
ui.declineButton.setVisibility(View.VISIBLE);
ui.declineButton.setVisibility(VISIBLE);
ui.declineButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -276,26 +285,38 @@ class ConversationAdapter extends RecyclerView.Adapter {
}
private void bindInvitation(InvitationHolder ui,
final ConversationForumInvitationItem item) {
final ConversationShareableInvitationItem item) {
ForumInvitationRequest fim = item.getForumInvitationMessage();
final InvitationRequest ir = item.getInvitationRequest();
String name = "";
int receivedRes = 0, sentRes = 0, buttonRes = 0;
if (ir instanceof ForumInvitationRequest) {
name = ((ForumInvitationRequest) ir).getForumName();
receivedRes = R.string.forum_invitation_received;
sentRes = R.string.forum_invitation_sent;
buttonRes = R.string.forum_show_invitations;
} else if (ir instanceof BlogInvitationRequest) {
name = ((BlogInvitationRequest) ir).getBlogAuthorName();
receivedRes = R.string.blogs_sharing_invitation_received;
sentRes = R.string.blogs_sharing_invitation_sent;
buttonRes = R.string.blogs_sharing_show_invitations;
}
String message = fim.getMessage();
String message = ir.getMessage();
if (StringUtils.isNullOrEmpty(message)) {
ui.messageLayout.setVisibility(View.GONE);
ui.messageLayout.setVisibility(GONE);
} else {
ui.messageLayout.setVisibility(View.VISIBLE);
ui.messageLayout.setVisibility(VISIBLE);
ui.message.body.setText(StringUtils.trim(message));
ui.message.date
.setText(AndroidUtils.formatDate(ctx, item.getTime()));
}
// Outgoing Invitation
if (item instanceof ConversationForumInvitationOutItem) {
ui.text.setText(ctx.getString(R.string.forum_invitation_sent,
fim.getForumName(), contactName));
ConversationForumInvitationOutItem i =
(ConversationForumInvitationOutItem) item;
if (item instanceof ConversationShareableInvitationOutItem) {
ui.text.setText(ctx.getString(sentRes, name, contactName));
ConversationShareableInvitationOutItem i =
(ConversationShareableInvitationOutItem) item;
if (i.isSeen()) {
ui.status.setImageResource(R.drawable.message_delivered);
ui.message.status.setImageResource(
@@ -312,22 +333,24 @@ class ConversationAdapter extends RecyclerView.Adapter {
}
// Incoming Invitation
else {
ui.text.setText(ctx.getString(R.string.forum_invitation_received,
contactName, fim.getForumName()));
ui.text.setText(ctx.getString(receivedRes, contactName, name));
if (fim.isAvailable()) {
ui.showForumsButton.setVisibility(View.VISIBLE);
ui.showForumsButton
if (ir.isAvailable()) {
final Class c = ir instanceof ForumInvitationRequest ?
InvitationsForumActivity.class :
InvitationsBlogActivity.class;
ui.showInvitationsButton.setText(ctx.getString(buttonRes));
ui.showInvitationsButton.setVisibility(VISIBLE);
ui.showInvitationsButton
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ctx,
ForumInvitationsActivity.class);
ctx.startActivity(intent);
Intent i = new Intent(ctx, c);
ctx.startActivity(i);
}
});
} else {
ui.showForumsButton.setVisibility(View.GONE);
ui.showInvitationsButton.setVisibility(GONE);
}
}
ui.date.setText(AndroidUtils.formatDate(ctx, item.getTime()));
@@ -345,7 +368,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
return items.get(position);
}
public ConversationItem getLastItem() {
ConversationItem getLastItem() {
if (items.size() > 0) {
return items.get(items.size() - 1);
} else {
@@ -353,7 +376,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
}
}
public SparseArray<IncomingItem> getIncomingMessages() {
SparseArray<IncomingItem> getIncomingMessages() {
SparseArray<IncomingItem> messages = new SparseArray<>();
for (int i = 0; i < items.size(); i++) {
@@ -365,7 +388,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
return messages;
}
public SparseArray<OutgoingItem> getOutgoingMessages() {
SparseArray<OutgoingItem> getOutgoingMessages() {
SparseArray<OutgoingItem> messages = new SparseArray<>();
for (int i = 0; i < items.size(); i++) {
@@ -377,7 +400,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
return messages;
}
public SparseArray<ConversationMessageItem> getPrivateMessages() {
SparseArray<ConversationMessageItem> getPrivateMessages() {
SparseArray<ConversationMessageItem> messages = new SparseArray<>();
for (int i = 0; i < items.size(); i++) {
@@ -408,7 +431,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
public TextView date;
public ImageView status;
public MessageHolder(View v, int type) {
MessageHolder(View v, int type) {
super(v);
layout = (ViewGroup) v.findViewById(R.id.msgLayout);
@@ -432,7 +455,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
private final TextView date;
private final ImageView status;
public IntroductionHolder(View v, int type) {
IntroductionHolder(View v, int type) {
super(v);
messageLayout = v.findViewById(R.id.messageLayout);
@@ -457,7 +480,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
private final TextView date;
private final ImageView status;
public NoticeHolder(View v, int type) {
NoticeHolder(View v, int type) {
super(v);
text = (TextView) v.findViewById(R.id.noticeText);
@@ -476,21 +499,21 @@ class ConversationAdapter extends RecyclerView.Adapter {
private final View messageLayout;
private final MessageHolder message;
private final TextView text;
private final Button showForumsButton;
private final Button showInvitationsButton;
private final TextView date;
private final ImageView status;
public InvitationHolder(View v, int type) {
InvitationHolder(View v, int type) {
super(v);
messageLayout = v.findViewById(R.id.messageLayout);
message = new MessageHolder(messageLayout,
type == FORUM_INVITATION_IN ? MSG_IN : MSG_OUT);
text = (TextView) v.findViewById(R.id.introductionText);
showForumsButton = (Button) v.findViewById(R.id.showForumsButton);
showInvitationsButton = (Button) v.findViewById(R.id.showInvitationsButton);
date = (TextView) v.findViewById(R.id.introductionTime);
if (type == FORUM_INVITATION_OUT) {
if (type == FORUM_INVITATION_OUT || type == BLOG_INVITATION_OUT) {
status = (ImageView) v.findViewById(R.id.introductionStatus);
} else {
status = null;
@@ -543,7 +566,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
}
}
public interface IntroductionHandler {
interface IntroductionHandler {
void respondToIntroduction(SessionId sessionId, boolean accept);
}
}

View File

@@ -1,32 +0,0 @@
package org.briarproject.android.contact;
import org.briarproject.api.forum.ForumInvitationRequest;
// This class is not thread-safe
public class ConversationForumInvitationInItem
extends ConversationForumInvitationItem
implements ConversationItem.IncomingItem {
private boolean read;
public ConversationForumInvitationInItem(ForumInvitationRequest fim) {
super(fim);
this.read = fim.isRead();
}
@Override
int getType() {
return FORUM_INVITATION_IN;
}
@Override
public boolean isRead() {
return read;
}
@Override
public void setRead(boolean read) {
this.read = read;
}
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.android.contact;
import org.briarproject.api.forum.ForumInvitationRequest;
abstract class ConversationForumInvitationItem extends ConversationItem {
private final ForumInvitationRequest fim;
public ConversationForumInvitationItem(ForumInvitationRequest fim) {
super(fim.getId(), fim.getTimestamp());
this.fim = fim;
}
public ForumInvitationRequest getForumInvitationMessage() {
return fim;
}
}

View File

@@ -3,13 +3,15 @@ package org.briarproject.android.contact;
import android.content.Context;
import org.briarproject.R;
import org.briarproject.api.forum.ForumInvitationRequest;
import org.briarproject.api.blogs.BlogInvitationResponse;
import org.briarproject.api.forum.ForumInvitationResponse;
import org.briarproject.api.introduction.IntroductionMessage;
import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.api.introduction.IntroductionResponse;
import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.api.sharing.InvitationMessage;
import org.briarproject.api.sharing.InvitationRequest;
import org.briarproject.api.sharing.InvitationResponse;
import org.briarproject.api.sync.MessageId;
// This class is not thread-safe
@@ -25,6 +27,8 @@ public abstract class ConversationItem {
final static int NOTICE_OUT = 6;
final static int FORUM_INVITATION_IN = 7;
final static int FORUM_INVITATION_OUT = 8;
final static int BLOG_INVITATION_IN = 9;
final static int BLOG_INVITATION_OUT = 10;
private MessageId id;
private long time;
@@ -97,15 +101,27 @@ public abstract class ConversationItem {
}
}
public static ConversationItem from(ForumInvitationRequest fim) {
public static ConversationItem from(InvitationRequest fim) {
if (fim.isLocal()) {
return new ConversationForumInvitationOutItem(fim);
return new ConversationShareableInvitationOutItem(fim);
} else {
return new ConversationForumInvitationInItem(fim);
return new ConversationShareableInvitationInItem(fim);
}
}
public static ConversationItem from(Context ctx, String contactName,
InvitationResponse ir) {
if (ir instanceof ForumInvitationResponse) {
return from(ctx, contactName, (ForumInvitationResponse) ir);
} else if (ir instanceof BlogInvitationResponse) {
return from(ctx, contactName, (BlogInvitationResponse) ir);
} else {
throw new IllegalArgumentException("Unknown Invitation Response.");
}
}
private static ConversationItem from(Context ctx, String contactName,
ForumInvitationResponse fir) {
if (fir.isLocal()) {
@@ -137,6 +153,38 @@ public abstract class ConversationItem {
}
}
private static ConversationItem from(Context ctx, String contactName,
BlogInvitationResponse fir) {
if (fir.isLocal()) {
String text;
if (fir.wasAccepted()) {
text = ctx.getString(
R.string.blogs_sharing_response_accepted_sent,
contactName);
} else {
text = ctx.getString(
R.string.blogs_sharing_response_declined_sent,
contactName);
}
return new ConversationNoticeOutItem(fir.getId(), text,
fir.getTimestamp(), fir.isSent(), fir.isSeen());
} else {
String text;
if (fir.wasAccepted()) {
text = ctx.getString(
R.string.blogs_sharing_response_accepted_received,
contactName);
} else {
text = ctx.getString(
R.string.blogs_sharing_response_declined_received,
contactName);
}
return new ConversationNoticeInItem(fir.getId(), text,
fir.getTimestamp(), fir.isRead());
}
}
/**
* This method should not be used to get user-facing objects,
* Its purpose is only to provide data for the contact list.

View File

@@ -0,0 +1,43 @@
package org.briarproject.android.contact;
import org.briarproject.api.blogs.BlogInvitationRequest;
import org.briarproject.api.forum.ForumInvitationRequest;
import org.briarproject.api.sharing.InvitationRequest;
// This class is not thread-safe
class ConversationShareableInvitationInItem
extends ConversationShareableInvitationItem
implements ConversationItem.IncomingItem {
private final int type;
private boolean read;
ConversationShareableInvitationInItem(InvitationRequest ir) {
super(ir);
if (ir instanceof ForumInvitationRequest) {
this.type = FORUM_INVITATION_IN;
} else if (ir instanceof BlogInvitationRequest) {
this.type = BLOG_INVITATION_IN;
} else {
throw new IllegalArgumentException("Unknown Invitation Type.");
}
this.read = ir.isRead();
}
@Override
int getType() {
return type;
}
@Override
public boolean isRead() {
return read;
}
@Override
public void setRead(boolean read) {
this.read = read;
}
}

View File

@@ -0,0 +1,18 @@
package org.briarproject.android.contact;
import org.briarproject.api.sharing.InvitationRequest;
abstract class ConversationShareableInvitationItem extends ConversationItem {
private final InvitationRequest fim;
ConversationShareableInvitationItem(InvitationRequest fim) {
super(fim.getId(), fim.getTimestamp());
this.fim = fim;
}
InvitationRequest getInvitationRequest() {
return fim;
}
}

View File

@@ -1,6 +1,8 @@
package org.briarproject.android.contact;
import org.briarproject.api.blogs.BlogInvitationRequest;
import org.briarproject.api.forum.ForumInvitationRequest;
import org.briarproject.api.sharing.InvitationRequest;
/**
* This class is needed and can not be replaced by an ConversationNoticeOutItem,
@@ -9,21 +11,31 @@ import org.briarproject.api.forum.ForumInvitationRequest;
* <p/>
* This class is not thread-safe
*/
public class ConversationForumInvitationOutItem
extends ConversationForumInvitationItem
class ConversationShareableInvitationOutItem
extends ConversationShareableInvitationItem
implements ConversationItem.OutgoingItem {
private final int type;
private boolean sent, seen;
public ConversationForumInvitationOutItem(ForumInvitationRequest fim) {
super(fim);
this.sent = fim.isSent();
this.seen = fim.isSeen();
ConversationShareableInvitationOutItem(InvitationRequest ir) {
super(ir);
if (ir instanceof ForumInvitationRequest) {
this.type = FORUM_INVITATION_OUT;
} else if (ir instanceof BlogInvitationRequest) {
this.type = BLOG_INVITATION_OUT;
} else {
throw new IllegalArgumentException("Unknown Invitation Type.");
}
this.sent = ir.isSent();
this.seen = ir.isSeen();
}
@Override
int getType() {
return FORUM_INVITATION_OUT;
return type;
}
@Override

View File

@@ -33,6 +33,8 @@ import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.controller.handler.UiResultHandler;
import org.briarproject.android.sharing.ShareForumActivity;
import org.briarproject.android.sharing.SharingStatusForumActivity;
import org.briarproject.android.util.AndroidUtils;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.android.util.TrustIndicatorView;
@@ -228,7 +230,7 @@ public class ForumActivity extends BriarActivity implements
options.toBundle());
return true;
case R.id.action_forum_sharing_status:
Intent i3 = new Intent(this, ForumSharingStatusActivity.class);
Intent i3 = new Intent(this, SharingStatusForumActivity.class);
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i3.putExtra(GROUP_ID, groupId.getBytes());
ActivityCompat.startActivity(this, i3, options.toBundle());

View File

@@ -1,188 +0,0 @@
package org.briarproject.android.forum;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.contact.Contact;
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.forum.Forum;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumSharingManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Logger;
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.forum.ForumInvitationAdapter.AvailableForumClickListener;
public class ForumInvitationsActivity extends BriarActivity
implements EventListener, AvailableForumClickListener {
private static final Logger LOG =
Logger.getLogger(ForumInvitationsActivity.class.getName());
private ForumInvitationAdapter adapter;
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile ForumManager forumManager;
@Inject
protected volatile ForumSharingManager forumSharingManager;
@Inject
protected volatile EventBus eventBus;
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_available_forums);
adapter = new ForumInvitationAdapter(this, this);
BriarRecyclerView list =
(BriarRecyclerView) findViewById(R.id.availableForumsView);
list.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(adapter);
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onResume() {
super.onResume();
eventBus.addListener(this);
loadForums(false);
}
@Override
public void onPause() {
super.onPause();
eventBus.removeListener(this);
adapter.clear();
}
private void loadForums(final boolean clear) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Collection<ForumInvitationItem> forums = new ArrayList<>();
long now = System.currentTimeMillis();
for (Forum f : forumSharingManager.getInvited()) {
boolean subscribed;
try {
forumManager.getForum(f.getId());
subscribed = true;
} catch (NoSuchGroupException e) {
subscribed = false;
}
Collection<Contact> c =
forumSharingManager.getSharedBy(f.getId());
forums.add(
new ForumInvitationItem(f, subscribed, c));
}
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms");
displayForums(forums, clear);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayForums(final Collection<ForumInvitationItem> forums,
final boolean clear) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (forums.isEmpty()) {
LOG.info("No forums available, finishing");
finish();
} else {
if (clear) adapter.clear();
adapter.addAll(forums);
}
}
});
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, reloading");
loadForums(true);
} else if (e instanceof GroupAddedEvent) {
GroupAddedEvent g = (GroupAddedEvent) e;
if (g.getGroup().getClientId().equals(forumManager.getClientId())) {
LOG.info("Forum added, reloading");
loadForums(false);
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent g = (GroupRemovedEvent) e;
if (g.getGroup().getClientId().equals(forumManager.getClientId())) {
LOG.info("Forum removed, reloading");
loadForums(true);
}
} else if (e instanceof ForumInvitationReceivedEvent) {
LOG.info("Available forums updated, reloading");
loadForums(false);
}
}
@Override
public void onItemClick(ForumInvitationItem item, boolean accept) {
respondToInvitation(item, 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();
// remove item and finish if it was the last
adapter.remove(item);
if (adapter.getItemCount() == 0) {
supportFinishAfterTransition();
}
}
private void respondToInvitation(final ForumInvitationItem item,
final boolean accept) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Forum f = item.getForum();
for (Contact c : item.getContacts()) {
forumSharingManager.respondToInvitation(f, c, accept);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
}

View File

@@ -16,6 +16,7 @@ import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.fragment.BaseEventFragment;
import org.briarproject.android.sharing.InvitationsForumActivity;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchGroupException;
@@ -285,6 +286,7 @@ public class ForumListFragment extends BaseEventFragment implements
@Override
public void onClick(View view) {
// snackbar click
startActivity(new Intent(getContext(), ForumInvitationsActivity.class));
Intent i = new Intent(getContext(), InvitationsForumActivity.class);
startActivity(i);
}
}

View File

@@ -0,0 +1,37 @@
package org.briarproject.android.sharing;
import android.content.Context;
import org.briarproject.R;
import org.briarproject.api.blogs.Blog;
class BlogInvitationAdapter extends InvitationAdapter {
BlogInvitationAdapter(Context ctx, AvailableForumClickListener listener) {
super(ctx, listener);
}
@Override
public void onBindViewHolder(InvitationsViewHolder ui, int position) {
super.onBindViewHolder(ui, position);
InvitationItem item = getItem(position);
Blog blog = (Blog) item.getShareable();
ui.avatar.setAuthorAvatar(blog.getAuthor());
ui.name.setText(ctx.getString(R.string.blogs_personal_blog,
blog.getAuthor().getName()));
if (item.isSubscribed()) {
ui.subscribed.setText(ctx.getString(R.string.blogs_sharing_exists,
blog.getAuthor().getName()));
}
}
int compareInvitations(InvitationItem o1, InvitationItem o2) {
return String.CASE_INSENSITIVE_ORDER
.compare(((Blog) o1.getShareable()).getAuthor().getName(),
((Blog) o2.getShareable()).getAuthor().getName());
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.android.forum;
package org.briarproject.android.sharing;
import android.content.Context;
import android.graphics.Color;

View File

@@ -1,4 +1,4 @@
package org.briarproject.android.forum;
package org.briarproject.android.sharing;
import android.content.Context;
import android.os.Build;
@@ -37,8 +37,8 @@ import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.forum.ShareForumActivity.CONTACTS;
import static org.briarproject.android.forum.ShareForumActivity.getContactsFromIds;
import static org.briarproject.android.sharing.ShareActivity.CONTACTS;
import static org.briarproject.android.sharing.ShareActivity.getContactsFromIds;
import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
public class ContactSelectorFragment extends BaseFragment implements
@@ -49,7 +49,7 @@ public class ContactSelectorFragment extends BaseFragment implements
private static final Logger LOG =
Logger.getLogger(ContactSelectorFragment.class.getName());
private ShareForumActivity shareForumActivity;
private ShareActivity shareActivity;
private Menu menu;
private BriarRecyclerView list;
private ContactSelectorAdapter adapter;
@@ -63,7 +63,7 @@ public class ContactSelectorFragment extends BaseFragment implements
@Inject
protected volatile ForumSharingManager forumSharingManager;
protected volatile GroupId groupId;
private volatile GroupId groupId;
public static ContactSelectorFragment newInstance(GroupId groupId) {
@@ -83,10 +83,10 @@ public class ContactSelectorFragment extends BaseFragment implements
public void onAttach(Context context) {
super.onAttach(context);
try {
shareForumActivity = (ShareForumActivity) context;
shareActivity = (ShareActivity) context;
} catch (ClassCastException e) {
throw new InstantiationError(
"This fragment is only meant to be attached to the ShareForumActivity");
"This fragment is only meant to be attached to a subclass of ShareActivity");
}
}
@@ -95,7 +95,8 @@ public class ContactSelectorFragment extends BaseFragment implements
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
groupId = new GroupId(getArguments().getByteArray(GROUP_ID));
Bundle args = getArguments();
groupId = new GroupId(args.getByteArray(GROUP_ID));
if (groupId == null) throw new IllegalStateException("No GroupId");
}
@@ -115,13 +116,13 @@ public class ContactSelectorFragment extends BaseFragment implements
list = (BriarRecyclerView) contentView.findViewById(R.id.contactList);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setAdapter(adapter);
list.setEmptyText(getString(R.string.no_contacts));
list.setEmptyText(getString(R.string.no_contacts_selector));
// restore selected contacts if available
if (savedInstanceState != null) {
ArrayList<Integer> intContacts =
savedInstanceState.getIntegerArrayList(CONTACTS);
selectedContacts = ShareForumActivity.getContactsFromIntegers(
selectedContacts = ShareActivity.getContactsFromIntegers(
intContacts);
}
@@ -160,11 +161,11 @@ public class ContactSelectorFragment extends BaseFragment implements
// Handle presses on the action bar items
switch (item.getItemId()) {
case android.R.id.home:
shareForumActivity.onBackPressed();
shareActivity.onBackPressed();
return true;
case R.id.action_share_forum:
selectedContacts = adapter.getSelectedContactIds();
shareForumActivity.showMessageScreen(groupId, selectedContacts);
shareActivity.showMessageScreen(groupId, selectedContacts);
return true;
default:
return super.onOptionsItemSelected(item);
@@ -185,7 +186,7 @@ public class ContactSelectorFragment extends BaseFragment implements
}
private void loadContacts(final Collection<ContactId> selection) {
shareForumActivity.runOnDbThread(new Runnable() {
shareActivity.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
@@ -199,8 +200,7 @@ public class ContactSelectorFragment extends BaseFragment implements
boolean selected = selection != null &&
selection.contains(c.getId());
// do we have already some sharing with that contact?
boolean disabled =
!forumSharingManager.canBeShared(groupId, c);
boolean disabled = shareActivity.isDisabled(groupId, c);
contacts.add(new SelectableContactListItem(c,
localAuthor, groupId, selected, disabled));
}
@@ -218,7 +218,7 @@ public class ContactSelectorFragment extends BaseFragment implements
}
private void displayContacts(final List<ContactListItem> contacts) {
shareForumActivity.runOnUiThread(new Runnable() {
shareActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (!contacts.isEmpty()) adapter.addAll(contacts);

View File

@@ -0,0 +1,31 @@
package org.briarproject.android.sharing;
import android.content.Context;
import org.briarproject.api.forum.Forum;
class ForumInvitationAdapter extends InvitationAdapter {
ForumInvitationAdapter(Context ctx, AvailableForumClickListener listener) {
super(ctx, listener);
}
@Override
public void onBindViewHolder(InvitationsViewHolder ui, int position) {
super.onBindViewHolder(ui, position);
InvitationItem item = getItem(position);
Forum forum = (Forum) item.getShareable();
ui.avatar.setText(forum.getName().substring(0, 1));
ui.avatar.setBackgroundBytes(item.getShareable().getId().getBytes());
ui.name.setText(forum.getName());
}
int compareInvitations(InvitationItem o1, InvitationItem o2) {
return String.CASE_INSENSITIVE_ORDER
.compare(((Forum) o1.getShareable()).getName(),
((Forum) o2.getShareable()).getName());
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.android.forum;
package org.briarproject.android.sharing;
import android.content.Context;
import android.support.v7.util.SortedList;
@@ -12,7 +12,6 @@ import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.util.TextAvatarView;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.forum.ForumSharingMessage;
import org.briarproject.util.StringUtils;
import java.util.ArrayList;
@@ -21,37 +20,32 @@ import java.util.Collection;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
class ForumInvitationAdapter extends
RecyclerView.Adapter<ForumInvitationAdapter.AvailableForumViewHolder> {
abstract class InvitationAdapter extends
RecyclerView.Adapter<InvitationAdapter.InvitationsViewHolder> {
private final Context ctx;
protected final Context ctx;
private final AvailableForumClickListener listener;
private final SortedList<ForumInvitationItem> forums =
new SortedList<>(ForumInvitationItem.class,
private final SortedList<InvitationItem> invitations =
new SortedList<>(InvitationItem.class,
new SortedListCallBacks());
ForumInvitationAdapter(Context ctx, AvailableForumClickListener listener) {
InvitationAdapter(Context ctx, AvailableForumClickListener listener) {
this.ctx = ctx;
this.listener = listener;
}
@Override
public AvailableForumViewHolder onCreateViewHolder(ViewGroup parent,
public InvitationsViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View v = LayoutInflater.from(ctx)
.inflate(R.layout.list_item_available_forum, parent, false);
return new AvailableForumViewHolder(v);
.inflate(R.layout.list_item_invitations, parent, false);
return new InvitationsViewHolder(v);
}
@Override
public void onBindViewHolder(AvailableForumViewHolder ui, int position) {
final ForumInvitationItem item = getItem(position);
ui.avatar.setText(item.getForum().getName().substring(0, 1));
ui.avatar.setBackgroundBytes(item.getForum().getId().getBytes());
ui.name.setText(item.getForum().getName());
public void onBindViewHolder(InvitationsViewHolder ui, int position) {
final InvitationItem item = getItem(position);
Collection<String> names = new ArrayList<>();
for (Contact c : item.getContacts()) names.add(c.getAuthor().getName());
@@ -80,40 +74,39 @@ class ForumInvitationAdapter extends
@Override
public int getItemCount() {
return forums.size();
return invitations.size();
}
public ForumInvitationItem getItem(int position) {
return forums.get(position);
public InvitationItem getItem(int position) {
return invitations.get(position);
}
public void add(ForumInvitationItem item) {
forums.add(item);
public void add(InvitationItem item) {
invitations.add(item);
}
public void addAll(Collection<ForumInvitationItem> list) {
forums.addAll(list);
public void addAll(Collection<InvitationItem> list) {
invitations.addAll(list);
}
public void remove(ForumInvitationItem item) {
forums.remove(item);
public void remove(InvitationItem item) {
invitations.remove(item);
}
public void clear() {
forums.clear();
invitations.clear();
}
static class AvailableForumViewHolder
extends RecyclerView.ViewHolder {
static class InvitationsViewHolder extends RecyclerView.ViewHolder {
private final TextAvatarView avatar;
private final TextView name;
private final TextView sharedBy;
private final TextView subscribed;
private final Button accept;
private final Button decline;
final TextAvatarView avatar;
final TextView name;
final TextView sharedBy;
final TextView subscribed;
final Button accept;
final Button decline;
AvailableForumViewHolder(View v) {
InvitationsViewHolder(View v) {
super(v);
avatar = (TextAvatarView) v.findViewById(R.id.avatarView);
@@ -125,15 +118,15 @@ class ForumInvitationAdapter extends
}
}
abstract int compareInvitations(InvitationItem o1, InvitationItem o2);
private class SortedListCallBacks
extends SortedList.Callback<ForumInvitationItem> {
extends SortedList.Callback<InvitationItem> {
@Override
public int compare(ForumInvitationItem o1,
ForumInvitationItem o2) {
return String.CASE_INSENSITIVE_ORDER
.compare(o1.getForum().getName(),
o2.getForum().getName());
public int compare(InvitationItem o1,
InvitationItem o2) {
return compareInvitations(o1, o2);
}
@Override
@@ -157,21 +150,21 @@ class ForumInvitationAdapter extends
}
@Override
public boolean areContentsTheSame(ForumInvitationItem oldItem,
ForumInvitationItem newItem) {
public boolean areContentsTheSame(InvitationItem oldItem,
InvitationItem newItem) {
return oldItem.isSubscribed() == newItem.isSubscribed() &&
oldItem.getContacts().equals(newItem.getContacts());
}
@Override
public boolean areItemsTheSame(ForumInvitationItem oldItem,
ForumInvitationItem newItem) {
return oldItem.getForum().equals(newItem.getForum());
public boolean areItemsTheSame(InvitationItem oldItem,
InvitationItem newItem) {
return oldItem.getShareable().equals(newItem.getShareable());
}
}
interface AvailableForumClickListener {
void onItemClick(ForumInvitationItem item, boolean accept);
void onItemClick(InvitationItem item, boolean accept);
}
}

View File

@@ -1,29 +1,29 @@
package org.briarproject.android.forum;
package org.briarproject.android.sharing;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.sharing.Shareable;
import java.util.Collection;
class ForumInvitationItem {
class InvitationItem {
private final Forum forum;
private final Shareable shareable;
private final boolean subscribed;
private final Collection<Contact> contacts;
ForumInvitationItem(Forum forum, boolean subscribed,
InvitationItem(Shareable shareable, boolean subscribed,
Collection<Contact> contacts) {
this.forum = forum;
this.shareable = shareable;
this.subscribed = subscribed;
this.contacts = contacts;
}
Forum getForum() {
return forum;
Shareable getShareable() {
return shareable;
}
public boolean isSubscribed() {
boolean isSubscribed() {
return subscribed;
}

View File

@@ -0,0 +1,117 @@
package org.briarproject.android.sharing;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.util.BriarRecyclerView;
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 java.util.Collection;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.widget.Toast.LENGTH_SHORT;
import static org.briarproject.android.sharing.InvitationAdapter.AvailableForumClickListener;
abstract class InvitationsActivity extends BriarActivity
implements EventListener, AvailableForumClickListener {
protected static final Logger LOG =
Logger.getLogger(InvitationsActivity.class.getName());
private InvitationAdapter adapter;
@Inject
protected EventBus eventBus;
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_invitations);
adapter = getAdapter(this, this);
BriarRecyclerView list =
(BriarRecyclerView) findViewById(R.id.invitationsView);
if (list != null) {
list.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(adapter);
}
}
@Override
public void onResume() {
super.onResume();
eventBus.addListener(this);
loadInvitations(false);
}
@Override
public void onPause() {
super.onPause();
eventBus.removeListener(this);
adapter.clear();
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, reloading...");
loadInvitations(true);
}
}
@Override
public void onItemClick(InvitationItem item, boolean accept) {
respondToInvitation(item, accept);
// show toast
int res = getDeclineRes();
if (accept) res = getAcceptRes();
Toast.makeText(this, res, LENGTH_SHORT).show();
// remove item and finish if it was the last
adapter.remove(item);
if (adapter.getItemCount() == 0) {
supportFinishAfterTransition();
}
}
abstract protected InvitationAdapter getAdapter(Context ctx,
AvailableForumClickListener listener);
abstract protected void loadInvitations(boolean clear);
abstract protected void respondToInvitation(final InvitationItem item,
final boolean accept);
abstract protected int getAcceptRes();
abstract protected int getDeclineRes();
protected void displayInvitations(
final Collection<InvitationItem> invitations, final boolean clear) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (invitations.isEmpty()) {
LOG.info("No more invitations available, finishing");
finish();
} else {
if (clear) adapter.clear();
adapter.addAll(invitations);
}
}
});
}
}

View File

@@ -0,0 +1,127 @@
package org.briarproject.android.sharing;
import android.content.Context;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.event.BlogInvitationReceivedEvent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.GroupAddedEvent;
import org.briarproject.api.event.GroupRemovedEvent;
import org.briarproject.api.sync.ClientId;
import java.util.ArrayList;
import java.util.Collection;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.sharing.InvitationAdapter.AvailableForumClickListener;
public class InvitationsBlogActivity extends InvitationsActivity {
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile BlogManager blogManager;
@Inject
protected volatile BlogSharingManager blogSharingManager;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void eventOccurred(Event e) {
super.eventOccurred(e);
if (e instanceof GroupAddedEvent) {
GroupAddedEvent g = (GroupAddedEvent) e;
ClientId cId = g.getGroup().getClientId();
if (cId.equals(blogManager.getClientId())) {
LOG.info("Blog added, reloading");
loadInvitations(false);
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent g = (GroupRemovedEvent) e;
ClientId cId = g.getGroup().getClientId();
if (cId.equals(blogManager.getClientId())) {
LOG.info("Blog removed, reloading");
loadInvitations(false);
}
} else if (e instanceof BlogInvitationReceivedEvent) {
LOG.info("Blog invitation received, reloading");
loadInvitations(false);
}
}
protected InvitationAdapter getAdapter(Context ctx,
AvailableForumClickListener listener) {
return new BlogInvitationAdapter(ctx, listener);
}
protected void loadInvitations(final boolean clear) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Collection<InvitationItem> invitations = new ArrayList<>();
long now = System.currentTimeMillis();
for (Blog b : blogSharingManager.getInvited()) {
boolean subscribed;
try {
blogManager.getBlog(b.getId());
subscribed = true;
} catch (NoSuchGroupException e) {
subscribed = false;
}
Collection<Contact> c =
blogSharingManager.getSharedBy(b.getId());
invitations.add(
new InvitationItem(b, subscribed, c));
}
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms");
displayInvitations(invitations, clear);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
protected void respondToInvitation(final InvitationItem item,
final boolean accept) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Blog b = (Blog) item.getShareable();
for (Contact c : item.getContacts()) {
blogSharingManager.respondToInvitation(b, c, accept);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
protected int getAcceptRes() {
return R.string.blogs_sharing_joined_toast;
}
protected int getDeclineRes() {
return R.string.blogs_sharing_declined_toast;
}
}

View File

@@ -0,0 +1,127 @@
package org.briarproject.android.sharing;
import android.content.Context;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.event.GroupAddedEvent;
import org.briarproject.api.event.GroupRemovedEvent;
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 javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.sharing.InvitationAdapter.AvailableForumClickListener;
public class InvitationsForumActivity extends InvitationsActivity {
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile ForumManager forumManager;
@Inject
protected volatile ForumSharingManager forumSharingManager;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void eventOccurred(Event e) {
super.eventOccurred(e);
if (e instanceof GroupAddedEvent) {
GroupAddedEvent g = (GroupAddedEvent) e;
ClientId cId = g.getGroup().getClientId();
if (cId.equals(forumManager.getClientId())) {
LOG.info("Forum added, reloading");
loadInvitations(false);
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent g = (GroupRemovedEvent) e;
ClientId cId = g.getGroup().getClientId();
if (cId.equals(forumManager.getClientId())) {
LOG.info("Forum removed, reloading");
loadInvitations(false);
}
} else if (e instanceof ForumInvitationReceivedEvent) {
LOG.info("Forum invitation received, reloading");
loadInvitations(false);
}
}
protected InvitationAdapter getAdapter(Context ctx,
AvailableForumClickListener listener) {
return new ForumInvitationAdapter(ctx, listener);
}
protected void loadInvitations(final boolean clear) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Collection<InvitationItem> forums = new ArrayList<>();
long now = System.currentTimeMillis();
for (Forum f : forumSharingManager.getInvited()) {
boolean subscribed;
try {
forumManager.getForum(f.getId());
subscribed = true;
} catch (NoSuchGroupException e) {
subscribed = false;
}
Collection<Contact> c =
forumSharingManager.getSharedBy(f.getId());
forums.add(
new InvitationItem(f, subscribed, c));
}
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms");
displayInvitations(forums, clear);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
protected void respondToInvitation(final InvitationItem item,
final boolean accept) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Forum f = (Forum) item.getShareable();
for (Contact c : item.getContacts()) {
forumSharingManager.respondToInvitation(f, c, accept);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
protected int getAcceptRes() {
return R.string.forum_joined_toast;
}
protected int getDeclineRes() {
return R.string.forum_declined_toast;
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.android.forum;
package org.briarproject.android.sharing;
import org.briarproject.android.contact.ContactListItem;
import org.briarproject.android.contact.ConversationItem;

View File

@@ -1,31 +1,31 @@
package org.briarproject.android.forum;
package org.briarproject.android.sharing;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
// TODO extend the BriarFragmentActivity ?
public class ShareForumActivity extends BriarActivity implements
public abstract class ShareActivity extends BriarActivity implements
BaseFragment.BaseFragmentListener {
public final static String CONTACTS = "contacts";
final static String CONTACTS = "contacts";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_share_forum);
setContentView(R.layout.activity_share);
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
@@ -36,34 +36,32 @@ public class ShareForumActivity extends BriarActivity implements
ContactSelectorFragment contactSelectorFragment =
ContactSelectorFragment.newInstance(groupId);
getSupportFragmentManager().beginTransaction()
.add(R.id.shareForumContainer, contactSelectorFragment)
.add(R.id.shareContainer, contactSelectorFragment)
.commit();
}
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
abstract ShareMessageFragment getMessageFragment(GroupId groupId,
Collection<ContactId> contacts);
public void showMessageScreen(GroupId groupId,
Collection<ContactId> contacts) {
abstract boolean isDisabled(GroupId groupId, Contact c) throws DbException;
ShareForumMessageFragment messageFragment =
ShareForumMessageFragment.newInstance(groupId, contacts);
void showMessageScreen(GroupId groupId, Collection<ContactId> contacts) {
ShareMessageFragment messageFragment =
getMessageFragment(groupId, contacts);
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(android.R.anim.fade_in,
android.R.anim.fade_out,
android.R.anim.slide_in_left,
android.R.anim.slide_out_right)
.replace(R.id.shareForumContainer, messageFragment,
.replace(R.id.shareContainer, messageFragment,
ContactSelectorFragment.TAG)
.addToBackStack(null)
.commit();
}
public static ArrayList<Integer> getContactsFromIds(
static ArrayList<Integer> getContactsFromIds(
Collection<ContactId> contacts) {
// transform ContactIds to Integers so they can be added to a bundle
@@ -74,13 +72,13 @@ public class ShareForumActivity extends BriarActivity implements
return intContacts;
}
public void sharingSuccessful(View v) {
void sharingSuccessful(View v) {
setResult(RESULT_OK);
hideSoftKeyboard(v);
supportFinishAfterTransition();
}
protected static Collection<ContactId> getContactsFromIntegers(
static Collection<ContactId> getContactsFromIntegers(
ArrayList<Integer> intContacts) {
// turn contact integers from a bundle back to ContactIds

View File

@@ -0,0 +1,35 @@
package org.briarproject.android.sharing;
import org.briarproject.android.ActivityComponent;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import javax.inject.Inject;
public class ShareBlogActivity extends ShareActivity {
@Inject
volatile BlogSharingManager blogSharingManager;
ShareMessageFragment getMessageFragment(GroupId groupId,
Collection<ContactId> contacts) {
return ShareBlogMessageFragment.newInstance(groupId, contacts);
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
/**
* This must only be called from a DbThread
*/
boolean isDisabled(GroupId groupId, Contact c) throws DbException {
return !blogSharingManager.canBeShared(groupId, c);
}
}

View File

@@ -0,0 +1,81 @@
package org.briarproject.android.sharing;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import javax.inject.Inject;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.WARNING;
public class ShareBlogMessageFragment extends ShareMessageFragment {
public final static String TAG = ShareBlogMessageFragment.class.getName();
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile BlogSharingManager blogSharingManager;
public static ShareBlogMessageFragment newInstance(GroupId groupId,
Collection<ContactId> contacts) {
ShareBlogMessageFragment fragment = new ShareBlogMessageFragment();
fragment.setArguments(getArguments(groupId, contacts));
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setTitle(R.string.blogs_sharing_share);
View v = super.onCreateView(inflater, container, savedInstanceState);
ui.button.setText(getString(R.string.blogs_sharing_button));
return v;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
protected void share(final String msg) {
listener.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
for (ContactId c : getContacts()) {
blogSharingManager.sendInvitation(getGroupId(), c, msg);
}
} catch (DbException e) {
sharingError();
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
protected void sharingError() {
runOnUiThread(new Runnable() {
@Override
public void run() {
int res = R.string.blogs_sharing_error;
Toast.makeText(getContext(), res, LENGTH_SHORT).show();
}
});
}
}

View File

@@ -0,0 +1,34 @@
package org.briarproject.android.sharing;
import org.briarproject.android.ActivityComponent;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import javax.inject.Inject;
public class ShareForumActivity extends ShareActivity {
@Inject
volatile ForumSharingManager forumSharingManager;
ShareMessageFragment getMessageFragment(GroupId groupId,
Collection<ContactId> contacts) {
return ShareForumMessageFragment.newInstance(groupId, contacts);
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
/**
* This must only be called from a DbThread
*/
boolean isDisabled(GroupId groupId, Contact c) throws DbException {
return !forumSharingManager.canBeShared(groupId, c);
}
}

View File

@@ -0,0 +1,79 @@
package org.briarproject.android.sharing;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import javax.inject.Inject;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.WARNING;
public class ShareForumMessageFragment extends ShareMessageFragment {
public final static String TAG = ShareForumMessageFragment.class.getName();
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile ForumSharingManager forumSharingManager;
public static ShareForumMessageFragment newInstance(GroupId groupId,
Collection<ContactId> contacts) {
ShareForumMessageFragment fragment = new ShareForumMessageFragment();
fragment.setArguments(getArguments(groupId, contacts));
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setTitle(R.string.forum_share_button);
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
protected void share(final String msg) {
listener.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
for (ContactId c : getContacts()) {
forumSharingManager.
sendInvitation(getGroupId(), c, msg);
}
} catch (DbException e) {
sharingError();
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
protected void sharingError() {
runOnUiThread(new Runnable() {
@Override
public void run() {
int res = R.string.forum_share_error;
Toast.makeText(getContext(), res, LENGTH_SHORT).show();
}
});
}
}

View File

@@ -1,21 +1,18 @@
package org.briarproject.android.forum;
package org.briarproject.android.sharing;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.sync.GroupId;
@@ -25,42 +22,41 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.forum.ShareForumActivity.CONTACTS;
import static org.briarproject.android.forum.ShareForumActivity.getContactsFromIds;
import static org.briarproject.android.sharing.ShareActivity.CONTACTS;
import static org.briarproject.android.sharing.ShareActivity.getContactsFromIds;
import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
public class ShareForumMessageFragment extends BaseFragment {
abstract class ShareMessageFragment extends BaseFragment {
public final static String TAG = "IntroductionMessageFragment";
public final static String TAG = ShareMessageFragment.class.getName();
private static final Logger LOG =
Logger.getLogger(ShareForumMessageFragment.class.getName());
protected static final Logger LOG = Logger.getLogger(TAG);
private ShareForumActivity shareForumActivity;
private ViewHolder ui;
protected ViewHolder ui;
private ShareActivity shareActivity;
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile ForumSharingManager forumSharingManager;
@Inject
protected volatile BlogSharingManager blogSharingManager;
private volatile GroupId groupId;
private volatile Collection<ContactId> contacts;
public static ShareForumMessageFragment newInstance(GroupId groupId, Collection<ContactId> contacts) {
protected static Bundle getArguments(GroupId groupId,
Collection<ContactId> contacts) {
Bundle args = new Bundle();
args.putByteArray(GROUP_ID, groupId.getBytes());
args.putIntegerArrayList(CONTACTS, getContactsFromIds(contacts));
ShareForumMessageFragment fragment = new ShareForumMessageFragment();
fragment.setArguments(args);
return fragment;
return args;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
shareForumActivity = (ShareForumActivity) context;
shareActivity = (ShareActivity) context;
} catch (ClassCastException e) {
throw new InstantiationError(
"This fragment is only meant to be attached to the ShareForumActivity");
@@ -71,17 +67,18 @@ public class ShareForumMessageFragment extends BaseFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// change toolbar text
ActionBar actionBar = shareForumActivity.getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(R.string.forum_share_button);
}
// allow for home button to act as back button
// allow for "up" button to act as back button
setHasOptionsMenu(true);
// get groupID and contactIDs from fragment arguments
groupId = new GroupId(getArguments().getByteArray(GROUP_ID));
ArrayList<Integer> intContacts =
getArguments().getIntegerArrayList(CONTACTS);
if (intContacts == null) throw new IllegalArgumentException();
contacts = ShareActivity.getContactsFromIntegers(intContacts);
// inflate view
View v = inflater.inflate(R.layout.share_forum_message, container,
View v = inflater.inflate(R.layout.fragment_share_message, container,
false);
ui = new ViewHolder(v);
ui.button.setOnClickListener(new View.OnClickListener() {
@@ -91,13 +88,6 @@ public class ShareForumMessageFragment extends BaseFragment {
}
});
// get groupID and contactIDs from fragment arguments
groupId = new GroupId(getArguments().getByteArray(GROUP_ID));
ArrayList<Integer> intContacts =
getArguments().getIntegerArrayList(CONTACTS);
if (intContacts == null) throw new IllegalArgumentException();
contacts = ShareForumActivity.getContactsFromIntegers(intContacts);
return v;
}
@@ -105,7 +95,7 @@ public class ShareForumMessageFragment extends BaseFragment {
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
shareForumActivity.onBackPressed();
shareActivity.onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
@@ -117,54 +107,40 @@ public class ShareForumMessageFragment extends BaseFragment {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
protected void setTitle(int res) {
shareActivity.setTitle(res);
}
public void onButtonClick() {
private void onButtonClick() {
// disable button to prevent accidental double invitations
ui.button.setEnabled(false);
String msg = ui.message.getText().toString();
shareForum(msg);
share(msg);
// don't wait for the introduction to be made before finishing activity
shareForumActivity.sharingSuccessful(ui.message);
// don't wait for the invitation to be made before finishing activity
shareActivity.sharingSuccessful(ui.message);
}
private void shareForum(final String msg) {
shareForumActivity.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
for (ContactId c : contacts) {
forumSharingManager.sendInvitation(groupId, c,
msg);
}
} catch (DbException e) {
sharingError();
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
abstract void share(final String msg);
abstract void sharingError();
protected Collection<ContactId> getContacts() {
return contacts;
}
private void sharingError() {
shareForumActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(shareForumActivity,
R.string.introduction_error, LENGTH_SHORT).show();
}
});
protected GroupId getGroupId() {
return groupId;
}
private static class ViewHolder {
protected void runOnUiThread(Runnable runnable) {
listener.runOnUiThread(runnable);
}
private final EditText message;
private final Button button;
protected static class ViewHolder {
protected final EditText message;
protected final Button button;
ViewHolder(View v) {
message = (EditText) v.findViewById(R.id.invitationMessageView);

View File

@@ -1,4 +1,4 @@
package org.briarproject.android.forum;
package org.briarproject.android.sharing;
import android.content.Intent;
import android.os.Bundle;
@@ -6,13 +6,11 @@ import android.support.v7.widget.LinearLayoutManager;
import android.view.MenuItem;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.contact.ContactListItem;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.GroupId;
@@ -26,26 +24,24 @@ import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
public class ForumSharingStatusActivity extends BriarActivity {
abstract class SharingStatusActivity extends BriarActivity {
private GroupId groupId;
private BriarRecyclerView sharedByList, sharedWithList;
private ForumSharingStatusAdapter sharedByAdapter, sharedWithAdapter;
private SharingStatusAdapter sharedByAdapter, sharedWithAdapter;
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile ForumSharingManager forumSharingManager;
@Inject
protected volatile IdentityManager identityManager;
public final static String TAG = "ForumSharingStatusActivity";
public final static String TAG = SharingStatusActivity.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_forum_sharing_status);
setContentView(R.layout.activity_sharing_status);
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
@@ -53,13 +49,13 @@ public class ForumSharingStatusActivity extends BriarActivity {
groupId = new GroupId(b);
sharedByList = (BriarRecyclerView) findViewById(R.id.sharedByView);
sharedByAdapter = new ForumSharingStatusAdapter(this);
sharedByAdapter = new SharingStatusAdapter(this);
sharedByList.setLayoutManager(new LinearLayoutManager(this));
sharedByList.setAdapter(sharedByAdapter);
sharedByList.setEmptyText(getString(R.string.nobody));
sharedWithList = (BriarRecyclerView) findViewById(R.id.sharedWithView);
sharedWithAdapter = new ForumSharingStatusAdapter(this);
sharedWithAdapter = new SharingStatusAdapter(this);
sharedWithList.setLayoutManager(new LinearLayoutManager(this));
sharedWithList.setAdapter(sharedWithAdapter);
sharedWithList.setEmptyText(getString(R.string.nobody));
@@ -85,9 +81,18 @@ public class ForumSharingStatusActivity extends BriarActivity {
}
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
/**
* This must only be called from the DbThread
*/
abstract protected Collection<Contact> getSharedWith() throws DbException;
/**
* This must only be called from the DbThread
*/
abstract protected Collection<Contact> getSharedBy() throws DbException;
protected GroupId getGroupId() {
return groupId;
}
private void loadSharedBy() {
@@ -96,9 +101,7 @@ public class ForumSharingStatusActivity extends BriarActivity {
public void run() {
List<ContactListItem> contactItems = new ArrayList<>();
try {
Collection<Contact> contacts =
forumSharingManager.getSharedBy(groupId);
for (Contact c : contacts) {
for (Contact c : getSharedBy()) {
LocalAuthor localAuthor = identityManager
.getLocalAuthor(c.getLocalAuthorId());
ContactListItem item =
@@ -134,9 +137,7 @@ public class ForumSharingStatusActivity extends BriarActivity {
public void run() {
List<ContactListItem> contactItems = new ArrayList<>();
try {
Collection<Contact> contacts =
forumSharingManager.getSharedWith(groupId);
for (Contact c : contacts) {
for (Contact c : getSharedWith()) {
LocalAuthor localAuthor = identityManager
.getLocalAuthor(c.getLocalAuthorId());
ContactListItem item =

View File

@@ -1,22 +1,18 @@
package org.briarproject.android.forum;
package org.briarproject.android.sharing;
import android.content.Context;
import android.support.v4.content.ContextCompat;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.contact.BaseContactListAdapter;
import org.briarproject.android.contact.ContactListItem;
public class ForumSharingStatusAdapter
class SharingStatusAdapter
extends BaseContactListAdapter<BaseContactListAdapter.BaseContactHolder> {
public ForumSharingStatusAdapter(Context context) {
SharingStatusAdapter(Context context) {
super(context, null);
}

View File

@@ -0,0 +1,37 @@
package org.briarproject.android.sharing;
import org.briarproject.android.ActivityComponent;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.db.DbException;
import java.util.Collection;
import javax.inject.Inject;
public class SharingStatusBlogActivity extends SharingStatusActivity {
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile BlogSharingManager blogSharingManager;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
/**
* This must only be called from the DbThread
*/
protected Collection<Contact> getSharedWith() throws DbException {
return blogSharingManager.getSharedWith(getGroupId());
}
/**
* This must only be called from the DbThread
*/
protected Collection<Contact> getSharedBy() throws DbException {
return blogSharingManager.getSharedBy(getGroupId());
}
}

View File

@@ -0,0 +1,37 @@
package org.briarproject.android.sharing;
import org.briarproject.android.ActivityComponent;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import java.util.Collection;
import javax.inject.Inject;
public class SharingStatusForumActivity extends SharingStatusActivity {
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile ForumSharingManager forumSharingManager;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
/**
* This must only be called from the DbThread
*/
protected Collection<Contact> getSharedWith() throws DbException {
return forumSharingManager.getSharedWith(getGroupId());
}
/**
* This must only be called from the DbThread
*/
protected Collection<Contact> getSharedBy() throws DbException {
return forumSharingManager.getSharedBy(getGroupId());
}
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.android.util;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
@@ -10,8 +11,10 @@ import android.widget.FrameLayout;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.api.identity.Author;
import de.hdodenhof.circleimageview.CircleImageView;
import im.delight.android.identicons.IdenticonDrawable;
public class TextAvatarView extends FrameLayout {
@@ -83,4 +86,10 @@ public class TextAvatarView extends FrameLayout {
}
}
public void setAuthorAvatar(Author author) {
Drawable drawable = new IdenticonDrawable(author.getId().getBytes());
background.setImageDrawable(drawable);
character.setVisibility(GONE);
}
}

View File

@@ -8,20 +8,20 @@ import org.briarproject.api.sync.MessageId;
public class BlogInvitationRequest extends InvitationRequest {
private final String blogTitle;
private final String blogAuthorName;
public BlogInvitationRequest(MessageId id, SessionId sessionId,
ContactId contactId, String blogTitle, String message,
ContactId contactId, String blogAuthorName, String message,
boolean available, long time, boolean local, boolean sent,
boolean seen, boolean read) {
super(id, sessionId, contactId, message, available, time, local, sent,
seen, read);
this.blogTitle = blogTitle;
this.blogAuthorName = blogAuthorName;
}
public String getBlogTitle() {
return blogTitle;
public String getBlogAuthorName() {
return blogAuthorName;
}
}

View File

@@ -0,0 +1,17 @@
package org.briarproject.api.blogs;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.sharing.InvitationResponse;
import org.briarproject.api.sync.MessageId;
public class BlogInvitationResponse extends InvitationResponse {
public BlogInvitationResponse(MessageId id, SessionId sessionId,
ContactId contactId, boolean accept, long time, boolean local,
boolean sent, boolean seen, boolean read) {
super(id, sessionId, contactId, accept, time, local, sent, seen, read);
}
}

View File

@@ -36,7 +36,7 @@ public interface BlogManager {
Collection<Blog> getBlogs(LocalAuthor localAuthor) throws DbException;
/** Returns only the personal blog of the given author. */
Blog getPersonalBlog(Author author) throws DbException;
Blog getPersonalBlog(Author author);
/** Returns all blogs to which the user subscribes. */
Collection<Blog> getBlogs() throws DbException;

View File

@@ -3,14 +3,14 @@ package org.briarproject.api.blogs;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.sharing.InvitationMessage;
import org.briarproject.api.sharing.SharingManager;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
public interface BlogSharingManager
extends SharingManager<Blog, BlogInvitationRequest> {
public interface BlogSharingManager extends SharingManager<Blog> {
/**
* Returns the unique ID of the blog sharing client.
@@ -34,7 +34,7 @@ public interface BlogSharingManager
* Returns all blogs sharing messages sent by the Contact
* identified by contactId.
*/
Collection<BlogInvitationRequest> getInvitationMessages(
Collection<InvitationMessage> getInvitationMessages(
ContactId contactId) throws DbException;
/**

View File

@@ -10,7 +10,7 @@ import org.briarproject.api.sync.GroupId;
import java.util.Collection;
public interface ForumSharingManager extends SharingManager<Forum, InvitationMessage> {
public interface ForumSharingManager extends SharingManager<Forum> {
/** Returns the unique ID of the forum sharing client. */
ClientId getClientId();

View File

@@ -20,6 +20,12 @@ public interface IdentityManager {
/** Returns the local pseudonym with the given ID. */
LocalAuthor getLocalAuthor(AuthorId a) throws DbException;
/** Returns the main local identity. */
LocalAuthor getLocalAuthor() throws DbException;
/** Returns the main local identity within the given Transaction. */
LocalAuthor getLocalAuthor(Transaction txn) throws DbException;
/** Returns all local pseudonyms. */
Collection<LocalAuthor> getLocalAuthors() throws DbException;

View File

@@ -8,7 +8,7 @@ import org.briarproject.api.sync.GroupId;
import java.util.Collection;
public interface SharingManager<S extends Shareable, IM extends InvitationMessage> {
public interface SharingManager<S extends Shareable> {
/** Returns the unique ID of the group sharing client. */
ClientId getClientId();
@@ -30,7 +30,7 @@ public interface SharingManager<S extends Shareable, IM extends InvitationMessag
* Returns all group sharing messages sent by the Contact
* identified by contactId.
*/
Collection<IM> getInvitationMessages(
Collection<InvitationMessage> getInvitationMessages(
ContactId contactId) throws DbException;
/** Returns all shareables to which the user has been invited. */

View File

@@ -69,7 +69,7 @@ class BlogFactoryImpl implements BlogFactory {
Author a =
authorFactory.createAuthor(blog.getString(1), blog.getRaw(2));
// TODO change permanent depending on how this will be used
boolean permanent = false;
boolean permanent = true;
return new Blog(g, blog.getString(0), description, a, permanent);
}

View File

@@ -282,7 +282,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
}
@Override
public Blog getPersonalBlog(Author author) throws DbException {
public Blog getPersonalBlog(Author author) {
return blogFactory.createPersonalBlog(author);
}

View File

@@ -10,9 +10,7 @@ import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
@@ -69,12 +67,22 @@ class IdentityManagerImpl implements IdentityManager {
return author;
}
@Override
public LocalAuthor getLocalAuthor() throws DbException {
return getLocalAuthors().iterator().next();
}
@Override
public LocalAuthor getLocalAuthor(Transaction txn) throws DbException {
return getLocalAuthors(txn).iterator().next();
}
@Override
public Collection<LocalAuthor> getLocalAuthors() throws DbException {
Collection<LocalAuthor> authors;
Transaction txn = db.startTransaction(true);
try {
authors = db.getLocalAuthors(txn);
authors = getLocalAuthors(txn);
txn.setComplete();
} finally {
db.endTransaction(txn);
@@ -82,6 +90,12 @@ class IdentityManagerImpl implements IdentityManager {
return authors;
}
private Collection<LocalAuthor> getLocalAuthors(Transaction txn)
throws DbException {
return db.getLocalAuthors(txn);
}
@Override
public void removeLocalAuthor(AuthorId a) throws DbException {
Transaction txn = db.startTransaction(false);

View File

@@ -4,6 +4,7 @@ import org.briarproject.api.FormatException;
import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogFactory;
import org.briarproject.api.blogs.BlogInvitationRequest;
import org.briarproject.api.blogs.BlogInvitationResponse;
import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogManager.RemoveBlogHook;
import org.briarproject.api.blogs.BlogSharingManager;
@@ -12,6 +13,7 @@ import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.MessageQueueManager;
import org.briarproject.api.clients.PrivateGroupFactory;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
@@ -24,6 +26,9 @@ import org.briarproject.api.event.BlogInvitationReceivedEvent;
import org.briarproject.api.event.BlogInvitationResponseReceivedEvent;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sharing.InvitationMessage;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
@@ -40,13 +45,17 @@ import static org.briarproject.api.blogs.BlogConstants.BLOG_PUBLIC_KEY;
import static org.briarproject.api.blogs.BlogConstants.BLOG_TITLE;
class BlogSharingManagerImpl extends
SharingManagerImpl<Blog, BlogInvitation, BlogInvitationRequest, BlogInviteeSessionState, BlogSharerSessionState, BlogInvitationReceivedEvent, BlogInvitationResponseReceivedEvent>
SharingManagerImpl<Blog, BlogInvitation, BlogInviteeSessionState, BlogSharerSessionState, BlogInvitationReceivedEvent, BlogInvitationResponseReceivedEvent>
implements BlogSharingManager, RemoveBlogHook {
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
"bee438b5de0b3a685badc4e49d76e72d"
+ "21e01c4b569a775112756bdae267a028"));
@Inject
IdentityManager identityManager;
private final BlogManager blogManager;
private final SFactory sFactory;
private final IFactory iFactory;
private final ISFactory isFactory;
@@ -64,6 +73,7 @@ class BlogSharingManagerImpl extends
super(db, messageQueueManager, clientHelper, metadataParser,
metadataEncoder, random, privateGroupFactory, clock);
this.blogManager = blogManager;
sFactory = new SFactory(authorFactory, blogFactory, blogManager);
iFactory = new IFactory();
isFactory = new ISFactory();
@@ -78,21 +88,37 @@ class BlogSharingManagerImpl extends
}
@Override
protected BlogInvitationRequest createInvitationRequest(MessageId id,
protected boolean canBeShared(Transaction txn, GroupId g, Contact c)
throws DbException {
// check if g is our personal blog
LocalAuthor author = identityManager.getLocalAuthor(txn);
Blog b = blogManager.getPersonalBlog(author);
if (b.getId().equals(g)) return false;
// check if g is c's personal blog
b = blogManager.getPersonalBlog(c.getAuthor());
if (b.getId().equals(g)) return false;
return super.canBeShared(txn, g, c);
}
@Override
protected InvitationMessage createInvitationRequest(MessageId id,
BlogInvitation msg, ContactId contactId, boolean available,
long time, boolean local, boolean sent, boolean seen,
boolean read) {
return new BlogInvitationRequest(id, msg.getSessionId(), contactId,
msg.getBlogTitle(), msg.getMessage(), available, time, local,
sent, seen, read);
msg.getBlogAuthorName(), msg.getMessage(), available, time,
local, sent, seen, read);
}
@Override
protected BlogInvitationRequest createInvitationResponse(MessageId id,
protected InvitationMessage createInvitationResponse(MessageId id,
SessionId sessionId, ContactId contactId, boolean accept, long time,
boolean local, boolean sent, boolean seen, boolean read) {
// TODO implement when doing blog sharing
return null;
return new BlogInvitationResponse(id, sessionId, contactId, accept,
time, local, sent, seen, read);
}
@Override

View File

@@ -58,7 +58,7 @@ class BlogSharingValidator extends BdfMessageValidator {
checkLength(name, 1, MAX_BLOG_TITLE_LENGTH);
String desc = body.getString(3);
checkLength(desc, 1, MAX_BLOG_DESC_LENGTH);
checkLength(desc, 0, MAX_BLOG_DESC_LENGTH);
BdfList author = body.getList(4);
checkSize(author, 2);

View File

@@ -37,7 +37,7 @@ import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
class ForumSharingManagerImpl extends
SharingManagerImpl<Forum, ForumInvitation, InvitationMessage, ForumInviteeSessionState, ForumSharerSessionState, ForumInvitationReceivedEvent, ForumInvitationResponseReceivedEvent>
SharingManagerImpl<Forum, ForumInvitation, ForumInviteeSessionState, ForumSharerSessionState, ForumInvitationReceivedEvent, ForumInvitationResponseReceivedEvent>
implements ForumSharingManager, ForumManager.RemoveForumHook {
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(

View File

@@ -83,9 +83,9 @@ import static org.briarproject.api.sharing.SharingMessage.BaseMessage;
import static org.briarproject.api.sharing.SharingMessage.Invitation;
import static org.briarproject.sharing.InviteeSessionState.State.AWAIT_LOCAL_RESPONSE;
abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM extends InvitationMessage, IS extends InviteeSessionState, SS extends SharerSessionState, IR extends InvitationReceivedEvent, IRR extends InvitationResponseReceivedEvent>
abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS extends InviteeSessionState, SS extends SharerSessionState, IR extends InvitationReceivedEvent, IRR extends InvitationResponseReceivedEvent>
extends BdfIncomingMessageHook
implements SharingManager<S, IM>, Client, AddContactHook,
implements SharingManager<S>, Client, AddContactHook,
RemoveContactHook {
private static final Logger LOG =
@@ -117,11 +117,11 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM
public abstract ClientId getClientId();
protected abstract IM createInvitationRequest(MessageId id, I msg,
protected abstract InvitationMessage createInvitationRequest(MessageId id, I msg,
ContactId contactId, boolean available, long time, boolean local,
boolean sent, boolean seen, boolean read);
protected abstract IM createInvitationResponse(MessageId id,
protected abstract InvitationMessage createInvitationResponse(MessageId id,
SessionId sessionId, ContactId contactId, boolean accept, long time,
boolean local, boolean sent, boolean seen, boolean read);
@@ -326,7 +326,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM
}
@Override
public Collection<IM> getInvitationMessages(ContactId contactId)
public Collection<InvitationMessage> getInvitationMessages(ContactId contactId)
throws DbException {
Transaction txn = db.startTransaction(true);
@@ -334,7 +334,8 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM
Contact contact = db.getContact(txn, contactId);
Group group = getContactGroup(contact);
Collection<IM> list = new ArrayList<IM>();
Collection<InvitationMessage> list =
new ArrayList<InvitationMessage>();
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, group.getId());
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
@@ -362,7 +363,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM
available = ((InviteeSessionState) s).getState() ==
AWAIT_LOCAL_RESPONSE;
}
IM im = createInvitationRequest(m.getKey(), msg,
InvitationMessage im = createInvitationRequest(m.getKey(), msg,
contactId, available, time, local,
status.isSent(), status.isSeen(), read);
list.add(im);
@@ -373,9 +374,9 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM
BaseMessage msg = BaseMessage
.from(getIFactory(), group.getId(), d);
SessionId sessionId = msg.getSessionId();
IM im = createInvitationResponse(m.getKey(), sessionId,
contactId, accept, time, local,
status.isSent(), status.isSeen(), read);
InvitationMessage im = createInvitationResponse(
m.getKey(), sessionId, contactId, accept, time,
local, status.isSent(), status.isSeen(), read);
list.add(im);
}
else {
@@ -499,7 +500,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM
return canBeShared;
}
private boolean canBeShared(Transaction txn, GroupId g, Contact c)
protected boolean canBeShared(Transaction txn, GroupId g, Contact c)
throws DbException {
try {

View File

@@ -27,6 +27,8 @@ public class SharingModule {
ForumSharingValidator forumSharingValidator;
@Inject
ForumSharingManager forumSharingManager;
@Inject
BlogSharingManager blogSharingManager;
}
@Provides