mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-19 14:19:53 +01:00
Merge branch '1800-group-list-view-model' into 'master'
Using ListAdapter for PrivateGroupList See merge request briar/briar!1327
This commit is contained in:
@@ -126,10 +126,11 @@ dependencies {
|
|||||||
testImplementation 'androidx.test:runner:1.3.0'
|
testImplementation 'androidx.test:runner:1.3.0'
|
||||||
testImplementation 'androidx.test.ext:junit:1.1.2'
|
testImplementation 'androidx.test.ext:junit:1.1.2'
|
||||||
testImplementation 'androidx.fragment:fragment-testing:1.2.5'
|
testImplementation 'androidx.fragment:fragment-testing:1.2.5'
|
||||||
|
testImplementation "androidx.arch.core:core-testing:2.1.0"
|
||||||
testImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
|
testImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
|
||||||
testImplementation 'org.robolectric:robolectric:4.3.1'
|
testImplementation 'org.robolectric:robolectric:4.3.1'
|
||||||
testImplementation 'org.mockito:mockito-core:3.1.0'
|
testImplementation 'org.mockito:mockito-core:3.1.0'
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.13.1'
|
||||||
testImplementation "org.jmock:jmock:$jmockVersion"
|
testImplementation "org.jmock:jmock:$jmockVersion"
|
||||||
testImplementation "org.jmock:jmock-junit4:$jmockVersion"
|
testImplementation "org.jmock:jmock-junit4:$jmockVersion"
|
||||||
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
|
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.contact.ContactManager;
|
|||||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||||
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
||||||
@@ -85,6 +86,8 @@ public interface AndroidComponent
|
|||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
Executor databaseExecutor();
|
Executor databaseExecutor();
|
||||||
|
|
||||||
|
TransactionManager transactionManager();
|
||||||
|
|
||||||
MessageTracker messageTracker();
|
MessageTracker messageTracker();
|
||||||
|
|
||||||
LifecycleManager lifecycleManager();
|
LifecycleManager lifecycleManager();
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
@Nullable
|
@Nullable
|
||||||
private GroupId blockedGroup = null;
|
private GroupId blockedGroup = null;
|
||||||
private boolean blockSignInReminder = false;
|
private boolean blockSignInReminder = false;
|
||||||
private boolean blockBlogs = false;
|
private boolean blockGroups = false, blockBlogs = false;
|
||||||
private long lastSound = 0;
|
private long lastSound = 0;
|
||||||
|
|
||||||
private volatile Settings settings = new Settings();
|
private volatile Settings settings = new Settings();
|
||||||
@@ -223,8 +223,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
if (s.getNamespace().equals(SETTINGS_NAMESPACE))
|
if (s.getNamespace().equals(SETTINGS_NAMESPACE))
|
||||||
settings = s.getSettings();
|
settings = s.getSettings();
|
||||||
} else if (e instanceof ConversationMessageReceivedEvent) {
|
} else if (e instanceof ConversationMessageReceivedEvent) {
|
||||||
ConversationMessageReceivedEvent p =
|
ConversationMessageReceivedEvent<?> p =
|
||||||
(ConversationMessageReceivedEvent) e;
|
(ConversationMessageReceivedEvent<?>) e;
|
||||||
showContactNotification(p.getContactId());
|
showContactNotification(p.getContactId());
|
||||||
} else if (e instanceof GroupMessageAddedEvent) {
|
} else if (e instanceof GroupMessageAddedEvent) {
|
||||||
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
|
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
|
||||||
@@ -385,6 +385,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void showGroupMessageNotification(GroupId g) {
|
private void showGroupMessageNotification(GroupId g) {
|
||||||
|
if (blockGroups) return;
|
||||||
if (g.equals(blockedGroup)) return;
|
if (g.equals(blockedGroup)) return;
|
||||||
groupCounts.add(g);
|
groupCounts.add(g);
|
||||||
updateGroupMessageNotification(true);
|
updateGroupMessageNotification(true);
|
||||||
@@ -681,6 +682,17 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void blockAllGroupMessageNotifications() {
|
||||||
|
androidExecutor.runOnUiThread((Runnable) () -> blockGroups = true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unblockAllGroupMessageNotifications() {
|
||||||
|
androidExecutor.runOnUiThread((Runnable) () -> blockGroups = false);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void blockAllBlogPostNotifications() {
|
public void blockAllBlogPostNotifications() {
|
||||||
androidExecutor.runOnUiThread((Runnable) () -> blockBlogs = true);
|
androidExecutor.runOnUiThread((Runnable) () -> blockBlogs = true);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import org.briarproject.briar.android.account.LockManagerImpl;
|
|||||||
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
|
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
|
||||||
import org.briarproject.briar.android.login.LoginModule;
|
import org.briarproject.briar.android.login.LoginModule;
|
||||||
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
|
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
|
||||||
|
import org.briarproject.briar.android.privategroup.list.GroupListModule;
|
||||||
import org.briarproject.briar.android.reporting.DevReportModule;
|
import org.briarproject.briar.android.reporting.DevReportModule;
|
||||||
import org.briarproject.briar.android.viewmodel.ViewModelModule;
|
import org.briarproject.briar.android.viewmodel.ViewModelModule;
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
@@ -65,7 +66,9 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
|||||||
LoginModule.class,
|
LoginModule.class,
|
||||||
NavDrawerModule.class,
|
NavDrawerModule.class,
|
||||||
ViewModelModule.class,
|
ViewModelModule.class,
|
||||||
DevReportModule.class
|
DevReportModule.class,
|
||||||
|
// below need to be within same scope as ViewModelProvider.Factory
|
||||||
|
GroupListModule.class,
|
||||||
})
|
})
|
||||||
public class AppModule {
|
public class AppModule {
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment;
|
|||||||
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity;
|
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity;
|
||||||
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationModule;
|
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationModule;
|
||||||
import org.briarproject.briar.android.privategroup.list.GroupListFragment;
|
import org.briarproject.briar.android.privategroup.list.GroupListFragment;
|
||||||
import org.briarproject.briar.android.privategroup.list.GroupListModule;
|
|
||||||
import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity;
|
import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity;
|
||||||
import org.briarproject.briar.android.privategroup.memberlist.GroupMemberModule;
|
import org.briarproject.briar.android.privategroup.memberlist.GroupMemberModule;
|
||||||
import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule;
|
import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule;
|
||||||
@@ -94,7 +93,6 @@ import dagger.Component;
|
|||||||
ForumModule.class,
|
ForumModule.class,
|
||||||
GroupInvitationModule.class,
|
GroupInvitationModule.class,
|
||||||
GroupConversationModule.class,
|
GroupConversationModule.class,
|
||||||
GroupListModule.class,
|
|
||||||
GroupMemberModule.class,
|
GroupMemberModule.class,
|
||||||
GroupRevealModule.class,
|
GroupRevealModule.class,
|
||||||
SharingModule.class
|
SharingModule.class
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewGroup.LayoutParams;
|
import android.view.ViewGroup.LayoutParams;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
@@ -240,7 +239,7 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
public void handleDbException(DbException e) {
|
public void handleException(Exception e) {
|
||||||
supportFinishAfterTransition();
|
supportFinishAfterTransition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ public class BlogFragment extends BaseFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -277,7 +277,7 @@ public class BlogFragment extends BaseFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -296,7 +296,7 @@ public class BlogFragment extends BaseFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -318,7 +318,7 @@ public class BlogFragment extends BaseFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -398,7 +398,7 @@ public class BlogFragment extends BaseFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class BlogPostFragment extends BasePostFragment implements BlogListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ public class FeedFragment extends BaseFragment implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ public class FeedFragment extends BaseFragment implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -242,7 +242,7 @@ public class FeedFragment extends BaseFragment implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class FeedPostFragment extends BasePostFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
|||||||
new UiExceptionHandler<DbException>(this) {
|
new UiExceptionHandler<DbException>(this) {
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
finish();
|
finish();
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import org.briarproject.bramble.api.contact.PendingContact;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchPendingContactException;
|
import org.briarproject.bramble.api.db.NoSuchPendingContactException;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveResult;
|
import org.briarproject.briar.android.viewmodel.LiveResult;
|
||||||
@@ -52,8 +54,10 @@ public class AddContactViewModel extends DbViewModel {
|
|||||||
AddContactViewModel(Application application,
|
AddContactViewModel(Application application,
|
||||||
ContactManager contactManager,
|
ContactManager contactManager,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
LifecycleManager lifecycleManager) {
|
LifecycleManager lifecycleManager,
|
||||||
super(application, dbExecutor, lifecycleManager);
|
TransactionManager db,
|
||||||
|
AndroidExecutor androidExecutor) {
|
||||||
|
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
|
|||||||
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
|
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
@@ -18,6 +19,7 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.rendezvous.RendezvousPoller;
|
import org.briarproject.bramble.api.rendezvous.RendezvousPoller;
|
||||||
import org.briarproject.bramble.api.rendezvous.event.RendezvousPollEvent;
|
import org.briarproject.bramble.api.rendezvous.event.RendezvousPollEvent;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -56,10 +58,12 @@ public class PendingContactListViewModel extends DbViewModel
|
|||||||
PendingContactListViewModel(Application application,
|
PendingContactListViewModel(Application application,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
LifecycleManager lifecycleManager,
|
LifecycleManager lifecycleManager,
|
||||||
|
TransactionManager db,
|
||||||
|
AndroidExecutor androidExecutor,
|
||||||
ContactManager contactManager,
|
ContactManager contactManager,
|
||||||
RendezvousPoller rendezvousPoller,
|
RendezvousPoller rendezvousPoller,
|
||||||
EventBus eventBus) {
|
EventBus eventBus) {
|
||||||
super(application, dbExecutor, lifecycleManager);
|
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
this.rendezvousPoller = rendezvousPoller;
|
this.rendezvousPoller = rendezvousPoller;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ public abstract class BaseContactSelectorFragment<I extends SelectableContactIte
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.briarproject.bramble.api.settings.SettingsManager;
|
|||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.briar.android.attachment.AttachmentCreator;
|
import org.briarproject.briar.android.attachment.AttachmentCreator;
|
||||||
import org.briarproject.briar.android.attachment.AttachmentManager;
|
import org.briarproject.briar.android.attachment.AttachmentManager;
|
||||||
import org.briarproject.briar.android.attachment.AttachmentResult;
|
import org.briarproject.briar.android.attachment.AttachmentResult;
|
||||||
@@ -106,6 +107,7 @@ public class ConversationViewModel extends DbViewModel
|
|||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
LifecycleManager lifecycleManager,
|
LifecycleManager lifecycleManager,
|
||||||
TransactionManager db,
|
TransactionManager db,
|
||||||
|
AndroidExecutor androidExecutor,
|
||||||
EventBus eventBus,
|
EventBus eventBus,
|
||||||
MessagingManager messagingManager,
|
MessagingManager messagingManager,
|
||||||
ContactManager contactManager,
|
ContactManager contactManager,
|
||||||
@@ -113,7 +115,7 @@ public class ConversationViewModel extends DbViewModel
|
|||||||
PrivateMessageFactory privateMessageFactory,
|
PrivateMessageFactory privateMessageFactory,
|
||||||
AttachmentRetriever attachmentRetriever,
|
AttachmentRetriever attachmentRetriever,
|
||||||
AttachmentCreator attachmentCreator) {
|
AttachmentCreator attachmentCreator) {
|
||||||
super(application, dbExecutor, lifecycleManager);
|
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.messagingManager = messagingManager;
|
this.messagingManager = messagingManager;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.view.View;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
@@ -14,6 +15,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
@@ -78,8 +80,10 @@ public class ImageViewModel extends DbViewModel implements EventListener {
|
|||||||
EventBus eventBus,
|
EventBus eventBus,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
LifecycleManager lifecycleManager,
|
LifecycleManager lifecycleManager,
|
||||||
|
TransactionManager db,
|
||||||
|
AndroidExecutor androidExecutor,
|
||||||
@IoExecutor Executor ioExecutor) {
|
@IoExecutor Executor ioExecutor) {
|
||||||
super(application, dbExecutor, lifecycleManager);
|
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||||
this.messagingManager = messagingManager;
|
this.messagingManager = messagingManager;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ public class ForumActivity extends
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ public class ForumListFragment extends BaseEventFragment implements
|
|||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
// TODO block all forum post notifications as well
|
||||||
notificationManager.clearAllForumPostNotifications();
|
notificationManager.clearAllForumPostNotifications();
|
||||||
loadForums();
|
loadForums();
|
||||||
loadAvailableForums();
|
loadAvailableForums();
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.content.Context;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.android.DestroyableContext;
|
import org.briarproject.briar.android.DestroyableContext;
|
||||||
@@ -77,7 +76,7 @@ public abstract class BaseFragment extends Fragment
|
|||||||
void showNextFragment(BaseFragment f);
|
void showNextFragment(BaseFragment f);
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
void handleDbException(DbException e);
|
void handleException(Exception e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
@@ -100,8 +99,8 @@ public abstract class BaseFragment extends Fragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
protected void handleDbException(DbException e) {
|
protected void handleException(Exception e) {
|
||||||
listener.handleDbException(e);
|
listener.handleException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import android.widget.TextView;
|
|||||||
import com.google.android.material.navigation.NavigationView;
|
import com.google.android.material.navigation.NavigationView;
|
||||||
import com.google.android.material.navigation.NavigationView.OnNavigationItemSelectedListener;
|
import com.google.android.material.navigation.NavigationView.OnNavigationItemSelectedListener;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
@@ -365,7 +364,7 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleDbException(DbException e) {
|
public void handleException(Exception e) {
|
||||||
// Do nothing for now
|
// Do nothing for now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import android.app.Application;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -51,8 +53,10 @@ public class NavDrawerViewModel extends DbViewModel {
|
|||||||
NavDrawerViewModel(Application app,
|
NavDrawerViewModel(Application app,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
LifecycleManager lifecycleManager,
|
LifecycleManager lifecycleManager,
|
||||||
|
TransactionManager db,
|
||||||
|
AndroidExecutor androidExecutor,
|
||||||
SettingsManager settingsManager) {
|
SettingsManager settingsManager) {
|
||||||
super(app, dbExecutor, lifecycleManager);
|
super(app, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||||
this.settingsManager = settingsManager;
|
this.settingsManager = settingsManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.content.IntentFilter;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
@@ -28,6 +29,7 @@ import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
|
|||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -85,10 +87,11 @@ public class PluginViewModel extends DbViewModel implements EventListener {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
PluginViewModel(Application app, @DatabaseExecutor Executor dbExecutor,
|
PluginViewModel(Application app, @DatabaseExecutor Executor dbExecutor,
|
||||||
LifecycleManager lifecycleManager,
|
LifecycleManager lifecycleManager, TransactionManager db,
|
||||||
SettingsManager settingsManager, PluginManager pluginManager,
|
AndroidExecutor androidExecutor, SettingsManager settingsManager,
|
||||||
EventBus eventBus, NetworkManager networkManager) {
|
PluginManager pluginManager, EventBus eventBus,
|
||||||
super(app, dbExecutor, lifecycleManager);
|
NetworkManager networkManager) {
|
||||||
|
super(app, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.settingsManager = settingsManager;
|
this.settingsManager = settingsManager;
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ public class GroupActivity extends
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ public class GroupActivity extends
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -264,7 +264,7 @@ public class GroupActivity extends
|
|||||||
// GroupRemovedEvent being fired
|
// GroupRemovedEvent being fired
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class CreateGroupActivity extends BriarActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public class GroupInviteActivity extends ContactSelectorActivity
|
|||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
setResult(RESULT_CANCELED);
|
setResult(RESULT_CANCELED);
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,19 @@ import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
|||||||
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
||||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||||
|
|
||||||
// This class is not thread-safe
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class GroupItem {
|
class GroupItem implements Comparable<GroupItem> {
|
||||||
|
|
||||||
private final PrivateGroup privateGroup;
|
private final PrivateGroup privateGroup;
|
||||||
private final AuthorInfo authorInfo;
|
private final AuthorInfo authorInfo;
|
||||||
private int messageCount, unreadCount;
|
private final int messageCount, unreadCount;
|
||||||
private long timestamp;
|
private final long timestamp;
|
||||||
private boolean dissolved;
|
private final boolean dissolved;
|
||||||
|
|
||||||
GroupItem(PrivateGroup privateGroup, AuthorInfo authorInfo,
|
GroupItem(PrivateGroup privateGroup, AuthorInfo authorInfo,
|
||||||
GroupCount count, boolean dissolved) {
|
GroupCount count, boolean dissolved) {
|
||||||
@@ -28,18 +32,22 @@ class GroupItem {
|
|||||||
this.dissolved = dissolved;
|
this.dissolved = dissolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addMessageHeader(GroupMessageHeader header) {
|
GroupItem(GroupItem item, GroupMessageHeader header) {
|
||||||
messageCount++;
|
this.privateGroup = item.privateGroup;
|
||||||
if (header.getTimestamp() > timestamp) {
|
this.authorInfo = item.authorInfo;
|
||||||
timestamp = header.getTimestamp();
|
this.messageCount = item.messageCount + 1;
|
||||||
}
|
this.unreadCount = item.unreadCount + (header.isRead() ? 0 : 1);
|
||||||
if (!header.isRead()) {
|
this.timestamp = Math.max(header.getTimestamp(), item.timestamp);
|
||||||
unreadCount++;
|
this.dissolved = item.dissolved;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PrivateGroup getPrivateGroup() {
|
GroupItem(GroupItem item, boolean isDissolved) {
|
||||||
return privateGroup;
|
this.privateGroup = item.privateGroup;
|
||||||
|
this.authorInfo = item.authorInfo;
|
||||||
|
this.messageCount = item.messageCount;
|
||||||
|
this.unreadCount = item.unreadCount;
|
||||||
|
this.timestamp = item.timestamp;
|
||||||
|
this.dissolved = isDissolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupId getId() {
|
GroupId getId() {
|
||||||
@@ -78,8 +86,27 @@ class GroupItem {
|
|||||||
return dissolved;
|
return dissolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setDissolved() {
|
@Override
|
||||||
dissolved = true;
|
public int hashCode() {
|
||||||
|
return getId().hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object o) {
|
||||||
|
return o instanceof GroupItem &&
|
||||||
|
getId().equals(((GroupItem) o).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(GroupItem o) {
|
||||||
|
if (this == o) return 0;
|
||||||
|
// The group with the latest message comes first
|
||||||
|
long aTime = getTimestamp(), bTime = o.getTimestamp();
|
||||||
|
if (aTime > bTime) return -1;
|
||||||
|
if (aTime < bTime) return 1;
|
||||||
|
// Break ties by group name
|
||||||
|
String aName = getName();
|
||||||
|
String bName = o.getName();
|
||||||
|
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,81 +1,52 @@
|
|||||||
package org.briarproject.briar.android.privategroup.list;
|
package org.briarproject.briar.android.privategroup.list;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener;
|
import org.briarproject.briar.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener;
|
||||||
import org.briarproject.briar.android.util.BriarAdapter;
|
|
||||||
|
|
||||||
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION;
|
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
|
||||||
|
import androidx.recyclerview.widget.ListAdapter;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
class GroupListAdapter extends BriarAdapter<GroupItem, GroupViewHolder> {
|
class GroupListAdapter extends ListAdapter<GroupItem, GroupViewHolder> {
|
||||||
|
|
||||||
private final OnGroupRemoveClickListener listener;
|
private final OnGroupRemoveClickListener listener;
|
||||||
|
|
||||||
GroupListAdapter(Context ctx, OnGroupRemoveClickListener listener) {
|
GroupListAdapter(OnGroupRemoveClickListener listener) {
|
||||||
super(ctx, GroupItem.class);
|
super(new GroupItemCallback());
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GroupViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
public GroupViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
View v = LayoutInflater.from(ctx).inflate(
|
View v = LayoutInflater.from(parent.getContext())
|
||||||
R.layout.list_item_group, parent, false);
|
.inflate(R.layout.list_item_group, parent, false);
|
||||||
return new GroupViewHolder(v);
|
return new GroupViewHolder(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(GroupViewHolder ui, int position) {
|
public void onBindViewHolder(GroupViewHolder ui, int position) {
|
||||||
ui.bindView(ctx, items.get(position), listener);
|
ui.bindView(getItem(position), listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private static class GroupItemCallback extends ItemCallback<GroupItem> {
|
||||||
public int compare(GroupItem a, GroupItem b) {
|
@Override
|
||||||
if (a == b) return 0;
|
public boolean areItemsTheSame(GroupItem a, GroupItem b) {
|
||||||
// The group with the latest message comes first
|
return a.equals(b);
|
||||||
long aTime = a.getTimestamp(), bTime = b.getTimestamp();
|
|
||||||
if (aTime > bTime) return -1;
|
|
||||||
if (aTime < bTime) return 1;
|
|
||||||
// Break ties by group name
|
|
||||||
String aName = a.getName();
|
|
||||||
String bName = b.getName();
|
|
||||||
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean areContentsTheSame(GroupItem a, GroupItem b) {
|
|
||||||
return a.getMessageCount() == b.getMessageCount() &&
|
|
||||||
a.getTimestamp() == b.getTimestamp() &&
|
|
||||||
a.getUnreadCount() == b.getUnreadCount() &&
|
|
||||||
a.isDissolved() == b.isDissolved();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean areItemsTheSame(GroupItem a, GroupItem b) {
|
|
||||||
return a.getId().equals(b.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
int findItemPosition(GroupId g) {
|
|
||||||
for (int i = 0; i < items.size(); i++) {
|
|
||||||
GroupItem item = items.get(i);
|
|
||||||
if (item.getId().equals(g)) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return INVALID_POSITION;
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeItem(GroupId groupId) {
|
@Override
|
||||||
int pos = findItemPosition(groupId);
|
public boolean areContentsTheSame(GroupItem a, GroupItem b) {
|
||||||
if (pos != INVALID_POSITION) items.removeItemAt(pos);
|
return a.getMessageCount() == b.getMessageCount() &&
|
||||||
|
a.getTimestamp() == b.getTimestamp() &&
|
||||||
|
a.getUnreadCount() == b.getUnreadCount() &&
|
||||||
|
a.isDissolved() == b.isDissolved();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
package org.briarproject.briar.android.privategroup.list;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
|
||||||
import org.briarproject.briar.android.controller.DbController;
|
|
||||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
|
||||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
|
||||||
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
interface GroupListController extends DbController {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The listener must be set right after the controller was injected
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
void setGroupListListener(GroupListListener listener);
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void unsetGroupListListener(GroupListListener listener);
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onStart();
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onStop();
|
|
||||||
|
|
||||||
void loadGroups(
|
|
||||||
ResultExceptionHandler<Collection<GroupItem>, DbException> result);
|
|
||||||
|
|
||||||
void removeGroup(GroupId g, ExceptionHandler<DbException> result);
|
|
||||||
|
|
||||||
void loadAvailableGroups(
|
|
||||||
ResultExceptionHandler<Integer, DbException> result);
|
|
||||||
|
|
||||||
interface GroupListListener {
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onGroupMessageAdded(GroupMessageHeader header);
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onGroupInvitationReceived();
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onGroupAdded(GroupId groupId);
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onGroupRemoved(GroupId groupId);
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onGroupDissolved(GroupId groupId);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -12,57 +12,50 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.controller.handler.UiExceptionHandler;
|
|
||||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
|
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
|
||||||
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity;
|
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity;
|
||||||
import org.briarproject.briar.android.privategroup.list.GroupListController.GroupListListener;
|
|
||||||
import org.briarproject.briar.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener;
|
import org.briarproject.briar.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener;
|
||||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.UiThread;
|
import androidx.annotation.UiThread;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
import static com.google.android.material.snackbar.Snackbar.LENGTH_INDEFINITE;
|
import static com.google.android.material.snackbar.Snackbar.LENGTH_INDEFINITE;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class GroupListFragment extends BaseFragment implements
|
public class GroupListFragment extends BaseFragment implements
|
||||||
GroupListListener, OnGroupRemoveClickListener, OnClickListener {
|
OnGroupRemoveClickListener, OnClickListener {
|
||||||
|
|
||||||
public final static String TAG = GroupListFragment.class.getName();
|
public final static String TAG = GroupListFragment.class.getName();
|
||||||
private static final Logger LOG = Logger.getLogger(TAG);
|
|
||||||
|
|
||||||
public static GroupListFragment newInstance() {
|
public static GroupListFragment newInstance() {
|
||||||
return new GroupListFragment();
|
return new GroupListFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GroupListController controller;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private GroupListViewModel viewModel;
|
||||||
private BriarRecyclerView list;
|
private BriarRecyclerView list;
|
||||||
private GroupListAdapter adapter;
|
private GroupListAdapter adapter;
|
||||||
private Snackbar snackbar;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectFragment(ActivityComponent component) {
|
public void injectFragment(ActivityComponent component) {
|
||||||
component.inject(this);
|
component.inject(this);
|
||||||
controller.setGroupListListener(this);
|
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||||
|
.get(GroupListViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -75,17 +68,32 @@ public class GroupListFragment extends BaseFragment implements
|
|||||||
|
|
||||||
View v = inflater.inflate(R.layout.list, container, false);
|
View v = inflater.inflate(R.layout.list, container, false);
|
||||||
|
|
||||||
adapter = new GroupListAdapter(getActivity(), this);
|
adapter = new GroupListAdapter(this);
|
||||||
list = v.findViewById(R.id.list);
|
list = v.findViewById(R.id.list);
|
||||||
list.setEmptyImage(R.drawable.ic_empty_state_group_list);
|
list.setEmptyImage(R.drawable.ic_empty_state_group_list);
|
||||||
list.setEmptyText(R.string.groups_list_empty);
|
list.setEmptyText(R.string.groups_list_empty);
|
||||||
list.setEmptyAction(R.string.groups_list_empty_action);
|
list.setEmptyAction(R.string.groups_list_empty_action);
|
||||||
list.setLayoutManager(new LinearLayoutManager(getContext()));
|
list.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
|
viewModel.getGroupItems().observe(getViewLifecycleOwner(), result ->
|
||||||
|
result.onError(this::handleException).onSuccess(items -> {
|
||||||
|
adapter.submitList(items);
|
||||||
|
if (requireNonNull(items).size() == 0) list.showData();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
snackbar = new BriarSnackbarBuilder()
|
Snackbar snackbar = new BriarSnackbarBuilder()
|
||||||
.setAction(R.string.show, this)
|
.setAction(R.string.show, this)
|
||||||
.make(list, "", LENGTH_INDEFINITE);
|
.make(list, "", LENGTH_INDEFINITE);
|
||||||
|
viewModel.getNumInvitations().observe(getViewLifecycleOwner(), num -> {
|
||||||
|
if (num == 0) {
|
||||||
|
snackbar.dismiss();
|
||||||
|
} else {
|
||||||
|
snackbar.setText(getResources().getQuantityString(
|
||||||
|
R.plurals.groups_invitations_open, num, num));
|
||||||
|
if (!snackbar.isShownOrQueued()) snackbar.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
@@ -93,25 +101,23 @@ public class GroupListFragment extends BaseFragment implements
|
|||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
controller.onStart();
|
viewModel.blockAllGroupMessageNotifications();
|
||||||
|
viewModel.clearAllGroupMessageNotifications();
|
||||||
|
// The attributes and sorting of the groups may have changed while we
|
||||||
|
// were stopped and we have no way finding out about them, so re-load
|
||||||
|
// e.g. less unread messages in a group after viewing it.
|
||||||
|
viewModel.loadGroups();
|
||||||
|
// The number of invitations might have changed while we were stopped
|
||||||
|
// e.g. because of accepting an invitation which does not trigger event
|
||||||
|
viewModel.loadNumInvitations();
|
||||||
list.startPeriodicUpdate();
|
list.startPeriodicUpdate();
|
||||||
loadGroups();
|
|
||||||
loadAvailableGroups();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
controller.onStop();
|
|
||||||
list.stopPeriodicUpdate();
|
list.stopPeriodicUpdate();
|
||||||
adapter.clear();
|
viewModel.unblockAllGroupMessageNotifications();
|
||||||
list.showProgressBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
controller.unsetGroupListListener(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -122,68 +128,18 @@ public class GroupListFragment extends BaseFragment implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == R.id.action_add_group) {
|
||||||
case R.id.action_add_group:
|
Intent i = new Intent(getContext(), CreateGroupActivity.class);
|
||||||
Intent i = new Intent(getContext(), CreateGroupActivity.class);
|
startActivity(i);
|
||||||
startActivity(i);
|
return true;
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@Override
|
@Override
|
||||||
public void onGroupRemoveClick(GroupItem item) {
|
public void onGroupRemoveClick(GroupItem item) {
|
||||||
controller.removeGroup(item.getId(),
|
viewModel.removeGroup(item.getId());
|
||||||
new UiExceptionHandler<DbException>(this) {
|
|
||||||
// result handled by GroupRemovedEvent and onGroupRemoved()
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleDbException(exception);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
@Override
|
|
||||||
public void onGroupMessageAdded(GroupMessageHeader header) {
|
|
||||||
adapter.incrementRevision();
|
|
||||||
int position = adapter.findItemPosition(header.getGroupId());
|
|
||||||
GroupItem item = adapter.getItemAt(position);
|
|
||||||
if (item != null) {
|
|
||||||
item.addMessageHeader(header);
|
|
||||||
adapter.updateItemAt(position, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onGroupInvitationReceived() {
|
|
||||||
loadAvailableGroups();
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
@Override
|
|
||||||
public void onGroupAdded(GroupId groupId) {
|
|
||||||
loadGroups();
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
@Override
|
|
||||||
public void onGroupRemoved(GroupId groupId) {
|
|
||||||
adapter.incrementRevision();
|
|
||||||
adapter.removeItem(groupId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onGroupDissolved(GroupId groupId) {
|
|
||||||
adapter.incrementRevision();
|
|
||||||
int position = adapter.findItemPosition(groupId);
|
|
||||||
GroupItem item = adapter.getItemAt(position);
|
|
||||||
if (item != null) {
|
|
||||||
item.setDissolved();
|
|
||||||
adapter.updateItemAt(position, item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -191,52 +147,6 @@ public class GroupListFragment extends BaseFragment implements
|
|||||||
return TAG;
|
return TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadGroups() {
|
|
||||||
int revision = adapter.getRevision();
|
|
||||||
controller.loadGroups(
|
|
||||||
new UiResultExceptionHandler<Collection<GroupItem>, DbException>(
|
|
||||||
this) {
|
|
||||||
@Override
|
|
||||||
public void onResultUi(Collection<GroupItem> groups) {
|
|
||||||
if (revision == adapter.getRevision()) {
|
|
||||||
adapter.incrementRevision();
|
|
||||||
if (groups.isEmpty()) list.showData();
|
|
||||||
else adapter.replaceAll(groups);
|
|
||||||
} else {
|
|
||||||
LOG.info("Concurrent update, reloading");
|
|
||||||
loadGroups();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleDbException(exception);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadAvailableGroups() {
|
|
||||||
controller.loadAvailableGroups(
|
|
||||||
new UiResultExceptionHandler<Integer, DbException>(this) {
|
|
||||||
@Override
|
|
||||||
public void onResultUi(Integer num) {
|
|
||||||
if (num == 0) {
|
|
||||||
snackbar.dismiss();
|
|
||||||
} else {
|
|
||||||
snackbar.setText(getResources().getQuantityString(
|
|
||||||
R.plurals.groups_invitations_open, num,
|
|
||||||
num));
|
|
||||||
if (!snackbar.isShownOrQueued()) snackbar.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleDbException(exception);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is handling the available groups snackbar action
|
* This method is handling the available groups snackbar action
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
package org.briarproject.briar.android.privategroup.list;
|
package org.briarproject.briar.android.privategroup.list;
|
||||||
|
|
||||||
import org.briarproject.briar.android.activity.ActivityScope;
|
import org.briarproject.briar.android.viewmodel.ViewModelKey;
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
import dagger.Binds;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.multibindings.IntoMap;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class GroupListModule {
|
public abstract class GroupListModule {
|
||||||
|
|
||||||
@ActivityScope
|
@Binds
|
||||||
@Provides
|
@IntoMap
|
||||||
GroupListController provideGroupListController(
|
@ViewModelKey(GroupListViewModel.class)
|
||||||
GroupListControllerImpl groupListController) {
|
abstract ViewModel bindGroupListViewModel(
|
||||||
return groupListController;
|
GroupListViewModel groupListViewModel);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package org.briarproject.briar.android.privategroup.list;
|
package org.briarproject.briar.android.privategroup.list;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
@@ -16,11 +19,12 @@ import org.briarproject.bramble.api.sync.ClientId;
|
|||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.event.GroupAddedEvent;
|
import org.briarproject.bramble.api.sync.event.GroupAddedEvent;
|
||||||
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
|
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
|
||||||
import org.briarproject.briar.android.controller.DbControllerImpl;
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
import org.briarproject.briar.android.viewmodel.LiveResult;
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||||
|
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
||||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||||
import org.briarproject.briar.api.privategroup.event.GroupDissolvedEvent;
|
import org.briarproject.briar.api.privategroup.event.GroupDissolvedEvent;
|
||||||
@@ -30,6 +34,7 @@ import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -38,10 +43,13 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.UiThread;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
@@ -49,11 +57,10 @@ import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT
|
|||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
class GroupListControllerImpl extends DbControllerImpl
|
class GroupListViewModel extends DbViewModel implements EventListener {
|
||||||
implements GroupListController, EventListener {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(GroupListControllerImpl.class.getName());
|
getLogger(GroupListViewModel.class.getName());
|
||||||
|
|
||||||
private final PrivateGroupManager groupManager;
|
private final PrivateGroupManager groupManager;
|
||||||
private final GroupInvitationManager groupInvitationManager;
|
private final GroupInvitationManager groupInvitationManager;
|
||||||
@@ -61,120 +68,137 @@ class GroupListControllerImpl extends DbControllerImpl
|
|||||||
private final AndroidNotificationManager notificationManager;
|
private final AndroidNotificationManager notificationManager;
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
|
|
||||||
// UI thread
|
private final MutableLiveData<LiveResult<List<GroupItem>>> groupItems =
|
||||||
@Nullable
|
new MutableLiveData<>();
|
||||||
private GroupListListener listener;
|
private final MutableLiveData<Integer> numInvitations =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GroupListControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
GroupListViewModel(Application application,
|
||||||
LifecycleManager lifecycleManager, PrivateGroupManager groupManager,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
|
LifecycleManager lifecycleManager,
|
||||||
|
TransactionManager db,
|
||||||
|
AndroidExecutor androidExecutor,
|
||||||
|
PrivateGroupManager groupManager,
|
||||||
GroupInvitationManager groupInvitationManager,
|
GroupInvitationManager groupInvitationManager,
|
||||||
ContactManager contactManager,
|
ContactManager contactManager,
|
||||||
AndroidNotificationManager notificationManager, EventBus eventBus) {
|
AndroidNotificationManager notificationManager, EventBus eventBus) {
|
||||||
super(dbExecutor, lifecycleManager);
|
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||||
this.groupManager = groupManager;
|
this.groupManager = groupManager;
|
||||||
this.groupInvitationManager = groupInvitationManager;
|
this.groupInvitationManager = groupInvitationManager;
|
||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
this.notificationManager = notificationManager;
|
this.notificationManager = notificationManager;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
|
this.eventBus.addListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setGroupListListener(GroupListListener listener) {
|
protected void onCleared() {
|
||||||
this.listener = listener;
|
super.onCleared();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unsetGroupListListener(GroupListListener listener) {
|
|
||||||
if (this.listener == listener) this.listener = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CallSuper
|
|
||||||
public void onStart() {
|
|
||||||
if (listener == null) throw new IllegalStateException();
|
|
||||||
eventBus.addListener(this);
|
|
||||||
notificationManager.clearAllGroupMessageNotifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CallSuper
|
|
||||||
public void onStop() {
|
|
||||||
eventBus.removeListener(this);
|
eventBus.removeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void clearAllGroupMessageNotifications() {
|
||||||
|
notificationManager.clearAllGroupMessageNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
void blockAllGroupMessageNotifications() {
|
||||||
|
notificationManager.blockAllGroupMessageNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
void unblockAllGroupMessageNotifications() {
|
||||||
|
notificationManager.unblockAllGroupMessageNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@CallSuper
|
|
||||||
public void eventOccurred(Event e) {
|
public void eventOccurred(Event e) {
|
||||||
if (listener == null) throw new IllegalStateException();
|
|
||||||
if (e instanceof GroupMessageAddedEvent) {
|
if (e instanceof GroupMessageAddedEvent) {
|
||||||
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
|
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
|
||||||
LOG.info("Private group message added");
|
LOG.info("Private group message added");
|
||||||
listener.onGroupMessageAdded(g.getHeader());
|
onGroupMessageAdded(g.getHeader());
|
||||||
} else if (e instanceof GroupInvitationRequestReceivedEvent) {
|
} else if (e instanceof GroupInvitationRequestReceivedEvent) {
|
||||||
LOG.info("Private group invitation received");
|
LOG.info("Private group invitation received");
|
||||||
listener.onGroupInvitationReceived();
|
loadNumInvitations();
|
||||||
} else if (e instanceof GroupAddedEvent) {
|
} else if (e instanceof GroupAddedEvent) {
|
||||||
GroupAddedEvent g = (GroupAddedEvent) e;
|
GroupAddedEvent g = (GroupAddedEvent) e;
|
||||||
ClientId id = g.getGroup().getClientId();
|
ClientId id = g.getGroup().getClientId();
|
||||||
if (id.equals(CLIENT_ID)) {
|
if (id.equals(CLIENT_ID)) {
|
||||||
LOG.info("Private group added");
|
LOG.info("Private group added");
|
||||||
listener.onGroupAdded(g.getGroup().getId());
|
loadGroups();
|
||||||
}
|
}
|
||||||
} else if (e instanceof GroupRemovedEvent) {
|
} else if (e instanceof GroupRemovedEvent) {
|
||||||
GroupRemovedEvent g = (GroupRemovedEvent) e;
|
GroupRemovedEvent g = (GroupRemovedEvent) e;
|
||||||
ClientId id = g.getGroup().getClientId();
|
ClientId id = g.getGroup().getClientId();
|
||||||
if (id.equals(CLIENT_ID)) {
|
if (id.equals(CLIENT_ID)) {
|
||||||
LOG.info("Private group removed");
|
LOG.info("Private group removed");
|
||||||
listener.onGroupRemoved(g.getGroup().getId());
|
onGroupRemoved(g.getGroup().getId());
|
||||||
}
|
}
|
||||||
} else if (e instanceof GroupDissolvedEvent) {
|
} else if (e instanceof GroupDissolvedEvent) {
|
||||||
GroupDissolvedEvent g = (GroupDissolvedEvent) e;
|
GroupDissolvedEvent g = (GroupDissolvedEvent) e;
|
||||||
LOG.info("Private group dissolved");
|
LOG.info("Private group dissolved");
|
||||||
listener.onGroupDissolved(g.getGroupId());
|
onGroupDissolved(g.getGroupId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
void loadGroups() {
|
||||||
public void loadGroups(
|
loadList(this::loadGroups, groupItems::setValue);
|
||||||
ResultExceptionHandler<Collection<GroupItem>, DbException> handler) {
|
|
||||||
runOnDbThread(() -> {
|
|
||||||
try {
|
|
||||||
long start = now();
|
|
||||||
Collection<PrivateGroup> groups =
|
|
||||||
groupManager.getPrivateGroups();
|
|
||||||
List<GroupItem> items = new ArrayList<>(groups.size());
|
|
||||||
Map<AuthorId, AuthorInfo> authorInfos = new HashMap<>();
|
|
||||||
for (PrivateGroup g : groups) {
|
|
||||||
try {
|
|
||||||
GroupId id = g.getId();
|
|
||||||
AuthorId authorId = g.getCreator().getId();
|
|
||||||
AuthorInfo authorInfo;
|
|
||||||
if (authorInfos.containsKey(authorId)) {
|
|
||||||
authorInfo = authorInfos.get(authorId);
|
|
||||||
} else {
|
|
||||||
authorInfo = contactManager.getAuthorInfo(authorId);
|
|
||||||
authorInfos.put(authorId, authorInfo);
|
|
||||||
}
|
|
||||||
GroupCount count = groupManager.getGroupCount(id);
|
|
||||||
boolean dissolved = groupManager.isDissolved(id);
|
|
||||||
items.add(
|
|
||||||
new GroupItem(g, authorInfo, count, dissolved));
|
|
||||||
} catch (NoSuchGroupException e) {
|
|
||||||
// Continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logDuration(LOG, "Loading groups", start);
|
|
||||||
handler.onResult(items);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
handler.onException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@DatabaseExecutor
|
||||||
public void removeGroup(GroupId g, ExceptionHandler<DbException> handler) {
|
private List<GroupItem> loadGroups(Transaction txn) throws DbException {
|
||||||
|
long start = now();
|
||||||
|
Collection<PrivateGroup> groups = groupManager.getPrivateGroups(txn);
|
||||||
|
List<GroupItem> items = new ArrayList<>(groups.size());
|
||||||
|
Map<AuthorId, AuthorInfo> authorInfos = new HashMap<>();
|
||||||
|
for (PrivateGroup g : groups) {
|
||||||
|
GroupId id = g.getId();
|
||||||
|
AuthorId authorId = g.getCreator().getId();
|
||||||
|
AuthorInfo authorInfo;
|
||||||
|
if (authorInfos.containsKey(authorId)) {
|
||||||
|
authorInfo = requireNonNull(authorInfos.get(authorId));
|
||||||
|
} else {
|
||||||
|
authorInfo = contactManager.getAuthorInfo(txn, authorId);
|
||||||
|
authorInfos.put(authorId, authorInfo);
|
||||||
|
}
|
||||||
|
GroupCount count = groupManager.getGroupCount(txn, id);
|
||||||
|
boolean dissolved = groupManager.isDissolved(txn, id);
|
||||||
|
items.add(new GroupItem(g, authorInfo, count, dissolved));
|
||||||
|
}
|
||||||
|
Collections.sort(items);
|
||||||
|
logDuration(LOG, "Loading groups", start);
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void onGroupMessageAdded(GroupMessageHeader header) {
|
||||||
|
GroupId g = header.getGroupId();
|
||||||
|
List<GroupItem> list = updateListItems(groupItems,
|
||||||
|
itemToTest -> itemToTest.getId().equals(g),
|
||||||
|
itemToUpdate -> new GroupItem(itemToUpdate, header));
|
||||||
|
if (list == null) return;
|
||||||
|
// re-sort as the order of items may have changed
|
||||||
|
Collections.sort(list);
|
||||||
|
groupItems.setValue(new LiveResult<>(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void onGroupDissolved(GroupId groupId) {
|
||||||
|
List<GroupItem> list = updateListItems(groupItems,
|
||||||
|
itemToTest -> itemToTest.getId().equals(groupId),
|
||||||
|
itemToUpdate -> new GroupItem(itemToUpdate, true));
|
||||||
|
if (list == null) return;
|
||||||
|
groupItems.setValue(new LiveResult<>(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void onGroupRemoved(GroupId groupId) {
|
||||||
|
List<GroupItem> list =
|
||||||
|
removeListItems(groupItems, i -> i.getId().equals(groupId));
|
||||||
|
if (list == null) return;
|
||||||
|
groupItems.setValue(new LiveResult<>(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeGroup(GroupId g) {
|
||||||
runOnDbThread(() -> {
|
runOnDbThread(() -> {
|
||||||
try {
|
try {
|
||||||
long start = now();
|
long start = now();
|
||||||
@@ -182,23 +206,26 @@ class GroupListControllerImpl extends DbControllerImpl
|
|||||||
logDuration(LOG, "Removing group", start);
|
logDuration(LOG, "Removing group", start);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
handler.onException(e);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
void loadNumInvitations() {
|
||||||
public void loadAvailableGroups(
|
|
||||||
ResultExceptionHandler<Integer, DbException> handler) {
|
|
||||||
runOnDbThread(() -> {
|
runOnDbThread(() -> {
|
||||||
try {
|
try {
|
||||||
handler.onResult(
|
int i = groupInvitationManager.getInvitations().size();
|
||||||
groupInvitationManager.getInvitations().size());
|
numInvitations.postValue(i);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
handler.onException(e);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LiveData<LiveResult<List<GroupItem>>> getGroupItems() {
|
||||||
|
return groupItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<Integer> getNumInvitations() {
|
||||||
|
return numInvitations;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
|
|||||||
|
|
||||||
private final static float ALPHA = 0.42f;
|
private final static float ALPHA = 0.42f;
|
||||||
|
|
||||||
|
private final Context ctx;
|
||||||
private final ViewGroup layout;
|
private final ViewGroup layout;
|
||||||
private final TextAvatarView avatar;
|
private final TextAvatarView avatar;
|
||||||
private final TextView name;
|
private final TextView name;
|
||||||
@@ -40,7 +41,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
|
|||||||
|
|
||||||
GroupViewHolder(View v) {
|
GroupViewHolder(View v) {
|
||||||
super(v);
|
super(v);
|
||||||
|
ctx = v.getContext();
|
||||||
layout = (ViewGroup) v;
|
layout = (ViewGroup) v;
|
||||||
avatar = v.findViewById(R.id.avatarView);
|
avatar = v.findViewById(R.id.avatarView);
|
||||||
name = v.findViewById(R.id.nameView);
|
name = v.findViewById(R.id.nameView);
|
||||||
@@ -51,8 +52,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
|
|||||||
remove = v.findViewById(R.id.removeButton);
|
remove = v.findViewById(R.id.removeButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
void bindView(Context ctx, GroupItem group,
|
void bindView(GroupItem group, OnGroupRemoveClickListener listener) {
|
||||||
OnGroupRemoveClickListener listener) {
|
|
||||||
// Avatar
|
// Avatar
|
||||||
avatar.setText(group.getName().substring(0, 1));
|
avatar.setText(group.getName().substring(0, 1));
|
||||||
avatar.setBackgroundBytes(group.getId().getBytes());
|
avatar.setBackgroundBytes(group.getId().getBytes());
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ public class GroupMemberListActivity extends BriarActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ public class RevealContactsActivity extends ContactSelectorActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ public class RevealContactsActivity extends ContactSelectorActivity
|
|||||||
new UiExceptionHandler<DbException>(this) {
|
new UiExceptionHandler<DbException>(this) {
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@ public class RevealContactsActivity extends ContactSelectorActivity
|
|||||||
new UiExceptionHandler<DbException>(this) {
|
new UiExceptionHandler<DbException>(this) {
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
supportFinishAfterTransition();
|
supportFinishAfterTransition();
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ public abstract class InvitationActivity<I extends InvitationItem>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ public abstract class InvitationActivity<I extends InvitationItem>
|
|||||||
new UiExceptionHandler<DbException>(this) {
|
new UiExceptionHandler<DbException>(this) {
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ public class ShareBlogActivity extends ShareActivity {
|
|||||||
Toast.makeText(ShareBlogActivity.this,
|
Toast.makeText(ShareBlogActivity.this,
|
||||||
R.string.blogs_sharing_error, LENGTH_SHORT)
|
R.string.blogs_sharing_error, LENGTH_SHORT)
|
||||||
.show();
|
.show();
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ public class ShareForumActivity extends ShareActivity {
|
|||||||
Toast.makeText(ShareForumActivity.this,
|
Toast.makeText(ShareForumActivity.this,
|
||||||
R.string.forum_share_error, LENGTH_SHORT)
|
R.string.forum_share_error, LENGTH_SHORT)
|
||||||
.show();
|
.show();
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -214,7 +214,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -351,7 +351,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
handleDbException(exception);
|
handleException(exception);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
getController().createAndStoreMessage(text, replyItem, handler);
|
getController().createAndStoreMessage(text, replyItem, handler);
|
||||||
|
|||||||
@@ -3,18 +3,33 @@ package org.briarproject.briar.android.viewmodel;
|
|||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
|
import org.briarproject.bramble.api.db.DbCallable;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.UiThread;
|
||||||
|
import androidx.arch.core.util.Function;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -25,17 +40,31 @@ public abstract class DbViewModel extends AndroidViewModel {
|
|||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
private final LifecycleManager lifecycleManager;
|
private final LifecycleManager lifecycleManager;
|
||||||
|
private final TransactionManager db;
|
||||||
|
private final AndroidExecutor androidExecutor;
|
||||||
|
|
||||||
public DbViewModel(
|
public DbViewModel(
|
||||||
@NonNull Application application,
|
@NonNull Application application,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
LifecycleManager lifecycleManager) {
|
LifecycleManager lifecycleManager,
|
||||||
|
TransactionManager db,
|
||||||
|
AndroidExecutor androidExecutor) {
|
||||||
super(application);
|
super(application);
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.lifecycleManager = lifecycleManager;
|
this.lifecycleManager = lifecycleManager;
|
||||||
|
this.db = db;
|
||||||
|
this.androidExecutor = androidExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void runOnDbThread(Runnable task) {
|
/**
|
||||||
|
* Runs the given task on the {@link DatabaseExecutor}
|
||||||
|
* and waits for the DB to open.
|
||||||
|
* <p>
|
||||||
|
* If you need a list of items to be displayed in a
|
||||||
|
* {@link RecyclerView.Adapter},
|
||||||
|
* use {@link #loadList(DbCallable, UiConsumer)} instead.
|
||||||
|
*/
|
||||||
|
protected void runOnDbThread(Runnable task) {
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
lifecycleManager.waitForDatabase();
|
lifecycleManager.waitForDatabase();
|
||||||
@@ -47,4 +76,120 @@ public abstract class DbViewModel extends AndroidViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a list of items on the {@link DatabaseExecutor} within a single
|
||||||
|
* {@link Transaction} and publishes it as a {@link LiveResult}
|
||||||
|
* to the {@link UiThread}.
|
||||||
|
* <p>
|
||||||
|
* Use this to ensure that modifications to your local list do not get
|
||||||
|
* overridden by database loads that were in progress while the modification
|
||||||
|
* was made.
|
||||||
|
* E.g. An event about the removal of a message causes the message item to
|
||||||
|
* be removed from the local list while all messages are reloaded.
|
||||||
|
* This method ensures that those operations can be processed on the
|
||||||
|
* UiThread in the correct order so that the removed message will not be
|
||||||
|
* re-added when the re-load completes.
|
||||||
|
*/
|
||||||
|
protected <T extends List<?>> void loadList(
|
||||||
|
DbCallable<T, DbException> task,
|
||||||
|
UiConsumer<LiveResult<T>> uiConsumer) {
|
||||||
|
dbExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
lifecycleManager.waitForDatabase();
|
||||||
|
db.transaction(true, txn -> {
|
||||||
|
T t = task.call(txn);
|
||||||
|
txn.attach(() -> uiConsumer.accept(new LiveResult<>(t)));
|
||||||
|
});
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.warning("Interrupted while waiting for database");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
androidExecutor.runOnUiThread(
|
||||||
|
() -> uiConsumer.accept(new LiveResult<>(e)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface UiConsumer<T> {
|
||||||
|
@UiThread
|
||||||
|
void accept(T t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a copy of the list available in the given LiveData
|
||||||
|
* and replaces items where the given test function returns true.
|
||||||
|
*
|
||||||
|
* @return a copy of the list in the LiveData with item(s) replaced
|
||||||
|
* or null when the
|
||||||
|
* <ul>
|
||||||
|
* <li> LiveData does not have a value
|
||||||
|
* <li> LiveResult in the LiveData has an error
|
||||||
|
* <li> test function did return false for all items in the list
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
protected <T> List<T> updateListItems(
|
||||||
|
LiveData<LiveResult<List<T>>> liveData, Function<T, Boolean> test,
|
||||||
|
Function<T, T> replacer) {
|
||||||
|
List<T> items = getListCopy(liveData);
|
||||||
|
if (items == null) return null;
|
||||||
|
|
||||||
|
ListIterator<T> iterator = items.listIterator();
|
||||||
|
boolean changed = false;
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
T item = iterator.next();
|
||||||
|
if (test.apply(item)) {
|
||||||
|
changed = true;
|
||||||
|
iterator.set(replacer.apply(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed ? items : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a copy of the list available in the given LiveData
|
||||||
|
* and removes the items from it where the given test function returns true.
|
||||||
|
*
|
||||||
|
* @return a copy of the list in the LiveData with item(s) removed
|
||||||
|
* or null when the
|
||||||
|
* <ul>
|
||||||
|
* <li> LiveData does not have a value
|
||||||
|
* <li> LiveResult in the LiveData has an error
|
||||||
|
* <li> test function did return false for all items in the list
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
protected <T> List<T> removeListItems(
|
||||||
|
LiveData<LiveResult<List<T>>> liveData, Function<T, Boolean> test) {
|
||||||
|
List<T> items = getListCopy(liveData);
|
||||||
|
if (items == null) return null;
|
||||||
|
|
||||||
|
ListIterator<T> iterator = items.listIterator();
|
||||||
|
boolean changed = false;
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
T item = iterator.next();
|
||||||
|
if (test.apply(item)) {
|
||||||
|
changed = true;
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed ? items : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a copy of the list of items from the given LiveData
|
||||||
|
* or null if it is not available.
|
||||||
|
* The list copy can be safely mutated.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private <T> List<T> getListCopy(LiveData<LiveResult<List<T>>> liveData) {
|
||||||
|
LiveResult<List<T>> value = liveData.getValue();
|
||||||
|
if (value == null) return null;
|
||||||
|
List<T> list = value.getResultOrNull();
|
||||||
|
if (list == null) return null;
|
||||||
|
return new ArrayList<>(list);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ package org.briarproject.briar.android.viewmodel;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.util.Consumer;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class LiveResult<T> {
|
public class LiveResult<T> {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private T result;
|
private final T result;
|
||||||
@Nullable
|
@Nullable
|
||||||
private Exception exception;
|
private final Exception exception;
|
||||||
|
|
||||||
public LiveResult(T result) {
|
public LiveResult(T result) {
|
||||||
this.result = result;
|
this.result = result;
|
||||||
@@ -36,4 +37,20 @@ public class LiveResult<T> {
|
|||||||
return exception != null;
|
return exception != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the given function, if {@link #hasError()} is true.
|
||||||
|
*/
|
||||||
|
public LiveResult<T> onError(Consumer<Exception> fun) {
|
||||||
|
if (exception != null) fun.accept(exception);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the given function, if {@link #hasError()} is false.
|
||||||
|
*/
|
||||||
|
public LiveResult<T> onSuccess(Consumer<T> fun) {
|
||||||
|
if (result != null) fun.accept(result);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,10 @@ public interface AndroidNotificationManager {
|
|||||||
|
|
||||||
void unblockNotification(GroupId g);
|
void unblockNotification(GroupId g);
|
||||||
|
|
||||||
|
void blockAllGroupMessageNotifications();
|
||||||
|
|
||||||
|
void unblockAllGroupMessageNotifications();
|
||||||
|
|
||||||
void blockAllBlogPostNotifications();
|
void blockAllBlogPostNotifications();
|
||||||
|
|
||||||
void unblockAllBlogPostNotifications();
|
void unblockAllBlogPostNotifications();
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
public class AndroidExecutorTestImpl implements AndroidExecutor {
|
||||||
|
|
||||||
|
private final Executor executor;
|
||||||
|
|
||||||
|
public AndroidExecutorTestImpl(Executor executor) {
|
||||||
|
this.executor = executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> Future<V> runOnBackgroundThread(Callable<V> c) {
|
||||||
|
throw new IllegalStateException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void runOnBackgroundThread(Runnable r) {
|
||||||
|
executor.execute(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> Future<V> runOnUiThread(Callable<V> c) {
|
||||||
|
throw new IllegalStateException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void runOnUiThread(Runnable r) {
|
||||||
|
executor.execute(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package org.briarproject.briar.android.privategroup.list;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
|
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||||
|
import org.briarproject.briar.android.AndroidExecutorTestImpl;
|
||||||
|
import org.briarproject.briar.android.viewmodel.LiveResult;
|
||||||
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
|
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||||
|
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||||
|
import org.briarproject.briar.api.privategroup.event.GroupDissolvedEvent;
|
||||||
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationItem;
|
||||||
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
import org.jmock.lib.legacy.ClassImposteriser;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
|
||||||
|
|
||||||
|
import static edu.emory.mathcs.backport.java.util.Collections.emptyList;
|
||||||
|
import static edu.emory.mathcs.backport.java.util.Collections.singletonList;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
|
import static org.briarproject.briar.android.viewmodel.LiveDataTestUtil.getOrAwaitValue;
|
||||||
|
import static org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||||
|
import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.CLIENT_ID;
|
||||||
|
import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.MAJOR_VERSION;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public class GroupListViewModelTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final InstantTaskExecutorRule testRule =
|
||||||
|
new InstantTaskExecutorRule();
|
||||||
|
|
||||||
|
private final LifecycleManager lifecycleManager =
|
||||||
|
context.mock(LifecycleManager.class);
|
||||||
|
private final TransactionManager db =
|
||||||
|
context.mock(TransactionManager.class);
|
||||||
|
private final PrivateGroupManager groupManager =
|
||||||
|
context.mock(PrivateGroupManager.class);
|
||||||
|
private final GroupInvitationManager groupInvitationManager =
|
||||||
|
context.mock(GroupInvitationManager.class);
|
||||||
|
private final ContactManager contactManager =
|
||||||
|
context.mock(ContactManager.class);
|
||||||
|
private final AndroidNotificationManager notificationManager =
|
||||||
|
context.mock(AndroidNotificationManager.class);
|
||||||
|
private final EventBus eventBus = context.mock(EventBus.class);
|
||||||
|
|
||||||
|
private final GroupListViewModel viewModel;
|
||||||
|
|
||||||
|
|
||||||
|
private final Group g1 = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
|
private final Group g2 = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
|
private final PrivateGroup privateGroup1 =
|
||||||
|
new PrivateGroup(g1, "foo", getAuthor(), getRandomBytes(2));
|
||||||
|
private final PrivateGroup privateGroup2 =
|
||||||
|
new PrivateGroup(g2, "bar", getAuthor(), getRandomBytes(2));
|
||||||
|
private final AuthorInfo authorInfo1 =
|
||||||
|
new AuthorInfo(AuthorInfo.Status.UNVERIFIED);
|
||||||
|
private final AuthorInfo authorInfo2 =
|
||||||
|
new AuthorInfo(AuthorInfo.Status.VERIFIED);
|
||||||
|
|
||||||
|
private final GroupCount groupCount1 = new GroupCount(2, 1, 23L);
|
||||||
|
private final GroupCount groupCount2 = new GroupCount(5, 3, 42L);
|
||||||
|
private final GroupItem item1 =
|
||||||
|
new GroupItem(privateGroup1, authorInfo1, groupCount1, false);
|
||||||
|
private final GroupItem item2 =
|
||||||
|
new GroupItem(privateGroup2, authorInfo2, groupCount2, false);
|
||||||
|
|
||||||
|
public GroupListViewModelTest() {
|
||||||
|
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||||
|
Application app = context.mock(Application.class);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(eventBus).addListener(with(any(EventListener.class)));
|
||||||
|
}});
|
||||||
|
Executor dbExecutor = new ImmediateExecutor();
|
||||||
|
AndroidExecutor androidExecutor =
|
||||||
|
new AndroidExecutorTestImpl(dbExecutor);
|
||||||
|
viewModel = new GroupListViewModel(app, dbExecutor, lifecycleManager,
|
||||||
|
db, androidExecutor, groupManager, groupInvitationManager,
|
||||||
|
contactManager, notificationManager, eventBus);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadGroupsException() throws Exception {
|
||||||
|
DbException dbException = new DbException();
|
||||||
|
|
||||||
|
Transaction txn = new Transaction(null, true);
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(lifecycleManager).waitForDatabase();
|
||||||
|
oneOf(db).transaction(with(true), withDbRunnable(txn));
|
||||||
|
oneOf(groupManager).getPrivateGroups(txn);
|
||||||
|
will(throwException(dbException));
|
||||||
|
}});
|
||||||
|
|
||||||
|
viewModel.loadGroups();
|
||||||
|
|
||||||
|
LiveResult<List<GroupItem>> result =
|
||||||
|
getOrAwaitValue(viewModel.getGroupItems());
|
||||||
|
assertTrue(result.hasError());
|
||||||
|
assertEquals(dbException, result.getException());
|
||||||
|
assertNull(result.getResultOrNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadGroups() throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, true);
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(lifecycleManager).waitForDatabase();
|
||||||
|
oneOf(db).transaction(with(true), withDbRunnable(txn));
|
||||||
|
oneOf(groupManager).getPrivateGroups(txn);
|
||||||
|
will(returnValue(Arrays.asList(privateGroup1, privateGroup2)));
|
||||||
|
}});
|
||||||
|
expectLoadGroup(txn, privateGroup1, authorInfo1, groupCount1, false);
|
||||||
|
expectLoadGroup(txn, privateGroup2, authorInfo2, groupCount2, false);
|
||||||
|
|
||||||
|
viewModel.loadGroups();
|
||||||
|
|
||||||
|
// unpack updated live data
|
||||||
|
LiveResult<List<GroupItem>> result =
|
||||||
|
getOrAwaitValue(viewModel.getGroupItems());
|
||||||
|
assertFalse(result.hasError());
|
||||||
|
List<GroupItem> liveList = result.getResultOrNull();
|
||||||
|
assertNotNull(liveList);
|
||||||
|
// list is sorted by last message timestamp
|
||||||
|
assertEquals(Arrays.asList(item2, item1), liveList);
|
||||||
|
|
||||||
|
// group 1 gets dissolved by creator
|
||||||
|
Event dissolvedEvent = new GroupDissolvedEvent(privateGroup1.getId());
|
||||||
|
viewModel.eventOccurred(dissolvedEvent);
|
||||||
|
result = getOrAwaitValue(viewModel.getGroupItems());
|
||||||
|
liveList = result.getResultOrNull();
|
||||||
|
assertNotNull(liveList);
|
||||||
|
assertEquals(2, liveList.size());
|
||||||
|
// assert that list update includes dissolved group item
|
||||||
|
for (GroupItem item : liveList) {
|
||||||
|
if (item.getId().equals(privateGroup1.getId())) {
|
||||||
|
assertTrue(item.isDissolved());
|
||||||
|
} else if (item.getId().equals(privateGroup2.getId())) {
|
||||||
|
assertFalse(item.isDissolved());
|
||||||
|
} else fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadNumInvitations() throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(lifecycleManager).waitForDatabase();
|
||||||
|
oneOf(groupInvitationManager).getInvitations();
|
||||||
|
will(returnValue(emptyList()));
|
||||||
|
}});
|
||||||
|
viewModel.loadNumInvitations();
|
||||||
|
|
||||||
|
int num = getOrAwaitValue(viewModel.getNumInvitations());
|
||||||
|
assertEquals(0, num);
|
||||||
|
|
||||||
|
PrivateGroup pg = context.mock(PrivateGroup.class);
|
||||||
|
Contact c = getContact();
|
||||||
|
GroupInvitationItem item = new GroupInvitationItem(pg, c);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(lifecycleManager).waitForDatabase();
|
||||||
|
oneOf(groupInvitationManager).getInvitations();
|
||||||
|
will(returnValue(singletonList(item)));
|
||||||
|
}});
|
||||||
|
viewModel.loadNumInvitations();
|
||||||
|
|
||||||
|
num = getOrAwaitValue(viewModel.getNumInvitations());
|
||||||
|
assertEquals(1, num);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectLoadGroup(Transaction txn, PrivateGroup privateGroup,
|
||||||
|
AuthorInfo authorInfo, GroupCount groupCount, boolean dissolved)
|
||||||
|
throws DbException {
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(contactManager)
|
||||||
|
.getAuthorInfo(txn, privateGroup.getCreator().getId());
|
||||||
|
will(returnValue(authorInfo));
|
||||||
|
oneOf(groupManager).getGroupCount(txn, privateGroup.getId());
|
||||||
|
will(returnValue(groupCount));
|
||||||
|
oneOf(groupManager).isDissolved(txn, privateGroup.getId());
|
||||||
|
will(returnValue(dissolved));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/* Copyright 2019 Google LLC.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
https://gist.github.com/JoseAlcerreca/1e9ee05dcdd6a6a6fa1cbfc125559bba
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.briarproject.briar.android.viewmodel;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
|
public class LiveDataTestUtil {
|
||||||
|
public static <T> T getOrAwaitValue(final LiveData<T> liveData)
|
||||||
|
throws InterruptedException {
|
||||||
|
final AtomicReference<T> data = new AtomicReference<>();
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
Observer<T> observer = new Observer<T>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(@Nullable T o) {
|
||||||
|
data.set(o);
|
||||||
|
latch.countDown();
|
||||||
|
liveData.removeObserver(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
liveData.observeForever(observer);
|
||||||
|
// Don't wait indefinitely if the LiveData is not set.
|
||||||
|
if (!latch.await(2, TimeUnit.SECONDS)) {
|
||||||
|
throw new RuntimeException("LiveData value was never set.");
|
||||||
|
}
|
||||||
|
return data.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,6 +66,11 @@ public interface PrivateGroupManager {
|
|||||||
*/
|
*/
|
||||||
void markGroupDissolved(Transaction txn, GroupId g) throws DbException;
|
void markGroupDissolved(Transaction txn, GroupId g) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given private group has been dissolved.
|
||||||
|
*/
|
||||||
|
boolean isDissolved(Transaction txn, GroupId g) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the given private group has been dissolved.
|
* Returns true if the given private group has been dissolved.
|
||||||
*/
|
*/
|
||||||
@@ -91,6 +96,12 @@ public interface PrivateGroupManager {
|
|||||||
*/
|
*/
|
||||||
Collection<PrivateGroup> getPrivateGroups() throws DbException;
|
Collection<PrivateGroup> getPrivateGroups() throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all private groups the user is a member of.
|
||||||
|
*/
|
||||||
|
Collection<PrivateGroup> getPrivateGroups(Transaction txn)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the text of the private group message with the given ID.
|
* Returns the text of the private group message with the given ID.
|
||||||
*/
|
*/
|
||||||
@@ -111,6 +122,11 @@ public interface PrivateGroupManager {
|
|||||||
*/
|
*/
|
||||||
boolean isMember(Transaction txn, GroupId g, Author a) throws DbException;
|
boolean isMember(Transaction txn, GroupId g, Author a) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the group count for the given private group.
|
||||||
|
*/
|
||||||
|
GroupCount getGroupCount(Transaction txn, GroupId g) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the group count for the given private group.
|
* Returns the group count for the given private group.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -270,22 +270,31 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<PrivateGroup> getPrivateGroups() throws DbException {
|
public Collection<PrivateGroup> getPrivateGroups(Transaction txn)
|
||||||
Collection<Group> groups;
|
throws DbException {
|
||||||
Transaction txn = db.startTransaction(true);
|
Collection<Group> groups = db.getGroups(txn, CLIENT_ID, MAJOR_VERSION);
|
||||||
|
Collection<PrivateGroup> privateGroups = new ArrayList<>(groups.size());
|
||||||
try {
|
try {
|
||||||
groups = db.getGroups(txn, CLIENT_ID, MAJOR_VERSION);
|
|
||||||
db.commitTransaction(txn);
|
|
||||||
} finally {
|
|
||||||
db.endTransaction(txn);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Collection<PrivateGroup> privateGroups =
|
|
||||||
new ArrayList<>(groups.size());
|
|
||||||
for (Group g : groups) {
|
for (Group g : groups) {
|
||||||
privateGroups.add(privateGroupFactory.parsePrivateGroup(g));
|
privateGroups.add(privateGroupFactory.parsePrivateGroup(g));
|
||||||
}
|
}
|
||||||
return privateGroups;
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
return privateGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<PrivateGroup> getPrivateGroups() throws DbException {
|
||||||
|
return db.transactionWithResult(true, this::getPrivateGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDissolved(Transaction txn, GroupId g) throws DbException {
|
||||||
|
try {
|
||||||
|
BdfDictionary meta =
|
||||||
|
clientHelper.getGroupMetadataAsDictionary(txn, g);
|
||||||
|
return meta.getBoolean(GROUP_KEY_DISSOLVED);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
@@ -293,12 +302,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDissolved(GroupId g) throws DbException {
|
public boolean isDissolved(GroupId g) throws DbException {
|
||||||
try {
|
return db.transactionWithResult(true, txn -> isDissolved(txn, g));
|
||||||
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(g);
|
|
||||||
return meta.getBoolean(GROUP_KEY_DISSOLVED);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -403,7 +407,8 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
|||||||
PrivateGroup privateGroup = getPrivateGroup(txn, g);
|
PrivateGroup privateGroup = getPrivateGroup(txn, g);
|
||||||
for (Entry<Author, Visibility> m : authors.entrySet()) {
|
for (Entry<Author, Visibility> m : authors.entrySet()) {
|
||||||
Author a = m.getKey();
|
Author a = m.getKey();
|
||||||
AuthorInfo authorInfo = contactManager.getAuthorInfo(txn, a.getId());
|
AuthorInfo authorInfo =
|
||||||
|
contactManager.getAuthorInfo(txn, a.getId());
|
||||||
Status status = authorInfo.getStatus();
|
Status status = authorInfo.getStatus();
|
||||||
Visibility v = m.getValue();
|
Visibility v = m.getValue();
|
||||||
ContactId c = null;
|
ContactId c = null;
|
||||||
@@ -450,6 +455,12 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupCount getGroupCount(Transaction txn, GroupId g)
|
||||||
|
throws DbException {
|
||||||
|
return messageTracker.getGroupCount(txn, g);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GroupCount getGroupCount(GroupId g) throws DbException {
|
public GroupCount getGroupCount(GroupId g) throws DbException {
|
||||||
return messageTracker.getGroupCount(g);
|
return messageTracker.getGroupCount(g);
|
||||||
|
|||||||
Reference in New Issue
Block a user