mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
13 Commits
1821-bluet
...
1881-threa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21d6dfe817 | ||
|
|
c1e83b22c1 | ||
|
|
635008fb60 | ||
|
|
b78569119a | ||
|
|
8372bb01b2 | ||
|
|
766718e75c | ||
|
|
1c107a851b | ||
|
|
db53e79d1d | ||
|
|
21e56284fb | ||
|
|
d393b79ced | ||
|
|
6611d7c02e | ||
|
|
ab43dd4986 | ||
|
|
36a9174781 |
24
.idea/runConfigurations/BridgeTest.xml
generated
24
.idea/runConfigurations/BridgeTest.xml
generated
@@ -1,24 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="BridgeTest" type="AndroidJUnit" factoryName="Android JUnit" nameIsGenerated="true">
|
||||
<module name="briar.bramble-java" />
|
||||
<useClassPathOnly />
|
||||
<extension name="coverage">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="org.briarproject.bramble.plugin.tor.*" />
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<option name="PACKAGE_NAME" value="org.briarproject.bramble.plugin.tor" />
|
||||
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="class" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$MODULE_DIR$" />
|
||||
<envs>
|
||||
<env name="OPTIONAL_TESTS" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
|
||||
</envs>
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.briarproject.bramble.api;
|
||||
|
||||
public interface ThrowingRunnable<T extends Throwable> {
|
||||
|
||||
void run() throws T;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.ThrowingRunnable;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.ThrowingRunnable;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
interface ThrowingRunnable<T extends Throwable> {
|
||||
|
||||
void run() throws T;
|
||||
}
|
||||
@@ -1,14 +1,5 @@
|
||||
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
|
||||
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
|
||||
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
|
||||
Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
|
||||
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
||||
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
||||
Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
|
||||
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
||||
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
||||
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
|
||||
Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
||||
Bridge obfs4 52.15.78.72:9443 02069A3C5362476936B62BA6F5ACC41ABD573A9B cert=ijYG/OKc7kqu2YzKNFfeXN7/BG2BOgfEP2KyYEiGDQthnHbsOiTWHeIG0WJVW+BckzDgKw iat-mode=0
|
||||
Bridge obfs4 13.58.29.242:9443 0C58939A77DA6B6B29D4B5236A75865659607AE0 cert=OylWIEHb/ezpq1zWxW0sgKRn+9ARH2eOcQOZ8/Gew+4l+oKOhQ2jUX/Y+FSl61JorXZUWA iat-mode=0
|
||||
Bridge obfs4 45.33.37.112:9443 60A609BB4ABE8D46E634AE81ED29ADAB7776B399 cert=t5v19WmNv5Sc2YPNr8RQids365W7MY8zJwQVkOxBjUMFomMWARDzsbYpcWLLcw0J9Gm+BQ iat-mode=0
|
||||
Bridge meek_lite 0.0.2.0:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
||||
@@ -448,12 +448,6 @@
|
||||
android:label="@string/pending_contact_requests"
|
||||
android:theme="@style/BriarTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".android.bluetoothsetup.BluetoothSetupActivity"
|
||||
android:label="Bluetooth Setup"
|
||||
android:theme="@style/BriarTheme"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
||||
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
|
||||
@@ -30,15 +30,16 @@ import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.android.account.DozeHelperModule;
|
||||
import org.briarproject.briar.android.account.LockManagerImpl;
|
||||
import org.briarproject.briar.android.account.SetupModule;
|
||||
import org.briarproject.briar.android.bluetoothsetup.BluetoothSetupModule;
|
||||
import org.briarproject.briar.android.contact.ContactListModule;
|
||||
import org.briarproject.briar.android.forum.ForumModule;
|
||||
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
|
||||
import org.briarproject.briar.android.login.LoginModule;
|
||||
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
|
||||
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
|
||||
import org.briarproject.briar.android.settings.SettingsModule;
|
||||
import org.briarproject.briar.android.privategroup.list.GroupListModule;
|
||||
import org.briarproject.briar.android.reporting.DevReportModule;
|
||||
import org.briarproject.briar.android.settings.SettingsModule;
|
||||
import org.briarproject.briar.android.sharing.SharingModule;
|
||||
import org.briarproject.briar.android.test.TestAvatarCreatorImpl;
|
||||
import org.briarproject.briar.android.viewmodel.ViewModelModule;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
@@ -79,10 +80,11 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
SettingsModule.class,
|
||||
DevReportModule.class,
|
||||
ContactListModule.class,
|
||||
BluetoothSetupModule.class,
|
||||
// below need to be within same scope as ViewModelProvider.Factory
|
||||
ForumModule.BindsModule.class,
|
||||
ForumModule.class,
|
||||
GroupListModule.class,
|
||||
GroupConversationModule.class,
|
||||
SharingModule.class,
|
||||
})
|
||||
public class AppModule {
|
||||
|
||||
|
||||
@@ -20,10 +20,6 @@ import org.briarproject.briar.android.blog.ReblogFragment;
|
||||
import org.briarproject.briar.android.blog.RssFeedImportActivity;
|
||||
import org.briarproject.briar.android.blog.RssFeedManageActivity;
|
||||
import org.briarproject.briar.android.blog.WriteBlogPostActivity;
|
||||
import org.briarproject.briar.android.bluetoothsetup.BluetoothSetupActivity;
|
||||
import org.briarproject.briar.android.bluetoothsetup.BluetoothSetupChooseFragment;
|
||||
import org.briarproject.briar.android.bluetoothsetup.BluetoothSetupPendingFragment;
|
||||
import org.briarproject.briar.android.bluetoothsetup.BluetoothSetupStartFragment;
|
||||
import org.briarproject.briar.android.contact.ContactListFragment;
|
||||
import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
|
||||
import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment;
|
||||
@@ -36,7 +32,6 @@ import org.briarproject.briar.android.conversation.ImageFragment;
|
||||
import org.briarproject.briar.android.forum.CreateForumActivity;
|
||||
import org.briarproject.briar.android.forum.ForumActivity;
|
||||
import org.briarproject.briar.android.forum.ForumListFragment;
|
||||
import org.briarproject.briar.android.forum.ForumModule;
|
||||
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
|
||||
import org.briarproject.briar.android.introduction.ContactChooserFragment;
|
||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||
@@ -54,7 +49,6 @@ import org.briarproject.briar.android.navdrawer.TransportsActivity;
|
||||
import org.briarproject.briar.android.panic.PanicPreferencesActivity;
|
||||
import org.briarproject.briar.android.panic.PanicResponderActivity;
|
||||
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
||||
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
|
||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
|
||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupFragment;
|
||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupModule;
|
||||
@@ -93,12 +87,10 @@ import dagger.Component;
|
||||
ActivityModule.class,
|
||||
BlogModule.class,
|
||||
CreateGroupModule.class,
|
||||
ForumModule.class,
|
||||
GroupInvitationModule.class,
|
||||
GroupConversationModule.class,
|
||||
GroupMemberModule.class,
|
||||
GroupRevealModule.class,
|
||||
SharingModule.class
|
||||
SharingModule.SharingLegacyModule.class
|
||||
}, dependencies = AndroidComponent.class)
|
||||
public interface ActivityComponent {
|
||||
|
||||
@@ -190,8 +182,6 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(CrashReportActivity crashReportActivity);
|
||||
|
||||
void inject(BluetoothSetupActivity activity);
|
||||
|
||||
// Fragments
|
||||
|
||||
void inject(AuthorNameFragment fragment);
|
||||
@@ -248,10 +238,4 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(ConfirmAvatarDialogFragment fragment);
|
||||
|
||||
void inject(BluetoothSetupStartFragment fragment);
|
||||
|
||||
void inject(BluetoothSetupChooseFragment fragment);
|
||||
|
||||
void inject(BluetoothSetupPendingFragment fragment);
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import org.briarproject.briar.android.BriarApplication;
|
||||
import org.briarproject.briar.android.DestroyableContext;
|
||||
import org.briarproject.briar.android.Localizer;
|
||||
import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.briar.android.forum.ForumModule;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
|
||||
import org.briarproject.briar.android.util.UiUtils;
|
||||
@@ -87,7 +86,6 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
activityComponent = DaggerActivityComponent.builder()
|
||||
.androidComponent(applicationComponent)
|
||||
.activityModule(getActivityModule())
|
||||
.forumModule(getForumModule())
|
||||
.build();
|
||||
injectActivity(activityComponent);
|
||||
super.onCreate(state);
|
||||
@@ -122,11 +120,6 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
return new ActivityModule(this);
|
||||
}
|
||||
|
||||
// TODO use a test module where this is used in tests
|
||||
protected ForumModule getForumModule() {
|
||||
return new ForumModule();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
@@ -160,7 +160,6 @@ public abstract class BriarActivity extends BaseActivity {
|
||||
* @param ownLayout true if the custom toolbar brings its own layout
|
||||
* @return the Toolbar object or null if content view did not contain one
|
||||
*/
|
||||
@Nullable
|
||||
protected Toolbar setUpCustomToolbar(boolean ownLayout) {
|
||||
// Custom Toolbar
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
package org.briarproject.briar.android.bluetoothsetup;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class BluetoothSetupActivity extends BriarActivity implements
|
||||
BaseFragmentListener {
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
private BluetoothSetupViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
setContentView(R.layout.activity_fragment_container);
|
||||
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||
.get(BluetoothSetupViewModel.class);
|
||||
|
||||
if (state == null) {
|
||||
showInitialFragment(new BluetoothSetupStartFragment());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package org.briarproject.briar.android.bluetoothsetup;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class BluetoothSetupChooseFragment extends BaseFragment {
|
||||
private static final String TAG =
|
||||
BluetoothSetupChooseFragment.class.getName();
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private BluetoothSetupViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
if (getActivity() == null || getContext() == null) return null;
|
||||
|
||||
viewModel = new ViewModelProvider(requireActivity())
|
||||
.get(BluetoothSetupViewModel.class);
|
||||
|
||||
View v =
|
||||
inflater.inflate(R.layout.fragment_bluetooth_setup_choose,
|
||||
container, false);
|
||||
|
||||
// TODO to enable when user picks a device from list
|
||||
|
||||
Button continueButton = v.findViewById(R.id.continueButton);
|
||||
continueButton.setOnClickListener(view -> {
|
||||
showNextFragment(new BluetoothSetupPendingFragment());
|
||||
});
|
||||
continueButton.setEnabled(true);
|
||||
|
||||
// RecyclerView devices = v.findViewById(R.id.devices);
|
||||
// devices.setHasFixedSize(true);
|
||||
// final LinearLayoutManager layoutManager =
|
||||
// new LinearLayoutManager(getActivity(),
|
||||
// LinearLayoutManager.VERTICAL, false);
|
||||
// devices.setLayoutManager(layoutManager);
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.briarproject.briar.android.bluetoothsetup;
|
||||
|
||||
import org.briarproject.briar.android.viewmodel.ViewModelKey;
|
||||
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.multibindings.IntoMap;
|
||||
|
||||
@Module
|
||||
public abstract class BluetoothSetupModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(BluetoothSetupViewModel.class)
|
||||
abstract ViewModel bindBluetoothSetupViewModel(
|
||||
BluetoothSetupViewModel bluetoothSetupViewModel);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package org.briarproject.briar.android.bluetoothsetup;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class BluetoothSetupPendingFragment extends BaseFragment {
|
||||
private static final String TAG =
|
||||
BluetoothSetupPendingFragment.class.getName();
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private BluetoothSetupViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
if (getActivity() == null || getContext() == null) return null;
|
||||
|
||||
viewModel = new ViewModelProvider(requireActivity())
|
||||
.get(BluetoothSetupViewModel.class);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_bluetooth_setup_pending,
|
||||
container, false);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
private void onContinueButtonClicked() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package org.briarproject.briar.android.bluetoothsetup;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class BluetoothSetupStartFragment extends BaseFragment {
|
||||
private static final String TAG =
|
||||
BluetoothSetupStartFragment.class.getName();
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private BluetoothSetupViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
if (getActivity() == null || getContext() == null) return null;
|
||||
|
||||
viewModel = new ViewModelProvider(requireActivity())
|
||||
.get(BluetoothSetupViewModel.class);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_bluetooth_setup_start,
|
||||
container, false);
|
||||
|
||||
// TODO device-BT and BT-plugin needs to be enabled at this point
|
||||
|
||||
Button startButton = v.findViewById(R.id.startButton);
|
||||
startButton.setOnClickListener(view -> {
|
||||
showNextFragment(new BluetoothSetupChooseFragment());
|
||||
});
|
||||
startButton.setEnabled(true);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package org.briarproject.briar.android.bluetoothsetup;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
@NotNullByDefault
|
||||
public class BluetoothSetupViewModel extends DbViewModel {
|
||||
|
||||
private final static Logger LOG =
|
||||
getLogger(BluetoothSetupViewModel.class.getName());
|
||||
|
||||
@Inject
|
||||
BluetoothSetupViewModel(Application application,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager,
|
||||
TransactionManager db,
|
||||
AndroidExecutor androidExecutor) {
|
||||
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.bluetoothsetup.BluetoothSetupActivity;
|
||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
||||
import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
|
||||
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
|
||||
@@ -135,11 +134,6 @@ public class ContactListFragment extends BaseFragment
|
||||
case R.id.action_add_contact_remotely:
|
||||
startActivity(
|
||||
new Intent(getContext(), AddContactActivity.class));
|
||||
return;
|
||||
case R.id.action_bluetooth_setup:
|
||||
startActivity(
|
||||
new Intent(getContext(),
|
||||
BluetoothSetupActivity.class));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,10 +45,8 @@ import androidx.arch.core.util.Function;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
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.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
|
||||
@NotNullByDefault
|
||||
@@ -173,14 +171,9 @@ class ContactListViewModel extends DbViewModel implements EventListener {
|
||||
}
|
||||
|
||||
void checkForPendingContacts() {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
boolean hasPending =
|
||||
!contactManager.getPendingContacts().isEmpty();
|
||||
hasPendingContacts.postValue(hasPending);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
boolean hasPending = !contactManager.getPendingContacts().isEmpty();
|
||||
hasPendingContacts.postValue(hasPending);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,9 @@ package org.briarproject.briar.android.contact.add.remote;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.NoSuchPendingContactException;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
@@ -18,7 +15,6 @@ import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||
import org.briarproject.briar.android.viewmodel.LiveResult;
|
||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -66,15 +62,10 @@ public class AddContactViewModel extends DbViewModel {
|
||||
}
|
||||
|
||||
private void loadHandshakeLink() {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
handshakeLink.postValue(contactManager.getHandshakeLink());
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
// the UI should stay disabled in this case,
|
||||
// leaving the user unable to proceed
|
||||
}
|
||||
});
|
||||
// If an exception is thrown the UI should stay disabled,
|
||||
// leaving the user unable to proceed
|
||||
runOnDbThreadOrLogException(() ->
|
||||
handshakeLink.postValue(contactManager.getHandshakeLink()));
|
||||
}
|
||||
|
||||
LiveData<String> getHandshakeLink() {
|
||||
@@ -106,17 +97,11 @@ public class AddContactViewModel extends DbViewModel {
|
||||
void addContact(String nickname) {
|
||||
if (remoteHandshakeLink == null) throw new IllegalStateException();
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
contactManager.addPendingContact(remoteHandshakeLink, nickname);
|
||||
addContactResult.postValue(new LiveResult<>(true));
|
||||
} catch (UnsupportedVersionException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
addContactResult.postValue(new LiveResult<>(e));
|
||||
} catch (DbException | FormatException
|
||||
| GeneralSecurityException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
addContactResult.postValue(new LiveResult<>(e));
|
||||
}
|
||||
contactManager.addPendingContact(remoteHandshakeLink, nickname);
|
||||
addContactResult.postValue(new LiveResult<>(true));
|
||||
}, e -> {
|
||||
logException(LOG, WARNING, e);
|
||||
addContactResult.postValue(new LiveResult<>(e));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -126,13 +111,13 @@ public class AddContactViewModel extends DbViewModel {
|
||||
|
||||
public void updatePendingContact(String name, PendingContact p) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
contactManager.removePendingContact(p.getId());
|
||||
addContact(name);
|
||||
} catch (NoSuchPendingContactException e) {
|
||||
contactManager.removePendingContact(p.getId());
|
||||
addContact(name);
|
||||
}, e -> {
|
||||
if (e instanceof NoSuchPendingContactException) {
|
||||
logException(LOG, WARNING, e);
|
||||
// no error in UI as pending contact was converted into contact
|
||||
} catch (DbException e) {
|
||||
} else {
|
||||
logException(LOG, WARNING, e);
|
||||
addContactResult.postValue(new LiveResult<>(e));
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.contact.PendingContactState;
|
||||
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
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.EventBus;
|
||||
@@ -33,10 +32,8 @@ import javax.inject.Inject;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@NotNullByDefault
|
||||
public class PendingContactListViewModel extends DbViewModel
|
||||
@@ -90,24 +87,20 @@ public class PendingContactListViewModel extends DbViewModel
|
||||
}
|
||||
|
||||
private void loadPendingContacts() {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
Collection<Pair<PendingContact, PendingContactState>> pairs =
|
||||
contactManager.getPendingContacts();
|
||||
List<PendingContactItem> items = new ArrayList<>(pairs.size());
|
||||
boolean online = pairs.isEmpty();
|
||||
for (Pair<PendingContact, PendingContactState> pair : pairs) {
|
||||
PendingContact p = pair.getFirst();
|
||||
PendingContactState state = pair.getSecond();
|
||||
long lastPoll = rendezvousPoller.getLastPollTime(p.getId());
|
||||
items.add(new PendingContactItem(p, state, lastPoll));
|
||||
online = online || state != OFFLINE;
|
||||
}
|
||||
pendingContacts.postValue(items);
|
||||
hasInternetConnection.postValue(online);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
Collection<Pair<PendingContact, PendingContactState>> pairs =
|
||||
contactManager.getPendingContacts();
|
||||
List<PendingContactItem> items = new ArrayList<>(pairs.size());
|
||||
boolean online = pairs.isEmpty();
|
||||
for (Pair<PendingContact, PendingContactState> pair : pairs) {
|
||||
PendingContact p = pair.getFirst();
|
||||
PendingContactState state = pair.getSecond();
|
||||
long lastPoll = rendezvousPoller.getLastPollTime(p.getId());
|
||||
items.add(new PendingContactItem(p, state, lastPoll));
|
||||
online = online || state != OFFLINE;
|
||||
}
|
||||
pendingContacts.postValue(items);
|
||||
hasInternetConnection.postValue(online);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -116,13 +109,8 @@ public class PendingContactListViewModel extends DbViewModel
|
||||
}
|
||||
|
||||
void removePendingContact(PendingContactId id) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
contactManager.removePendingContact(id);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
runOnDbThreadOrLogException(() ->
|
||||
contactManager.removePendingContact(id));
|
||||
}
|
||||
|
||||
LiveData<Boolean> getHasInternetConnection() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Collection;
|
||||
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
@Deprecated
|
||||
@NotNullByDefault
|
||||
public interface SharingController {
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
@Deprecated
|
||||
@NotNullByDefault
|
||||
public class SharingControllerImpl implements SharingController, EventListener {
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ public class ConversationViewModel extends DbViewModel
|
||||
AttachmentReceivedEvent a = (AttachmentReceivedEvent) e;
|
||||
if (a.getContactId().equals(contactId)) {
|
||||
LOG.info("Attachment received");
|
||||
runOnDbThread(() -> attachmentRetriever
|
||||
runOnDbThreadOrLogException(() -> attachmentRetriever
|
||||
.loadAttachmentItem(a.getMessageId()));
|
||||
}
|
||||
} else if (e instanceof AvatarUpdatedEvent) {
|
||||
@@ -194,44 +194,36 @@ public class ConversationViewModel extends DbViewModel
|
||||
|
||||
private void loadContact(ContactId contactId) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
Contact c = contactManager.getContact(contactId);
|
||||
AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
|
||||
contactItem.postValue(new ContactItem(c, authorInfo));
|
||||
logDuration(LOG, "Loading contact", start);
|
||||
start = now();
|
||||
checkFeaturesAndOnboarding(contactId);
|
||||
logDuration(LOG, "Checking for image support", start);
|
||||
} catch (NoSuchContactException e) {
|
||||
long start = now();
|
||||
Contact c = contactManager.getContact(contactId);
|
||||
AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
|
||||
contactItem.postValue(new ContactItem(c, authorInfo));
|
||||
logDuration(LOG, "Loading contact", start);
|
||||
start = now();
|
||||
checkFeaturesAndOnboarding(contactId);
|
||||
logDuration(LOG, "Checking for image support", start);
|
||||
}, e -> {
|
||||
if (e instanceof NoSuchContactException) {
|
||||
contactDeleted.postValue(true);
|
||||
} catch (DbException e) {
|
||||
} else {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void markMessageRead(GroupId g, MessageId m) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
messagingManager.setReadFlag(g, m, true);
|
||||
logDuration(LOG, "Marking read", start);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
long start = now();
|
||||
messagingManager.setReadFlag(g, m, true);
|
||||
logDuration(LOG, "Marking read", start);
|
||||
});
|
||||
}
|
||||
|
||||
void setContactAlias(String alias) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
contactManager.setContactAlias(requireNonNull(contactId),
|
||||
alias.isEmpty() ? null : alias);
|
||||
loadContact(contactId);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
contactManager.setContactAlias(requireNonNull(contactId),
|
||||
alias.isEmpty() ? null : alias);
|
||||
loadContact(contactId);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -327,21 +319,17 @@ public class ConversationViewModel extends DbViewModel
|
||||
@UiThread
|
||||
private void storeMessage(PrivateMessage m) {
|
||||
attachmentCreator.onAttachmentsSent(m.getMessage().getId());
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
messagingManager.addLocalMessage(m);
|
||||
logDuration(LOG, "Storing message", start);
|
||||
Message message = m.getMessage();
|
||||
PrivateMessageHeader h = new PrivateMessageHeader(
|
||||
message.getId(), message.getGroupId(),
|
||||
message.getTimestamp(), true, true, false, false,
|
||||
m.hasText(), m.getAttachmentHeaders());
|
||||
// TODO add text to cache when available here
|
||||
addedHeader.postEvent(h);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
long start = now();
|
||||
messagingManager.addLocalMessage(m);
|
||||
logDuration(LOG, "Storing message", start);
|
||||
Message message = m.getMessage();
|
||||
PrivateMessageHeader h = new PrivateMessageHeader(
|
||||
message.getId(), message.getGroupId(),
|
||||
message.getTimestamp(), true, true, false, false,
|
||||
m.hasText(), m.getAttachmentHeaders());
|
||||
// TODO add text to cache when available here
|
||||
addedHeader.postEvent(h);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -383,12 +371,7 @@ public class ConversationViewModel extends DbViewModel
|
||||
|
||||
@UiThread
|
||||
void recheckFeaturesAndOnboarding(ContactId contactId) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
checkFeaturesAndOnboarding(contactId);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
runOnDbThreadOrLogException(() ->
|
||||
checkFeaturesAndOnboarding(contactId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.net.Uri;
|
||||
import android.view.View;
|
||||
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
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.EventBus;
|
||||
@@ -198,14 +197,12 @@ public class ImageViewModel extends DbViewModel implements EventListener {
|
||||
private void saveImage(AttachmentItem attachment, OutputStreamProvider osp,
|
||||
@Nullable Runnable afterCopy) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
Attachment a =
|
||||
attachmentReader.getAttachment(attachment.getHeader());
|
||||
copyImageFromDb(a, osp, afterCopy);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
saveState.postEvent(true);
|
||||
}
|
||||
Attachment a =
|
||||
attachmentReader.getAttachment(attachment.getHeader());
|
||||
copyImageFromDb(a, osp, afterCopy);
|
||||
}, e -> {
|
||||
logException(LOG, WARNING, e);
|
||||
saveState.postEvent(true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,52 +6,54 @@ import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.briar.android.forum.ForumController.ForumListener;
|
||||
import org.briarproject.briar.android.sharing.ForumSharingStatusActivity;
|
||||
import org.briarproject.briar.android.sharing.ShareForumActivity;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter;
|
||||
import org.briarproject.briar.android.threaded.ThreadListActivity;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController;
|
||||
import org.briarproject.briar.api.forum.Forum;
|
||||
import org.briarproject.briar.android.threaded.ThreadListViewModel;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_FORUM;
|
||||
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEXT_LENGTH;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ForumActivity extends
|
||||
ThreadListActivity<Forum, ForumPostItem, ThreadItemAdapter<ForumPostItem>>
|
||||
implements ForumListener {
|
||||
ThreadListActivity<ForumPostItem, ThreadItemAdapter<ForumPostItem>> {
|
||||
|
||||
@Inject
|
||||
ForumController forumController;
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private ForumViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||
.get(ForumViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThreadListController<Forum, ForumPostItem> getController() {
|
||||
return forumController;
|
||||
protected ThreadListViewModel<ForumPostItem> getViewModel() {
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThreadItemAdapter<ForumPostItem> createAdapter() {
|
||||
return new ThreadItemAdapter<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,36 +61,33 @@ public class ForumActivity extends
|
||||
super.onCreate(state);
|
||||
|
||||
Toolbar toolbar = setUpCustomToolbar(false);
|
||||
|
||||
Intent i = getIntent();
|
||||
String groupName = i.getStringExtra(GROUP_NAME);
|
||||
if (groupName != null) setTitle(groupName);
|
||||
else loadNamedGroup();
|
||||
|
||||
// Open member list on Toolbar click
|
||||
if (toolbar != null) {
|
||||
toolbar.setOnClickListener(v -> {
|
||||
Intent i1 = new Intent(ForumActivity.this,
|
||||
ForumSharingStatusActivity.class);
|
||||
i1.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivity(i1);
|
||||
});
|
||||
toolbar.setOnClickListener(v -> {
|
||||
Intent i = new Intent(ForumActivity.this,
|
||||
ForumSharingStatusActivity.class);
|
||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivity(i);
|
||||
});
|
||||
|
||||
String groupName = getIntent().getStringExtra(GROUP_NAME);
|
||||
if (groupName != null) {
|
||||
setTitle(groupName);
|
||||
} else {
|
||||
observeOnce(viewModel.loadForum(), this, forum ->
|
||||
setTitle(forum.getName())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNamedGroupLoaded(Forum forum) {
|
||||
setTitle(forum.getName());
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
viewModel.clearForumPostNotification();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThreadItemAdapter<ForumPostItem> createAdapter(
|
||||
LinearLayoutManager layoutManager) {
|
||||
return new ThreadItemAdapter<>(this, layoutManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
protected void onActivityResult(int request, int result,
|
||||
@Nullable Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
|
||||
if (request == REQUEST_SHARE_FORUM && result == RESULT_OK) {
|
||||
@@ -101,32 +100,31 @@ public class ForumActivity extends
|
||||
// Inflate the menu items for use in the action bar
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.forum_actions, menu);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
super.onCreateOptionsMenu(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_forum_share:
|
||||
Intent i2 = new Intent(this, ShareForumActivity.class);
|
||||
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
i2.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivityForResult(i2, REQUEST_SHARE_FORUM);
|
||||
return true;
|
||||
case R.id.action_forum_sharing_status:
|
||||
Intent i3 = new Intent(this, ForumSharingStatusActivity.class);
|
||||
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
i3.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivity(i3);
|
||||
return true;
|
||||
case R.id.action_forum_delete:
|
||||
showUnsubscribeDialog();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.action_forum_share) {
|
||||
Intent i2 = new Intent(this, ShareForumActivity.class);
|
||||
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
i2.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivityForResult(i2, REQUEST_SHARE_FORUM);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_forum_sharing_status) {
|
||||
Intent i3 = new Intent(this, ForumSharingStatusActivity.class);
|
||||
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
i3.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivity(i3);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_forum_delete) {
|
||||
showUnsubscribeDialog();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -135,7 +133,7 @@ public class ForumActivity extends
|
||||
}
|
||||
|
||||
private void showUnsubscribeDialog() {
|
||||
OnClickListener okListener = (dialog, which) -> deleteForum();
|
||||
OnClickListener okListener = (dialog, which) -> viewModel.deleteForum();
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this,
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setTitle(getString(R.string.dialog_title_leave_forum));
|
||||
@@ -145,27 +143,4 @@ public class ForumActivity extends
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void deleteForum() {
|
||||
forumController.deleteNamedGroup(
|
||||
new UiResultExceptionHandler<Void, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(Void v) {
|
||||
Toast.makeText(ForumActivity.this,
|
||||
R.string.forum_left_toast, LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onForumLeft(ContactId c) {
|
||||
sharingController.remove(c);
|
||||
setToolbarSubTitle(sharingController.getTotalCount(),
|
||||
sharingController.getOnlineCount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.briarproject.briar.android.forum;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController;
|
||||
import org.briarproject.briar.api.forum.Forum;
|
||||
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
@NotNullByDefault
|
||||
interface ForumController extends ThreadListController<Forum, ForumPostItem> {
|
||||
|
||||
interface ForumListener extends ThreadListListener<ForumPostItem> {
|
||||
@UiThread
|
||||
void onForumLeft(ContactId c);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
package org.briarproject.briar.android.forum;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.briar.android.forum.ForumController.ForumListener;
|
||||
import org.briarproject.briar.android.threaded.ThreadListControllerImpl;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||
import org.briarproject.briar.api.forum.Forum;
|
||||
import org.briarproject.briar.api.forum.ForumInvitationResponse;
|
||||
import org.briarproject.briar.api.forum.ForumManager;
|
||||
import org.briarproject.briar.api.forum.ForumPost;
|
||||
import org.briarproject.briar.api.forum.ForumPostHeader;
|
||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||
import org.briarproject.briar.api.forum.event.ForumInvitationResponseReceivedEvent;
|
||||
import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent;
|
||||
import org.briarproject.briar.api.sharing.event.ContactLeftShareableEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@NotNullByDefault
|
||||
class ForumControllerImpl extends
|
||||
ThreadListControllerImpl<Forum, ForumPostItem, ForumPostHeader, ForumPost, ForumListener>
|
||||
implements ForumController {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ForumControllerImpl.class.getName());
|
||||
|
||||
private final ForumManager forumManager;
|
||||
private final ForumSharingManager forumSharingManager;
|
||||
|
||||
@Inject
|
||||
ForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager, IdentityManager identityManager,
|
||||
@CryptoExecutor Executor cryptoExecutor,
|
||||
ForumManager forumManager, ForumSharingManager forumSharingManager,
|
||||
EventBus eventBus, Clock clock, MessageTracker messageTracker,
|
||||
AndroidNotificationManager notificationManager) {
|
||||
super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor,
|
||||
eventBus, clock, notificationManager, messageTracker);
|
||||
this.forumManager = forumManager;
|
||||
this.forumSharingManager = forumSharingManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStart() {
|
||||
super.onActivityStart();
|
||||
notificationManager.clearForumPostNotification(getGroupId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
super.eventOccurred(e);
|
||||
|
||||
if (e instanceof ForumPostReceivedEvent) {
|
||||
ForumPostReceivedEvent f = (ForumPostReceivedEvent) e;
|
||||
if (f.getGroupId().equals(getGroupId())) {
|
||||
LOG.info("Forum post received, adding...");
|
||||
listener.onItemReceived(buildItem(f.getHeader(), f.getText()));
|
||||
}
|
||||
} else if (e instanceof ForumInvitationResponseReceivedEvent) {
|
||||
ForumInvitationResponseReceivedEvent f =
|
||||
(ForumInvitationResponseReceivedEvent) e;
|
||||
ForumInvitationResponse r = f.getMessageHeader();
|
||||
if (r.getShareableId().equals(getGroupId()) && r.wasAccepted()) {
|
||||
LOG.info("Forum invitation was accepted");
|
||||
listener.onInvitationAccepted(f.getContactId());
|
||||
}
|
||||
} else if (e instanceof ContactLeftShareableEvent) {
|
||||
ContactLeftShareableEvent c = (ContactLeftShareableEvent) e;
|
||||
if (c.getGroupId().equals(getGroupId())) {
|
||||
LOG.info("Forum left by contact");
|
||||
listener.onForumLeft(c.getContactId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Forum loadNamedGroup() throws DbException {
|
||||
return forumManager.getForum(getGroupId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<ForumPostHeader> loadHeaders() throws DbException {
|
||||
return forumManager.getPostHeaders(getGroupId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String loadMessageText(ForumPostHeader h) throws DbException {
|
||||
return forumManager.getPostText(h.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void markRead(MessageId id) throws DbException {
|
||||
forumManager.setReadFlag(getGroupId(), id, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadSharingContacts(
|
||||
ResultExceptionHandler<Collection<ContactId>, DbException> handler) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
Collection<Contact> contacts =
|
||||
forumSharingManager.getSharedWith(getGroupId());
|
||||
Collection<ContactId> contactIds =
|
||||
new ArrayList<>(contacts.size());
|
||||
for (Contact c : contacts) contactIds.add(c.getId());
|
||||
handler.onResult(contactIds);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
handler.onException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createAndStoreMessage(String text,
|
||||
@Nullable ForumPostItem parentItem,
|
||||
ResultExceptionHandler<ForumPostItem, DbException> handler) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
LocalAuthor author = identityManager.getLocalAuthor();
|
||||
GroupCount count = forumManager.getGroupCount(getGroupId());
|
||||
long timestamp = max(count.getLatestMsgTime() + 1,
|
||||
clock.currentTimeMillis());
|
||||
MessageId parentId = parentItem != null ?
|
||||
parentItem.getId() : null;
|
||||
createMessage(text, timestamp, parentId, author, handler);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
handler.onException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createMessage(String text, long timestamp,
|
||||
@Nullable MessageId parentId, LocalAuthor author,
|
||||
ResultExceptionHandler<ForumPostItem, DbException> handler) {
|
||||
cryptoExecutor.execute(() -> {
|
||||
LOG.info("Creating forum post...");
|
||||
ForumPost msg = forumManager.createLocalPost(getGroupId(), text,
|
||||
timestamp, parentId, author);
|
||||
storePost(msg, text, handler);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ForumPostHeader addLocalMessage(ForumPost p) throws DbException {
|
||||
return forumManager.addLocalPost(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteNamedGroup(Forum forum) throws DbException {
|
||||
forumManager.removeForum(forum);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ForumPostItem buildItem(ForumPostHeader header, String text) {
|
||||
return new ForumPostItem(header, text);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,10 +40,8 @@ import androidx.annotation.UiThread;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
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.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
import static org.briarproject.briar.api.forum.ForumManager.CLIENT_ID;
|
||||
|
||||
@@ -164,15 +162,11 @@ class ForumListViewModel extends DbViewModel implements EventListener {
|
||||
}
|
||||
|
||||
void loadForumInvitations() {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
int available = forumSharingManager.getInvitations().size();
|
||||
logDuration(LOG, "Loading available", start);
|
||||
numInvitations.postValue(available);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
long start = now();
|
||||
int available = forumSharingManager.getInvitations().size();
|
||||
logDuration(LOG, "Loading available", start);
|
||||
numInvitations.postValue(available);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,23 @@
|
||||
package org.briarproject.briar.android.forum;
|
||||
|
||||
import org.briarproject.briar.android.activity.ActivityScope;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.viewmodel.ViewModelKey;
|
||||
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
|
||||
@Module
|
||||
public class ForumModule {
|
||||
public interface ForumModule {
|
||||
|
||||
@Module
|
||||
public interface BindsModule {
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(ForumListViewModel.class)
|
||||
ViewModel bindForumListViewModel(ForumListViewModel forumListViewModel);
|
||||
}
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(ForumListViewModel.class)
|
||||
ViewModel bindForumListViewModel(ForumListViewModel forumListViewModel);
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
ForumController provideForumController(BaseActivity activity,
|
||||
ForumControllerImpl forumController) {
|
||||
activity.addLifecycleController(forumController);
|
||||
return forumController;
|
||||
}
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(ForumViewModel.class)
|
||||
ViewModel bindForumViewModel(ForumViewModel forumViewModel);
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.android.threaded.ThreadItem;
|
||||
import org.briarproject.briar.api.forum.ForumPostHeader;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
@NotThreadSafe
|
||||
@@ -17,9 +16,4 @@ class ForumPostItem extends ThreadItem {
|
||||
h.getAuthorInfo(), h.isRead());
|
||||
}
|
||||
|
||||
ForumPostItem(MessageId messageId, @Nullable MessageId parentId,
|
||||
String text, long timestamp, Author author, AuthorInfo authorInfo) {
|
||||
super(messageId, parentId, text, timestamp, author, authorInfo, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
package org.briarproject.briar.android.forum;
|
||||
|
||||
import android.app.Application;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
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.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.sharing.SharingController;
|
||||
import org.briarproject.briar.android.threaded.ThreadListViewModel;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||
import org.briarproject.briar.api.client.PostHeader;
|
||||
import org.briarproject.briar.api.forum.Forum;
|
||||
import org.briarproject.briar.api.forum.ForumInvitationResponse;
|
||||
import org.briarproject.briar.api.forum.ForumManager;
|
||||
import org.briarproject.briar.api.forum.ForumPost;
|
||||
import org.briarproject.briar.api.forum.ForumPostHeader;
|
||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||
import org.briarproject.briar.api.forum.event.ForumInvitationResponseReceivedEvent;
|
||||
import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent;
|
||||
import org.briarproject.briar.api.sharing.event.ContactLeftShareableEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.lang.Math.max;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class ForumViewModel extends ThreadListViewModel<ForumPostItem> {
|
||||
|
||||
private static final Logger LOG = getLogger(ForumViewModel.class.getName());
|
||||
|
||||
private final ForumManager forumManager;
|
||||
private final ForumSharingManager forumSharingManager;
|
||||
|
||||
@Inject
|
||||
ForumViewModel(Application application,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager,
|
||||
TransactionManager db,
|
||||
AndroidExecutor androidExecutor,
|
||||
IdentityManager identityManager,
|
||||
AndroidNotificationManager notificationManager,
|
||||
SharingController sharingController,
|
||||
@CryptoExecutor Executor cryptoExecutor,
|
||||
Clock clock,
|
||||
MessageTracker messageTracker,
|
||||
EventBus eventBus,
|
||||
ForumManager forumManager,
|
||||
ForumSharingManager forumSharingManager) {
|
||||
super(application, dbExecutor, lifecycleManager, db, androidExecutor,
|
||||
identityManager, notificationManager, sharingController,
|
||||
cryptoExecutor, clock, messageTracker, eventBus);
|
||||
this.forumManager = forumManager;
|
||||
this.forumSharingManager = forumSharingManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof ForumPostReceivedEvent) {
|
||||
ForumPostReceivedEvent f = (ForumPostReceivedEvent) e;
|
||||
if (f.getGroupId().equals(groupId)) {
|
||||
LOG.info("Forum post received, adding...");
|
||||
ForumPostItem item = buildItem(f.getHeader(), f.getText());
|
||||
addItem(item);
|
||||
}
|
||||
} else if (e instanceof ForumInvitationResponseReceivedEvent) {
|
||||
ForumInvitationResponseReceivedEvent f =
|
||||
(ForumInvitationResponseReceivedEvent) e;
|
||||
ForumInvitationResponse r = f.getMessageHeader();
|
||||
if (r.getShareableId().equals(groupId) && r.wasAccepted()) {
|
||||
LOG.info("Forum invitation was accepted");
|
||||
sharingController.add(f.getContactId());
|
||||
}
|
||||
} else if (e instanceof ContactLeftShareableEvent) {
|
||||
ContactLeftShareableEvent c = (ContactLeftShareableEvent) e;
|
||||
if (c.getGroupId().equals(groupId)) {
|
||||
LOG.info("Forum left by contact");
|
||||
sharingController.remove(c.getContactId());
|
||||
}
|
||||
} else {
|
||||
super.eventOccurred(e);
|
||||
}
|
||||
}
|
||||
|
||||
void clearForumPostNotification() {
|
||||
notificationManager.clearForumPostNotification(groupId);
|
||||
}
|
||||
|
||||
LiveData<Forum> loadForum() {
|
||||
MutableLiveData<Forum> forum = new MutableLiveData<>();
|
||||
runOnDbThreadOrLogException(() ->
|
||||
forum.postValue(forumManager.getForum(groupId)));
|
||||
return forum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadItems() {
|
||||
loadList(txn -> {
|
||||
long start = now();
|
||||
List<ForumPostHeader> headers =
|
||||
forumManager.getPostHeaders(txn, groupId);
|
||||
logDuration(LOG, "Loading headers", start);
|
||||
return createItems(txn, headers, this::buildItem);
|
||||
}, this::setItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createAndStoreMessage(String text,
|
||||
@Nullable MessageId parentId) {
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
LocalAuthor author = identityManager.getLocalAuthor();
|
||||
GroupCount count = forumManager.getGroupCount(groupId);
|
||||
long timestamp = max(count.getLatestMsgTime() + 1,
|
||||
clock.currentTimeMillis());
|
||||
createMessage(text, timestamp, parentId, author);
|
||||
});
|
||||
}
|
||||
|
||||
private void createMessage(String text, long timestamp,
|
||||
@Nullable MessageId parentId, LocalAuthor author) {
|
||||
cryptoExecutor.execute(() -> {
|
||||
LOG.info("Creating forum post...");
|
||||
ForumPost msg = forumManager.createLocalPost(groupId, text,
|
||||
timestamp, parentId, author);
|
||||
storePost(msg, text);
|
||||
});
|
||||
}
|
||||
|
||||
private void storePost(ForumPost msg, String text) {
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
long start = now();
|
||||
ForumPostHeader header = forumManager.addLocalPost(msg);
|
||||
addItemAsync(buildItem(header, text));
|
||||
logDuration(LOG, "Storing forum post", start);
|
||||
});
|
||||
}
|
||||
|
||||
private ForumPostItem buildItem(ForumPostHeader header, String text) {
|
||||
return new ForumPostItem(header, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String loadMessageText(Transaction txn, PostHeader header)
|
||||
throws DbException {
|
||||
return forumManager.getPostText(txn, header.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void markItemRead(ForumPostItem item) {
|
||||
runOnDbThreadOrLogException(() ->
|
||||
forumManager.setReadFlag(groupId, item.getId(), true));
|
||||
}
|
||||
|
||||
public void loadSharingContacts() {
|
||||
runOnDbThreadOrLogException(true, txn -> {
|
||||
Collection<Contact> contacts =
|
||||
forumSharingManager.getSharedWith(txn, groupId);
|
||||
Collection<ContactId> contactIds = new ArrayList<>(contacts.size());
|
||||
for (Contact c : contacts) contactIds.add(c.getId());
|
||||
txn.attach(() -> sharingController.addAll(contactIds));
|
||||
});
|
||||
}
|
||||
|
||||
void deleteForum() {
|
||||
runOnDbThreadOrLogException(() ->
|
||||
forumManager.removeForum(forumManager.getForum(groupId)));
|
||||
Toast.makeText(getApplication(), R.string.forum_left_toast,
|
||||
LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android.navdrawer;
|
||||
import android.app.Application;
|
||||
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
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.nullsafety.NotNullByDefault;
|
||||
@@ -66,34 +65,27 @@ public class NavDrawerViewModel extends DbViewModel {
|
||||
|
||||
@UiThread
|
||||
void checkExpiryWarning() {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
Settings settings =
|
||||
settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||
int warningInt = settings.getInt(EXPIRY_DATE_WARNING, 0);
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||
int warningInt = settings.getInt(EXPIRY_DATE_WARNING, 0);
|
||||
|
||||
if (warningInt == 0) {
|
||||
// we have not warned before
|
||||
if (warningInt == 0) {
|
||||
// we have not warned before
|
||||
showExpiryWarning.postValue(true);
|
||||
} else {
|
||||
long warningLong = warningInt * 1000L;
|
||||
long now = System.currentTimeMillis();
|
||||
long daysSinceLastWarning =
|
||||
(now - warningLong) / DAYS.toMillis(1);
|
||||
long daysBeforeExpiry = (EXPIRY_DATE - now) / DAYS.toMillis(1);
|
||||
|
||||
if (daysSinceLastWarning >= 30) {
|
||||
showExpiryWarning.postValue(true);
|
||||
} else if (daysBeforeExpiry <= 3 && daysSinceLastWarning > 0) {
|
||||
showExpiryWarning.postValue(true);
|
||||
} else {
|
||||
long warningLong = warningInt * 1000L;
|
||||
long now = System.currentTimeMillis();
|
||||
long daysSinceLastWarning =
|
||||
(now - warningLong) / DAYS.toMillis(1);
|
||||
long daysBeforeExpiry =
|
||||
(EXPIRY_DATE - now) / DAYS.toMillis(1);
|
||||
|
||||
if (daysSinceLastWarning >= 30) {
|
||||
showExpiryWarning.postValue(true);
|
||||
} else if (daysBeforeExpiry <= 3 &&
|
||||
daysSinceLastWarning > 0) {
|
||||
showExpiryWarning.postValue(true);
|
||||
} else {
|
||||
showExpiryWarning.postValue(false);
|
||||
}
|
||||
showExpiryWarning.postValue(false);
|
||||
}
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -101,15 +93,11 @@ public class NavDrawerViewModel extends DbViewModel {
|
||||
@UiThread
|
||||
void expiryWarningDismissed() {
|
||||
showExpiryWarning.setValue(false);
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
Settings settings = new Settings();
|
||||
int date = (int) (System.currentTimeMillis() / 1000L);
|
||||
settings.putInt(EXPIRY_DATE_WARNING, date);
|
||||
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
Settings settings = new Settings();
|
||||
int date = (int) (System.currentTimeMillis() / 1000L);
|
||||
settings.putInt(EXPIRY_DATE_WARNING, date);
|
||||
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -125,15 +113,12 @@ public class NavDrawerViewModel extends DbViewModel {
|
||||
return;
|
||||
}
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
Settings settings =
|
||||
settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||
boolean ask = settings.getBoolean(DOZE_ASK_AGAIN, true);
|
||||
shouldAskForDozeWhitelisting.postValue(ask);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
shouldAskForDozeWhitelisting.postValue(true);
|
||||
}
|
||||
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||
boolean ask = settings.getBoolean(DOZE_ASK_AGAIN, true);
|
||||
shouldAskForDozeWhitelisting.postValue(ask);
|
||||
}, e -> {
|
||||
logException(LOG, WARNING, e);
|
||||
shouldAskForDozeWhitelisting.postValue(true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -145,30 +130,21 @@ public class NavDrawerViewModel extends DbViewModel {
|
||||
@UiThread
|
||||
void checkTransportsOnboarding() {
|
||||
if (showTransportsOnboarding.getValue() != null) return;
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
Settings settings =
|
||||
settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||
boolean show =
|
||||
settings.getBoolean(SHOW_TRANSPORTS_ONBOARDING, true);
|
||||
showTransportsOnboarding.postValue(show);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||
boolean show =
|
||||
settings.getBoolean(SHOW_TRANSPORTS_ONBOARDING, true);
|
||||
showTransportsOnboarding.postValue(show);
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void transportsOnboardingShown() {
|
||||
showTransportsOnboarding.setValue(false);
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
Settings settings = new Settings();
|
||||
settings.putBoolean(SHOW_TRANSPORTS_ONBOARDING, false);
|
||||
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
Settings settings = new Settings();
|
||||
settings.putBoolean(SHOW_TRANSPORTS_ONBOARDING, false);
|
||||
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,12 +45,10 @@ import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
|
||||
@NotNullByDefault
|
||||
@@ -185,20 +183,16 @@ public class PluginViewModel extends DbViewModel implements EventListener {
|
||||
}
|
||||
|
||||
private void loadSettings() {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
boolean tor = isPluginEnabled(TorConstants.ID,
|
||||
TorConstants.DEFAULT_PREF_PLUGIN_ENABLE);
|
||||
torEnabledSetting.postValue(tor);
|
||||
boolean wifi = isPluginEnabled(LanTcpConstants.ID,
|
||||
LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE);
|
||||
wifiEnabledSetting.postValue(wifi);
|
||||
boolean bt = isPluginEnabled(BluetoothConstants.ID,
|
||||
BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE);
|
||||
btEnabledSetting.postValue(bt);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
boolean tor = isPluginEnabled(TorConstants.ID,
|
||||
TorConstants.DEFAULT_PREF_PLUGIN_ENABLE);
|
||||
torEnabledSetting.postValue(tor);
|
||||
boolean wifi = isPluginEnabled(LanTcpConstants.ID,
|
||||
LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE);
|
||||
wifiEnabledSetting.postValue(wifi);
|
||||
boolean bt = isPluginEnabled(BluetoothConstants.ID,
|
||||
BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE);
|
||||
btEnabledSetting.postValue(bt);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -222,14 +216,10 @@ public class PluginViewModel extends DbViewModel implements EventListener {
|
||||
}
|
||||
|
||||
private void mergeSettings(Settings s, String namespace) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
settingsManager.mergeSettings(s, namespace);
|
||||
logDuration(LOG, "Merging settings", start);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
long start = now();
|
||||
settingsManager.mergeSettings(s, namespace);
|
||||
logDuration(LOG, "Merging settings", start);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,67 +1,59 @@
|
||||
package org.briarproject.briar.android.privategroup.conversation;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
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.privategroup.conversation.GroupController.GroupListener;
|
||||
import org.briarproject.briar.android.privategroup.creation.GroupInviteActivity;
|
||||
import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity;
|
||||
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
|
||||
import org.briarproject.briar.android.threaded.ThreadListActivity;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||
import org.briarproject.briar.api.privategroup.Visibility;
|
||||
import org.briarproject.briar.android.threaded.ThreadListViewModel;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_GROUP_INVITE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_TEXT_LENGTH;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class GroupActivity extends
|
||||
ThreadListActivity<PrivateGroup, GroupMessageItem, GroupMessageAdapter>
|
||||
implements GroupListener, OnClickListener {
|
||||
ThreadListActivity<GroupMessageItem, GroupMessageAdapter> {
|
||||
|
||||
@Inject
|
||||
GroupController controller;
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
@Nullable
|
||||
private Boolean isCreator = null;
|
||||
private boolean isDissolved = false;
|
||||
private MenuItem revealMenuItem, inviteMenuItem, leaveMenuItem,
|
||||
dissolveMenuItem;
|
||||
private GroupViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||
.get(GroupViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThreadListController<PrivateGroup, GroupMessageItem> getController() {
|
||||
return controller;
|
||||
protected ThreadListViewModel<GroupMessageItem> getViewModel() {
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GroupMessageAdapter createAdapter() {
|
||||
return new GroupMessageAdapter(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,65 +61,33 @@ public class GroupActivity extends
|
||||
super.onCreate(state);
|
||||
|
||||
Toolbar toolbar = setUpCustomToolbar(false);
|
||||
|
||||
Intent i = getIntent();
|
||||
String groupName = i.getStringExtra(GROUP_NAME);
|
||||
if (groupName != null) setTitle(groupName);
|
||||
loadNamedGroup();
|
||||
|
||||
// Open member list on Toolbar click
|
||||
if (toolbar != null) {
|
||||
toolbar.setOnClickListener(v -> {
|
||||
Intent i1 = new Intent(GroupActivity.this,
|
||||
GroupMemberListActivity.class);
|
||||
i1.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivity(i1);
|
||||
});
|
||||
}
|
||||
toolbar.setOnClickListener(v -> {
|
||||
Intent i = new Intent(GroupActivity.this,
|
||||
GroupMemberListActivity.class);
|
||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivity(i);
|
||||
});
|
||||
|
||||
String groupName = getIntent().getStringExtra(GROUP_NAME);
|
||||
if (groupName != null) setTitle(groupName);
|
||||
observeOnce(viewModel.getPrivateGroup(), this, privateGroup ->
|
||||
setTitle(privateGroup.getName())
|
||||
);
|
||||
observeOnce(viewModel.isCreator(), this, adapter::setIsCreator);
|
||||
|
||||
// start with group disabled and enable when not dissolved
|
||||
setGroupEnabled(false);
|
||||
viewModel.isDissolved().observe(this, dissolved -> {
|
||||
setGroupEnabled(!dissolved);
|
||||
if (dissolved) onGroupDissolved();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GroupMessageAdapter createAdapter(
|
||||
LinearLayoutManager layoutManager) {
|
||||
return new GroupMessageAdapter(this, layoutManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadItems() {
|
||||
controller.isDissolved(
|
||||
new UiResultExceptionHandler<Boolean, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(Boolean isDissolved) {
|
||||
setGroupEnabled(!isDissolved);
|
||||
GroupActivity.super.loadItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNamedGroupLoaded(PrivateGroup group) {
|
||||
setTitle(group.getName());
|
||||
controller.loadLocalAuthor(
|
||||
new UiResultExceptionHandler<LocalAuthor, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(LocalAuthor author) {
|
||||
isCreator = group.getCreator().equals(author);
|
||||
adapter.setPerspective(isCreator);
|
||||
showMenuItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
viewModel.clearGroupMessageNotifications();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -136,74 +96,61 @@ public class GroupActivity extends
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.group_actions, menu);
|
||||
|
||||
revealMenuItem = menu.findItem(R.id.action_group_reveal);
|
||||
inviteMenuItem = menu.findItem(R.id.action_group_invite);
|
||||
leaveMenuItem = menu.findItem(R.id.action_group_leave);
|
||||
dissolveMenuItem = menu.findItem(R.id.action_group_dissolve);
|
||||
|
||||
// all role-dependent items are invisible until we know our role
|
||||
revealMenuItem.setVisible(false);
|
||||
inviteMenuItem.setVisible(false);
|
||||
leaveMenuItem.setVisible(false);
|
||||
dissolveMenuItem.setVisible(false);
|
||||
|
||||
// show items based on role
|
||||
showMenuItems();
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
// show items based on role (which will not change, so observe once)
|
||||
observeOnce(viewModel.isCreator(), this, isCreator -> {
|
||||
menu.findItem(R.id.action_group_reveal).setVisible(!isCreator);
|
||||
menu.findItem(R.id.action_group_invite).setVisible(isCreator);
|
||||
menu.findItem(R.id.action_group_leave).setVisible(!isCreator);
|
||||
menu.findItem(R.id.action_group_dissolve).setVisible(isCreator);
|
||||
});
|
||||
super.onCreateOptionsMenu(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_group_member_list:
|
||||
Intent i1 = new Intent(this, GroupMemberListActivity.class);
|
||||
i1.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivity(i1);
|
||||
return true;
|
||||
case R.id.action_group_reveal:
|
||||
if (isCreator == null || isCreator)
|
||||
throw new IllegalStateException();
|
||||
Intent i2 = new Intent(this, RevealContactsActivity.class);
|
||||
i2.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivity(i2);
|
||||
return true;
|
||||
case R.id.action_group_invite:
|
||||
if (isCreator == null || !isCreator)
|
||||
throw new IllegalStateException();
|
||||
Intent i3 = new Intent(this, GroupInviteActivity.class);
|
||||
i3.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivityForResult(i3, REQUEST_GROUP_INVITE);
|
||||
return true;
|
||||
case R.id.action_group_leave:
|
||||
if (isCreator == null || isCreator)
|
||||
throw new IllegalStateException();
|
||||
showLeaveGroupDialog();
|
||||
return true;
|
||||
case R.id.action_group_dissolve:
|
||||
if (isCreator == null || !isCreator)
|
||||
throw new IllegalStateException();
|
||||
showDissolveGroupDialog();
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.action_group_member_list) {
|
||||
Intent i = new Intent(this, GroupMemberListActivity.class);
|
||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivity(i);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_group_reveal) {
|
||||
if (viewModel.isCreator().getValue())
|
||||
throw new IllegalStateException();
|
||||
Intent i = new Intent(this, RevealContactsActivity.class);
|
||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivity(i);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_group_invite) {
|
||||
if (!viewModel.isCreator().getValue())
|
||||
throw new IllegalStateException();
|
||||
Intent i = new Intent(this, GroupInviteActivity.class);
|
||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivityForResult(i, REQUEST_GROUP_INVITE);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_group_leave) {
|
||||
if (viewModel.isCreator().getValue())
|
||||
throw new IllegalStateException();
|
||||
showLeaveGroupDialog();
|
||||
return true;
|
||||
} else if (itemId == R.id.action_group_dissolve) {
|
||||
if (!viewModel.isCreator().getValue())
|
||||
throw new IllegalStateException();
|
||||
showDissolveGroupDialog();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
protected void onActivityResult(int request, int result,
|
||||
@Nullable Intent data) {
|
||||
if (request == REQUEST_GROUP_INVITE && result == RESULT_OK) {
|
||||
displaySnackbar(R.string.groups_invitation_sent);
|
||||
} else super.onActivityResult(request, result, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemReceived(GroupMessageItem item) {
|
||||
super.onItemReceived(item);
|
||||
if (item instanceof JoinMessageItem) {
|
||||
if (((JoinMessageItem) item).isInitial()) loadSharingContacts();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMaxTextLength() {
|
||||
return MAX_GROUP_POST_TEXT_LENGTH;
|
||||
@@ -211,11 +158,10 @@ public class GroupActivity extends
|
||||
|
||||
@Override
|
||||
public void onReplyClick(GroupMessageItem item) {
|
||||
if (!isDissolved) super.onReplyClick(item);
|
||||
if (!viewModel.isDissolved().getValue()) super.onReplyClick(item);
|
||||
}
|
||||
|
||||
private void setGroupEnabled(boolean enabled) {
|
||||
isDissolved = !enabled;
|
||||
sendController.setReady(enabled);
|
||||
list.getRecyclerView().setAlpha(enabled ? 1f : 0.5f);
|
||||
|
||||
@@ -227,21 +173,13 @@ public class GroupActivity extends
|
||||
}
|
||||
}
|
||||
|
||||
private void showMenuItems() {
|
||||
// we need to have the menu items and know if we are the creator
|
||||
if (leaveMenuItem == null || isCreator == null) return;
|
||||
revealMenuItem.setVisible(!isCreator);
|
||||
inviteMenuItem.setVisible(isCreator);
|
||||
leaveMenuItem.setVisible(!isCreator);
|
||||
dissolveMenuItem.setVisible(isCreator);
|
||||
}
|
||||
|
||||
private void showLeaveGroupDialog() {
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(this, R.style.BriarDialogTheme);
|
||||
builder.setTitle(getString(R.string.groups_leave_dialog_title));
|
||||
builder.setMessage(getString(R.string.groups_leave_dialog_message));
|
||||
builder.setNegativeButton(R.string.dialog_button_leave, this);
|
||||
builder.setNegativeButton(R.string.dialog_button_leave,
|
||||
(d, w) -> deleteGroup());
|
||||
builder.setPositiveButton(R.string.cancel, null);
|
||||
builder.show();
|
||||
}
|
||||
@@ -251,37 +189,19 @@ public class GroupActivity extends
|
||||
new AlertDialog.Builder(this, R.style.BriarDialogTheme);
|
||||
builder.setTitle(getString(R.string.groups_dissolve_dialog_title));
|
||||
builder.setMessage(getString(R.string.groups_dissolve_dialog_message));
|
||||
builder.setNegativeButton(R.string.groups_dissolve_button, this);
|
||||
builder.setNegativeButton(R.string.groups_dissolve_button,
|
||||
(d, w) -> deleteGroup());
|
||||
builder.setPositiveButton(R.string.cancel, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
controller.deleteNamedGroup(
|
||||
new UiExceptionHandler<DbException>(this) {
|
||||
// The activity is going to be destroyed by the
|
||||
// GroupRemovedEvent being fired
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
private void deleteGroup() {
|
||||
// The activity is going to be destroyed by the
|
||||
// GroupRemovedEvent being fired
|
||||
viewModel.deletePrivateGroup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContactRelationshipRevealed(AuthorId memberId, ContactId c,
|
||||
Visibility v) {
|
||||
adapter.updateVisibility(memberId, v);
|
||||
|
||||
sharingController.add(c);
|
||||
setToolbarSubTitle(sharingController.getTotalCount(),
|
||||
sharingController.getOnlineCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGroupDissolved() {
|
||||
setGroupEnabled(false);
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(this, R.style.BriarDialogTheme);
|
||||
builder.setTitle(getString(R.string.groups_dissolved_dialog_title));
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package org.briarproject.briar.android.privategroup.conversation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||
import org.briarproject.briar.api.privategroup.Visibility;
|
||||
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
public interface GroupController
|
||||
extends ThreadListController<PrivateGroup, GroupMessageItem> {
|
||||
|
||||
void loadLocalAuthor(
|
||||
ResultExceptionHandler<LocalAuthor, DbException> handler);
|
||||
|
||||
void isDissolved(
|
||||
ResultExceptionHandler<Boolean, DbException> handler);
|
||||
|
||||
interface GroupListener extends ThreadListListener<GroupMessageItem> {
|
||||
|
||||
@UiThread
|
||||
void onContactRelationshipRevealed(AuthorId memberId,
|
||||
ContactId contactId, Visibility v);
|
||||
|
||||
@UiThread
|
||||
void onGroupDissolved();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
package org.briarproject.briar.android.privategroup.conversation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.briar.android.privategroup.conversation.GroupController.GroupListener;
|
||||
import org.briarproject.briar.android.threaded.ThreadListControllerImpl;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||
import org.briarproject.briar.api.privategroup.GroupMember;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessage;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
||||
import org.briarproject.briar.api.privategroup.JoinMessageHeader;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||
import org.briarproject.briar.api.privategroup.event.ContactRelationshipRevealedEvent;
|
||||
import org.briarproject.briar.api.privategroup.event.GroupDissolvedEvent;
|
||||
import org.briarproject.briar.api.privategroup.event.GroupInvitationResponseReceivedEvent;
|
||||
import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class GroupControllerImpl extends
|
||||
ThreadListControllerImpl<PrivateGroup, GroupMessageItem, GroupMessageHeader, GroupMessage, GroupListener>
|
||||
implements GroupController {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(GroupControllerImpl.class.getName());
|
||||
|
||||
private final PrivateGroupManager privateGroupManager;
|
||||
private final GroupMessageFactory groupMessageFactory;
|
||||
|
||||
@Inject
|
||||
GroupControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager, IdentityManager identityManager,
|
||||
@CryptoExecutor Executor cryptoExecutor,
|
||||
PrivateGroupManager privateGroupManager,
|
||||
GroupMessageFactory groupMessageFactory, EventBus eventBus,
|
||||
MessageTracker messageTracker, Clock clock,
|
||||
AndroidNotificationManager notificationManager) {
|
||||
super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor,
|
||||
eventBus, clock, notificationManager, messageTracker);
|
||||
this.privateGroupManager = privateGroupManager;
|
||||
this.groupMessageFactory = groupMessageFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStart() {
|
||||
super.onActivityStart();
|
||||
notificationManager.clearGroupMessageNotification(getGroupId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
super.eventOccurred(e);
|
||||
|
||||
if (e instanceof GroupMessageAddedEvent) {
|
||||
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
|
||||
if (!g.isLocal() && g.getGroupId().equals(getGroupId())) {
|
||||
LOG.info("Group message received, adding...");
|
||||
listener.onItemReceived(buildItem(g.getHeader(), g.getText()));
|
||||
}
|
||||
} else if (e instanceof ContactRelationshipRevealedEvent) {
|
||||
ContactRelationshipRevealedEvent c =
|
||||
(ContactRelationshipRevealedEvent) e;
|
||||
if (getGroupId().equals(c.getGroupId())) {
|
||||
listener.onContactRelationshipRevealed(c.getMemberId(),
|
||||
c.getContactId(), c.getVisibility());
|
||||
}
|
||||
} else if (e instanceof GroupInvitationResponseReceivedEvent) {
|
||||
GroupInvitationResponseReceivedEvent g =
|
||||
(GroupInvitationResponseReceivedEvent) e;
|
||||
GroupInvitationResponse r = g.getMessageHeader();
|
||||
if (getGroupId().equals(r.getShareableId()) && r.wasAccepted()) {
|
||||
listener.onInvitationAccepted(g.getContactId());
|
||||
}
|
||||
} else if (e instanceof GroupDissolvedEvent) {
|
||||
GroupDissolvedEvent g = (GroupDissolvedEvent) e;
|
||||
if (getGroupId().equals(g.getGroupId())) {
|
||||
listener.onGroupDissolved();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrivateGroup loadNamedGroup() throws DbException {
|
||||
return privateGroupManager.getPrivateGroup(getGroupId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<GroupMessageHeader> loadHeaders() throws DbException {
|
||||
return privateGroupManager.getHeaders(getGroupId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String loadMessageText(GroupMessageHeader header)
|
||||
throws DbException {
|
||||
if (header instanceof JoinMessageHeader) {
|
||||
// will be looked up later
|
||||
return "";
|
||||
}
|
||||
return privateGroupManager.getMessageText(header.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void markRead(MessageId id) throws DbException {
|
||||
privateGroupManager.setReadFlag(getGroupId(), id, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadSharingContacts(
|
||||
ResultExceptionHandler<Collection<ContactId>, DbException> handler) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
Collection<GroupMember> members =
|
||||
privateGroupManager.getMembers(getGroupId());
|
||||
Collection<ContactId> contactIds = new ArrayList<>();
|
||||
for (GroupMember m : members) {
|
||||
if (m.getContactId() != null)
|
||||
contactIds.add(m.getContactId());
|
||||
}
|
||||
handler.onResult(contactIds);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
handler.onException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createAndStoreMessage(String text,
|
||||
@Nullable GroupMessageItem parentItem,
|
||||
ResultExceptionHandler<GroupMessageItem, DbException> handler) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
LocalAuthor author = identityManager.getLocalAuthor();
|
||||
MessageId parentId = null;
|
||||
MessageId previousMsgId =
|
||||
privateGroupManager.getPreviousMsgId(getGroupId());
|
||||
GroupCount count =
|
||||
privateGroupManager.getGroupCount(getGroupId());
|
||||
long timestamp = count.getLatestMsgTime();
|
||||
if (parentItem != null) parentId = parentItem.getId();
|
||||
timestamp = max(clock.currentTimeMillis(), timestamp + 1);
|
||||
createMessage(text, timestamp, parentId, author, previousMsgId,
|
||||
handler);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
handler.onException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createMessage(String text, long timestamp,
|
||||
@Nullable MessageId parentId, LocalAuthor author,
|
||||
MessageId previousMsgId,
|
||||
ResultExceptionHandler<GroupMessageItem, DbException> handler) {
|
||||
cryptoExecutor.execute(() -> {
|
||||
LOG.info("Creating group message...");
|
||||
GroupMessage msg = groupMessageFactory
|
||||
.createGroupMessage(getGroupId(), timestamp,
|
||||
parentId, author, text, previousMsgId);
|
||||
storePost(msg, text, handler);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GroupMessageHeader addLocalMessage(GroupMessage message)
|
||||
throws DbException {
|
||||
return privateGroupManager.addLocalMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteNamedGroup(PrivateGroup group) throws DbException {
|
||||
privateGroupManager.removePrivateGroup(group.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GroupMessageItem buildItem(GroupMessageHeader header,
|
||||
String text) {
|
||||
if (header instanceof JoinMessageHeader) {
|
||||
return new JoinMessageItem((JoinMessageHeader) header, text);
|
||||
}
|
||||
return new GroupMessageItem(header, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadLocalAuthor(
|
||||
ResultExceptionHandler<LocalAuthor, DbException> handler) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
LocalAuthor author = identityManager.getLocalAuthor();
|
||||
handler.onResult(author);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
handler.onException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isDissolved(
|
||||
ResultExceptionHandler<Boolean, DbException> handler) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
boolean isDissolved =
|
||||
privateGroupManager.isDissolved(getGroupId());
|
||||
handler.onResult(isDissolved);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
handler.onException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +1,18 @@
|
||||
package org.briarproject.briar.android.privategroup.conversation;
|
||||
|
||||
import org.briarproject.briar.android.activity.ActivityScope;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.viewmodel.ViewModelKey;
|
||||
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
|
||||
@Module
|
||||
public class GroupConversationModule {
|
||||
public interface GroupConversationModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(GroupViewModel.class)
|
||||
ViewModel bindGroupViewModel(GroupViewModel groupViewModel);
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
GroupController provideGroupController(BaseActivity activity,
|
||||
GroupControllerImpl groupController) {
|
||||
activity.addLifecycleController(groupController);
|
||||
return groupController;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,19 +4,14 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.threaded.BaseThreadItemViewHolder;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter;
|
||||
import org.briarproject.briar.android.threaded.ThreadPostViewHolder;
|
||||
import org.briarproject.briar.api.privategroup.Visibility;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
@@ -24,15 +19,14 @@ class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
|
||||
|
||||
private boolean isCreator = false;
|
||||
|
||||
GroupMessageAdapter(ThreadItemListener<GroupMessageItem> listener,
|
||||
LinearLayoutManager layoutManager) {
|
||||
super(listener, layoutManager);
|
||||
GroupMessageAdapter(ThreadItemListener<GroupMessageItem> listener) {
|
||||
super(listener);
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
GroupMessageItem item = items.get(position);
|
||||
GroupMessageItem item = getItem(position);
|
||||
return item.getLayout();
|
||||
}
|
||||
|
||||
@@ -47,30 +41,9 @@ class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
|
||||
return new ThreadPostViewHolder<>(v);
|
||||
}
|
||||
|
||||
void setPerspective(boolean isCreator) {
|
||||
void setIsCreator(boolean isCreator) {
|
||||
this.isCreator = isCreator;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
void updateVisibility(AuthorId memberId, Visibility v) {
|
||||
int position = findItemPosition(memberId);
|
||||
if (position != NO_POSITION) {
|
||||
GroupMessageItem item = items.get(position);
|
||||
if (item instanceof JoinMessageItem) {
|
||||
((JoinMessageItem) item).setVisibility(v);
|
||||
notifyItemChanged(findItemPosition(item), item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int findItemPosition(AuthorId a) {
|
||||
int count = items.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
GroupMessageItem item = items.get(i);
|
||||
if (item.getAuthor().getId().equals(a))
|
||||
return i;
|
||||
}
|
||||
return NO_POSITION; // Not found
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
package org.briarproject.briar.android.privategroup.conversation;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
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.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.android.sharing.SharingController;
|
||||
import org.briarproject.briar.android.threaded.ThreadListViewModel;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||
import org.briarproject.briar.api.client.PostHeader;
|
||||
import org.briarproject.briar.api.privategroup.GroupMember;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessage;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
||||
import org.briarproject.briar.api.privategroup.JoinMessageHeader;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||
import org.briarproject.briar.api.privategroup.event.ContactRelationshipRevealedEvent;
|
||||
import org.briarproject.briar.api.privategroup.event.GroupDissolvedEvent;
|
||||
import org.briarproject.briar.api.privategroup.event.GroupInvitationResponseReceivedEvent;
|
||||
import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
|
||||
|
||||
private static final Logger LOG = getLogger(GroupViewModel.class.getName());
|
||||
|
||||
private final PrivateGroupManager privateGroupManager;
|
||||
private final GroupMessageFactory groupMessageFactory;
|
||||
|
||||
private final MutableLiveData<PrivateGroup> privateGroup =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> isCreator = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> isDissolved =
|
||||
new MutableLiveData<>();
|
||||
|
||||
@Inject
|
||||
GroupViewModel(Application application,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager,
|
||||
TransactionManager db,
|
||||
AndroidExecutor androidExecutor,
|
||||
EventBus eventBus,
|
||||
IdentityManager identityManager,
|
||||
AndroidNotificationManager notificationManager,
|
||||
SharingController sharingController,
|
||||
@CryptoExecutor Executor cryptoExecutor,
|
||||
Clock clock,
|
||||
MessageTracker messageTracker,
|
||||
PrivateGroupManager privateGroupManager,
|
||||
GroupMessageFactory groupMessageFactory) {
|
||||
super(application, dbExecutor, lifecycleManager, db, androidExecutor,
|
||||
identityManager, notificationManager, sharingController,
|
||||
cryptoExecutor, clock, messageTracker, eventBus);
|
||||
this.privateGroupManager = privateGroupManager;
|
||||
this.groupMessageFactory = groupMessageFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof GroupMessageAddedEvent) {
|
||||
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
|
||||
// only act on non-local messages in this group
|
||||
if (!g.isLocal() && g.getGroupId().equals(groupId)) {
|
||||
LOG.info("Group message received, adding...");
|
||||
GroupMessageItem item = buildItem(g.getHeader(), g.getText());
|
||||
addItem(item);
|
||||
if (item instanceof JoinMessageItem &&
|
||||
(((JoinMessageItem) item).isInitial())) {
|
||||
loadSharingContacts();
|
||||
}
|
||||
}
|
||||
} else if (e instanceof GroupInvitationResponseReceivedEvent) {
|
||||
GroupInvitationResponseReceivedEvent g =
|
||||
(GroupInvitationResponseReceivedEvent) e;
|
||||
GroupInvitationResponse r = g.getMessageHeader();
|
||||
if (r.getShareableId().equals(groupId) && r.wasAccepted()) {
|
||||
sharingController.add(g.getContactId());
|
||||
}
|
||||
} else if (e instanceof ContactRelationshipRevealedEvent) {
|
||||
ContactRelationshipRevealedEvent c =
|
||||
(ContactRelationshipRevealedEvent) e;
|
||||
if (c.getGroupId().equals(groupId)) {
|
||||
sharingController.add(c.getContactId());
|
||||
}
|
||||
} else if (e instanceof GroupDissolvedEvent) {
|
||||
GroupDissolvedEvent g = (GroupDissolvedEvent) e;
|
||||
if (g.getGroupId().equals(groupId)) {
|
||||
isDissolved.setValue(true);
|
||||
}
|
||||
} else {
|
||||
super.eventOccurred(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGroupId(GroupId groupId) {
|
||||
super.setGroupId(groupId);
|
||||
loadPrivateGroup(groupId);
|
||||
}
|
||||
|
||||
public void clearGroupMessageNotifications() {
|
||||
notificationManager.clearGroupMessageNotification(groupId);
|
||||
}
|
||||
|
||||
private void loadPrivateGroup(GroupId groupId) {
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
PrivateGroup g = privateGroupManager.getPrivateGroup(groupId);
|
||||
privateGroup.postValue(g);
|
||||
Author author = identityManager.getLocalAuthor();
|
||||
isCreator.postValue(g.getCreator().equals(author));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadItems() {
|
||||
loadList(txn -> {
|
||||
// check first if group is dissolved
|
||||
isDissolved
|
||||
.postValue(privateGroupManager.isDissolved(txn, groupId));
|
||||
// now continue to load the items
|
||||
long start = now();
|
||||
List<GroupMessageHeader> headers =
|
||||
privateGroupManager.getHeaders(txn, groupId);
|
||||
logDuration(LOG, "Loading headers", start);
|
||||
return createItems(txn, headers, this::buildItem);
|
||||
}, this::setItems);
|
||||
}
|
||||
|
||||
private GroupMessageItem buildItem(GroupMessageHeader header, String text) {
|
||||
if (header instanceof JoinMessageHeader) {
|
||||
return new JoinMessageItem((JoinMessageHeader) header, text);
|
||||
}
|
||||
return new GroupMessageItem(header, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String loadMessageText(
|
||||
Transaction txn, PostHeader header) throws DbException {
|
||||
if (header instanceof JoinMessageHeader) {
|
||||
// will be looked up later
|
||||
return "";
|
||||
}
|
||||
return privateGroupManager.getMessageText(txn, header.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createAndStoreMessage(String text,
|
||||
@Nullable MessageId parentId) {
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
LocalAuthor author = identityManager.getLocalAuthor();
|
||||
MessageId previousMsgId =
|
||||
privateGroupManager.getPreviousMsgId(groupId);
|
||||
GroupCount count = privateGroupManager.getGroupCount(groupId);
|
||||
long timestamp = count.getLatestMsgTime();
|
||||
timestamp = max(clock.currentTimeMillis(), timestamp + 1);
|
||||
createMessage(text, timestamp, parentId, author, previousMsgId);
|
||||
});
|
||||
}
|
||||
|
||||
private void createMessage(String text, long timestamp,
|
||||
@Nullable MessageId parentId, LocalAuthor author,
|
||||
MessageId previousMsgId) {
|
||||
cryptoExecutor.execute(() -> {
|
||||
LOG.info("Creating group message...");
|
||||
GroupMessage msg = groupMessageFactory.createGroupMessage(groupId,
|
||||
timestamp, parentId, author, text, previousMsgId);
|
||||
storePost(msg, text);
|
||||
});
|
||||
}
|
||||
|
||||
private void storePost(GroupMessage msg, String text) {
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
long start = now();
|
||||
GroupMessageHeader header =
|
||||
privateGroupManager.addLocalMessage(msg);
|
||||
addItemAsync(buildItem(header, text));
|
||||
logDuration(LOG, "Storing group message", start);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void markItemRead(GroupMessageItem item) {
|
||||
runOnDbThreadOrLogException(() ->
|
||||
privateGroupManager.setReadFlag(groupId, item.getId(), true));
|
||||
}
|
||||
|
||||
public void loadSharingContacts() {
|
||||
runOnDbThreadOrLogException(true, txn -> {
|
||||
Collection<GroupMember> members =
|
||||
privateGroupManager.getMembers(txn, groupId);
|
||||
Collection<ContactId> contactIds = new ArrayList<>();
|
||||
for (GroupMember m : members) {
|
||||
if (m.getContactId() != null) contactIds.add(m.getContactId());
|
||||
}
|
||||
txn.attach(() -> sharingController.addAll(contactIds));
|
||||
});
|
||||
}
|
||||
|
||||
void deletePrivateGroup() {
|
||||
runOnDbThreadOrLogException(() ->
|
||||
privateGroupManager.removePrivateGroup(groupId));
|
||||
}
|
||||
|
||||
LiveData<PrivateGroup> getPrivateGroup() {
|
||||
return privateGroup;
|
||||
}
|
||||
|
||||
LiveData<Boolean> isCreator() {
|
||||
return isCreator;
|
||||
}
|
||||
|
||||
LiveData<Boolean> isDissolved() {
|
||||
return isDissolved;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.briar.android.privategroup.conversation;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.api.privategroup.JoinMessageHeader;
|
||||
import org.briarproject.briar.api.privategroup.Visibility;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
@@ -13,12 +12,10 @@ import androidx.annotation.UiThread;
|
||||
@NotThreadSafe
|
||||
class JoinMessageItem extends GroupMessageItem {
|
||||
|
||||
private Visibility visibility;
|
||||
private final boolean isInitial;
|
||||
|
||||
JoinMessageItem(JoinMessageHeader h, String text) {
|
||||
super(h, text);
|
||||
this.visibility = h.getVisibility();
|
||||
this.isInitial = h.isInitial();
|
||||
}
|
||||
|
||||
@@ -33,14 +30,6 @@ class JoinMessageItem extends GroupMessageItem {
|
||||
return R.layout.list_item_group_join_notice;
|
||||
}
|
||||
|
||||
Visibility getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
void setVisibility(Visibility visibility) {
|
||||
this.visibility = visibility;
|
||||
}
|
||||
|
||||
boolean isInitial() {
|
||||
return isInitial;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.briar.android.privategroup.list;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
@@ -49,10 +48,8 @@ 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.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID;
|
||||
|
||||
@@ -200,25 +197,17 @@ class GroupListViewModel extends DbViewModel implements EventListener {
|
||||
}
|
||||
|
||||
void removeGroup(GroupId g) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
groupManager.removePrivateGroup(g);
|
||||
logDuration(LOG, "Removing group", start);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
long start = now();
|
||||
groupManager.removePrivateGroup(g);
|
||||
logDuration(LOG, "Removing group", start);
|
||||
});
|
||||
}
|
||||
|
||||
void loadNumInvitations() {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
int i = groupInvitationManager.getInvitations().size();
|
||||
numInvitations.postValue(i);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
int i = groupInvitationManager.getInvitations().size();
|
||||
numInvitations.postValue(i);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.briarproject.briar.android.sharing;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface SharingController {
|
||||
|
||||
/**
|
||||
* Call this when the owning ViewModel gets cleared,
|
||||
* so the {@link EventBus} can get unregistered.
|
||||
*/
|
||||
void onCleared();
|
||||
|
||||
/**
|
||||
* Adds one contact to be tracked.
|
||||
*/
|
||||
@UiThread
|
||||
void add(ContactId c);
|
||||
|
||||
/**
|
||||
* Adds a collection of contacts to be tracked.
|
||||
*/
|
||||
@UiThread
|
||||
void addAll(Collection<ContactId> contacts);
|
||||
|
||||
/**
|
||||
* Call this when the contact identified by c is no longer sharing
|
||||
* the given group identified by GroupId g.
|
||||
*/
|
||||
@UiThread
|
||||
void remove(ContactId c);
|
||||
|
||||
/**
|
||||
* Returns the total number of contacts that have been added.
|
||||
*/
|
||||
LiveData<SharingInfo> getSharingInfo();
|
||||
|
||||
class SharingInfo {
|
||||
public final int total, online;
|
||||
|
||||
SharingInfo(int total, int online) {
|
||||
this.total = total;
|
||||
this.online = online;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package org.briarproject.briar.android.sharing;
|
||||
|
||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
@NotNullByDefault
|
||||
public class SharingControllerImpl implements SharingController, EventListener {
|
||||
|
||||
private final EventBus eventBus;
|
||||
private final ConnectionRegistry connectionRegistry;
|
||||
|
||||
// UI thread
|
||||
private final Set<ContactId> contacts = new HashSet<>();
|
||||
private final MutableLiveData<SharingInfo> sharingInfo =
|
||||
new MutableLiveData<>();
|
||||
|
||||
@Inject
|
||||
SharingControllerImpl(EventBus eventBus,
|
||||
ConnectionRegistry connectionRegistry) {
|
||||
this.eventBus = eventBus;
|
||||
this.connectionRegistry = connectionRegistry;
|
||||
eventBus.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCleared() {
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof ContactConnectedEvent) {
|
||||
setConnected(((ContactConnectedEvent) e).getContactId());
|
||||
} else if (e instanceof ContactDisconnectedEvent) {
|
||||
setConnected(((ContactDisconnectedEvent) e).getContactId());
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void setConnected(ContactId c) {
|
||||
if (contacts.contains(c)) {
|
||||
updateLiveData();
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void updateLiveData() {
|
||||
int online = getOnlineCount();
|
||||
sharingInfo.setValue(new SharingInfo(contacts.size(), online));
|
||||
}
|
||||
|
||||
private int getOnlineCount() {
|
||||
int online = 0;
|
||||
for (ContactId c : contacts) {
|
||||
if (connectionRegistry.isConnected(c)) online++;
|
||||
}
|
||||
return online;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
public void addAll(Collection<ContactId> c) {
|
||||
contacts.addAll(c);
|
||||
updateLiveData();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
public void add(ContactId c) {
|
||||
contacts.add(c);
|
||||
updateLiveData();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
public void remove(ContactId c) {
|
||||
contacts.remove(c);
|
||||
updateLiveData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveData<SharingInfo> getSharingInfo() {
|
||||
return sharingInfo;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,36 +9,47 @@ import dagger.Provides;
|
||||
@Module
|
||||
public class SharingModule {
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
ShareForumController provideShareForumController(
|
||||
ShareForumControllerImpl shareForumController) {
|
||||
return shareForumController;
|
||||
@Module
|
||||
@Deprecated
|
||||
public static class SharingLegacyModule {
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
ShareForumController provideShareForumController(
|
||||
ShareForumControllerImpl shareForumController) {
|
||||
return shareForumController;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
BlogInvitationController provideInvitationBlogController(
|
||||
BaseActivity activity,
|
||||
BlogInvitationControllerImpl blogInvitationController) {
|
||||
activity.addLifecycleController(blogInvitationController);
|
||||
return blogInvitationController;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
ForumInvitationController provideInvitationForumController(
|
||||
BaseActivity activity,
|
||||
ForumInvitationControllerImpl forumInvitationController) {
|
||||
activity.addLifecycleController(forumInvitationController);
|
||||
return forumInvitationController;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
ShareBlogController provideShareBlogController(
|
||||
ShareBlogControllerImpl shareBlogController) {
|
||||
return shareBlogController;
|
||||
}
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
BlogInvitationController provideInvitationBlogController(
|
||||
BaseActivity activity,
|
||||
BlogInvitationControllerImpl blogInvitationController) {
|
||||
activity.addLifecycleController(blogInvitationController);
|
||||
return blogInvitationController;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
ForumInvitationController provideInvitationForumController(
|
||||
BaseActivity activity,
|
||||
ForumInvitationControllerImpl forumInvitationController) {
|
||||
activity.addLifecycleController(forumInvitationController);
|
||||
return forumInvitationController;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
ShareBlogController provideShareBlogController(
|
||||
ShareBlogControllerImpl shareBlogController) {
|
||||
return shareBlogController;
|
||||
SharingController provideSharingController(
|
||||
SharingControllerImpl sharingController) {
|
||||
return sharingController;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.MessageTree;
|
||||
import org.briarproject.briar.api.client.MessageTree.MessageNode;
|
||||
import org.briarproject.briar.client.MessageTreeImpl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
public class NestedTreeList<T extends MessageNode> implements Iterable<T> {
|
||||
|
||||
private final MessageTree<T> tree = new MessageTreeImpl<>();
|
||||
private List<T> depthFirstCollection = new ArrayList<>();
|
||||
|
||||
public void addAll(Collection<T> collection) {
|
||||
tree.add(collection);
|
||||
depthFirstCollection = new ArrayList<>(tree.depthFirstOrder());
|
||||
}
|
||||
|
||||
public void add(T elem) {
|
||||
tree.add(elem);
|
||||
depthFirstCollection = new ArrayList<>(tree.depthFirstOrder());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
tree.clear();
|
||||
depthFirstCollection.clear();
|
||||
}
|
||||
|
||||
public T get(int index) {
|
||||
return depthFirstCollection.get(index);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return depthFirstCollection.size();
|
||||
}
|
||||
|
||||
public boolean contains(MessageId m) {
|
||||
return tree.contains(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return depthFirstCollection.iterator();
|
||||
}
|
||||
}
|
||||
@@ -99,4 +99,14 @@ public abstract class ThreadItem implements MessageNode {
|
||||
return highlighted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return messageId.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
return o instanceof ThreadItem &&
|
||||
messageId.equals(((ThreadItem) o).messageId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,39 +4,47 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.util.ItemReturningAdapter;
|
||||
import org.briarproject.briar.android.util.VersionedAdapter;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
|
||||
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
|
||||
|
||||
@UiThread
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ThreadItemAdapter<I extends ThreadItem>
|
||||
extends RecyclerView.Adapter<BaseThreadItemViewHolder<I>>
|
||||
implements VersionedAdapter, ItemReturningAdapter<I> {
|
||||
extends ListAdapter<I, BaseThreadItemViewHolder<I>>
|
||||
implements ItemReturningAdapter<I> {
|
||||
|
||||
static final int UNDEFINED = -1;
|
||||
|
||||
protected final NestedTreeList<I> items = new NestedTreeList<>();
|
||||
private final ThreadItemListener<I> listener;
|
||||
private final LinearLayoutManager layoutManager;
|
||||
|
||||
private volatile int revision = 0;
|
||||
public ThreadItemAdapter(ThreadItemListener<I> listener) {
|
||||
super(new DiffUtil.ItemCallback<I>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(I a, I b) {
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
public ThreadItemAdapter(ThreadItemListener<I> listener,
|
||||
LinearLayoutManager layoutManager) {
|
||||
@Override
|
||||
public boolean areContentsTheSame(I a, I b) {
|
||||
return a.isHighlighted() == b.isHighlighted() &&
|
||||
a.isRead() && b.isRead();
|
||||
}
|
||||
});
|
||||
this.listener = listener;
|
||||
this.layoutManager = layoutManager;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -51,76 +59,27 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull BaseThreadItemViewHolder<I> ui,
|
||||
int position) {
|
||||
I item = items.get(position);
|
||||
I item = getItem(position);
|
||||
ui.bind(item, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementRevision() {
|
||||
revision++;
|
||||
}
|
||||
|
||||
void setItemWithIdVisible(MessageId messageId) {
|
||||
int pos = 0;
|
||||
for (I item : items) {
|
||||
if (item.getId().equals(messageId)) {
|
||||
layoutManager.scrollToPosition(pos);
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
public void setItems(Collection<I> items) {
|
||||
this.items.clear();
|
||||
this.items.addAll(items);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void add(I item) {
|
||||
items.add(item);
|
||||
notifyItemInserted(findItemPosition(item));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public I getItemAt(int position) {
|
||||
if (position == NO_POSITION || position >= items.size()) {
|
||||
return null;
|
||||
}
|
||||
return items.get(position);
|
||||
}
|
||||
|
||||
protected int findItemPosition(@Nullable I item) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items.get(i).equals(item)) return i;
|
||||
public int findItemPosition(MessageId id) {
|
||||
for (int i = 0; i < getItemCount(); i++) {
|
||||
if (id.equals(getItem(i).getId())) return i;
|
||||
}
|
||||
return NO_POSITION; // Not found
|
||||
}
|
||||
|
||||
boolean contains(MessageId m) {
|
||||
return items.contains(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights the item with the given {@link MessageId}
|
||||
* and disables the highlight for a previously highlighted item, if any.
|
||||
*
|
||||
* <p>
|
||||
* Only one item can be highlighted at a time.
|
||||
*/
|
||||
void setHighlightedItem(@Nullable MessageId id) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
I item = items.get(i);
|
||||
if (id != null && item.getId().equals(id)) {
|
||||
for (int i = 0; i < getItemCount(); i++) {
|
||||
I item = getItem(i);
|
||||
if (item.getId().equals(id)) {
|
||||
item.setHighlighted(true);
|
||||
notifyItemChanged(i, item);
|
||||
} else if (item.isHighlighted()) {
|
||||
@@ -132,20 +91,28 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
|
||||
@Nullable
|
||||
I getHighlightedItem() {
|
||||
for (I i : items) {
|
||||
if (i.isHighlighted()) return i;
|
||||
for (int i = 0; i < getItemCount(); i++) {
|
||||
I item = getItem(i);
|
||||
if (item.isHighlighted()) return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
MessageId getFirstVisibleMessageId(LinearLayoutManager layoutManager) {
|
||||
int position = layoutManager.findFirstVisibleItemPosition();
|
||||
if (position == NO_POSITION) return null;
|
||||
return getItemAt(position).getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position of the first unread item below the current viewport
|
||||
*/
|
||||
int getVisibleUnreadPosBottom() {
|
||||
int getVisibleUnreadPosBottom(LinearLayoutManager layoutManager) {
|
||||
int positionBottom = layoutManager.findLastVisibleItemPosition();
|
||||
if (positionBottom == NO_POSITION) return NO_POSITION;
|
||||
for (int i = positionBottom + 1; i < items.size(); i++) {
|
||||
if (!items.get(i).isRead()) return i;
|
||||
for (int i = positionBottom + 1; i < getItemCount(); i++) {
|
||||
if (!getItem(i).isRead()) return i;
|
||||
}
|
||||
return NO_POSITION;
|
||||
}
|
||||
@@ -153,11 +120,11 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
/**
|
||||
* Returns the position of the first unread item above the current viewport
|
||||
*/
|
||||
int getVisibleUnreadPosTop() {
|
||||
int getVisibleUnreadPosTop(LinearLayoutManager layoutManager) {
|
||||
int positionTop = layoutManager.findFirstVisibleItemPosition();
|
||||
int position = NO_POSITION;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (i < positionTop && !items.get(i).isRead()) {
|
||||
for (int i = 0; i < getItemCount(); i++) {
|
||||
if (i < positionTop && !getItem(i).isRead()) {
|
||||
position = i;
|
||||
} else if (i >= positionTop) {
|
||||
return position;
|
||||
@@ -166,6 +133,11 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
return NO_POSITION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public I getItemAt(int position) {
|
||||
return getItem(position);
|
||||
}
|
||||
|
||||
public interface ThreadItemListener<I> {
|
||||
void onReplyClick(I item);
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface ThreadItemList<I extends ThreadItem> extends List<I> {
|
||||
|
||||
@Nullable
|
||||
MessageId getFirstVisibleItemId();
|
||||
|
||||
void setFirstVisibleId(@Nullable MessageId bottomVisibleItemId);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class ThreadItemListImpl<I extends ThreadItem> extends ArrayList<I>
|
||||
implements ThreadItemList<I> {
|
||||
|
||||
private MessageId bottomVisibleItemId;
|
||||
|
||||
@Override
|
||||
public MessageId getFirstVisibleItemId() {
|
||||
return bottomVisibleItemId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFirstVisibleId(@Nullable MessageId bottomVisibleItemId) {
|
||||
this.bottomVisibleItemId = bottomVisibleItemId;
|
||||
}
|
||||
}
|
||||
@@ -2,25 +2,18 @@ package org.briarproject.briar.android.threaded;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.controller.SharingController;
|
||||
import org.briarproject.briar.android.controller.SharingController.SharingListener;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.briar.android.sharing.SharingController.SharingInfo;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListDataSource;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener;
|
||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.android.view.TextInputView;
|
||||
@@ -28,18 +21,13 @@ import org.briarproject.briar.android.view.TextSendController;
|
||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||
import org.briarproject.briar.android.view.UnreadMessageButton;
|
||||
import org.briarproject.briar.api.attachment.AttachmentHeader;
|
||||
import org.briarproject.briar.api.client.NamedGroup;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
@@ -48,32 +36,19 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadItem, A extends ThreadItemAdapter<I>>
|
||||
extends BriarActivity
|
||||
implements ThreadListListener<I>, SendListener, SharingListener,
|
||||
ThreadItemListener<I>, ThreadListDataSource {
|
||||
public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadItemAdapter<I>>
|
||||
extends BriarActivity implements SendListener, ThreadItemListener<I> {
|
||||
|
||||
protected static final String KEY_REPLY_ID = "replyId";
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ThreadListActivity.class.getName());
|
||||
|
||||
protected A adapter;
|
||||
private ThreadScrollListener<I> scrollListener;
|
||||
protected final A adapter = createAdapter();
|
||||
protected abstract ThreadListViewModel<I> getViewModel();
|
||||
protected abstract A createAdapter();
|
||||
protected BriarRecyclerView list;
|
||||
private LinearLayoutManager layoutManager;
|
||||
protected TextInputView textInput;
|
||||
protected TextSendController sendController;
|
||||
protected GroupId groupId;
|
||||
@Nullable
|
||||
private Parcelable layoutManagerState;
|
||||
@Nullable
|
||||
private MessageId replyId;
|
||||
|
||||
protected abstract ThreadListController<G, I> getController();
|
||||
|
||||
@Inject
|
||||
protected SharingController sharingController;
|
||||
private LinearLayoutManager layoutManager;
|
||||
private ThreadScrollListener<I> scrollListener;
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
@@ -86,7 +61,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException("No GroupId in intent.");
|
||||
groupId = new GroupId(b);
|
||||
getController().setGroupId(groupId);
|
||||
ThreadListViewModel<I> viewModel = getViewModel();
|
||||
viewModel.setGroupId(groupId);
|
||||
|
||||
textInput = findViewById(R.id.text_input_container);
|
||||
sendController = new TextSendController(textInput, this, false);
|
||||
@@ -100,131 +76,41 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
||||
list = findViewById(R.id.list);
|
||||
layoutManager = new LinearLayoutManager(this);
|
||||
list.setLayoutManager(layoutManager);
|
||||
adapter = createAdapter(layoutManager);
|
||||
list.setAdapter(adapter);
|
||||
scrollListener = new ThreadScrollListener<>(adapter, getController(),
|
||||
scrollListener = new ThreadScrollListener<>(adapter, viewModel,
|
||||
upButton, downButton);
|
||||
list.getRecyclerView().addOnScrollListener(scrollListener);
|
||||
|
||||
upButton.setOnClickListener(v -> {
|
||||
int position = adapter.getVisibleUnreadPosTop();
|
||||
int position = adapter.getVisibleUnreadPosTop(layoutManager);
|
||||
if (position != NO_POSITION) {
|
||||
list.getRecyclerView().scrollToPosition(position);
|
||||
}
|
||||
});
|
||||
downButton.setOnClickListener(v -> {
|
||||
int position = adapter.getVisibleUnreadPosBottom();
|
||||
int position = adapter.getVisibleUnreadPosBottom(layoutManager);
|
||||
if (position != NO_POSITION) {
|
||||
list.getRecyclerView().scrollToPosition(position);
|
||||
}
|
||||
});
|
||||
|
||||
if (state != null) {
|
||||
byte[] replyIdBytes = state.getByteArray(KEY_REPLY_ID);
|
||||
if (replyIdBytes != null) replyId = new MessageId(replyIdBytes);
|
||||
}
|
||||
viewModel.getItems().observe(this, result -> result
|
||||
.onError(this::handleException)
|
||||
.onSuccess(this::displayItems)
|
||||
);
|
||||
|
||||
sharingController.setSharingListener(this);
|
||||
loadSharingContacts();
|
||||
}
|
||||
viewModel.getSharingInfo().observe(this, this::setToolbarSubTitle);
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MessageId getFirstVisibleMessageId() {
|
||||
if (layoutManager != null && adapter != null) {
|
||||
int position =
|
||||
layoutManager.findFirstVisibleItemPosition();
|
||||
I i = adapter.getItemAt(position);
|
||||
return i == null ? null : i.getId();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract A createAdapter(LinearLayoutManager layoutManager);
|
||||
|
||||
protected void loadNamedGroup() {
|
||||
getController().loadNamedGroup(
|
||||
new UiResultExceptionHandler<G, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(G groupItem) {
|
||||
onNamedGroupLoaded(groupItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
protected abstract void onNamedGroupLoaded(G groupItem);
|
||||
|
||||
protected void loadItems() {
|
||||
int revision = adapter.getRevision();
|
||||
getController().loadItems(
|
||||
new UiResultExceptionHandler<ThreadItemList<I>, DbException>(
|
||||
this) {
|
||||
@Override
|
||||
public void onResultUi(ThreadItemList<I> items) {
|
||||
if (revision == adapter.getRevision()) {
|
||||
adapter.incrementRevision();
|
||||
if (items.isEmpty()) {
|
||||
list.showData();
|
||||
} else {
|
||||
displayItems(items);
|
||||
updateTextInput();
|
||||
}
|
||||
} else {
|
||||
LOG.info("Concurrent update, reloading");
|
||||
loadItems();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayItems(ThreadItemList<I> items) {
|
||||
adapter.setItems(items);
|
||||
MessageId messageId = items.getFirstVisibleItemId();
|
||||
if (messageId != null)
|
||||
adapter.setItemWithIdVisible(messageId);
|
||||
list.showData();
|
||||
if (layoutManagerState == null) {
|
||||
list.scrollToPosition(0); // Scroll to the top
|
||||
} else {
|
||||
layoutManager.onRestoreInstanceState(layoutManagerState);
|
||||
}
|
||||
}
|
||||
|
||||
protected void loadSharingContacts() {
|
||||
getController().loadSharingContacts(
|
||||
new UiResultExceptionHandler<Collection<ContactId>, DbException>(
|
||||
this) {
|
||||
@Override
|
||||
public void onResultUi(Collection<ContactId> contacts) {
|
||||
sharingController.addAll(contacts);
|
||||
int online = sharingController.getOnlineCount();
|
||||
setToolbarSubTitle(contacts.size(), online);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
viewModel.getGroupRemoved().observe(this, removed -> {
|
||||
if (removed) supportFinishAfterTransition();
|
||||
});
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
sharingController.onStart();
|
||||
loadItems();
|
||||
getViewModel().blockNotifications();
|
||||
list.startPeriodicUpdate();
|
||||
}
|
||||
|
||||
@@ -232,91 +118,98 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
sharingController.onStop();
|
||||
getViewModel().unblockNotifications();
|
||||
list.stopPeriodicUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (layoutManager != null) {
|
||||
layoutManagerState = layoutManager.onSaveInstanceState();
|
||||
outState.putParcelable("layoutManager", layoutManagerState);
|
||||
}
|
||||
if (replyId != null) {
|
||||
outState.putByteArray(KEY_REPLY_ID, replyId.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
layoutManagerState = savedInstanceState.getParcelable("layoutManager");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
supportFinishAfterTransition();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
supportFinishAfterTransition();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (adapter.getHighlightedItem() != null) {
|
||||
textInput.clearText();
|
||||
replyId = null;
|
||||
getViewModel().setReplyId(null);
|
||||
updateTextInput();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
// store list position, so we can restore it when coming back here
|
||||
if (layoutManager != null && adapter != null) {
|
||||
MessageId id = adapter.getFirstVisibleMessageId(layoutManager);
|
||||
getViewModel().storeMessageId(id);
|
||||
}
|
||||
}
|
||||
|
||||
protected void displayItems(List<I> items) {
|
||||
if (items.isEmpty()) {
|
||||
list.showData();
|
||||
} else {
|
||||
adapter.submitList(items, () -> {
|
||||
// do stuff *after* list had been updated
|
||||
scrollAfterListCommitted();
|
||||
updateTextInput();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls to the first visible item last time the activity was open,
|
||||
* if one exists and this is the first time, the list gets displayed.
|
||||
* Or scrolls to a locally added item that has just been added to the list.
|
||||
*/
|
||||
private void scrollAfterListCommitted() {
|
||||
MessageId restoredFirstVisibleItemId =
|
||||
getViewModel().getAndResetRestoredMessageId();
|
||||
MessageId scrollToItem =
|
||||
getViewModel().getAndResetScrollToItem();
|
||||
if (restoredFirstVisibleItemId != null) {
|
||||
scrollToItemAtTop(restoredFirstVisibleItemId);
|
||||
} else if (scrollToItem != null) {
|
||||
scrollToItemAtTop(scrollToItem);
|
||||
}
|
||||
scrollListener.updateUnreadButtons(layoutManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReplyClick(I item) {
|
||||
replyId = item.getId();
|
||||
getViewModel().setReplyId(item.getId());
|
||||
updateTextInput();
|
||||
// FIXME This does not work for a hardware keyboard
|
||||
if (textInput.isKeyboardOpen()) {
|
||||
scrollToItemAtTop(item);
|
||||
scrollToItemAtTop(item.getId());
|
||||
} else {
|
||||
// wait with scrolling until keyboard opened
|
||||
textInput.setOnKeyboardShownListener(() -> {
|
||||
scrollToItemAtTop(item);
|
||||
scrollToItemAtTop(item.getId());
|
||||
textInput.setOnKeyboardShownListener(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharingInfoUpdated(int total, int online) {
|
||||
setToolbarSubTitle(total, online);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInvitationAccepted(ContactId c) {
|
||||
sharingController.add(c);
|
||||
setToolbarSubTitle(sharingController.getTotalCount(),
|
||||
sharingController.getOnlineCount());
|
||||
}
|
||||
|
||||
protected void setToolbarSubTitle(int total, int online) {
|
||||
protected void setToolbarSubTitle(SharingInfo sharingInfo) {
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setSubtitle(
|
||||
getString(R.string.shared_with, total, online));
|
||||
actionBar.setSubtitle(getString(R.string.shared_with,
|
||||
sharingInfo.total, sharingInfo.online));
|
||||
}
|
||||
}
|
||||
|
||||
private void scrollToItemAtTop(I item) {
|
||||
int position = adapter.findItemPosition(item);
|
||||
private void scrollToItemAtTop(MessageId messageId) {
|
||||
int position = adapter.findItemPosition(messageId);
|
||||
if (position != NO_POSITION) {
|
||||
layoutManager
|
||||
.scrollToPositionWithOffset(position, 0);
|
||||
layoutManager.scrollToPositionWithOffset(position, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,6 +220,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
||||
}
|
||||
|
||||
private void updateTextInput() {
|
||||
MessageId replyId = getViewModel().getReplyId();
|
||||
if (replyId != null) {
|
||||
textInput.setHint(R.string.forum_message_reply_hint);
|
||||
textInput.showSoftKeyboard();
|
||||
@@ -341,54 +235,14 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
||||
List<AttachmentHeader> headers) {
|
||||
if (isNullOrEmpty(text)) throw new AssertionError();
|
||||
|
||||
I replyItem = adapter.getHighlightedItem();
|
||||
UiResultExceptionHandler<I, DbException> handler =
|
||||
new UiResultExceptionHandler<I, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(I result) {
|
||||
addItem(result, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleException(exception);
|
||||
}
|
||||
};
|
||||
getController().createAndStoreMessage(text, replyItem, handler);
|
||||
MessageId replyId = getViewModel().getReplyId();
|
||||
getViewModel().createAndStoreMessage(text, replyId);
|
||||
textInput.hideSoftKeyboard();
|
||||
textInput.clearText();
|
||||
replyId = null;
|
||||
getViewModel().setReplyId(null);
|
||||
updateTextInput();
|
||||
}
|
||||
|
||||
protected abstract int getMaxTextLength();
|
||||
|
||||
@Override
|
||||
public void onItemReceived(I item) {
|
||||
addItem(item, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGroupRemoved() {
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
private void addItem(I item, boolean isLocal) {
|
||||
adapter.incrementRevision();
|
||||
MessageId parent = item.getParentId();
|
||||
if (parent != null && !adapter.contains(parent)) {
|
||||
// We've incremented the adapter's revision, so the item will be
|
||||
// loaded when its parent has been loaded
|
||||
LOG.info("Ignoring item with missing parent");
|
||||
return;
|
||||
}
|
||||
adapter.add(item);
|
||||
|
||||
if (isLocal) {
|
||||
scrollToItemAtTop(item);
|
||||
} else {
|
||||
scrollListener.updateUnreadButtons(layoutManager);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.briar.api.client.NamedGroup;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface ThreadListController<G extends NamedGroup, I extends ThreadItem>
|
||||
extends ActivityLifecycleController {
|
||||
|
||||
void setGroupId(GroupId groupId);
|
||||
|
||||
void loadNamedGroup(ResultExceptionHandler<G, DbException> handler);
|
||||
|
||||
void loadSharingContacts(
|
||||
ResultExceptionHandler<Collection<ContactId>, DbException> handler);
|
||||
|
||||
void loadItems(
|
||||
ResultExceptionHandler<ThreadItemList<I>, DbException> handler);
|
||||
|
||||
void markItemRead(I item);
|
||||
|
||||
void markItemsRead(Collection<I> items);
|
||||
|
||||
void createAndStoreMessage(String text, @Nullable I parentItem,
|
||||
ResultExceptionHandler<I, DbException> handler);
|
||||
|
||||
void deleteNamedGroup(ExceptionHandler<DbException> handler);
|
||||
|
||||
interface ThreadListListener<I> extends ThreadListDataSource {
|
||||
|
||||
@UiThread
|
||||
void onItemReceived(I item);
|
||||
|
||||
@UiThread
|
||||
void onGroupRemoved();
|
||||
|
||||
@UiThread
|
||||
void onInvitationAccepted(ContactId c);
|
||||
}
|
||||
|
||||
interface ThreadListDataSource {
|
||||
|
||||
@UiThread @Nullable
|
||||
MessageId getFirstVisibleMessageId();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
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.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.android.controller.DbControllerImpl;
|
||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.NamedGroup;
|
||||
import org.briarproject.briar.api.client.PostHeader;
|
||||
import org.briarproject.briar.api.client.ThreadedMessage;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends ThreadItem, H extends PostHeader, M extends ThreadedMessage, L extends ThreadListListener<I>>
|
||||
extends DbControllerImpl
|
||||
implements ThreadListController<G, I>, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ThreadListControllerImpl.class.getName());
|
||||
|
||||
private final EventBus eventBus;
|
||||
private final MessageTracker messageTracker;
|
||||
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
||||
|
||||
private volatile GroupId groupId;
|
||||
|
||||
protected final IdentityManager identityManager;
|
||||
protected final AndroidNotificationManager notificationManager;
|
||||
protected final Executor cryptoExecutor;
|
||||
protected final Clock clock;
|
||||
|
||||
// UI thread
|
||||
protected L listener;
|
||||
|
||||
protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager, IdentityManager identityManager,
|
||||
@CryptoExecutor Executor cryptoExecutor, EventBus eventBus,
|
||||
Clock clock, AndroidNotificationManager notificationManager,
|
||||
MessageTracker messageTracker) {
|
||||
super(dbExecutor, lifecycleManager);
|
||||
this.identityManager = identityManager;
|
||||
this.cryptoExecutor = cryptoExecutor;
|
||||
this.notificationManager = notificationManager;
|
||||
this.clock = clock;
|
||||
this.eventBus = eventBus;
|
||||
this.messageTracker = messageTracker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGroupId(GroupId groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onActivityCreate(Activity activity) {
|
||||
listener = (L) activity;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onActivityStart() {
|
||||
notificationManager.blockNotification(getGroupId());
|
||||
eventBus.addListener(this);
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onActivityStop() {
|
||||
notificationManager.unblockNotification(getGroupId());
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroy() {
|
||||
MessageId messageId = listener.getFirstVisibleMessageId();
|
||||
if (messageId != null) {
|
||||
dbExecutor.execute(() -> {
|
||||
try {
|
||||
messageTracker.storeMessageId(groupId, messageId);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof GroupRemovedEvent) {
|
||||
GroupRemovedEvent s = (GroupRemovedEvent) e;
|
||||
if (s.getGroup().getId().equals(getGroupId())) {
|
||||
LOG.info("Group removed");
|
||||
listener.onGroupRemoved();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadNamedGroup(
|
||||
ResultExceptionHandler<G, DbException> handler) {
|
||||
checkGroupId();
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
G groupItem = loadNamedGroup();
|
||||
logDuration(LOG, "Loading group", start);
|
||||
handler.onResult(groupItem);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
handler.onException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
protected abstract G loadNamedGroup() throws DbException;
|
||||
|
||||
@Override
|
||||
public void loadItems(
|
||||
ResultExceptionHandler<ThreadItemList<I>, DbException> handler) {
|
||||
checkGroupId();
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
// Load headers
|
||||
long start = now();
|
||||
Collection<H> headers = loadHeaders();
|
||||
logDuration(LOG, "Loading headers", start);
|
||||
|
||||
// Load bodies into cache
|
||||
start = now();
|
||||
for (H header : headers) {
|
||||
if (!textCache.containsKey(header.getId())) {
|
||||
textCache.put(header.getId(),
|
||||
loadMessageText(header));
|
||||
}
|
||||
}
|
||||
logDuration(LOG, "Loading bodies", start);
|
||||
|
||||
// Build and hand over items
|
||||
handler.onResult(buildItems(headers));
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
handler.onException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
protected abstract Collection<H> loadHeaders() throws DbException;
|
||||
|
||||
@DatabaseExecutor
|
||||
protected abstract String loadMessageText(H header) throws DbException;
|
||||
|
||||
@Override
|
||||
public void markItemRead(I item) {
|
||||
markItemsRead(Collections.singletonList(item));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markItemsRead(Collection<I> items) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
for (I i : items) {
|
||||
markRead(i.getId());
|
||||
}
|
||||
logDuration(LOG, "Marking read", start);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
protected abstract void markRead(MessageId id) throws DbException;
|
||||
|
||||
protected void storePost(M msg, String text,
|
||||
ResultExceptionHandler<I, DbException> resultHandler) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
H header = addLocalMessage(msg);
|
||||
textCache.put(msg.getMessage().getId(), text);
|
||||
logDuration(LOG, "Storing message", start);
|
||||
resultHandler.onResult(buildItem(header, text));
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
resultHandler.onException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
protected abstract H addLocalMessage(M message) throws DbException;
|
||||
|
||||
@Override
|
||||
public void deleteNamedGroup(ExceptionHandler<DbException> handler) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
G groupItem = loadNamedGroup();
|
||||
deleteNamedGroup(groupItem);
|
||||
logDuration(LOG, "Removing group", start);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
handler.onException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
protected abstract void deleteNamedGroup(G groupItem) throws DbException;
|
||||
|
||||
private ThreadItemList<I> buildItems(Collection<H> headers)
|
||||
throws DbException {
|
||||
ThreadItemList<I> items = new ThreadItemListImpl<>();
|
||||
for (H h : headers) {
|
||||
items.add(buildItem(h, textCache.get(h.getId())));
|
||||
}
|
||||
MessageId msgId = messageTracker.loadStoredMessageId(groupId);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loaded last top visible message id " + msgId);
|
||||
items.setFirstVisibleId(msgId);
|
||||
return items;
|
||||
}
|
||||
|
||||
protected abstract I buildItem(H header, String text);
|
||||
|
||||
protected GroupId getGroupId() {
|
||||
checkGroupId();
|
||||
return groupId;
|
||||
}
|
||||
|
||||
private void checkGroupId() {
|
||||
if (groupId == null) throw new IllegalStateException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
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.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.android.sharing.SharingController;
|
||||
import org.briarproject.briar.android.sharing.SharingController.SharingInfo;
|
||||
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||
import org.briarproject.briar.android.viewmodel.LiveResult;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.MessageTree;
|
||||
import org.briarproject.briar.api.client.PostHeader;
|
||||
import org.briarproject.briar.client.MessageTreeImpl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public abstract class ThreadListViewModel<I extends ThreadItem>
|
||||
extends DbViewModel
|
||||
implements EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ThreadListViewModel.class.getName());
|
||||
|
||||
protected final IdentityManager identityManager;
|
||||
protected final AndroidNotificationManager notificationManager;
|
||||
protected final SharingController sharingController;
|
||||
protected final Executor cryptoExecutor;
|
||||
protected final Clock clock;
|
||||
private final MessageTracker messageTracker;
|
||||
private final EventBus eventBus;
|
||||
|
||||
@DatabaseExecutor
|
||||
private final MessageTree<I> messageTree = new MessageTreeImpl<>();
|
||||
private final MutableLiveData<LiveResult<List<I>>> items =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> groupRemoved =
|
||||
new MutableLiveData<>();
|
||||
private final AtomicReference<MessageId> scrollToItem =
|
||||
new AtomicReference<>();
|
||||
|
||||
protected volatile GroupId groupId;
|
||||
@Nullable
|
||||
private MessageId replyId;
|
||||
private final AtomicReference<MessageId> storedMessageId =
|
||||
new AtomicReference<>();
|
||||
|
||||
public ThreadListViewModel(Application application,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager,
|
||||
TransactionManager db,
|
||||
AndroidExecutor androidExecutor,
|
||||
IdentityManager identityManager,
|
||||
AndroidNotificationManager notificationManager,
|
||||
SharingController sharingController,
|
||||
@CryptoExecutor Executor cryptoExecutor,
|
||||
Clock clock,
|
||||
MessageTracker messageTracker,
|
||||
EventBus eventBus) {
|
||||
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||
this.identityManager = identityManager;
|
||||
this.notificationManager = notificationManager;
|
||||
this.cryptoExecutor = cryptoExecutor;
|
||||
this.clock = clock;
|
||||
this.sharingController = sharingController;
|
||||
this.messageTracker = messageTracker;
|
||||
this.eventBus = eventBus;
|
||||
this.eventBus.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
eventBus.removeListener(this);
|
||||
sharingController.onCleared();
|
||||
}
|
||||
|
||||
/**
|
||||
* Needs to be called right after initialization,
|
||||
* before calling any other methods.
|
||||
*/
|
||||
@CallSuper
|
||||
public void setGroupId(GroupId groupId) {
|
||||
this.groupId = groupId;
|
||||
loadStoredMessageId();
|
||||
loadItems();
|
||||
loadSharingContacts();
|
||||
}
|
||||
|
||||
public void blockNotifications() {
|
||||
notificationManager.blockNotification(groupId);
|
||||
}
|
||||
|
||||
public void unblockNotifications() {
|
||||
notificationManager.unblockNotification(groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof GroupRemovedEvent) {
|
||||
GroupRemovedEvent s = (GroupRemovedEvent) e;
|
||||
if (s.getGroup().getId().equals(groupId)) {
|
||||
LOG.info("Group removed");
|
||||
groupRemoved.setValue(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadStoredMessageId() {
|
||||
runOnDbThreadOrLogException(() -> {
|
||||
storedMessageId.set(messageTracker.loadStoredMessageId(groupId));
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Loaded last top visible message id " +
|
||||
storedMessageId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public abstract void loadItems();
|
||||
|
||||
public abstract void createAndStoreMessage(String text,
|
||||
@Nullable MessageId parentMessageId);
|
||||
|
||||
/**
|
||||
* Loads the ContactIds of all contacts the group is shared with
|
||||
* and adds them to {@link SharingController}.
|
||||
*/
|
||||
public abstract void loadSharingContacts();
|
||||
|
||||
@UiThread
|
||||
protected void setItems(LiveResult<List<I>> items) {
|
||||
this.items.setValue(items);
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
protected <H extends PostHeader> List<I> createItems(
|
||||
Transaction txn, Collection<H> headers, ItemGetter<H, I> itemGetter)
|
||||
throws DbException {
|
||||
long start = now();
|
||||
List<I> items = new ArrayList<>();
|
||||
for (H header : headers) {
|
||||
String text = loadMessageText(txn, header);
|
||||
items.add(itemGetter.getItem(header, text));
|
||||
}
|
||||
logDuration(LOG, "Loading bodies and creating items", start);
|
||||
|
||||
messageTree.clear();
|
||||
messageTree.add(items);
|
||||
return messageTree.depthFirstOrder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a remote item on the UI thread.
|
||||
* The list will not scroll, but show an unread indicator.
|
||||
*/
|
||||
@UiThread
|
||||
protected void addItem(I item) {
|
||||
messageTree.add(item);
|
||||
items.setValue(new LiveResult<>(messageTree.depthFirstOrder()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a local item from the DB thread.
|
||||
* The list will scroll to the new item.
|
||||
*/
|
||||
@DatabaseExecutor
|
||||
protected void addItemAsync(I item) {
|
||||
messageTree.add(item);
|
||||
scrollToItem.set(item.getId());
|
||||
items.postValue(new LiveResult<>(messageTree.depthFirstOrder()));
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
protected abstract String loadMessageText(Transaction txn,
|
||||
PostHeader header) throws DbException;
|
||||
|
||||
@UiThread
|
||||
public void setReplyId(@Nullable MessageId id) {
|
||||
replyId = id;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Nullable
|
||||
public MessageId getReplyId() {
|
||||
return replyId;
|
||||
}
|
||||
|
||||
void storeMessageId(@Nullable MessageId messageId) {
|
||||
if (messageId != null) {
|
||||
runOnDbThreadOrLogException(() ->
|
||||
messageTracker.storeMessageId(groupId, messageId));
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void markItemRead(I item);
|
||||
|
||||
@Nullable
|
||||
MessageId getAndResetRestoredMessageId() {
|
||||
return storedMessageId.getAndSet(null);
|
||||
}
|
||||
|
||||
LiveData<LiveResult<List<I>>> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
LiveData<SharingInfo> getSharingInfo() {
|
||||
return sharingController.getSharingInfo();
|
||||
}
|
||||
|
||||
LiveData<Boolean> getGroupRemoved() {
|
||||
return groupRemoved;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
MessageId getAndResetScrollToItem() {
|
||||
return scrollToItem.getAndSet(null);
|
||||
}
|
||||
|
||||
public interface ItemGetter<H extends PostHeader, I> {
|
||||
I getItem(H header, String text);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,15 +20,15 @@ class ThreadScrollListener<I extends ThreadItem>
|
||||
private static final Logger LOG =
|
||||
getLogger(ThreadScrollListener.class.getName());
|
||||
|
||||
private final ThreadListController<?, I> controller;
|
||||
private final ThreadListViewModel<I> viewModel;
|
||||
private final UnreadMessageButton upButton, downButton;
|
||||
|
||||
ThreadScrollListener(ThreadItemAdapter<I> adapter,
|
||||
ThreadListController<?, I> controller,
|
||||
ThreadListViewModel<I> viewModel,
|
||||
UnreadMessageButton upButton,
|
||||
UnreadMessageButton downButton) {
|
||||
super(adapter);
|
||||
this.controller = controller;
|
||||
this.viewModel = viewModel;
|
||||
this.upButton = upButton;
|
||||
this.downButton = downButton;
|
||||
}
|
||||
@@ -44,7 +44,7 @@ class ThreadScrollListener<I extends ThreadItem>
|
||||
protected void onItemVisible(I item) {
|
||||
if (!item.isRead()) {
|
||||
item.setRead(true);
|
||||
controller.markItemRead(item);
|
||||
viewModel.markItemRead(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@ package org.briarproject.briar.android.viewmodel;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import org.briarproject.bramble.api.ThrowingRunnable;
|
||||
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.DbRunnable;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
@@ -23,6 +25,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.arch.core.util.Function;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -57,14 +60,15 @@ public abstract class DbViewModel extends AndroidViewModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given task on the {@link DatabaseExecutor}
|
||||
* and waits for the DB to open.
|
||||
* Waits for the DB to open and runs the given task on the
|
||||
* {@link DatabaseExecutor}.
|
||||
* <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) {
|
||||
protected void runOnDbThread(ThrowingRunnable<Exception> task,
|
||||
Consumer<Exception> err) {
|
||||
dbExecutor.execute(() -> {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
@@ -72,10 +76,63 @@ public abstract class DbViewModel extends AndroidViewModel {
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting for database");
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (Exception e) {
|
||||
err.accept(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the DB to open and runs the given task on the
|
||||
* {@link DatabaseExecutor}.
|
||||
* <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(boolean readOnly,
|
||||
DbRunnable<Exception> task, Consumer<Exception> err) {
|
||||
dbExecutor.execute(() -> {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
db.transaction(readOnly, task);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting for database");
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (Exception e) {
|
||||
err.accept(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the DB to open and runs the given task on the
|
||||
* {@link DatabaseExecutor}. If the task throws a {@link DbException}
|
||||
* it's caught and logged.
|
||||
* <p>
|
||||
* If you need a list of items to be displayed in a
|
||||
* {@link RecyclerView.Adapter},
|
||||
* use {@link #loadList(DbCallable, UiConsumer)} instead.
|
||||
*/
|
||||
protected void runOnDbThreadOrLogException(boolean readOnly,
|
||||
DbRunnable<Exception> task) {
|
||||
runOnDbThread(readOnly, task, e -> logException(LOG, WARNING, e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the DB to open and runs the given task on the
|
||||
* {@link DatabaseExecutor}. If the task throws a {@link DbException}
|
||||
* it's caught and logged.
|
||||
* <p>
|
||||
* If you need a list of items to be displayed in a
|
||||
* {@link RecyclerView.Adapter},
|
||||
* use {@link #loadList(DbCallable, UiConsumer)} instead.
|
||||
*/
|
||||
protected void runOnDbThreadOrLogException(
|
||||
ThrowingRunnable<Exception> task) {
|
||||
runOnDbThread(task, e -> logException(LOG, WARNING, e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a list of items on the {@link DatabaseExecutor} within a single
|
||||
* {@link Transaction} and publishes it as a {@link LiveResult}
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="499.24373dp"
|
||||
android:height="175.49413dp"
|
||||
android:viewportWidth="499.24373"
|
||||
android:viewportHeight="175.49413">
|
||||
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M459.809,171.16 L455.482,166.826 L448.412,164.399
|
||||
C444.524,163.064,439.689,161.403,437.668,160.708 L433.994,159.445
|
||||
L437.518,159.272 C445.76,158.868,450.506,155.265,451.854,148.388
|
||||
C452.65,144.327,452.983,131.09,452.684,115.347
|
||||
C452.531,107.263,452.518,99.9306,452.656,99.0522
|
||||
C452.794,98.1738,453.191,96.6321,453.539,95.6262
|
||||
C454.761,92.0938,453.879,83.7179,451.681,77.9947
|
||||
C451.333,77.0872,449.747,73.7797,448.157,70.6447
|
||||
C443.999,62.4444,443.652,61.7028,443.264,60.1606
|
||||
C442.805,58.3392,443.052,54.7253,443.726,53.3947
|
||||
C444.392,52.0797,445.883,50.4785,446.972,49.9081
|
||||
C448.271,49.2279,449.588,50.0301,451.658,52.7632
|
||||
C455.08,57.2792,465.659,72.2041,467.39,74.9562
|
||||
C471.136,80.9126,473.346,86.1212,475.362,93.7447
|
||||
C476.017,96.2197,477.247,100.742,478.097,103.795
|
||||
C478.946,106.847,480.811,113.953,482.241,119.585 L484.841,129.825
|
||||
L489.491,135.018 C492.048,137.874,495.288,141.482,496.691,143.035
|
||||
C498.093,144.588,499.241,146.01,499.241,146.196
|
||||
C499.241,146.531,464.741,175.495,464.342,175.495
|
||||
C464.227,175.495,462.186,173.545,459.807,171.161 Z M365.944,154.772
|
||||
C364.687,154.144,363.739,153.393,363.03,152.465
|
||||
C360.941,149.729,361.08,154.834,361.166,83.9825 L361.244,19.9437
|
||||
L361.905,18.7088 C362.92,16.8111,363.897,15.7979,365.645,14.8299
|
||||
L367.245,13.9437 L405.029,13.8641
|
||||
C447.485,13.7747,444.431,13.6158,447.145,16.056
|
||||
C448.02,16.8431,448.902,18.0155,449.329,18.9598
|
||||
C450.042,20.5368,450.045,20.5982,450.135,33.1639 L450.225,45.7841
|
||||
L448.427,45.654 C447.001,45.5509,446.346,45.661,445.261,46.1861
|
||||
C443.248,47.1606,441.321,49.0848,440.146,51.2936 L439.109,53.2436
|
||||
L439.102,40.4186 L439.095,27.5936 L405.495,27.5936 L371.895,27.5936
|
||||
L371.895,78.8936 L371.895,130.194 L405.494,130.194 L439.093,130.194
|
||||
L439.169,95.769 C439.242,62.8088,439.267,61.4078,439.76,62.844
|
||||
C440.043,63.669,441.566,66.8415,443.143,69.894
|
||||
C448.636,80.5202,448.548,79.7541,448.539,117.097
|
||||
C448.532,144.718,448.415,147.048,446.877,150.206
|
||||
C445.806,152.405,444.166,153.916,441.819,154.866 L440.145,155.543
|
||||
L403.977,155.623 L367.809,155.703 L365.945,154.772 Z M408.344,149.734
|
||||
C411.215,148.425,412.802,146.056,412.774,143.117
|
||||
C412.735,139.001,409.665,135.894,405.638,135.894
|
||||
C403.518,135.894,402.071,136.503,400.474,138.066
|
||||
C398.229,140.264,397.72,143.505,399.173,146.353
|
||||
C399.886,147.751,401.694,149.362,403.135,149.883
|
||||
C404.632,150.425,406.975,150.358,408.344,149.734 Z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M39.4343,171.16 L43.7609,166.826 L50.8305,164.399
|
||||
C54.7188,163.064,59.5534,161.403,61.574,160.708 L65.248,159.445 L61.724,159.272
|
||||
C53.4822,158.868,48.7362,155.265,47.3877,148.388
|
||||
C46.5914,144.327,46.2588,131.09,46.5575,115.347
|
||||
C46.7109,107.263,46.7237,99.9306,46.5859,99.0522
|
||||
C46.4482,98.1738,46.0506,96.6321,45.7025,95.6262
|
||||
C44.48,92.0938,45.3625,83.7179,47.5602,77.9947
|
||||
C47.9087,77.0872,49.4945,73.7797,51.0844,70.6447
|
||||
C55.2429,62.4444,55.589,61.7028,55.9773,60.1606
|
||||
C56.4359,58.3392,56.1889,54.7253,55.5148,53.3947
|
||||
C54.8486,52.0797,53.3578,50.4785,52.2686,49.9081
|
||||
C50.9697,49.2279,49.6529,50.0301,47.5822,52.7632
|
||||
C44.1607,57.2792,33.5808,72.2041,31.8503,74.9562
|
||||
C28.1048,80.9126,25.8947,86.1212,23.8781,93.7447
|
||||
C23.2234,96.2197,21.9929,100.742,21.1435,103.795
|
||||
C20.2942,106.847,18.4295,113.953,16.9996,119.585 L14.4,129.824 L9.75,135.017
|
||||
C7.1925,137.873,3.9525,141.481,2.55,143.034 C1.1475,144.587,0,146.009,0,146.195
|
||||
C0,146.53,34.4996,175.494,34.8991,175.494
|
||||
C35.0138,175.494,37.0547,173.544,39.4343,171.16 Z M133.299,154.772
|
||||
C134.557,154.144,135.505,153.393,136.213,152.465
|
||||
C138.302,149.729,138.163,154.834,138.077,83.9825 L137.999,19.9437
|
||||
L137.338,18.7088 C136.323,16.8111,135.346,15.7979,133.598,14.8299
|
||||
L131.998,13.9437 L94.2141,13.8641
|
||||
C51.7582,13.7747,54.8117,13.6158,52.0976,16.056
|
||||
C51.2222,16.8431,50.3404,18.0155,49.9137,18.9598
|
||||
C49.2011,20.5368,49.1976,20.5982,49.1081,33.1639 L49.0182,45.7841
|
||||
L50.8164,45.654 C52.2422,45.5509,52.8978,45.661,53.9824,46.1861
|
||||
C55.9953,47.1606,57.9223,49.0848,59.0972,51.2936 L60.1344,53.2436
|
||||
L60.1414,40.4186 L60.1484,27.5936 L93.7484,27.5936 L127.348,27.5936
|
||||
L127.348,78.8936 L127.348,130.194 L93.7493,130.194 L60.1506,130.194
|
||||
L60.0743,95.769 C60.0013,62.8088,59.9761,61.4078,59.4833,62.844
|
||||
C59.2002,63.669,57.6777,66.8415,56.0999,69.894
|
||||
C50.6074,80.5202,50.695,79.7541,50.7039,117.097
|
||||
C50.7109,144.718,50.8275,147.048,52.3659,150.206
|
||||
C53.4373,152.405,55.0773,153.916,57.4241,154.866 L59.0979,155.543
|
||||
L95.2656,155.623 L131.433,155.703 L133.297,154.772 Z M90.8996,149.734
|
||||
C88.0284,148.425,86.4419,146.056,86.4696,143.117
|
||||
C86.5084,139.001,89.5784,135.894,93.606,135.894
|
||||
C95.7256,135.894,97.1733,136.503,98.7696,138.066
|
||||
C101.015,140.264,101.524,143.505,100.071,146.353
|
||||
C99.3579,147.751,97.5496,149.362,96.1089,149.883
|
||||
C94.6118,150.425,92.2689,150.358,90.9,149.734 Z" />
|
||||
<path
|
||||
android:fillColor="#0a3d91"
|
||||
android:pathData="M247.254,75.9792 L251.99,75.9792
|
||||
C265.155,75.9792,275.753,86.5777,275.753,99.7427 L275.753,131.917
|
||||
C275.753,145.082,265.154,155.68,251.99,155.68 L247.254,155.68
|
||||
C234.089,155.68,223.491,145.081,223.491,131.917 L223.491,99.7424
|
||||
C223.491,86.5774,234.09,75.9789,247.254,75.9789 Z" />
|
||||
<path
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeWidth="4.32805729"
|
||||
android:pathData="M236.311,102.927 L261.218,127.997 L249.214,142.533 L249.214,90.5963
|
||||
L261.218,104.479 L236.311,128.896" />
|
||||
<path
|
||||
android:fillColor="#0a3d91"
|
||||
android:strokeWidth="7.55000019"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeLineCap="round"
|
||||
android:pathData="M143.679,27.5571 C142.089,30.1809,143.861,33.1318,146.464,34.1998
|
||||
C149.159,35.7722,152.635,39.187,155.612,36.432
|
||||
C157.845,34.3925,156.213,30.7041,153.571,29.7617
|
||||
C150.507,28.085,147.019,24.2469,143.68,27.5572 Z M351.705,27.1294
|
||||
C348.937,29.09,344.636,29.8244,343.327,33.1721
|
||||
C342.685,35.9031,345.263,38.1647,347.911,37.5986
|
||||
C351.113,35.9607,354.968,34.7562,357.089,31.6509
|
||||
C357.658,29.0465,355.596,26.3002,352.821,26.9052
|
||||
C352.405,26.7975,352.061,26.9402,351.704,27.1294 Z M324.943,40.3012
|
||||
C322.009,42.0092,317.623,42.224,316.012,45.4542
|
||||
C315.104,48.1085,317.444,50.6134,320.136,50.3152
|
||||
C323.492,49.0109,327.456,48.2025,329.873,45.3129
|
||||
C330.687,42.7752,328.896,39.8433,326.077,40.184
|
||||
C325.673,40.0383,325.317,40.1458,324.944,40.3011 Z M169.707,42.7641
|
||||
C168.488,45.476,170.464,48.2246,173.151,48.9095
|
||||
C176.009,49.9903,179.82,53.0273,182.463,50.1434
|
||||
C184.778,48.1069,182.802,44.308,180.053,43.7252
|
||||
C176.725,42.5494,172.668,39.0116,169.707,42.7641 Z M296.98,50.3813
|
||||
C293.857,51.6905,289.488,51.352,287.488,54.3514
|
||||
C286.256,56.8701,288.261,59.6497,290.968,59.6911
|
||||
C294.455,58.7838,298.535,58.5783,301.271,55.952
|
||||
C302.385,53.5298,300.959,50.4059,298.119,50.401
|
||||
C297.736,50.2053,297.369,50.274,296.98,50.3813 Z M199.49,51.9262
|
||||
C197.261,53.388,196.81,56.7921,199.214,58.3031
|
||||
C201.801,59.7737,205.129,60.258,208.054,60.7984
|
||||
C210.839,61.1306,212.8,58.3192,211.945,55.7497
|
||||
C209.902,52.47,205.473,53.0161,202.228,51.6605
|
||||
C201.315,51.749,200.402,51.8376,199.49,51.9261 Z M268.004,56.434
|
||||
C264.728,57.2229,260.543,56.2461,258.068,58.816
|
||||
C256.418,61.0841,257.911,64.1697,260.572,64.6779
|
||||
C264.184,64.3855,268.29,64.7686,271.412,62.5839
|
||||
C272.874,60.3563,271.935,57.0522,269.127,56.6245
|
||||
C268.778,56.3753,268.405,56.3852,268.004,56.434 Z M228.555,57.852
|
||||
C226.622,59.6879,226.79,63.1186,229.426,64.1735
|
||||
C232.235,65.1623,235.597,65.0202,238.57,65.0349
|
||||
C241.369,64.8621,242.796,61.7451,241.494,59.3702
|
||||
C238.89,56.5186,234.647,57.8284,231.201,57.1003
|
||||
C230.319,57.3509,229.437,57.6015,228.555,57.8521 Z" />
|
||||
</vector>
|
||||
@@ -1,125 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_large">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepOne"
|
||||
style="@style/StepBubble"
|
||||
android:text="@string/step_1"
|
||||
app:layout_constraintBottom_toTopOf="@+id/stepOneText"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepOneText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:text="Pick contact"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/stepOne" />
|
||||
|
||||
<View
|
||||
android:id="@+id/stepConnector"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:alpha="0.5"
|
||||
android:background="@color/briar_accent"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/stepOne"
|
||||
app:layout_constraintEnd_toStartOf="@+id/stepTwo"
|
||||
app:layout_constraintStart_toEndOf="@+id/stepOne"
|
||||
app:layout_constraintTop_toTopOf="@+id/stepOne" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepTwo"
|
||||
style="@style/StepBubble.Upcoming"
|
||||
android:text="@string/step_2"
|
||||
app:layout_constraintBottom_toTopOf="@+id/stepTwoText"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline"
|
||||
app:layout_constraintTop_toTopOf="@+id/stepOne"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepTwoText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:alpha="0.5"
|
||||
android:text="Wait for setup"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/stepTwo" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/discoveryText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="Pick the nearby contact to perform the setup with."
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/contacts"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/stepOneText" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/contacts"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginVertical="32dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/continueButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/discoveryText" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/continueButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/continue_button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:enabled="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/commentTextView2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="148dp"
|
||||
android:layout_marginLeft="148dp"
|
||||
android:layout_marginTop="124dp"
|
||||
android:text="list of all [contact-name-and-avatar]"
|
||||
android:textColor="@color/briar_red_500"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
@@ -1,112 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_large">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/stepOne"
|
||||
style="@style/StepBubble.Completed"
|
||||
app:layout_constraintBottom_toTopOf="@+id/stepOneText"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepOneText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:text="Pick contact"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/stepOne" />
|
||||
|
||||
<View
|
||||
android:id="@+id/stepConnector"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="@color/briar_accent"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/stepOne"
|
||||
app:layout_constraintEnd_toStartOf="@+id/stepTwo"
|
||||
app:layout_constraintStart_toEndOf="@+id/stepOne"
|
||||
app:layout_constraintTop_toTopOf="@+id/stepOne" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepTwo"
|
||||
style="@style/StepBubble"
|
||||
android:text="@string/step_2"
|
||||
app:layout_constraintBottom_toTopOf="@+id/stepTwoText"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline"
|
||||
app:layout_constraintTop_toTopOf="@+id/stepOne"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepTwoText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:text="Wait for setup"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/stepTwo" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pendingText"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:textSize="16sp"
|
||||
android:text="Waiting for a connection to be established with:\n\n[chosen contact-name-and-avatar]"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/stepOneText" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/doneButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Done"
|
||||
android:enabled="false"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintVertical_bias="1.0" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
||||
@@ -1,56 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_large">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/diagram"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:paddingBottom="@dimen/margin_large"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/bluetooth"
|
||||
app:layout_constraintBottom_toTopOf="@id/explanationText"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/explanationText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="left|start"
|
||||
android:text="To be able to communicate over Bluetooth with contacts that were previously added at a distance, Briar needs to learn the Bluetooth address of your device. This only needs to be done once.\n\nTo accomplish this, you need to have one of your contacts nearby to perform the Bluetooth Setup.\n\nBefore you start the setup, your contact also needs bring up the Bluetooth Setup screen by navigating [here-and-here].\n\nNote: Bluetooth Pairing is not a required part of the setup."
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/startButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/diagram" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/startButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:enabled="false"
|
||||
android:text="Start"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintVertical_bias="1.0"
|
||||
tools:enabled="true" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
@@ -7,20 +8,13 @@
|
||||
android:icon="@drawable/ic_nearby"
|
||||
android:orderInCategory="3"
|
||||
android:title="@string/add_contact_nearby_title"
|
||||
app:showAsAction="never" />
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_add_contact_remotely"
|
||||
android:icon="@drawable/ic_link_menu"
|
||||
android:orderInCategory="2"
|
||||
android:title="@string/add_contact_remotely_title"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_bluetooth_setup"
|
||||
android:icon="@drawable/ic_link_menu"
|
||||
android:orderInCategory="4"
|
||||
android:title="Bluetooth setup"
|
||||
app:showAsAction="never" />
|
||||
app:showAsAction="never"/>
|
||||
|
||||
</menu>
|
||||
|
||||
@@ -1,36 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_group_invite"
|
||||
android:icon="@drawable/social_share_white"
|
||||
android:title="@string/groups_invite_members"
|
||||
app:showAsAction="ifRoom"/>
|
||||
android:visible="false"
|
||||
app:showAsAction="ifRoom"
|
||||
tools:visible="true" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_group_member_list"
|
||||
android:icon="@drawable/ic_group_white"
|
||||
android:title="@string/groups_member_list"
|
||||
app:showAsAction="never"/>
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_group_reveal"
|
||||
android:icon="@drawable/ic_visibility_white"
|
||||
android:title="@string/groups_reveal_contacts"
|
||||
app:showAsAction="never"/>
|
||||
android:visible="false"
|
||||
app:showAsAction="never"
|
||||
tools:visible="true" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_group_leave"
|
||||
android:icon="@drawable/action_delete_white"
|
||||
android:title="@string/groups_leave"
|
||||
app:showAsAction="never"/>
|
||||
android:visible="false"
|
||||
app:showAsAction="never"
|
||||
tools:visible="true" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_group_dissolve"
|
||||
android:icon="@drawable/action_delete_white"
|
||||
android:title="@string/groups_dissolve"
|
||||
app:showAsAction="never"/>
|
||||
android:visible="false"
|
||||
app:showAsAction="never"
|
||||
tools:visible="true" />
|
||||
|
||||
</menu>
|
||||
@@ -1,120 +0,0 @@
|
||||
package org.briarproject.briar.android.forum;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.briar.api.identity.AuthorInfo;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemList;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemListImpl;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static org.briarproject.briar.api.identity.AuthorInfo.Status.UNKNOWN;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEXT_LENGTH;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(sdk = 21)
|
||||
public class ForumActivityTest {
|
||||
|
||||
private final static MessageId[] MESSAGE_IDS = new MessageId[6];
|
||||
|
||||
static {
|
||||
for (int i = 0; i < MESSAGE_IDS.length; i++)
|
||||
MESSAGE_IDS[i] = new MessageId(getRandomId());
|
||||
}
|
||||
|
||||
private final static MessageId[] PARENT_IDS = {
|
||||
null,
|
||||
MESSAGE_IDS[0],
|
||||
MESSAGE_IDS[1],
|
||||
MESSAGE_IDS[2],
|
||||
MESSAGE_IDS[0],
|
||||
null
|
||||
};
|
||||
|
||||
/*
|
||||
1
|
||||
-> 2
|
||||
-> 3
|
||||
-> 4
|
||||
5
|
||||
6
|
||||
*/
|
||||
private final static int[] LEVELS = {
|
||||
0, 1, 2, 3, 1, 0
|
||||
};
|
||||
|
||||
private TestForumActivity forumActivity;
|
||||
@Captor
|
||||
private ArgumentCaptor<UiResultExceptionHandler<ThreadItemList<ForumPostItem>, DbException>>
|
||||
rc;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra("briar.GROUP_ID", getRandomId());
|
||||
forumActivity = Robolectric.buildActivity(TestForumActivity.class,
|
||||
intent).create().start().resume().get();
|
||||
}
|
||||
|
||||
private ThreadItemList<ForumPostItem> getDummyData() {
|
||||
ForumPostItem[] forumPostItems = new ForumPostItem[6];
|
||||
for (int i = 0; i < forumPostItems.length; i++) {
|
||||
Author author = getAuthor();
|
||||
String text = getRandomString(MAX_FORUM_POST_TEXT_LENGTH);
|
||||
forumPostItems[i] = new ForumPostItem(MESSAGE_IDS[i], PARENT_IDS[i],
|
||||
text, System.currentTimeMillis(), author,
|
||||
new AuthorInfo(UNKNOWN));
|
||||
forumPostItems[i].setLevel(LEVELS[i]);
|
||||
}
|
||||
ThreadItemList<ForumPostItem> list = new ThreadItemListImpl<>();
|
||||
list.addAll(Arrays.asList(forumPostItems));
|
||||
return list;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedEntries() {
|
||||
ForumController mc = forumActivity.getController();
|
||||
ThreadItemList<ForumPostItem> dummyData = getDummyData();
|
||||
verify(mc, times(1)).loadItems(rc.capture());
|
||||
rc.getValue().onResult(dummyData);
|
||||
ThreadItemAdapter<ForumPostItem> adapter = forumActivity.getAdapter();
|
||||
Assert.assertNotNull(adapter);
|
||||
assertEquals(6, adapter.getItemCount());
|
||||
assertEquals(dummyData.get(0).getText(),
|
||||
adapter.getItemAt(0).getText());
|
||||
assertEquals(dummyData.get(1).getText(),
|
||||
adapter.getItemAt(1).getText());
|
||||
assertEquals(dummyData.get(2).getText(),
|
||||
adapter.getItemAt(2).getText());
|
||||
assertEquals(dummyData.get(3).getText(),
|
||||
adapter.getItemAt(3).getText());
|
||||
assertEquals(dummyData.get(4).getText(),
|
||||
adapter.getItemAt(4).getText());
|
||||
assertEquals(dummyData.get(5).getText(),
|
||||
adapter.getItemAt(5).getText());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package org.briarproject.briar.android.forum;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityModule;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.controller.BriarController;
|
||||
import org.briarproject.briar.android.controller.BriarControllerImpl;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This class exposes the ForumController and offers the possibility to
|
||||
* override it.
|
||||
*/
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class TestForumActivity extends ForumActivity {
|
||||
|
||||
@Override
|
||||
public ForumController getController() {
|
||||
return forumController;
|
||||
}
|
||||
|
||||
public ThreadItemAdapter<ForumPostItem> getAdapter() {
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
setTheme(R.style.BriarTheme_NoActionBar);
|
||||
super.onCreate(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ActivityModule getActivityModule() {
|
||||
return new ActivityModule(this) {
|
||||
|
||||
@Override
|
||||
protected BriarController provideBriarController(
|
||||
BriarControllerImpl briarController) {
|
||||
BriarController c = Mockito.mock(BriarController.class);
|
||||
Mockito.when(c.accountSignedIn()).thenReturn(true);
|
||||
return c;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ForumModule getForumModule() {
|
||||
return new ForumModule() {
|
||||
@Override
|
||||
ForumController provideForumController(BaseActivity activity,
|
||||
ForumControllerImpl forumController) {
|
||||
return Mockito.mock(ForumController.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -15,11 +15,9 @@ public interface MessageTree<T extends MessageTree.MessageNode> {
|
||||
|
||||
void add(T node);
|
||||
|
||||
void setComparator(Comparator<T> comparator);
|
||||
|
||||
void clear();
|
||||
|
||||
Collection<T> depthFirstOrder();
|
||||
List<T> depthFirstOrder();
|
||||
|
||||
boolean contains(MessageId m);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -84,11 +85,22 @@ public interface ForumManager {
|
||||
*/
|
||||
String getPostText(MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the text of the forum post with the given ID.
|
||||
*/
|
||||
String getPostText(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the headers of all posts in the given forum.
|
||||
*/
|
||||
Collection<ForumPostHeader> getPostHeaders(GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the headers of all posts in the given forum.
|
||||
*/
|
||||
List<ForumPostHeader> getPostHeaders(Transaction txn, GroupId g)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Registers a hook to be called whenever a forum is removed.
|
||||
*/
|
||||
@@ -97,7 +109,6 @@ public interface ForumManager {
|
||||
/**
|
||||
* Returns the group count for the given forum.
|
||||
*/
|
||||
@Deprecated
|
||||
GroupCount getGroupCount(GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,21 +8,14 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
public class JoinMessageHeader extends GroupMessageHeader {
|
||||
|
||||
private final Visibility visibility;
|
||||
private final boolean isInitial;
|
||||
|
||||
public JoinMessageHeader(GroupMessageHeader h, Visibility visibility,
|
||||
boolean isInitial) {
|
||||
public JoinMessageHeader(GroupMessageHeader h, boolean isInitial) {
|
||||
super(h.getGroupId(), h.getId(), h.getParentId(), h.getTimestamp(),
|
||||
h.getAuthor(), h.getAuthorInfo(), h.isRead());
|
||||
this.visibility = visibility;
|
||||
this.isInitial = isInitial;
|
||||
}
|
||||
|
||||
public Visibility getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
public boolean isInitial() {
|
||||
return isInitial;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface PrivateGroupManager {
|
||||
@@ -107,16 +108,33 @@ public interface PrivateGroupManager {
|
||||
*/
|
||||
String getMessageText(MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the text of the private group message with the given ID.
|
||||
*/
|
||||
String getMessageText(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the headers of all messages in the given private group.
|
||||
*/
|
||||
Collection<GroupMessageHeader> getHeaders(GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the headers of all messages in the given private group.
|
||||
*/
|
||||
List<GroupMessageHeader> getHeaders(Transaction txn, GroupId g)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all members of the given private group.
|
||||
*/
|
||||
Collection<GroupMember> getMembers(GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all members of the given private group.
|
||||
*/
|
||||
Collection<GroupMember> getMembers(Transaction txn, GroupId g)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the given author is a member of the given private group.
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.api.sharing;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
@@ -45,6 +46,12 @@ public interface SharingManager<S extends Shareable>
|
||||
*/
|
||||
Collection<Contact> getSharedWith(GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all contacts with whom the given group is shared.
|
||||
*/
|
||||
Collection<Contact> getSharedWith(Transaction txn, GroupId g)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the group not already shared and no invitation is open
|
||||
*/
|
||||
|
||||
@@ -30,7 +30,7 @@ public class MessageTreeImpl<T extends MessageTree.MessageNode>
|
||||
private final List<List<T>> unsortedLists = new ArrayList<>();
|
||||
|
||||
@SuppressWarnings("UseCompareMethod")
|
||||
private Comparator<T> comparator = (o1, o2) ->
|
||||
private final Comparator<T> comparator = (o1, o2) ->
|
||||
Long.valueOf(o1.getTimestamp()).compareTo(o2.getTimestamp());
|
||||
|
||||
@Override
|
||||
@@ -79,6 +79,7 @@ public class MessageTreeImpl<T extends MessageTree.MessageNode>
|
||||
@GuardedBy("this")
|
||||
private void sortUnsorted() {
|
||||
for (List<T> list : unsortedLists) {
|
||||
//noinspection Java8ListSort
|
||||
Collections.sort(list, comparator);
|
||||
}
|
||||
unsortedLists.clear();
|
||||
@@ -95,17 +96,7 @@ public class MessageTreeImpl<T extends MessageTree.MessageNode>
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setComparator(Comparator<T> comparator) {
|
||||
this.comparator = comparator;
|
||||
// Sort all lists with the new comparator
|
||||
Collections.sort(roots, comparator);
|
||||
for (Map.Entry<MessageId, List<T>> entry : nodeMap.entrySet()) {
|
||||
Collections.sort(entry.getValue(), comparator);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Collection<T> depthFirstOrder() {
|
||||
public synchronized List<T> depthFirstOrder() {
|
||||
List<T> orderedList = new ArrayList<>();
|
||||
for (T root : roots) {
|
||||
traverse(orderedList, root, 0);
|
||||
|
||||
@@ -192,6 +192,15 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPostText(Transaction txn, MessageId m) throws DbException {
|
||||
try {
|
||||
return getPostText(clientHelper.getMessageAsList(txn, m));
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getPostText(BdfList body) throws FormatException {
|
||||
// Parent ID, author, text, signature
|
||||
return body.getString(2);
|
||||
@@ -200,33 +209,35 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
||||
@Override
|
||||
public Collection<ForumPostHeader> getPostHeaders(GroupId g)
|
||||
throws DbException {
|
||||
return db.transactionWithResult(true, txn -> getPostHeaders(txn, g));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ForumPostHeader> getPostHeaders(Transaction txn, GroupId g)
|
||||
throws DbException {
|
||||
try {
|
||||
return db.transactionWithResult(true, txn -> {
|
||||
Collection<ForumPostHeader> headers = new ArrayList<>();
|
||||
Map<MessageId, BdfDictionary> metadata =
|
||||
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||
// get all authors we need to get the info for
|
||||
Set<AuthorId> authors = new HashSet<>();
|
||||
for (Entry<MessageId, BdfDictionary> entry :
|
||||
metadata.entrySet()) {
|
||||
BdfList authorList = entry.getValue().getList(KEY_AUTHOR);
|
||||
Author a = clientHelper.parseAndValidateAuthor(authorList);
|
||||
authors.add(a.getId());
|
||||
}
|
||||
// get information for all authors
|
||||
Map<AuthorId, AuthorInfo> authorInfos = new HashMap<>();
|
||||
for (AuthorId id : authors) {
|
||||
authorInfos.put(id, authorManager.getAuthorInfo(txn, id));
|
||||
}
|
||||
// Parse the metadata
|
||||
for (Entry<MessageId, BdfDictionary> entry :
|
||||
metadata.entrySet()) {
|
||||
BdfDictionary meta = entry.getValue();
|
||||
headers.add(getForumPostHeader(txn, entry.getKey(), meta,
|
||||
authorInfos));
|
||||
}
|
||||
return headers;
|
||||
});
|
||||
List<ForumPostHeader> headers = new ArrayList<>();
|
||||
Map<MessageId, BdfDictionary> metadata =
|
||||
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||
// get all authors we need to get the info for
|
||||
Set<AuthorId> authors = new HashSet<>();
|
||||
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
|
||||
BdfList authorList = entry.getValue().getList(KEY_AUTHOR);
|
||||
Author a = clientHelper.parseAndValidateAuthor(authorList);
|
||||
authors.add(a.getId());
|
||||
}
|
||||
// get information for all authors
|
||||
Map<AuthorId, AuthorInfo> authorInfos = new HashMap<>();
|
||||
for (AuthorId id : authors) {
|
||||
authorInfos.put(id, authorManager.getAuthorInfo(txn, id));
|
||||
}
|
||||
// Parse the metadata
|
||||
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
|
||||
BdfDictionary meta = entry.getValue();
|
||||
headers.add(getForumPostHeader(txn, entry.getKey(), meta,
|
||||
authorInfos));
|
||||
}
|
||||
return headers;
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
addMember(txn, m.getMessage().getGroupId(), m.getMember(), VISIBLE);
|
||||
setPreviousMsgId(txn, m.getMessage().getGroupId(),
|
||||
m.getMessage().getId());
|
||||
attachJoinMessageAddedEvent(txn, m.getMessage(), meta, true, VISIBLE);
|
||||
attachJoinMessageAddedEvent(txn, m.getMessage(), meta, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -315,6 +315,16 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessageText(Transaction txn, MessageId m)
|
||||
throws DbException {
|
||||
try {
|
||||
return getMessageText(clientHelper.getMessageAsList(txn, m));
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getMessageText(BdfList body) throws FormatException {
|
||||
// Message type (0), member (1), parent ID (2), previous message ID (3),
|
||||
// text (4), signature (5)
|
||||
@@ -324,8 +334,13 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
@Override
|
||||
public Collection<GroupMessageHeader> getHeaders(GroupId g)
|
||||
throws DbException {
|
||||
Collection<GroupMessageHeader> headers = new ArrayList<>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
return db.transactionWithResult(true, txn -> getHeaders(txn, g));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GroupMessageHeader> getHeaders(Transaction txn, GroupId g)
|
||||
throws DbException {
|
||||
List<GroupMessageHeader> headers = new ArrayList<>();
|
||||
try {
|
||||
Map<MessageId, BdfDictionary> metadata =
|
||||
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||
@@ -339,27 +354,20 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
for (AuthorId id : authors) {
|
||||
authorInfos.put(id, authorManager.getAuthorInfo(txn, id));
|
||||
}
|
||||
// get current visibilities for join messages
|
||||
Map<Author, Visibility> visibilities = getMembers(txn, g);
|
||||
// parse the metadata
|
||||
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
|
||||
BdfDictionary meta = entry.getValue();
|
||||
if (meta.getLong(KEY_TYPE) == JOIN.getInt()) {
|
||||
Author member = getAuthor(meta);
|
||||
Visibility v = visibilities.get(member);
|
||||
headers.add(getJoinMessageHeader(txn, g, entry.getKey(),
|
||||
meta, authorInfos, v));
|
||||
meta, authorInfos));
|
||||
} else {
|
||||
headers.add(getGroupMessageHeader(txn, g, entry.getKey(),
|
||||
meta, authorInfos));
|
||||
}
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
return headers;
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,46 +397,46 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
|
||||
private JoinMessageHeader getJoinMessageHeader(Transaction txn, GroupId g,
|
||||
MessageId id, BdfDictionary meta,
|
||||
Map<AuthorId, AuthorInfo> authorInfos, Visibility v)
|
||||
Map<AuthorId, AuthorInfo> authorInfos)
|
||||
throws DbException, FormatException {
|
||||
|
||||
GroupMessageHeader header =
|
||||
getGroupMessageHeader(txn, g, id, meta, authorInfos);
|
||||
boolean creator = meta.getBoolean(KEY_INITIAL_JOIN_MSG);
|
||||
return new JoinMessageHeader(header, v, creator);
|
||||
return new JoinMessageHeader(header, creator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<GroupMember> getMembers(GroupId g) throws DbException {
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
Collection<GroupMember> members = new ArrayList<>();
|
||||
Map<Author, Visibility> authors = getMembers(txn, g);
|
||||
LocalAuthor la = identityManager.getLocalAuthor(txn);
|
||||
PrivateGroup privateGroup = getPrivateGroup(txn, g);
|
||||
for (Entry<Author, Visibility> m : authors.entrySet()) {
|
||||
Author a = m.getKey();
|
||||
AuthorInfo authorInfo =
|
||||
authorManager.getAuthorInfo(txn, a.getId());
|
||||
Status status = authorInfo.getStatus();
|
||||
Visibility v = m.getValue();
|
||||
ContactId c = null;
|
||||
if (v != INVISIBLE &&
|
||||
(status == VERIFIED || status == UNVERIFIED)) {
|
||||
c = contactManager.getContact(txn, a.getId(), la.getId())
|
||||
.getId();
|
||||
}
|
||||
boolean isCreator = privateGroup.getCreator().equals(a);
|
||||
members.add(new GroupMember(a, authorInfo, isCreator, c, v));
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
return members;
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
public Collection<GroupMember> getMembers(GroupId g)
|
||||
throws DbException {
|
||||
return db.transactionWithResult(true, txn -> getMembers(txn, g));
|
||||
}
|
||||
|
||||
private Map<Author, Visibility> getMembers(Transaction txn, GroupId g)
|
||||
@Override
|
||||
public Collection<GroupMember> getMembers(Transaction txn, GroupId g)
|
||||
throws DbException {
|
||||
Collection<GroupMember> members = new ArrayList<>();
|
||||
Map<Author, Visibility> authors = getMemberAuthors(txn, g);
|
||||
LocalAuthor la = identityManager.getLocalAuthor(txn);
|
||||
PrivateGroup privateGroup = getPrivateGroup(txn, g);
|
||||
for (Entry<Author, Visibility> m : authors.entrySet()) {
|
||||
Author a = m.getKey();
|
||||
AuthorInfo authorInfo = authorManager.getAuthorInfo(txn, a.getId());
|
||||
Status status = authorInfo.getStatus();
|
||||
Visibility v = m.getValue();
|
||||
ContactId c = null;
|
||||
if (v != INVISIBLE &&
|
||||
(status == VERIFIED || status == UNVERIFIED)) {
|
||||
c = contactManager.getContact(txn, a.getId(), la.getId())
|
||||
.getId();
|
||||
}
|
||||
boolean isCreator = privateGroup.getCreator().equals(a);
|
||||
members.add(new GroupMember(a, authorInfo, isCreator, c, v));
|
||||
}
|
||||
return members;
|
||||
}
|
||||
|
||||
private Map<Author, Visibility> getMemberAuthors(Transaction txn, GroupId g)
|
||||
throws DbException {
|
||||
try {
|
||||
BdfDictionary meta =
|
||||
@@ -450,7 +458,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
@Override
|
||||
public boolean isMember(Transaction txn, GroupId g, Author a)
|
||||
throws DbException {
|
||||
for (Author member : getMembers(txn, g).keySet()) {
|
||||
for (Author member : getMemberAuthors(txn, g).keySet()) {
|
||||
if (member.equals(a)) return true;
|
||||
}
|
||||
return false;
|
||||
@@ -544,7 +552,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
addMember(txn, m.getGroupId(), member, v);
|
||||
// track message and broadcast event
|
||||
messageTracker.trackIncomingMessage(txn, m);
|
||||
attachJoinMessageAddedEvent(txn, m, meta, false, v);
|
||||
attachJoinMessageAddedEvent(txn, m, meta, false);
|
||||
}
|
||||
|
||||
private void handleGroupMessage(Transaction txn, Message m,
|
||||
@@ -594,10 +602,10 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
}
|
||||
|
||||
private void attachJoinMessageAddedEvent(Transaction txn, Message m,
|
||||
BdfDictionary meta, boolean local, Visibility v)
|
||||
BdfDictionary meta, boolean local)
|
||||
throws DbException, FormatException {
|
||||
JoinMessageHeader header = getJoinMessageHeader(txn, m.getGroupId(),
|
||||
m.getId(), meta, Collections.emptyMap(), v);
|
||||
m.getId(), meta, Collections.emptyMap());
|
||||
txn.attach(new GroupMessageAddedEvent(m.getGroupId(), header, "",
|
||||
local));
|
||||
}
|
||||
|
||||
@@ -426,17 +426,17 @@ abstract class SharingManagerImpl<S extends Shareable>
|
||||
|
||||
@Override
|
||||
public Collection<Contact> getSharedWith(GroupId g) throws DbException {
|
||||
return db.transactionWithResult(true, txn -> getSharedWith(txn, g));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Contact> getSharedWith(Transaction txn, GroupId g)
|
||||
throws DbException {
|
||||
// TODO report also pending invitations
|
||||
Collection<Contact> contacts = new ArrayList<>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
if (db.getGroupVisibility(txn, c.getId(), g) == SHARED)
|
||||
contacts.add(c);
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
if (db.getGroupVisibility(txn, c.getId(), g) == SHARED)
|
||||
contacts.add(c);
|
||||
}
|
||||
return contacts;
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.briarproject.briar.api.identity.AuthorInfo.Status.VERIFIED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.briar.api.identity.AuthorInfo.Status.VERIFIED;
|
||||
import static org.briarproject.briar.api.privategroup.Visibility.INVISIBLE;
|
||||
import static org.briarproject.briar.api.privategroup.Visibility.REVEALED_BY_CONTACT;
|
||||
import static org.briarproject.briar.api.privategroup.Visibility.REVEALED_BY_US;
|
||||
@@ -124,10 +124,8 @@ public class PrivateGroupManagerIntegrationTest
|
||||
addGroup();
|
||||
|
||||
// create and add test message with no previousMsgId
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
GroupMessage msg = groupMessageFactory
|
||||
.createGroupMessage(groupId0, clock.currentTimeMillis(), null,
|
||||
author0, "test", null);
|
||||
GroupMessage msg = groupMessageFactory.createGroupMessage(groupId0,
|
||||
clock.currentTimeMillis(), null, author0, "test", null);
|
||||
groupManager0.addLocalMessage(msg);
|
||||
|
||||
// sync test message
|
||||
@@ -342,23 +340,19 @@ public class PrivateGroupManagerIntegrationTest
|
||||
Collection<GroupMember> members0 = groupManager0.getMembers(groupId0);
|
||||
assertEquals(2, members0.size());
|
||||
for (GroupMember m : members0) {
|
||||
if (m.getAuthor().equals(author0)) {
|
||||
assertEquals(VISIBLE, m.getVisibility());
|
||||
} else {
|
||||
if (!m.getAuthor().equals(author0)) {
|
||||
assertEquals(author1, m.getAuthor());
|
||||
assertEquals(VISIBLE, m.getVisibility());
|
||||
}
|
||||
assertEquals(VISIBLE, m.getVisibility());
|
||||
}
|
||||
|
||||
Collection<GroupMember> members1 = groupManager1.getMembers(groupId0);
|
||||
assertEquals(2, members1.size());
|
||||
for (GroupMember m : members1) {
|
||||
if (m.getAuthor().equals(author1)) {
|
||||
assertEquals(VISIBLE, m.getVisibility());
|
||||
} else {
|
||||
if (!m.getAuthor().equals(author1)) {
|
||||
assertEquals(author0, m.getAuthor());
|
||||
assertEquals(VISIBLE, m.getVisibility());
|
||||
}
|
||||
assertEquals(VISIBLE, m.getVisibility());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,27 +362,11 @@ public class PrivateGroupManagerIntegrationTest
|
||||
|
||||
Collection<GroupMessageHeader> headers0 =
|
||||
groupManager0.getHeaders(groupId0);
|
||||
for (GroupMessageHeader h : headers0) {
|
||||
if (h instanceof JoinMessageHeader) {
|
||||
JoinMessageHeader j = (JoinMessageHeader) h;
|
||||
// all relationships of the creator are visible
|
||||
assertEquals(VISIBLE, j.getVisibility());
|
||||
}
|
||||
}
|
||||
assertEquals(2, headers0.size());
|
||||
|
||||
Collection<GroupMessageHeader> headers1 =
|
||||
groupManager1.getHeaders(groupId0);
|
||||
for (GroupMessageHeader h : headers1) {
|
||||
if (h instanceof JoinMessageHeader) {
|
||||
JoinMessageHeader j = (JoinMessageHeader) h;
|
||||
if (h.getAuthor().equals(author1))
|
||||
// we are visible to ourselves
|
||||
assertEquals(VISIBLE, j.getVisibility());
|
||||
else
|
||||
// our relationship to the creator is visible
|
||||
assertEquals(VISIBLE, j.getVisibility());
|
||||
}
|
||||
}
|
||||
assertEquals(2, headers1.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -463,34 +441,6 @@ public class PrivateGroupManagerIntegrationTest
|
||||
assertEquals(REVEALED_BY_CONTACT, m.getVisibility());
|
||||
}
|
||||
}
|
||||
|
||||
// assert that join messages reflect revealed relationship
|
||||
Collection<GroupMessageHeader> headers1 =
|
||||
groupManager1.getHeaders(groupId0);
|
||||
for (GroupMessageHeader h : headers1) {
|
||||
if (h instanceof JoinMessageHeader) {
|
||||
JoinMessageHeader j = (JoinMessageHeader) h;
|
||||
if (h.getAuthor().equals(author2))
|
||||
// 1 revealed the relationship to 2
|
||||
assertEquals(REVEALED_BY_US, j.getVisibility());
|
||||
else
|
||||
// 1's other relationship (to 1 and creator) are visible
|
||||
assertEquals(VISIBLE, j.getVisibility());
|
||||
}
|
||||
}
|
||||
Collection<GroupMessageHeader> headers2 =
|
||||
groupManager2.getHeaders(groupId0);
|
||||
for (GroupMessageHeader h : headers2) {
|
||||
if (h instanceof JoinMessageHeader) {
|
||||
JoinMessageHeader j = (JoinMessageHeader) h;
|
||||
if (h.getAuthor().equals(author1))
|
||||
// 2's relationship was revealed by 1
|
||||
assertEquals(REVEALED_BY_CONTACT, j.getVisibility());
|
||||
else
|
||||
// 2's other relationship (to 2 and creator) are visible
|
||||
assertEquals(VISIBLE, j.getVisibility());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user