mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-14 19:59:05 +01:00
Compare commits
65 Commits
lock-down-
...
1866-blog-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4f38e81ea | ||
|
|
4074ac8578 | ||
|
|
e97478a21a | ||
|
|
726ebcea3f | ||
|
|
2f969775d8 | ||
|
|
d3b855318c | ||
|
|
95104d3383 | ||
|
|
6860a04e8b | ||
|
|
33c24f8655 | ||
|
|
1fa4b78474 | ||
|
|
b678de7529 | ||
|
|
ab1ed0ff5a | ||
|
|
ad20e5230a | ||
|
|
ae923e5777 | ||
|
|
46b4204805 | ||
|
|
2257c005b3 | ||
|
|
eb9ff9c954 | ||
|
|
4f08f81779 | ||
|
|
2b0815aaac | ||
|
|
a9e83491d3 | ||
|
|
ee967c5d8f | ||
|
|
43740777d4 | ||
|
|
d5b0556ea2 | ||
|
|
227f00c10c | ||
|
|
8b4ff2dc8a | ||
|
|
4be2afb915 | ||
|
|
74447b8ec3 | ||
|
|
d95242bd7e | ||
|
|
51794424ce | ||
|
|
5db099bae6 | ||
|
|
a2faa3bd3b | ||
|
|
a3fb7b5680 | ||
|
|
264d110dbd | ||
|
|
839b871a45 | ||
|
|
2fb4825b8f | ||
|
|
3f9a66b1b6 | ||
|
|
d796916387 | ||
|
|
fe07b760ea | ||
|
|
b4a5fe6772 | ||
|
|
e21e6267d7 | ||
|
|
d7afbdf690 | ||
|
|
c5d2661c1d | ||
|
|
b738bdd14e | ||
|
|
629cff20a3 | ||
|
|
6cfb70db95 | ||
|
|
737ecfb620 | ||
|
|
5a424b178e | ||
|
|
59f4e7c34a | ||
|
|
2480824d69 | ||
|
|
a6c2000d81 | ||
|
|
a38a3139d9 | ||
|
|
4c8adaa02b | ||
|
|
8a534b4503 | ||
|
|
e5b2275c82 | ||
|
|
5159593825 | ||
|
|
a546fecc01 | ||
|
|
3e7e37f5f6 | ||
|
|
d095ba0b15 | ||
|
|
7fab97d26c | ||
|
|
6fbc82ee27 | ||
|
|
885b03cfd7 | ||
|
|
f81bfcafeb | ||
|
|
6b61725c6a | ||
|
|
fb2b4209cf | ||
|
|
e4a66615a7 |
@@ -8,11 +8,15 @@ android {
|
|||||||
compileSdkVersion 30
|
compileSdkVersion 30
|
||||||
buildToolsVersion '30.0.2'
|
buildToolsVersion '30.0.2'
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
doNotStrip '**/*.so'
|
||||||
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 10214
|
versionCode 10218
|
||||||
versionName "1.2.14"
|
versionName "1.2.18"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|||||||
@@ -175,6 +175,11 @@ class AndroidBluetoothPlugin
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
IoUtils.tryToClose(s, LOG, WARNING);
|
IoUtils.tryToClose(s, LOG, WARNING);
|
||||||
throw e;
|
throw e;
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
// BluetoothSocket#connect() may throw an NPE under unknown
|
||||||
|
// circumstances
|
||||||
|
IoUtils.tryToClose(s, LOG, WARNING);
|
||||||
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,12 +114,8 @@ public class AndroidUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of supported content types for image attachments.
|
* Returns an array of supported content types for image attachments.
|
||||||
* GIFs can't be compressed on API < 24 so they're not supported.
|
|
||||||
* <p>
|
|
||||||
* TODO: Remove this restriction when large message support is added
|
|
||||||
*/
|
*/
|
||||||
public static String[] getSupportedImageContentTypes() {
|
public static String[] getSupportedImageContentTypes() {
|
||||||
if (SDK_INT < 24) return new String[] {"image/jpeg", "image/png"};
|
return new String[] {"image/jpeg", "image/png", "image/gif"};
|
||||||
else return new String[] {"image/jpeg", "image/png", "image/gif"};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUV
|
|||||||
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg 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 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.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 144.217.20.138:80 FB70B257C162BF1038CA669D568D76F5B7F0BABB cert=vYIV5MgrghGQvZPIi1tJwnzorMgqgmlKaB77Y3Z9Q/v94wZBOAXkW+fdx4aSxLVnKO+xNw iat-mode=0
|
||||||
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg 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:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
||||||
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA 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 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 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 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 obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ dependencies {
|
|||||||
implementation fileTree(dir: 'libs', include: '*.jar')
|
implementation fileTree(dir: 'libs', include: '*.jar')
|
||||||
implementation 'net.java.dev.jna:jna:4.5.2'
|
implementation 'net.java.dev.jna:jna:4.5.2'
|
||||||
implementation 'net.java.dev.jna:jna-platform:4.5.2'
|
implementation 'net.java.dev.jna:jna-platform:4.5.2'
|
||||||
tor 'org.briarproject:tor:0.3.5.13@zip'
|
tor 'org.briarproject:tor:0.3.5.13-1@zip'
|
||||||
tor 'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a@zip'
|
tor 'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a@zip'
|
||||||
|
|
||||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
|
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.OsUtils.isLinux;
|
import static org.briarproject.bramble.util.OsUtils.isLinux;
|
||||||
|
|
||||||
@@ -96,8 +97,15 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
|
|||||||
String architecture = null;
|
String architecture = null;
|
||||||
if (isLinux()) {
|
if (isLinux()) {
|
||||||
String arch = System.getProperty("os.arch");
|
String arch = System.getProperty("os.arch");
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("System's os.arch is " + arch);
|
||||||
|
}
|
||||||
if (arch.equals("amd64")) {
|
if (arch.equals("amd64")) {
|
||||||
architecture = "linux-x86_64";
|
architecture = "linux-x86_64";
|
||||||
|
} else if (arch.equals("aarch64")) {
|
||||||
|
architecture = "linux-aarch64";
|
||||||
|
} else if (arch.equals("arm")) {
|
||||||
|
architecture = "linux-armhf";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (architecture == null) {
|
if (architecture == null) {
|
||||||
@@ -105,6 +113,10 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("The selected architecture for Tor is " + architecture);
|
||||||
|
}
|
||||||
|
|
||||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();
|
TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ dependencyVerification {
|
|||||||
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
|
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
|
||||||
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
|
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
|
||||||
'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
|
'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
|
||||||
'org.briarproject:tor:0.3.5.13:tor-0.3.5.13.zip:1c5f0b821ee2aadb0ea04aa96caab3ca0a08370cce8de81c2dfe04d172f8a2a0',
|
'org.briarproject:tor:0.3.5.13-1:tor-0.3.5.13-1.zip:ef35c16bf8dc1f4c75ed71d9f55e4514f383d124ec96b859aca647c990927c99',
|
||||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||||
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
|
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
|
||||||
|
|||||||
@@ -19,11 +19,15 @@ android {
|
|||||||
compileSdkVersion 30
|
compileSdkVersion 30
|
||||||
buildToolsVersion '30.0.2'
|
buildToolsVersion '30.0.2'
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
doNotStrip '**/*.so'
|
||||||
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 10214
|
versionCode 10218
|
||||||
versionName "1.2.14"
|
versionName "1.2.18"
|
||||||
applicationId "org.briarproject.briar.android"
|
applicationId "org.briarproject.briar.android"
|
||||||
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
@@ -95,6 +99,7 @@ dependencies {
|
|||||||
implementation project(path: ':bramble-core', configuration: 'default')
|
implementation project(path: ':bramble-core', configuration: 'default')
|
||||||
implementation project(':bramble-android')
|
implementation project(':bramble-android')
|
||||||
|
|
||||||
|
implementation 'androidx.fragment:fragment:1.3.0'
|
||||||
implementation 'androidx.preference:preference:1.1.1'
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.3.1'
|
implementation 'androidx.exifinterface:exifinterface:1.3.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||||
|
|||||||
@@ -27,7 +27,10 @@
|
|||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission
|
||||||
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="18"
|
||||||
|
tools:ignore="ScopedStorage" />
|
||||||
|
|
||||||
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
@@ -38,6 +41,7 @@
|
|||||||
android:name="org.briarproject.briar.android.BriarApplicationImpl"
|
android:name="org.briarproject.briar.android.BriarApplicationImpl"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:banner="@mipmap/tv_banner"
|
android:banner="@mipmap/tv_banner"
|
||||||
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher_round"
|
android:icon="@mipmap/ic_launcher_round"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:logo="@mipmap/ic_launcher_round"
|
android:logo="@mipmap/ic_launcher_round"
|
||||||
|
|||||||
@@ -30,8 +30,10 @@ import org.briarproject.bramble.util.StringUtils;
|
|||||||
import org.briarproject.briar.android.account.DozeHelperModule;
|
import org.briarproject.briar.android.account.DozeHelperModule;
|
||||||
import org.briarproject.briar.android.account.LockManagerImpl;
|
import org.briarproject.briar.android.account.LockManagerImpl;
|
||||||
import org.briarproject.briar.android.account.SetupModule;
|
import org.briarproject.briar.android.account.SetupModule;
|
||||||
|
import org.briarproject.briar.android.blog.BlogModule;
|
||||||
import org.briarproject.briar.android.contact.ContactListModule;
|
import org.briarproject.briar.android.contact.ContactListModule;
|
||||||
import org.briarproject.briar.android.forum.ForumModule;
|
import org.briarproject.briar.android.forum.ForumModule;
|
||||||
|
import org.briarproject.briar.android.introduction.IntroductionModule;
|
||||||
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
|
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
|
||||||
import org.briarproject.briar.android.logging.LoggingModule;
|
import org.briarproject.briar.android.logging.LoggingModule;
|
||||||
import org.briarproject.briar.android.login.LoginModule;
|
import org.briarproject.briar.android.login.LoginModule;
|
||||||
@@ -82,7 +84,9 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
|||||||
SettingsModule.class,
|
SettingsModule.class,
|
||||||
DevReportModule.class,
|
DevReportModule.class,
|
||||||
ContactListModule.class,
|
ContactListModule.class,
|
||||||
|
IntroductionModule.class,
|
||||||
// below need to be within same scope as ViewModelProvider.Factory
|
// below need to be within same scope as ViewModelProvider.Factory
|
||||||
|
BlogModule.class,
|
||||||
ForumModule.class,
|
ForumModule.class,
|
||||||
GroupListModule.class,
|
GroupListModule.class,
|
||||||
GroupConversationModule.class,
|
GroupConversationModule.class,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import javax.inject.Inject;
|
|||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||||
import static android.app.NotificationManager.IMPORTANCE_NONE;
|
import static android.app.NotificationManager.IMPORTANCE_LOW;
|
||||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
import static android.content.Intent.ACTION_SHUTDOWN;
|
import static android.content.Intent.ACTION_SHUTDOWN;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||||
@@ -46,6 +46,7 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
|
|||||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.os.Process.myPid;
|
||||||
import static androidx.core.app.NotificationCompat.VISIBILITY_SECRET;
|
import static androidx.core.app.NotificationCompat.VISIBILITY_SECRET;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
@@ -56,8 +57,10 @@ import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
|
|||||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID;
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID;
|
||||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_NOTIFICATION_ID;
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_NOTIFICATION_ID;
|
||||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID;
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID;
|
||||||
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_OLD_ID;
|
||||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID;
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID;
|
||||||
import static org.briarproject.briar.api.android.LockManager.ACTION_LOCK;
|
import static org.briarproject.briar.api.android.LockManager.ACTION_LOCK;
|
||||||
|
import static org.briarproject.briar.api.android.LockManager.EXTRA_PID;
|
||||||
|
|
||||||
public class BriarService extends Service {
|
public class BriarService extends Service {
|
||||||
|
|
||||||
@@ -120,11 +123,17 @@ public class BriarService extends Service {
|
|||||||
if (SDK_INT >= 26) {
|
if (SDK_INT >= 26) {
|
||||||
NotificationManager nm = (NotificationManager)
|
NotificationManager nm = (NotificationManager)
|
||||||
requireNonNull(getSystemService(NOTIFICATION_SERVICE));
|
requireNonNull(getSystemService(NOTIFICATION_SERVICE));
|
||||||
|
// Delete the old notification channel, which had
|
||||||
|
// IMPORTANCE_NONE and showed a badge
|
||||||
|
nm.deleteNotificationChannel(ONGOING_CHANNEL_OLD_ID);
|
||||||
|
// Use IMPORTANCE_LOW so the system doesn't show its own
|
||||||
|
// notification on API 26-27
|
||||||
NotificationChannel ongoingChannel = new NotificationChannel(
|
NotificationChannel ongoingChannel = new NotificationChannel(
|
||||||
ONGOING_CHANNEL_ID,
|
ONGOING_CHANNEL_ID,
|
||||||
getString(R.string.ongoing_notification_title),
|
getString(R.string.ongoing_notification_title),
|
||||||
IMPORTANCE_NONE);
|
IMPORTANCE_LOW);
|
||||||
ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET);
|
ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET);
|
||||||
|
ongoingChannel.setShowBadge(false);
|
||||||
nm.createNotificationChannel(ongoingChannel);
|
nm.createNotificationChannel(ongoingChannel);
|
||||||
NotificationChannel failureChannel = new NotificationChannel(
|
NotificationChannel failureChannel = new NotificationChannel(
|
||||||
FAILURE_CHANNEL_ID,
|
FAILURE_CHANNEL_ID,
|
||||||
@@ -203,7 +212,12 @@ public class BriarService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
if (ACTION_LOCK.equals(intent.getAction())) {
|
if (ACTION_LOCK.equals(intent.getAction())) {
|
||||||
lockManager.setLocked(true);
|
int pid = intent.getIntExtra(EXTRA_PID, -1);
|
||||||
|
if (pid == myPid()) lockManager.setLocked(true);
|
||||||
|
else if (LOG.isLoggable(WARNING)) {
|
||||||
|
LOG.warning("Tried to lock process " + pid + " but this is " +
|
||||||
|
myPid());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return START_NOT_STICKY; // Don't restart automatically if killed
|
return START_NOT_STICKY; // Don't restart automatically if killed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ public class DozeFragment extends SetupFragment
|
|||||||
private DozeView dozeView;
|
private DozeView dozeView;
|
||||||
private HuaweiView huaweiView;
|
private HuaweiView huaweiView;
|
||||||
private Button next;
|
private Button next;
|
||||||
private ProgressBar progressBar;
|
|
||||||
private boolean secondAttempt = false;
|
private boolean secondAttempt = false;
|
||||||
|
|
||||||
public static DozeFragment newInstance() {
|
public static DozeFragment newInstance() {
|
||||||
@@ -58,11 +57,19 @@ public class DozeFragment extends SetupFragment
|
|||||||
huaweiView = v.findViewById(R.id.huaweiView);
|
huaweiView = v.findViewById(R.id.huaweiView);
|
||||||
huaweiView.setOnCheckedChangedListener(this);
|
huaweiView.setOnCheckedChangedListener(this);
|
||||||
next = v.findViewById(R.id.next);
|
next = v.findViewById(R.id.next);
|
||||||
progressBar = v.findViewById(R.id.progress);
|
ProgressBar progressBar = v.findViewById(R.id.progress);
|
||||||
|
|
||||||
dozeView.setOnButtonClickListener(this::askForDozeWhitelisting);
|
dozeView.setOnButtonClickListener(this::askForDozeWhitelisting);
|
||||||
next.setOnClickListener(this);
|
next.setOnClickListener(this);
|
||||||
|
|
||||||
|
viewModel.getIsCreatingAccount()
|
||||||
|
.observe(getViewLifecycleOwner(), isCreatingAccount -> {
|
||||||
|
if (isCreatingAccount) {
|
||||||
|
next.setVisibility(INVISIBLE);
|
||||||
|
progressBar.setVisibility(VISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,15 +111,6 @@ public class DozeFragment extends SetupFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
setNextClicked();
|
|
||||||
viewModel.dozeExceptionConfirmed();
|
viewModel.dozeExceptionConfirmed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
void setNextClicked() {
|
|
||||||
super.setNextClicked();
|
|
||||||
|
|
||||||
next.setVisibility(INVISIBLE);
|
|
||||||
progressBar.setVisibility(VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,10 @@ import androidx.lifecycle.LiveData;
|
|||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
import static android.app.AlarmManager.ELAPSED_REALTIME;
|
import static android.app.AlarmManager.ELAPSED_REALTIME;
|
||||||
|
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
import static android.app.PendingIntent.getService;
|
import static android.app.PendingIntent.getService;
|
||||||
import static android.content.Context.ALARM_SERVICE;
|
import static android.content.Context.ALARM_SERVICE;
|
||||||
|
import static android.os.Process.myPid;
|
||||||
import static android.os.SystemClock.elapsedRealtime;
|
import static android.os.SystemClock.elapsedRealtime;
|
||||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
@@ -75,23 +77,25 @@ public class LockManagerImpl implements LockManager, Service, EventListener {
|
|||||||
LockManagerImpl(Application app, SettingsManager settingsManager,
|
LockManagerImpl(Application app, SettingsManager settingsManager,
|
||||||
AndroidNotificationManager notificationManager,
|
AndroidNotificationManager notificationManager,
|
||||||
@DatabaseExecutor Executor dbExecutor) {
|
@DatabaseExecutor Executor dbExecutor) {
|
||||||
this.appContext = app.getApplicationContext();
|
appContext = app.getApplicationContext();
|
||||||
this.settingsManager = settingsManager;
|
this.settingsManager = settingsManager;
|
||||||
this.notificationManager = notificationManager;
|
this.notificationManager = notificationManager;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.alarmManager =
|
alarmManager =
|
||||||
(AlarmManager) appContext.getSystemService(ALARM_SERVICE);
|
(AlarmManager) appContext.getSystemService(ALARM_SERVICE);
|
||||||
Intent i =
|
Intent i =
|
||||||
new Intent(ACTION_LOCK, null, appContext, BriarService.class);
|
new Intent(ACTION_LOCK, null, appContext, BriarService.class);
|
||||||
this.lockIntent = getService(appContext, 0, i, 0);
|
i.putExtra(EXTRA_PID, myPid());
|
||||||
this.timeoutNever = Integer.valueOf(
|
// When not using FLAG_UPDATE_CURRENT, the intent might have no extras
|
||||||
|
lockIntent = getService(appContext, 0, i, FLAG_UPDATE_CURRENT);
|
||||||
|
timeoutNever = Integer.parseInt(
|
||||||
appContext.getString(R.string.pref_lock_timeout_value_never));
|
appContext.getString(R.string.pref_lock_timeout_value_never));
|
||||||
this.timeoutDefault = Integer.valueOf(
|
timeoutDefault = Integer.parseInt(
|
||||||
appContext.getString(R.string.pref_lock_timeout_value_default));
|
appContext.getString(R.string.pref_lock_timeout_value_default));
|
||||||
this.timeoutMinutes = timeoutNever;
|
timeoutMinutes = timeoutNever;
|
||||||
|
|
||||||
// setting this in the constructor makes #getValue() @NonNull
|
// setting this in the constructor makes #getValue() @NonNull
|
||||||
this.lockable.setValue(false);
|
lockable.setValue(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -148,7 +152,7 @@ public class LockManagerImpl implements LockManager, Service, EventListener {
|
|||||||
boolean oldValue = lockable.getValue();
|
boolean oldValue = lockable.getValue();
|
||||||
boolean newValue = hasScreenLock(appContext) && lockableSetting;
|
boolean newValue = hasScreenLock(appContext) && lockableSetting;
|
||||||
if (oldValue != newValue) {
|
if (oldValue != newValue) {
|
||||||
this.lockable.setValue(newValue);
|
lockable.setValue(newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ public class SetPasswordFragment extends SetupFragment {
|
|||||||
private TextInputEditText passwordConfirmation;
|
private TextInputEditText passwordConfirmation;
|
||||||
private StrengthMeter strengthMeter;
|
private StrengthMeter strengthMeter;
|
||||||
private Button nextButton;
|
private Button nextButton;
|
||||||
private ProgressBar progressBar;
|
|
||||||
|
|
||||||
public static SetPasswordFragment newInstance() {
|
public static SetPasswordFragment newInstance() {
|
||||||
return new SetPasswordFragment();
|
return new SetPasswordFragment();
|
||||||
@@ -64,7 +63,7 @@ public class SetPasswordFragment extends SetupFragment {
|
|||||||
v.findViewById(R.id.password_confirm_wrapper);
|
v.findViewById(R.id.password_confirm_wrapper);
|
||||||
passwordConfirmation = v.findViewById(R.id.password_confirm);
|
passwordConfirmation = v.findViewById(R.id.password_confirm);
|
||||||
nextButton = v.findViewById(R.id.next);
|
nextButton = v.findViewById(R.id.next);
|
||||||
progressBar = v.findViewById(R.id.progress);
|
ProgressBar progressBar = v.findViewById(R.id.progress);
|
||||||
|
|
||||||
passwordEntry.addTextChangedListener(this);
|
passwordEntry.addTextChangedListener(this);
|
||||||
passwordConfirmation.addTextChangedListener(this);
|
passwordConfirmation.addTextChangedListener(this);
|
||||||
@@ -75,6 +74,17 @@ public class SetPasswordFragment extends SetupFragment {
|
|||||||
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
|
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.getIsCreatingAccount()
|
||||||
|
.observe(getViewLifecycleOwner(), isCreatingAccount -> {
|
||||||
|
if (isCreatingAccount) {
|
||||||
|
nextButton.setVisibility(INVISIBLE);
|
||||||
|
progressBar.setVisibility(VISIBLE);
|
||||||
|
// this also avoids the keyboard popping up
|
||||||
|
passwordEntry.setFocusable(false);
|
||||||
|
passwordConfirmation.setFocusable(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,20 +126,6 @@ public class SetPasswordFragment extends SetupFragment {
|
|||||||
IBinder token = passwordEntry.getWindowToken();
|
IBinder token = passwordEntry.getWindowToken();
|
||||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||||
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
|
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
|
||||||
|
|
||||||
setNextClicked();
|
|
||||||
viewModel.setPassword(passwordEntry.getText().toString());
|
viewModel.setPassword(passwordEntry.getText().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
void setNextClicked() {
|
|
||||||
super.setNextClicked();
|
|
||||||
|
|
||||||
passwordEntry.setFocusable(false);
|
|
||||||
passwordConfirmation.setFocusable(false);
|
|
||||||
if (!viewModel.needToShowDozeFragment()) {
|
|
||||||
nextButton.setVisibility(INVISIBLE);
|
|
||||||
progressBar.setVisibility(VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.view.KeyEvent;
|
|||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.TextView.OnEditorActionListener;
|
import android.widget.TextView.OnEditorActionListener;
|
||||||
@@ -19,8 +18,6 @@ import org.briarproject.briar.android.fragment.BaseFragment;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
@@ -35,7 +32,6 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
|
|||||||
OnEditorActionListener, OnClickListener {
|
OnEditorActionListener, OnClickListener {
|
||||||
|
|
||||||
private final static String STATE_KEY_CLICKED = "setupFragmentClicked";
|
private final static String STATE_KEY_CLICKED = "setupFragmentClicked";
|
||||||
private boolean clicked = false;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
@@ -48,27 +44,6 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
|
|||||||
.get(SetupViewModel.class);
|
.get(SetupViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
clicked = savedInstanceState.getBoolean(STATE_KEY_CLICKED);
|
|
||||||
}
|
|
||||||
if (clicked) {
|
|
||||||
setNextClicked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putBoolean(STATE_KEY_CLICKED, clicked);
|
|
||||||
}
|
|
||||||
|
|
||||||
@CallSuper
|
|
||||||
void setNextClicked() {
|
|
||||||
this.clicked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.help_action, menu);
|
inflater.inflate(R.menu.help_action, menu);
|
||||||
@@ -114,5 +89,4 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
|
|||||||
public void afterTextChanged(Editable editable) {
|
public void afterTextChanged(Editable editable) {
|
||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME;
|
import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME;
|
||||||
@@ -36,6 +38,8 @@ class SetupViewModel extends AndroidViewModel {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private String authorName, password;
|
private String authorName, password;
|
||||||
private final MutableLiveEvent<State> state = new MutableLiveEvent<>();
|
private final MutableLiveEvent<State> state = new MutableLiveEvent<>();
|
||||||
|
private final MutableLiveData<Boolean> isCreatingAccount =
|
||||||
|
new MutableLiveData<>(false);
|
||||||
|
|
||||||
private final AccountManager accountManager;
|
private final AccountManager accountManager;
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
@@ -67,6 +71,10 @@ class SetupViewModel extends AndroidViewModel {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LiveData<Boolean> getIsCreatingAccount() {
|
||||||
|
return isCreatingAccount;
|
||||||
|
}
|
||||||
|
|
||||||
void setAuthorName(String authorName) {
|
void setAuthorName(String authorName) {
|
||||||
this.authorName = authorName;
|
this.authorName = authorName;
|
||||||
state.setEvent(SET_PASSWORD);
|
state.setEvent(SET_PASSWORD);
|
||||||
@@ -97,6 +105,7 @@ class SetupViewModel extends AndroidViewModel {
|
|||||||
private void createAccount() {
|
private void createAccount() {
|
||||||
if (authorName == null) throw new IllegalStateException();
|
if (authorName == null) throw new IllegalStateException();
|
||||||
if (password == null) throw new IllegalStateException();
|
if (password == null) throw new IllegalStateException();
|
||||||
|
isCreatingAccount.setValue(true);
|
||||||
ioExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
if (accountManager.createAccount(authorName, password)) {
|
if (accountManager.createAccount(authorName, password)) {
|
||||||
LOG.info("Created account");
|
LOG.info("Created account");
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class UnlockActivity extends BaseActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode,
|
protected void onActivityResult(int requestCode, int resultCode,
|
||||||
Intent data) {
|
@Nullable Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
if (requestCode == REQUEST_KEYGUARD_UNLOCK) {
|
if (requestCode == REQUEST_KEYGUARD_UNLOCK) {
|
||||||
if (resultCode == RESULT_OK) unlock();
|
if (resultCode == RESULT_OK) unlock();
|
||||||
|
|||||||
@@ -11,10 +11,8 @@ import org.briarproject.briar.android.account.SetupActivity;
|
|||||||
import org.briarproject.briar.android.account.UnlockActivity;
|
import org.briarproject.briar.android.account.UnlockActivity;
|
||||||
import org.briarproject.briar.android.blog.BlogActivity;
|
import org.briarproject.briar.android.blog.BlogActivity;
|
||||||
import org.briarproject.briar.android.blog.BlogFragment;
|
import org.briarproject.briar.android.blog.BlogFragment;
|
||||||
import org.briarproject.briar.android.blog.BlogModule;
|
|
||||||
import org.briarproject.briar.android.blog.BlogPostFragment;
|
import org.briarproject.briar.android.blog.BlogPostFragment;
|
||||||
import org.briarproject.briar.android.blog.FeedFragment;
|
import org.briarproject.briar.android.blog.FeedFragment;
|
||||||
import org.briarproject.briar.android.blog.FeedPostFragment;
|
|
||||||
import org.briarproject.briar.android.blog.ReblogActivity;
|
import org.briarproject.briar.android.blog.ReblogActivity;
|
||||||
import org.briarproject.briar.android.blog.ReblogFragment;
|
import org.briarproject.briar.android.blog.ReblogFragment;
|
||||||
import org.briarproject.briar.android.blog.RssFeedImportActivity;
|
import org.briarproject.briar.android.blog.RssFeedImportActivity;
|
||||||
@@ -85,7 +83,6 @@ import dagger.Component;
|
|||||||
@ActivityScope
|
@ActivityScope
|
||||||
@Component(modules = {
|
@Component(modules = {
|
||||||
ActivityModule.class,
|
ActivityModule.class,
|
||||||
BlogModule.class,
|
|
||||||
CreateGroupModule.class,
|
CreateGroupModule.class,
|
||||||
GroupInvitationModule.class,
|
GroupInvitationModule.class,
|
||||||
GroupMemberModule.class,
|
GroupMemberModule.class,
|
||||||
@@ -152,8 +149,6 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(BlogPostFragment fragment);
|
void inject(BlogPostFragment fragment);
|
||||||
|
|
||||||
void inject(FeedPostFragment fragment);
|
|
||||||
|
|
||||||
void inject(ReblogFragment fragment);
|
void inject(ReblogFragment fragment);
|
||||||
|
|
||||||
void inject(ReblogActivity activity);
|
void inject(ReblogActivity activity);
|
||||||
|
|||||||
@@ -28,9 +28,12 @@ import javax.inject.Inject;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
|
||||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.briar.android.attachment.AttachmentItem.State.AVAILABLE;
|
import static org.briarproject.briar.android.attachment.AttachmentItem.State.AVAILABLE;
|
||||||
@@ -86,7 +89,17 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
|
|||||||
List<AttachmentHeader> headers = messageHeader.getAttachmentHeaders();
|
List<AttachmentHeader> headers = messageHeader.getAttachmentHeaders();
|
||||||
List<LiveData<AttachmentItem>> items = new ArrayList<>(headers.size());
|
List<LiveData<AttachmentItem>> items = new ArrayList<>(headers.size());
|
||||||
boolean needsSize = headers.size() == 1;
|
boolean needsSize = headers.size() == 1;
|
||||||
|
List<String> supported = asList(getSupportedImageContentTypes());
|
||||||
for (AttachmentHeader h : headers) {
|
for (AttachmentHeader h : headers) {
|
||||||
|
// Fail early if we don't support the content type
|
||||||
|
if (!supported.contains(h.getContentType())) {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Unsupported content type " + h.getContentType());
|
||||||
|
}
|
||||||
|
AttachmentItem item = new AttachmentItem(h, "", ERROR);
|
||||||
|
items.add(new MutableLiveData<>(item));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// try cache for existing item live data
|
// try cache for existing item live data
|
||||||
MutableLiveData<AttachmentItem> liveData =
|
MutableLiveData<AttachmentItem> liveData =
|
||||||
itemsWithSize.get(h.getMessageId());
|
itemsWithSize.get(h.getMessageId());
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
|
||||||
|
|
||||||
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.handler.ExceptionHandler;
|
|
||||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
|
||||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
interface BaseController {
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onStart();
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onStop();
|
|
||||||
|
|
||||||
void loadBlogPosts(GroupId g,
|
|
||||||
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);
|
|
||||||
|
|
||||||
void loadBlogPost(BlogPostHeader header,
|
|
||||||
ResultExceptionHandler<BlogPostItem, DbException> handler);
|
|
||||||
|
|
||||||
void loadBlogPost(GroupId g, MessageId m,
|
|
||||||
ResultExceptionHandler<BlogPostItem, DbException> handler);
|
|
||||||
|
|
||||||
void repeatPost(BlogPostItem item, @Nullable String comment,
|
|
||||||
ExceptionHandler<DbException> handler);
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
interface BlogListener {
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onBlogPostAdded(BlogPostHeader header, boolean local);
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onBlogRemoved();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
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.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.briar.android.controller.DbControllerImpl;
|
|
||||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
|
||||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
|
||||||
import org.briarproject.briar.api.blog.Blog;
|
|
||||||
import org.briarproject.briar.api.blog.BlogCommentHeader;
|
|
||||||
import org.briarproject.briar.api.blog.BlogManager;
|
|
||||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
|
||||||
import org.briarproject.briar.util.HtmlUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
|
||||||
|
|
||||||
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;
|
|
||||||
import static org.briarproject.briar.util.HtmlUtils.ARTICLE;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
abstract class BaseControllerImpl extends DbControllerImpl
|
|
||||||
implements BaseController, EventListener {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(BaseControllerImpl.class.getName());
|
|
||||||
|
|
||||||
protected final EventBus eventBus;
|
|
||||||
protected final AndroidNotificationManager notificationManager;
|
|
||||||
protected final IdentityManager identityManager;
|
|
||||||
protected final BlogManager blogManager;
|
|
||||||
|
|
||||||
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
|
||||||
private final Map<MessageId, BlogPostHeader> headerCache =
|
|
||||||
new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
BaseControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
|
||||||
LifecycleManager lifecycleManager, EventBus eventBus,
|
|
||||||
AndroidNotificationManager notificationManager,
|
|
||||||
IdentityManager identityManager, BlogManager blogManager) {
|
|
||||||
super(dbExecutor, lifecycleManager);
|
|
||||||
this.eventBus = eventBus;
|
|
||||||
this.notificationManager = notificationManager;
|
|
||||||
this.identityManager = identityManager;
|
|
||||||
this.blogManager = blogManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CallSuper
|
|
||||||
public void onStart() {
|
|
||||||
eventBus.addListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CallSuper
|
|
||||||
public void onStop() {
|
|
||||||
eventBus.removeListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadBlogPosts(GroupId groupId,
|
|
||||||
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {
|
|
||||||
runOnDbThread(() -> {
|
|
||||||
try {
|
|
||||||
Collection<BlogPostItem> items = loadItems(groupId);
|
|
||||||
handler.onResult(items);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
handler.onException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Collection<BlogPostItem> loadItems(GroupId groupId) throws DbException {
|
|
||||||
long start = now();
|
|
||||||
Collection<BlogPostHeader> headers =
|
|
||||||
blogManager.getPostHeaders(groupId);
|
|
||||||
logDuration(LOG, "Loading headers", start);
|
|
||||||
Collection<BlogPostItem> items = new ArrayList<>(headers.size());
|
|
||||||
start = now();
|
|
||||||
for (BlogPostHeader h : headers) {
|
|
||||||
headerCache.put(h.getId(), h);
|
|
||||||
BlogPostItem item = getItem(h);
|
|
||||||
items.add(item);
|
|
||||||
}
|
|
||||||
logDuration(LOG, "Loading bodies", start);
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadBlogPost(BlogPostHeader header,
|
|
||||||
ResultExceptionHandler<BlogPostItem, DbException> handler) {
|
|
||||||
|
|
||||||
String text = textCache.get(header.getId());
|
|
||||||
if (text != null) {
|
|
||||||
LOG.info("Loaded text from cache");
|
|
||||||
handler.onResult(new BlogPostItem(header, text));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
runOnDbThread(() -> {
|
|
||||||
try {
|
|
||||||
long start = now();
|
|
||||||
BlogPostItem item = getItem(header);
|
|
||||||
logDuration(LOG, "Loading text", start);
|
|
||||||
handler.onResult(item);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
handler.onException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadBlogPost(GroupId g, MessageId m,
|
|
||||||
ResultExceptionHandler<BlogPostItem, DbException> handler) {
|
|
||||||
|
|
||||||
BlogPostHeader header = headerCache.get(m);
|
|
||||||
if (header != null) {
|
|
||||||
LOG.info("Loaded header from cache");
|
|
||||||
loadBlogPost(header, handler);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
runOnDbThread(() -> {
|
|
||||||
try {
|
|
||||||
long start = now();
|
|
||||||
BlogPostHeader header1 = getPostHeader(g, m);
|
|
||||||
BlogPostItem item = getItem(header1);
|
|
||||||
logDuration(LOG, "Loading post", start);
|
|
||||||
handler.onResult(item);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
handler.onException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void repeatPost(BlogPostItem item, @Nullable String comment,
|
|
||||||
ExceptionHandler<DbException> handler) {
|
|
||||||
runOnDbThread(() -> {
|
|
||||||
try {
|
|
||||||
LocalAuthor a = identityManager.getLocalAuthor();
|
|
||||||
Blog b = blogManager.getPersonalBlog(a);
|
|
||||||
BlogPostHeader h = item.getHeader();
|
|
||||||
blogManager.addLocalComment(a, b.getId(), comment, h);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
handler.onException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private BlogPostHeader getPostHeader(GroupId g, MessageId m)
|
|
||||||
throws DbException {
|
|
||||||
BlogPostHeader header = headerCache.get(m);
|
|
||||||
if (header == null) {
|
|
||||||
header = blogManager.getPostHeader(g, m);
|
|
||||||
headerCache.put(m, header);
|
|
||||||
}
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
@DatabaseExecutor
|
|
||||||
private BlogPostItem getItem(BlogPostHeader h) throws DbException {
|
|
||||||
String text;
|
|
||||||
if (h instanceof BlogCommentHeader) {
|
|
||||||
BlogCommentHeader c = (BlogCommentHeader) h;
|
|
||||||
BlogCommentItem item = new BlogCommentItem(c);
|
|
||||||
text = getPostText(item.getPostHeader().getId());
|
|
||||||
item.setText(text);
|
|
||||||
return item;
|
|
||||||
} else {
|
|
||||||
text = getPostText(h.getId());
|
|
||||||
return new BlogPostItem(h, text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@DatabaseExecutor
|
|
||||||
private String getPostText(MessageId m) throws DbException {
|
|
||||||
String text = textCache.get(m);
|
|
||||||
if (text == null) {
|
|
||||||
text = HtmlUtils.clean(blogManager.getPostText(m), ARTICLE);
|
|
||||||
textCache.put(m, text);
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
|
|
||||||
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.fragment.BaseFragment;
|
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
|
||||||
import static android.view.View.INVISIBLE;
|
|
||||||
import static android.view.View.VISIBLE;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
abstract class BasePostFragment extends BaseFragment {
|
|
||||||
|
|
||||||
static final String POST_ID = "briar.POST_ID";
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(BasePostFragment.class.getName());
|
|
||||||
|
|
||||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
|
||||||
|
|
||||||
protected MessageId postId;
|
|
||||||
private ProgressBar progressBar;
|
|
||||||
private BlogPostViewHolder ui;
|
|
||||||
private BlogPostItem post;
|
|
||||||
private Runnable refresher;
|
|
||||||
|
|
||||||
@CallSuper
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater,
|
|
||||||
@Nullable ViewGroup container,
|
|
||||||
@Nullable Bundle savedInstanceState) {
|
|
||||||
// retrieve MessageId of blog post from arguments
|
|
||||||
byte[] p = requireArguments().getByteArray(POST_ID);
|
|
||||||
if (p == null) throw new IllegalStateException("No post ID in args");
|
|
||||||
postId = new MessageId(p);
|
|
||||||
|
|
||||||
View view = inflater.inflate(R.layout.fragment_blog_post, container,
|
|
||||||
false);
|
|
||||||
progressBar = view.findViewById(R.id.progressBar);
|
|
||||||
progressBar.setVisibility(VISIBLE);
|
|
||||||
ui = new BlogPostViewHolder(view, true, new OnBlogPostClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onBlogPostClick(BlogPostItem post) {
|
|
||||||
// We're already there
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthorClick(BlogPostItem post) {
|
|
||||||
if (getContext() == null) return;
|
|
||||||
Intent i = new Intent(getContext(), BlogActivity.class);
|
|
||||||
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
|
|
||||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
getContext().startActivity(i);
|
|
||||||
}
|
|
||||||
}, getFragmentManager());
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@CallSuper
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
startPeriodicUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@CallSuper
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
stopPeriodicUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
protected void onBlogPostLoaded(BlogPostItem post) {
|
|
||||||
progressBar.setVisibility(INVISIBLE);
|
|
||||||
this.post = post;
|
|
||||||
ui.bindItem(post);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startPeriodicUpdate() {
|
|
||||||
refresher = () -> {
|
|
||||||
LOG.info("Updating Content...");
|
|
||||||
ui.updateDate(post.getTimestamp());
|
|
||||||
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
|
|
||||||
};
|
|
||||||
LOG.info("Adding Handler Callback");
|
|
||||||
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopPeriodicUpdate() {
|
|
||||||
if (refresher != null) {
|
|
||||||
LOG.info("Removing Handler Callback");
|
|
||||||
handler.removeCallbacks(refresher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
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.EventBus;
|
||||||
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
|
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.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
|
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.blog.Blog;
|
||||||
|
import org.briarproject.briar.api.blog.BlogCommentHeader;
|
||||||
|
import org.briarproject.briar.api.blog.BlogManager;
|
||||||
|
import org.briarproject.briar.api.blog.BlogPostHeader;
|
||||||
|
import org.briarproject.briar.util.HtmlUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
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.util.HtmlUtils.ARTICLE;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
abstract class BaseViewModel extends DbViewModel implements EventListener {
|
||||||
|
|
||||||
|
private static final Logger LOG = getLogger(BaseViewModel.class.getName());
|
||||||
|
|
||||||
|
private final EventBus eventBus;
|
||||||
|
protected final IdentityManager identityManager;
|
||||||
|
protected final AndroidNotificationManager notificationManager;
|
||||||
|
protected final BlogManager blogManager;
|
||||||
|
|
||||||
|
protected final MutableLiveData<LiveResult<ListUpdate>> blogPosts =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
|
||||||
|
BaseViewModel(Application application,
|
||||||
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
|
LifecycleManager lifecycleManager,
|
||||||
|
TransactionManager db,
|
||||||
|
AndroidExecutor androidExecutor,
|
||||||
|
EventBus eventBus,
|
||||||
|
IdentityManager identityManager,
|
||||||
|
AndroidNotificationManager notificationManager,
|
||||||
|
BlogManager blogManager) {
|
||||||
|
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.identityManager = identityManager;
|
||||||
|
this.notificationManager = notificationManager;
|
||||||
|
this.blogManager = blogManager;
|
||||||
|
eventBus.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCleared() {
|
||||||
|
super.onCleared();
|
||||||
|
eventBus.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DatabaseExecutor
|
||||||
|
protected List<BlogPostItem> loadBlogPosts(Transaction txn, GroupId groupId)
|
||||||
|
throws DbException {
|
||||||
|
long start = now();
|
||||||
|
List<BlogPostHeader> headers =
|
||||||
|
blogManager.getPostHeaders(txn, groupId);
|
||||||
|
logDuration(LOG, "Loading headers", start);
|
||||||
|
List<BlogPostItem> items = new ArrayList<>(headers.size());
|
||||||
|
start = now();
|
||||||
|
for (BlogPostHeader h : headers) {
|
||||||
|
BlogPostItem item = getItem(txn, h);
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
logDuration(LOG, "Loading bodies", start);
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DatabaseExecutor
|
||||||
|
protected BlogPostItem getItem(Transaction txn, BlogPostHeader h)
|
||||||
|
throws DbException {
|
||||||
|
String text;
|
||||||
|
if (h instanceof BlogCommentHeader) {
|
||||||
|
BlogCommentHeader c = (BlogCommentHeader) h;
|
||||||
|
BlogCommentItem item = new BlogCommentItem(c);
|
||||||
|
text = getPostText(txn, item.getPostHeader().getId());
|
||||||
|
item.setText(text);
|
||||||
|
return item;
|
||||||
|
} else {
|
||||||
|
text = getPostText(txn, h.getId());
|
||||||
|
return new BlogPostItem(h, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DatabaseExecutor
|
||||||
|
private String getPostText(Transaction txn, MessageId m)
|
||||||
|
throws DbException {
|
||||||
|
return HtmlUtils.clean(blogManager.getPostText(txn, m), ARTICLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<LiveResult<BlogPostItem>> loadBlogPost(GroupId g, MessageId m) {
|
||||||
|
MutableLiveData<LiveResult<BlogPostItem>> result =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
runOnDbThread(true, txn -> {
|
||||||
|
long start = now();
|
||||||
|
BlogPostHeader header = blogManager.getPostHeader(txn, g, m);
|
||||||
|
BlogPostItem item = getItem(txn, header);
|
||||||
|
logDuration(LOG, "Loading post", start);
|
||||||
|
result.postValue(new LiveResult<>(item));
|
||||||
|
}, e -> {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
result.postValue(new LiveResult<>(e));
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onBlogPostAdded(BlogPostHeader header, boolean local) {
|
||||||
|
runOnDbThread(true, txn -> {
|
||||||
|
BlogPostItem item = getItem(txn, header);
|
||||||
|
txn.attach(() -> onBlogPostItemAdded(item, local));
|
||||||
|
}, this::handleException);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void onBlogPostItemAdded(BlogPostItem item, boolean local) {
|
||||||
|
List<BlogPostItem> items = addListItem(getBlogPostItems(), item);
|
||||||
|
if (items != null) {
|
||||||
|
Collections.sort(items);
|
||||||
|
blogPosts.setValue(new LiveResult<>(new ListUpdate(local, items)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void repeatPost(BlogPostItem item, @Nullable String comment) {
|
||||||
|
runOnDbThread(() -> {
|
||||||
|
try {
|
||||||
|
LocalAuthor a = identityManager.getLocalAuthor();
|
||||||
|
Blog b = blogManager.getPersonalBlog(a);
|
||||||
|
BlogPostHeader h = item.getHeader();
|
||||||
|
blogManager.addLocalComment(a, b.getId(), comment, h);
|
||||||
|
} catch (DbException e) {
|
||||||
|
handleException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<LiveResult<ListUpdate>> getBlogPosts() {
|
||||||
|
return blogPosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
protected List<BlogPostItem> getBlogPostItems() {
|
||||||
|
LiveResult<ListUpdate> value = blogPosts.getValue();
|
||||||
|
if (value == null) return null;
|
||||||
|
ListUpdate result = value.getResultOrNull();
|
||||||
|
return result == null ? null : result.getItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ListUpdate {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final Boolean postAddedWasLocal;
|
||||||
|
private final List<BlogPostItem> items;
|
||||||
|
|
||||||
|
ListUpdate(@Nullable Boolean postAddedWasLocal,
|
||||||
|
List<BlogPostItem> items) {
|
||||||
|
this.postAddedWasLocal = postAddedWasLocal;
|
||||||
|
this.items = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Boolean getPostAddedWasLocal() {
|
||||||
|
return postAddedWasLocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BlogPostItem> getItems() {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,9 @@ import javax.annotation.Nullable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -23,7 +26,16 @@ public class BlogActivity extends BriarActivity
|
|||||||
implements BaseFragmentListener {
|
implements BaseFragmentListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BlogController blogController;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private BlogViewModel viewModel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectActivity(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||||
|
.get(BlogViewModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle state) {
|
public void onCreate(@Nullable Bundle state) {
|
||||||
@@ -31,32 +43,36 @@ public class BlogActivity extends BriarActivity
|
|||||||
|
|
||||||
// GroupId from Intent
|
// GroupId from Intent
|
||||||
Intent i = getIntent();
|
Intent i = getIntent();
|
||||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
GroupId groupId =
|
||||||
if (b == null) throw new IllegalStateException("No group ID in intent");
|
new GroupId(requireNonNull(i.getByteArrayExtra(GROUP_ID)));
|
||||||
GroupId groupId = new GroupId(b);
|
viewModel.setGroupId(groupId);
|
||||||
blogController.setGroupId(groupId);
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_fragment_container_toolbar);
|
setContentView(R.layout.activity_fragment_container_toolbar);
|
||||||
Toolbar toolbar = setUpCustomToolbar(false);
|
Toolbar toolbar = setUpCustomToolbar(false);
|
||||||
|
|
||||||
// Open Sharing Status on Toolbar click
|
// Open Sharing Status on Toolbar click
|
||||||
if (toolbar != null) {
|
toolbar.setOnClickListener(v -> {
|
||||||
toolbar.setOnClickListener(v -> {
|
Intent i1 = new Intent(BlogActivity.this,
|
||||||
Intent i1 = new Intent(BlogActivity.this,
|
BlogSharingStatusActivity.class);
|
||||||
BlogSharingStatusActivity.class);
|
i1.putExtra(GROUP_ID, groupId.getBytes());
|
||||||
i1.putExtra(GROUP_ID, groupId.getBytes());
|
startActivity(i1);
|
||||||
startActivity(i1);
|
});
|
||||||
});
|
|
||||||
}
|
viewModel.getBlog().observe(this, blog ->
|
||||||
|
setTitle(blog.getBlog().getAuthor().getName())
|
||||||
|
);
|
||||||
|
viewModel.getSharingInfo().observe(this, info ->
|
||||||
|
setToolbarSubTitle(info.total, info.online)
|
||||||
|
);
|
||||||
|
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
showInitialFragment(BlogFragment.newInstance(groupId));
|
showInitialFragment(BlogFragment.newInstance(groupId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void setToolbarSubTitle(int total, int online) {
|
||||||
public void injectActivity(ActivityComponent component) {
|
requireNonNull(getSupportActionBar())
|
||||||
component.inject(this);
|
.setSubtitle(getString(R.string.shared_with, total, online));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import java.util.Collections;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
// This class is not thread-safe
|
import javax.annotation.concurrent.NotThreadSafe;
|
||||||
|
|
||||||
|
@NotThreadSafe
|
||||||
class BlogCommentItem extends BlogPostItem {
|
class BlogCommentItem extends BlogPostItem {
|
||||||
|
|
||||||
private static final BlogCommentComparator COMPARATOR =
|
private static final BlogCommentComparator COMPARATOR =
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
|
||||||
|
|
||||||
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.handler.ResultExceptionHandler;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
public interface BlogController extends BaseController {
|
|
||||||
|
|
||||||
void setGroupId(GroupId g);
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void setBlogSharingListener(BlogSharingListener listener);
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void unsetBlogSharingListener(BlogSharingListener listener);
|
|
||||||
|
|
||||||
void loadBlogPosts(
|
|
||||||
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);
|
|
||||||
|
|
||||||
void loadBlogPost(MessageId m,
|
|
||||||
ResultExceptionHandler<BlogPostItem, DbException> handler);
|
|
||||||
|
|
||||||
void loadBlog(ResultExceptionHandler<BlogItem, DbException> handler);
|
|
||||||
|
|
||||||
void deleteBlog(ResultExceptionHandler<Void, DbException> handler);
|
|
||||||
|
|
||||||
void loadSharingContacts(
|
|
||||||
ResultExceptionHandler<Collection<ContactId>, DbException> handler);
|
|
||||||
|
|
||||||
interface BlogSharingListener extends BlogListener {
|
|
||||||
@UiThread
|
|
||||||
void onBlogInvitationAccepted(ContactId c);
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onBlogLeft(ContactId c);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@@ -12,33 +10,25 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.blog.BaseViewModel.ListUpdate;
|
||||||
import org.briarproject.briar.android.blog.BlogController.BlogSharingListener;
|
|
||||||
import org.briarproject.briar.android.controller.SharingController;
|
|
||||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.sharing.BlogSharingStatusActivity;
|
import org.briarproject.briar.android.sharing.BlogSharingStatusActivity;
|
||||||
import org.briarproject.briar.android.sharing.ShareBlogActivity;
|
import org.briarproject.briar.android.sharing.ShareBlogActivity;
|
||||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
import org.briarproject.briar.android.widget.LinkDialogFragment;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.UiThread;
|
import androidx.annotation.UiThread;
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView.LayoutManager;
|
import androidx.recyclerview.widget.RecyclerView.LayoutManager;
|
||||||
|
|
||||||
@@ -49,30 +39,22 @@ import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG;
|
|||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_BLOG;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_BLOG;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
|
||||||
import static org.briarproject.briar.android.controller.SharingController.SharingListener;
|
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class BlogFragment extends BaseFragment
|
public class BlogFragment extends BaseFragment
|
||||||
implements BlogSharingListener, SharingListener,
|
implements OnBlogPostClickListener {
|
||||||
OnBlogPostClickListener {
|
|
||||||
|
|
||||||
private final static String TAG = BlogFragment.class.getName();
|
private final static String TAG = BlogFragment.class.getName();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BlogController blogController;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
@Inject
|
|
||||||
SharingController sharingController;
|
|
||||||
@Nullable
|
|
||||||
private Parcelable layoutManagerState;
|
|
||||||
|
|
||||||
private GroupId groupId;
|
private GroupId groupId;
|
||||||
private BlogPostAdapter adapter;
|
private BlogViewModel viewModel;
|
||||||
private LayoutManager layoutManager;
|
private final BlogPostAdapter adapter = new BlogPostAdapter(false, this);
|
||||||
private BriarRecyclerView list;
|
private BriarRecyclerView list;
|
||||||
private MenuItem writeButton, deleteButton;
|
|
||||||
private boolean isMyBlog = false, canDeleteBlog = false;
|
|
||||||
|
|
||||||
static BlogFragment newInstance(GroupId groupId) {
|
static BlogFragment newInstance(GroupId groupId) {
|
||||||
BlogFragment f = new BlogFragment();
|
BlogFragment f = new BlogFragment();
|
||||||
@@ -87,8 +69,8 @@ public class BlogFragment extends BaseFragment
|
|||||||
@Override
|
@Override
|
||||||
public void injectFragment(ActivityComponent component) {
|
public void injectFragment(ActivityComponent component) {
|
||||||
component.inject(this);
|
component.inject(this);
|
||||||
blogController.setBlogSharingListener(this);
|
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||||
sharingController.setSharingListener(this);
|
.get(BlogViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -103,95 +85,75 @@ public class BlogFragment extends BaseFragment
|
|||||||
|
|
||||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||||
|
|
||||||
adapter = new BlogPostAdapter(requireActivity(), this,
|
|
||||||
getFragmentManager());
|
|
||||||
list = v.findViewById(R.id.postList);
|
list = v.findViewById(R.id.postList);
|
||||||
layoutManager = new LinearLayoutManager(getActivity());
|
LayoutManager layoutManager = new LinearLayoutManager(getActivity());
|
||||||
list.setLayoutManager(layoutManager);
|
list.setLayoutManager(layoutManager);
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
list.showProgressBar();
|
list.showProgressBar();
|
||||||
list.setEmptyText(getString(R.string.blogs_other_blog_empty_state));
|
list.setEmptyText(getString(R.string.blogs_other_blog_empty_state));
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
viewModel.getBlogPosts().observe(getViewLifecycleOwner(), result ->
|
||||||
layoutManagerState =
|
result.onError(this::handleException)
|
||||||
savedInstanceState.getParcelable("layoutManager");
|
.onSuccess(this::onBlogPostsLoaded)
|
||||||
}
|
);
|
||||||
|
viewModel.getBlogRemoved().observe(getViewLifecycleOwner(), removed -> {
|
||||||
|
if (removed) finish();
|
||||||
|
});
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
sharingController.onStart();
|
viewModel.blockAndClearNotifications();
|
||||||
loadBlog();
|
|
||||||
loadSharedContacts();
|
|
||||||
loadBlogPosts(false);
|
|
||||||
list.startPeriodicUpdate();
|
list.startPeriodicUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
sharingController.onStop();
|
viewModel.unblockNotifications();
|
||||||
list.stopPeriodicUpdate();
|
list.stopPeriodicUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
blogController.unsetBlogSharingListener(this);
|
|
||||||
sharingController.unsetSharingListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
if (layoutManager != null) {
|
|
||||||
layoutManagerState = layoutManager.onSaveInstanceState();
|
|
||||||
outState.putParcelable("layoutManager", layoutManagerState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.blogs_blog_actions, menu);
|
inflater.inflate(R.menu.blogs_blog_actions, menu);
|
||||||
writeButton = menu.findItem(R.id.action_write_blog_post);
|
MenuItem writeButton = menu.findItem(R.id.action_write_blog_post);
|
||||||
if (isMyBlog) writeButton.setVisible(true);
|
MenuItem deleteButton = menu.findItem(R.id.action_blog_delete);
|
||||||
deleteButton = menu.findItem(R.id.action_blog_delete);
|
viewModel.getBlog().observe(getViewLifecycleOwner(), blog -> {
|
||||||
if (canDeleteBlog) deleteButton.setEnabled(true);
|
if (blog.isOurs()) writeButton.setVisible(true);
|
||||||
|
if (blog.canBeRemoved()) deleteButton.setEnabled(true);
|
||||||
|
});
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
int itemId = item.getItemId();
|
||||||
case R.id.action_write_blog_post:
|
if (itemId == R.id.action_write_blog_post) {
|
||||||
Intent i = new Intent(getActivity(),
|
Intent i = new Intent(getActivity(), WriteBlogPostActivity.class);
|
||||||
WriteBlogPostActivity.class);
|
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
startActivityForResult(i, REQUEST_WRITE_BLOG_POST);
|
||||||
startActivityForResult(i, REQUEST_WRITE_BLOG_POST);
|
return true;
|
||||||
return true;
|
} else if (itemId == R.id.action_blog_share) {
|
||||||
case R.id.action_blog_share:
|
Intent i = new Intent(getActivity(), ShareBlogActivity.class);
|
||||||
Intent i2 = new Intent(getActivity(), ShareBlogActivity.class);
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||||
i2.putExtra(GROUP_ID, groupId.getBytes());
|
startActivityForResult(i, REQUEST_SHARE_BLOG);
|
||||||
startActivityForResult(i2, REQUEST_SHARE_BLOG);
|
return true;
|
||||||
return true;
|
} else if (itemId == R.id.action_blog_sharing_status) {
|
||||||
case R.id.action_blog_sharing_status:
|
Intent i =
|
||||||
Intent i3 = new Intent(getActivity(),
|
new Intent(getActivity(), BlogSharingStatusActivity.class);
|
||||||
BlogSharingStatusActivity.class);
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||||
i3.putExtra(GROUP_ID, groupId.getBytes());
|
startActivity(i);
|
||||||
startActivity(i3);
|
return true;
|
||||||
return true;
|
} else if (itemId == R.id.action_blog_delete) {
|
||||||
case R.id.action_blog_delete:
|
showDeleteDialog();
|
||||||
showDeleteDialog();
|
return true;
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -201,7 +163,6 @@ public class BlogFragment extends BaseFragment
|
|||||||
|
|
||||||
if (request == REQUEST_WRITE_BLOG_POST && result == RESULT_OK) {
|
if (request == REQUEST_WRITE_BLOG_POST && result == RESULT_OK) {
|
||||||
displaySnackbar(R.string.blogs_blog_post_created, true);
|
displaySnackbar(R.string.blogs_blog_post_created, true);
|
||||||
loadBlogPosts(true);
|
|
||||||
} else if (request == REQUEST_SHARE_BLOG && result == RESULT_OK) {
|
} else if (request == REQUEST_SHARE_BLOG && result == RESULT_OK) {
|
||||||
displaySnackbar(R.string.blogs_sharing_snackbar, false);
|
displaySnackbar(R.string.blogs_sharing_snackbar, false);
|
||||||
}
|
}
|
||||||
@@ -212,35 +173,25 @@ public class BlogFragment extends BaseFragment
|
|||||||
return TAG;
|
return TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void onBlogPostsLoaded(ListUpdate update) {
|
||||||
public void onBlogPostAdded(BlogPostHeader header, boolean local) {
|
adapter.submitList(update.getItems(), () -> {
|
||||||
blogController.loadBlogPost(header,
|
Boolean wasLocal = update.getPostAddedWasLocal();
|
||||||
new UiResultExceptionHandler<BlogPostItem, DbException>(
|
if (wasLocal != null && wasLocal) {
|
||||||
this) {
|
list.scrollToPosition(0);
|
||||||
@Override
|
displaySnackbar(R.string.blogs_blog_post_created,
|
||||||
public void onResultUi(BlogPostItem post) {
|
false);
|
||||||
adapter.add(post);
|
} else if (wasLocal != null) {
|
||||||
if (local) {
|
displaySnackbar(R.string.blogs_blog_post_received,
|
||||||
list.scrollToPosition(0);
|
true);
|
||||||
displaySnackbar(R.string.blogs_blog_post_created,
|
}
|
||||||
false);
|
list.showData();
|
||||||
} else {
|
});
|
||||||
displaySnackbar(R.string.blogs_blog_post_received,
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleException(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBlogPostClick(BlogPostItem post) {
|
public void onBlogPostClick(BlogPostItem post) {
|
||||||
BlogPostFragment f = BlogPostFragment.newInstance(post.getId());
|
BlogPostFragment f =
|
||||||
|
BlogPostFragment.newInstance(groupId, post.getId(), false);
|
||||||
showNextFragment(f);
|
showNextFragment(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,111 +207,10 @@ public class BlogFragment extends BaseFragment
|
|||||||
getContext().startActivity(i);
|
getContext().startActivity(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadBlogPosts(boolean reload) {
|
|
||||||
blogController.loadBlogPosts(
|
|
||||||
new UiResultExceptionHandler<Collection<BlogPostItem>,
|
|
||||||
DbException>(this) {
|
|
||||||
@Override
|
|
||||||
public void onResultUi(Collection<BlogPostItem> posts) {
|
|
||||||
if (posts.isEmpty()) {
|
|
||||||
list.showData();
|
|
||||||
} else {
|
|
||||||
adapter.addAll(posts);
|
|
||||||
if (reload || layoutManagerState == null) {
|
|
||||||
list.scrollToPosition(0);
|
|
||||||
} else {
|
|
||||||
layoutManager.onRestoreInstanceState(
|
|
||||||
layoutManagerState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleException(exception);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadBlog() {
|
|
||||||
blogController.loadBlog(
|
|
||||||
new UiResultExceptionHandler<BlogItem, DbException>(this) {
|
|
||||||
@Override
|
|
||||||
public void onResultUi(BlogItem blog) {
|
|
||||||
setToolbarTitle(blog.getBlog().getAuthor());
|
|
||||||
if (blog.isOurs())
|
|
||||||
showWriteButton();
|
|
||||||
if (blog.canBeRemoved())
|
|
||||||
enableDeleteButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleException(exception);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setToolbarTitle(Author a) {
|
|
||||||
getActivity().setTitle(a.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadSharedContacts() {
|
|
||||||
blogController.loadSharingContacts(
|
|
||||||
new UiResultExceptionHandler<Collection<ContactId>,
|
|
||||||
DbException>(this) {
|
|
||||||
@Override
|
|
||||||
public void onResultUi(Collection<ContactId> contacts) {
|
|
||||||
sharingController.addAll(contacts);
|
|
||||||
int online = sharingController.getOnlineCount();
|
|
||||||
setToolbarSubTitle(contacts.size(), online);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleException(exception);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBlogInvitationAccepted(ContactId c) {
|
public void onLinkClick(String url) {
|
||||||
sharingController.add(c);
|
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
|
||||||
setToolbarSubTitle(sharingController.getTotalCount(),
|
f.show(getParentFragmentManager(), f.getUniqueTag());
|
||||||
sharingController.getOnlineCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBlogLeft(ContactId c) {
|
|
||||||
sharingController.remove(c);
|
|
||||||
setToolbarSubTitle(sharingController.getTotalCount(),
|
|
||||||
sharingController.getOnlineCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSharingInfoUpdated(int total, int online) {
|
|
||||||
setToolbarSubTitle(total, online);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setToolbarSubTitle(int total, int online) {
|
|
||||||
ActionBar actionBar =
|
|
||||||
((BriarActivity) getActivity()).getSupportActionBar();
|
|
||||||
if (actionBar != null) {
|
|
||||||
actionBar.setSubtitle(
|
|
||||||
getString(R.string.shared_with, total, online));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showWriteButton() {
|
|
||||||
isMyBlog = true;
|
|
||||||
if (writeButton != null)
|
|
||||||
writeButton.setVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enableDeleteButton() {
|
|
||||||
canDeleteBlog = true;
|
|
||||||
if (deleteButton != null)
|
|
||||||
deleteButton.setEnabled(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displaySnackbar(int stringId, boolean scroll) {
|
private void displaySnackbar(int stringId, boolean scroll) {
|
||||||
@@ -373,38 +223,21 @@ public class BlogFragment extends BaseFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showDeleteDialog() {
|
private void showDeleteDialog() {
|
||||||
DialogInterface.OnClickListener okListener =
|
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext(),
|
||||||
(dialog, which) -> deleteBlog();
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
|
|
||||||
R.style.BriarDialogTheme);
|
R.style.BriarDialogTheme);
|
||||||
builder.setTitle(getString(R.string.blogs_remove_blog));
|
builder.setTitle(getString(R.string.blogs_remove_blog));
|
||||||
builder.setMessage(
|
builder.setMessage(
|
||||||
getString(R.string.blogs_remove_blog_dialog_message));
|
getString(R.string.blogs_remove_blog_dialog_message));
|
||||||
builder.setPositiveButton(R.string.cancel, null);
|
builder.setPositiveButton(R.string.cancel, null);
|
||||||
builder.setNegativeButton(R.string.blogs_remove_blog_ok, okListener);
|
builder.setNegativeButton(R.string.blogs_remove_blog_ok,
|
||||||
|
(dialog, which) -> deleteBlog());
|
||||||
builder.show();
|
builder.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteBlog() {
|
private void deleteBlog() {
|
||||||
blogController.deleteBlog(
|
viewModel.deleteBlog();
|
||||||
new UiResultExceptionHandler<Void, DbException>(this) {
|
Toast.makeText(getActivity(), R.string.blogs_blog_removed, LENGTH_SHORT)
|
||||||
@Override
|
.show();
|
||||||
public void onResultUi(Void result) {
|
|
||||||
Toast.makeText(getActivity(),
|
|
||||||
R.string.blogs_blog_removed, LENGTH_SHORT)
|
|
||||||
.show();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleException(exception);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBlogRemoved() {
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,23 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
import org.briarproject.briar.android.activity.ActivityScope;
|
import org.briarproject.briar.android.viewmodel.ViewModelKey;
|
||||||
import org.briarproject.briar.android.activity.BaseActivity;
|
|
||||||
import org.briarproject.briar.android.controller.SharingController;
|
|
||||||
import org.briarproject.briar.android.controller.SharingControllerImpl;
|
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
import dagger.Binds;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.multibindings.IntoMap;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class BlogModule {
|
public interface BlogModule {
|
||||||
|
|
||||||
@ActivityScope
|
@Binds
|
||||||
@Provides
|
@IntoMap
|
||||||
BlogController provideBlogController(BaseActivity activity,
|
@ViewModelKey(FeedViewModel.class)
|
||||||
BlogControllerImpl blogController) {
|
ViewModel bindFeedViewModel(FeedViewModel feedViewModel);
|
||||||
activity.addLifecycleController(blogController);
|
|
||||||
return blogController;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ActivityScope
|
@Binds
|
||||||
@Provides
|
@IntoMap
|
||||||
FeedController provideFeedController(FeedControllerImpl feedController) {
|
@ViewModelKey(BlogViewModel.class)
|
||||||
return feedController;
|
ViewModel bindBlogViewModel(BlogViewModel blogViewModel);
|
||||||
}
|
|
||||||
|
|
||||||
@ActivityScope
|
|
||||||
@Provides
|
|
||||||
SharingController provideSharingController(
|
|
||||||
SharingControllerImpl sharingController) {
|
|
||||||
return sharingController;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -8,52 +7,44 @@ import android.view.ViewGroup;
|
|||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.util.BriarAdapter;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.recyclerview.widget.ListAdapter;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
class BlogPostAdapter extends BriarAdapter<BlogPostItem, BlogPostViewHolder> {
|
class BlogPostAdapter extends ListAdapter<BlogPostItem, BlogPostViewHolder> {
|
||||||
|
|
||||||
|
private final boolean authorClickable;
|
||||||
private final OnBlogPostClickListener listener;
|
private final OnBlogPostClickListener listener;
|
||||||
@Nullable
|
|
||||||
private final FragmentManager fragmentManager;
|
|
||||||
|
|
||||||
BlogPostAdapter(Context ctx, OnBlogPostClickListener listener,
|
BlogPostAdapter(boolean authorClickable, OnBlogPostClickListener listener) {
|
||||||
@Nullable FragmentManager fragmentManager) {
|
super(new DiffUtil.ItemCallback<BlogPostItem>() {
|
||||||
super(ctx, BlogPostItem.class);
|
@Override
|
||||||
|
public boolean areItemsTheSame(BlogPostItem a, BlogPostItem b) {
|
||||||
|
return a.getId().equals(b.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(BlogPostItem a, BlogPostItem b) {
|
||||||
|
return a.isRead() == b.isRead();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.authorClickable = authorClickable;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
this.fragmentManager = fragmentManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BlogPostViewHolder onCreateViewHolder(ViewGroup parent,
|
public BlogPostViewHolder onCreateViewHolder(ViewGroup parent,
|
||||||
int viewType) {
|
int viewType) {
|
||||||
View v = LayoutInflater.from(ctx).inflate(
|
View v = LayoutInflater.from(parent.getContext()).inflate(
|
||||||
R.layout.list_item_blog_post, parent, false);
|
R.layout.list_item_blog_post, parent, false);
|
||||||
return new BlogPostViewHolder(v, false, listener, fragmentManager);
|
return new BlogPostViewHolder(v, false, listener, authorClickable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(BlogPostViewHolder ui, int position) {
|
public void onBindViewHolder(BlogPostViewHolder ui, int position) {
|
||||||
ui.bindItem(getItemAt(position));
|
ui.bindItem(getItem(position));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(BlogPostItem a, BlogPostItem b) {
|
|
||||||
return a.compareTo(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean areContentsTheSame(BlogPostItem a, BlogPostItem b) {
|
|
||||||
return a.isRead() == b.isRead();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean areItemsTheSame(BlogPostItem a, BlogPostItem b) {
|
|
||||||
return a.getId().equals(b.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +1,163 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.blog.BaseController.BlogListener;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
import org.briarproject.briar.android.widget.LinkDialogFragment;
|
||||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
|
||||||
|
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.UiThread;
|
import androidx.annotation.UiThread;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
|
import static android.view.View.INVISIBLE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
|
||||||
|
|
||||||
@UiThread
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class BlogPostFragment extends BasePostFragment implements BlogListener {
|
public class BlogPostFragment extends BaseFragment
|
||||||
|
implements OnBlogPostClickListener {
|
||||||
|
|
||||||
private static final String TAG = BlogPostFragment.class.getName();
|
private static final String TAG = BlogPostFragment.class.getName();
|
||||||
|
private static final Logger LOG = getLogger(TAG);
|
||||||
|
|
||||||
|
static final String POST_ID = "briar.POST_ID";
|
||||||
|
static final String IS_FEED = "briar.IS_FEED";
|
||||||
|
|
||||||
|
protected BlogViewModel viewModel;
|
||||||
|
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
private BlogPostViewHolder ui;
|
||||||
|
private BlogPostItem post;
|
||||||
|
private Runnable refresher;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BlogController blogController;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
static BlogPostFragment newInstance(MessageId postId) {
|
static BlogPostFragment newInstance(GroupId blogId, MessageId postId,
|
||||||
|
boolean isFeed) {
|
||||||
BlogPostFragment f = new BlogPostFragment();
|
BlogPostFragment f = new BlogPostFragment();
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putByteArray(GROUP_ID, blogId.getBytes());
|
||||||
bundle.putByteArray(POST_ID, postId.getBytes());
|
bundle.putByteArray(POST_ID, postId.getBytes());
|
||||||
|
bundle.putBoolean(IS_FEED, isFeed);
|
||||||
f.setArguments(bundle);
|
f.setArguments(bundle);
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectFragment(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||||
|
.get(BlogViewModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
Bundle args = requireArguments();
|
||||||
|
GroupId groupId =
|
||||||
|
new GroupId(requireNonNull(args.getByteArray(GROUP_ID)));
|
||||||
|
MessageId postId =
|
||||||
|
new MessageId(requireNonNull(args.getByteArray(POST_ID)));
|
||||||
|
boolean isFeed = args.getBoolean(IS_FEED);
|
||||||
|
|
||||||
|
View view = inflater.inflate(R.layout.fragment_blog_post, container,
|
||||||
|
false);
|
||||||
|
progressBar = view.findViewById(R.id.progressBar);
|
||||||
|
progressBar.setVisibility(VISIBLE);
|
||||||
|
ui = new BlogPostViewHolder(view, true, this, isFeed);
|
||||||
|
LifecycleOwner owner = getViewLifecycleOwner();
|
||||||
|
viewModel.loadBlogPost(groupId, postId).observe(owner, result ->
|
||||||
|
result.onError(this::handleException)
|
||||||
|
.onSuccess(this::onBlogPostLoaded)
|
||||||
|
);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
startPeriodicUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
stopPeriodicUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void onBlogPostLoaded(BlogPostItem post) {
|
||||||
|
progressBar.setVisibility(INVISIBLE);
|
||||||
|
this.post = post;
|
||||||
|
ui.bindItem(post);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBlogPostClick(BlogPostItem post) {
|
||||||
|
// We're already there
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthorClick(BlogPostItem post) {
|
||||||
|
Intent i = new Intent(requireContext(), BlogActivity.class);
|
||||||
|
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
|
||||||
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
requireContext().startActivity(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLinkClick(String url) {
|
||||||
|
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
|
||||||
|
f.show(getParentFragmentManager(), f.getUniqueTag());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startPeriodicUpdate() {
|
||||||
|
refresher = () -> {
|
||||||
|
LOG.info("Updating Content...");
|
||||||
|
ui.updateDate(post.getTimestamp());
|
||||||
|
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
|
||||||
|
};
|
||||||
|
LOG.info("Adding Handler Callback");
|
||||||
|
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopPeriodicUpdate() {
|
||||||
|
if (refresher != null) {
|
||||||
|
LOG.info("Removing Handler Callback");
|
||||||
|
handler.removeCallbacks(refresher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUniqueTag() {
|
public String getUniqueTag() {
|
||||||
return TAG;
|
return TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectFragment(ActivityComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
blogController.loadBlogPost(postId,
|
|
||||||
new UiResultExceptionHandler<BlogPostItem, DbException>(
|
|
||||||
this) {
|
|
||||||
@Override
|
|
||||||
public void onResultUi(BlogPostItem post) {
|
|
||||||
onBlogPostLoaded(post);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleException(exception);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBlogPostAdded(BlogPostHeader header, boolean local) {
|
|
||||||
// doesn't matter here
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBlogRemoved() {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class BlogPostItem implements Comparable<BlogPostItem> {
|
|||||||
private final BlogPostHeader header;
|
private final BlogPostHeader header;
|
||||||
@Nullable
|
@Nullable
|
||||||
protected String text;
|
protected String text;
|
||||||
private boolean read;
|
private final boolean read;
|
||||||
|
|
||||||
BlogPostItem(BlogPostHeader header, @Nullable String text) {
|
BlogPostItem(BlogPostHeader header, @Nullable String text) {
|
||||||
this.header = header;
|
this.header = header;
|
||||||
@@ -74,9 +74,6 @@ public class BlogPostItem implements Comparable<BlogPostItem> {
|
|||||||
|
|
||||||
protected static int compare(BlogPostHeader h1, BlogPostHeader h2) {
|
protected static int compare(BlogPostHeader h1, BlogPostHeader h2) {
|
||||||
// The newest post comes first
|
// The newest post comes first
|
||||||
long aTime = h1.getTimeReceived(), bTime = h2.getTimeReceived();
|
return Long.compare(h2.getTimeReceived(), h1.getTimeReceived());
|
||||||
if (aTime > bTime) return -1;
|
|
||||||
if (aTime < bTime) return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.view.AuthorView;
|
import org.briarproject.briar.android.view.AuthorView;
|
||||||
@@ -17,23 +18,24 @@ import org.briarproject.briar.api.blog.BlogPostHeader;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.UiThread;
|
import androidx.annotation.UiThread;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||||
import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
|
import static org.briarproject.briar.android.blog.BlogPostFragment.POST_ID;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.TEASER_LENGTH;
|
import static org.briarproject.briar.android.util.UiUtils.TEASER_LENGTH;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getSpanned;
|
import static org.briarproject.briar.android.util.UiUtils.getSpanned;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getTeaser;
|
import static org.briarproject.briar.android.util.UiUtils.getTeaser;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
|
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
|
||||||
import static org.briarproject.briar.api.blog.MessageType.POST;
|
import static org.briarproject.briar.android.view.AuthorView.COMMENTER;
|
||||||
|
import static org.briarproject.briar.android.view.AuthorView.REBLOGGER;
|
||||||
|
import static org.briarproject.briar.android.view.AuthorView.RSS_FEED_REBLOGGED;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
|
@NotNullByDefault
|
||||||
class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
private final Context ctx;
|
private final Context ctx;
|
||||||
@@ -43,20 +45,16 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
private final ImageButton reblogButton;
|
private final ImageButton reblogButton;
|
||||||
private final TextView text;
|
private final TextView text;
|
||||||
private final ViewGroup commentContainer;
|
private final ViewGroup commentContainer;
|
||||||
private final boolean fullText;
|
private final boolean fullText, authorClickable;
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private final OnBlogPostClickListener listener;
|
private final OnBlogPostClickListener listener;
|
||||||
@Nullable
|
|
||||||
private final FragmentManager fragmentManager;
|
|
||||||
|
|
||||||
BlogPostViewHolder(View v, boolean fullText,
|
BlogPostViewHolder(View v, boolean fullText,
|
||||||
@NonNull OnBlogPostClickListener listener,
|
OnBlogPostClickListener listener, boolean authorClickable) {
|
||||||
@Nullable FragmentManager fragmentManager) {
|
|
||||||
super(v);
|
super(v);
|
||||||
this.fullText = fullText;
|
this.fullText = fullText;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
this.fragmentManager = fragmentManager;
|
this.authorClickable = authorClickable;
|
||||||
|
|
||||||
ctx = v.getContext();
|
ctx = v.getContext();
|
||||||
layout = v.findViewById(R.id.postLayout);
|
layout = v.findViewById(R.id.postLayout);
|
||||||
@@ -67,10 +65,6 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
commentContainer = v.findViewById(R.id.commentContainer);
|
commentContainer = v.findViewById(R.id.commentContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setVisibility(int visibility) {
|
|
||||||
layout.setVisibility(visibility);
|
|
||||||
}
|
|
||||||
|
|
||||||
void hideReblogButton() {
|
void hideReblogButton() {
|
||||||
reblogButton.setVisibility(GONE);
|
reblogButton.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
@@ -103,7 +97,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
author.setPersona(
|
author.setPersona(
|
||||||
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
|
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
|
||||||
// TODO make author clickable more often #624
|
// TODO make author clickable more often #624
|
||||||
if (!fullText && item.getHeader().getType() == POST) {
|
if (authorClickable) {
|
||||||
author.setAuthorClickable(v -> listener.onAuthorClick(item));
|
author.setAuthorClickable(v -> listener.onAuthorClick(item));
|
||||||
} else {
|
} else {
|
||||||
author.setAuthorNotClickable();
|
author.setAuthorNotClickable();
|
||||||
@@ -114,7 +108,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
if (fullText) {
|
if (fullText) {
|
||||||
text.setText(postText);
|
text.setText(postText);
|
||||||
text.setTextIsSelectable(true);
|
text.setTextIsSelectable(true);
|
||||||
makeLinksClickable(text, fragmentManager);
|
makeLinksClickable(text, listener::onLinkClick);
|
||||||
} else {
|
} else {
|
||||||
text.setTextIsSelectable(false);
|
text.setTextIsSelectable(false);
|
||||||
if (postText.length() > TEASER_LENGTH)
|
if (postText.length() > TEASER_LENGTH)
|
||||||
@@ -147,17 +141,16 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
reblogger.setAuthorClickable(v -> listener.onAuthorClick(item));
|
reblogger.setAuthorClickable(v -> listener.onAuthorClick(item));
|
||||||
}
|
}
|
||||||
reblogger.setVisibility(VISIBLE);
|
reblogger.setVisibility(VISIBLE);
|
||||||
reblogger.setPersona(AuthorView.REBLOGGER);
|
reblogger.setPersona(REBLOGGER);
|
||||||
|
|
||||||
author.setPersona(item.getHeader().getRootPost().isRssFeed() ?
|
author.setPersona(item.getHeader().getRootPost().isRssFeed() ?
|
||||||
AuthorView.RSS_FEED_REBLOGGED :
|
RSS_FEED_REBLOGGED : COMMENTER);
|
||||||
AuthorView.COMMENTER);
|
|
||||||
|
|
||||||
// comments
|
// comments
|
||||||
|
// TODO use nested RecyclerView instead like we do for Image Attachments
|
||||||
for (BlogCommentHeader c : item.getComments()) {
|
for (BlogCommentHeader c : item.getComments()) {
|
||||||
View v = LayoutInflater.from(ctx)
|
View v = LayoutInflater.from(ctx).inflate(
|
||||||
.inflate(R.layout.list_item_blog_comment,
|
R.layout.list_item_blog_comment, commentContainer, false);
|
||||||
commentContainer, false);
|
|
||||||
|
|
||||||
AuthorView author = v.findViewById(R.id.authorView);
|
AuthorView author = v.findViewById(R.id.authorView);
|
||||||
TextView text = v.findViewById(R.id.textView);
|
TextView text = v.findViewById(R.id.textView);
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Application;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
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.sync.event.GroupRemovedEvent;
|
||||||
import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
import org.briarproject.briar.android.sharing.SharingController;
|
||||||
|
import org.briarproject.briar.android.sharing.SharingController.SharingInfo;
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
import org.briarproject.briar.api.blog.Blog;
|
import org.briarproject.briar.api.blog.Blog;
|
||||||
import org.briarproject.briar.api.blog.BlogInvitationResponse;
|
import org.briarproject.briar.api.blog.BlogInvitationResponse;
|
||||||
@@ -35,85 +35,54 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
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.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
class BlogControllerImpl extends BaseControllerImpl
|
class BlogViewModel extends BaseViewModel {
|
||||||
implements ActivityLifecycleController, BlogController, EventListener {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG = getLogger(BlogViewModel.class.getName());
|
||||||
Logger.getLogger(BlogControllerImpl.class.getName());
|
|
||||||
|
|
||||||
private final BlogSharingManager blogSharingManager;
|
private final BlogSharingManager blogSharingManager;
|
||||||
|
private final SharingController sharingController;
|
||||||
|
|
||||||
// UI thread
|
private volatile GroupId groupId;
|
||||||
@Nullable
|
|
||||||
private BlogSharingListener listener;
|
|
||||||
|
|
||||||
private volatile GroupId groupId = null;
|
private final MutableLiveData<BlogItem> blog = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<Boolean> blogRemoved =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BlogControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
BlogViewModel(Application application,
|
||||||
LifecycleManager lifecycleManager, EventBus eventBus,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
|
LifecycleManager lifecycleManager,
|
||||||
|
TransactionManager db,
|
||||||
|
AndroidExecutor androidExecutor,
|
||||||
|
EventBus eventBus,
|
||||||
|
IdentityManager identityManager,
|
||||||
AndroidNotificationManager notificationManager,
|
AndroidNotificationManager notificationManager,
|
||||||
IdentityManager identityManager, BlogManager blogManager,
|
BlogManager blogManager,
|
||||||
BlogSharingManager blogSharingManager) {
|
BlogSharingManager blogSharingManager,
|
||||||
super(dbExecutor, lifecycleManager, eventBus, notificationManager,
|
SharingController sharingController) {
|
||||||
identityManager, blogManager);
|
super(application, dbExecutor, lifecycleManager, db, androidExecutor,
|
||||||
|
eventBus, identityManager, notificationManager, blogManager);
|
||||||
this.blogSharingManager = blogSharingManager;
|
this.blogSharingManager = blogSharingManager;
|
||||||
}
|
this.sharingController = sharingController;
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreate(Activity activity) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityStart() {
|
|
||||||
super.onStart();
|
|
||||||
notificationManager.blockNotification(groupId);
|
|
||||||
notificationManager.clearBlogPostNotification(groupId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityStop() {
|
|
||||||
super.onStop();
|
|
||||||
notificationManager.unblockNotification(groupId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityDestroy() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setGroupId(GroupId g) {
|
|
||||||
groupId = g;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBlogSharingListener(BlogSharingListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unsetBlogSharingListener(BlogSharingListener listener) {
|
|
||||||
if (this.listener == listener) this.listener = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void eventOccurred(Event e) {
|
public void eventOccurred(Event e) {
|
||||||
if (groupId == null || listener == null)
|
|
||||||
throw new IllegalStateException();
|
|
||||||
if (e instanceof BlogPostAddedEvent) {
|
if (e instanceof BlogPostAddedEvent) {
|
||||||
BlogPostAddedEvent b = (BlogPostAddedEvent) e;
|
BlogPostAddedEvent b = (BlogPostAddedEvent) e;
|
||||||
if (b.getGroupId().equals(groupId)) {
|
if (b.getGroupId().equals(groupId)) {
|
||||||
LOG.info("Blog post added");
|
LOG.info("Blog post added");
|
||||||
listener.onBlogPostAdded(b.getHeader(), b.isLocal());
|
onBlogPostAdded(b.getHeader(), b.isLocal());
|
||||||
}
|
}
|
||||||
} else if (e instanceof BlogInvitationResponseReceivedEvent) {
|
} else if (e instanceof BlogInvitationResponseReceivedEvent) {
|
||||||
BlogInvitationResponseReceivedEvent b =
|
BlogInvitationResponseReceivedEvent b =
|
||||||
@@ -121,41 +90,36 @@ class BlogControllerImpl extends BaseControllerImpl
|
|||||||
BlogInvitationResponse r = b.getMessageHeader();
|
BlogInvitationResponse r = b.getMessageHeader();
|
||||||
if (r.getShareableId().equals(groupId) && r.wasAccepted()) {
|
if (r.getShareableId().equals(groupId) && r.wasAccepted()) {
|
||||||
LOG.info("Blog invitation accepted");
|
LOG.info("Blog invitation accepted");
|
||||||
listener.onBlogInvitationAccepted(b.getContactId());
|
sharingController.add(b.getContactId());
|
||||||
}
|
}
|
||||||
} else if (e instanceof ContactLeftShareableEvent) {
|
} else if (e instanceof ContactLeftShareableEvent) {
|
||||||
ContactLeftShareableEvent s = (ContactLeftShareableEvent) e;
|
ContactLeftShareableEvent s = (ContactLeftShareableEvent) e;
|
||||||
if (s.getGroupId().equals(groupId)) {
|
if (s.getGroupId().equals(groupId)) {
|
||||||
LOG.info("Blog left by contact");
|
LOG.info("Blog left by contact");
|
||||||
listener.onBlogLeft(s.getContactId());
|
sharingController.remove(s.getContactId());
|
||||||
}
|
}
|
||||||
} else if (e instanceof GroupRemovedEvent) {
|
} else if (e instanceof GroupRemovedEvent) {
|
||||||
GroupRemovedEvent g = (GroupRemovedEvent) e;
|
GroupRemovedEvent g = (GroupRemovedEvent) e;
|
||||||
if (g.getGroup().getId().equals(groupId)) {
|
if (g.getGroup().getId().equals(groupId)) {
|
||||||
LOG.info("Blog removed");
|
LOG.info("Blog removed");
|
||||||
listener.onBlogRemoved();
|
blogRemoved.setValue(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void loadBlogPosts(
|
* Set this before calling any other methods.
|
||||||
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {
|
*/
|
||||||
if (groupId == null) throw new IllegalStateException();
|
@UiThread
|
||||||
loadBlogPosts(groupId, handler);
|
public void setGroupId(GroupId groupId) {
|
||||||
|
if (this.groupId == groupId) return; // configuration change
|
||||||
|
this.groupId = groupId;
|
||||||
|
loadBlog(groupId);
|
||||||
|
loadBlogPosts(groupId);
|
||||||
|
loadSharingContacts(groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void loadBlog(GroupId groupId) {
|
||||||
public void loadBlogPost(MessageId m,
|
|
||||||
ResultExceptionHandler<BlogPostItem, DbException> handler) {
|
|
||||||
if (groupId == null) throw new IllegalStateException();
|
|
||||||
loadBlogPost(groupId, m, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadBlog(
|
|
||||||
ResultExceptionHandler<BlogItem, DbException> handler) {
|
|
||||||
if (groupId == null) throw new IllegalStateException();
|
|
||||||
runOnDbThread(() -> {
|
runOnDbThread(() -> {
|
||||||
try {
|
try {
|
||||||
long start = now();
|
long start = now();
|
||||||
@@ -163,50 +127,65 @@ class BlogControllerImpl extends BaseControllerImpl
|
|||||||
Blog b = blogManager.getBlog(groupId);
|
Blog b = blogManager.getBlog(groupId);
|
||||||
boolean ours = a.getId().equals(b.getAuthor().getId());
|
boolean ours = a.getId().equals(b.getAuthor().getId());
|
||||||
boolean removable = blogManager.canBeRemoved(b);
|
boolean removable = blogManager.canBeRemoved(b);
|
||||||
BlogItem blog = new BlogItem(b, ours, removable);
|
blog.postValue(new BlogItem(b, ours, removable));
|
||||||
logDuration(LOG, "Loading blog", start);
|
logDuration(LOG, "Loading blog", start);
|
||||||
handler.onResult(blog);
|
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
handleException(e);
|
||||||
handler.onException(e);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
void blockAndClearNotifications() {
|
||||||
public void deleteBlog(ResultExceptionHandler<Void, DbException> handler) {
|
notificationManager.blockNotification(groupId);
|
||||||
if (groupId == null) throw new IllegalStateException();
|
notificationManager.clearBlogPostNotification(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unblockNotifications() {
|
||||||
|
notificationManager.unblockNotification(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadBlogPosts(GroupId groupId) {
|
||||||
|
loadList(txn -> new ListUpdate(null, loadBlogPosts(txn, groupId)),
|
||||||
|
blogPosts::setValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadSharingContacts(GroupId groupId) {
|
||||||
|
runOnDbThread(true, txn -> {
|
||||||
|
Collection<Contact> contacts =
|
||||||
|
blogSharingManager.getSharedWith(txn, groupId);
|
||||||
|
txn.attach(() -> onSharingContactsLoaded(contacts));
|
||||||
|
}, this::handleException);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void onSharingContactsLoaded(Collection<Contact> contacts) {
|
||||||
|
Collection<ContactId> contactIds = new ArrayList<>(contacts.size());
|
||||||
|
for (Contact c : contacts) contactIds.add(c.getId());
|
||||||
|
sharingController.addAll(contactIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteBlog() {
|
||||||
runOnDbThread(() -> {
|
runOnDbThread(() -> {
|
||||||
try {
|
try {
|
||||||
long start = now();
|
long start = now();
|
||||||
Blog b = blogManager.getBlog(groupId);
|
Blog b = blogManager.getBlog(groupId);
|
||||||
blogManager.removeBlog(b);
|
blogManager.removeBlog(b);
|
||||||
logDuration(LOG, "Removing blog", start);
|
logDuration(LOG, "Removing blog", start);
|
||||||
handler.onResult(null);
|
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
handleException(e);
|
||||||
handler.onException(e);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
LiveData<BlogItem> getBlog() {
|
||||||
public void loadSharingContacts(
|
return blog;
|
||||||
ResultExceptionHandler<Collection<ContactId>, DbException> handler) {
|
|
||||||
if (groupId == null) throw new IllegalStateException();
|
|
||||||
runOnDbThread(() -> {
|
|
||||||
try {
|
|
||||||
Collection<Contact> contacts =
|
|
||||||
blogSharingManager.getSharedWith(groupId);
|
|
||||||
Collection<ContactId> contactIds =
|
|
||||||
new ArrayList<>(contacts.size());
|
|
||||||
for (Contact c : contacts) contactIds.add(c.getId());
|
|
||||||
handler.onResult(contactIds);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
handler.onException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LiveData<Boolean> getBlogRemoved() {
|
||||||
|
return blogRemoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<SharingInfo> getSharingInfo() {
|
||||||
|
return sharingController.getSharingInfo();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
|
||||||
import org.briarproject.briar.api.blog.Blog;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
public interface FeedController extends BaseController {
|
|
||||||
|
|
||||||
void loadBlogPosts(
|
|
||||||
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);
|
|
||||||
|
|
||||||
void loadPersonalBlog(ResultExceptionHandler<Blog, DbException> handler);
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void setFeedListener(FeedListener listener);
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void unsetFeedListener(FeedListener listener);
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
interface FeedListener extends BlogListener {
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onBlogAdded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
|
||||||
import org.briarproject.bramble.api.db.NoSuchMessageException;
|
|
||||||
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.lifecycle.LifecycleManager;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.sync.event.GroupAddedEvent;
|
|
||||||
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
|
|
||||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
|
||||||
import org.briarproject.briar.api.blog.Blog;
|
|
||||||
import org.briarproject.briar.api.blog.BlogManager;
|
|
||||||
import org.briarproject.briar.api.blog.event.BlogPostAddedEvent;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
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;
|
|
||||||
import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
class FeedControllerImpl extends BaseControllerImpl implements FeedController {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(FeedControllerImpl.class.getName());
|
|
||||||
|
|
||||||
// UI thread
|
|
||||||
@Nullable
|
|
||||||
private FeedListener listener;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
FeedControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
|
||||||
LifecycleManager lifecycleManager, EventBus eventBus,
|
|
||||||
AndroidNotificationManager notificationManager,
|
|
||||||
IdentityManager identityManager, BlogManager blogManager) {
|
|
||||||
super(dbExecutor, lifecycleManager, eventBus, notificationManager,
|
|
||||||
identityManager, blogManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
if (listener == null) throw new IllegalStateException();
|
|
||||||
notificationManager.blockAllBlogPostNotifications();
|
|
||||||
notificationManager.clearAllBlogPostNotifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
notificationManager.unblockAllBlogPostNotifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFeedListener(FeedListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unsetFeedListener(FeedListener listener) {
|
|
||||||
if (this.listener == listener) this.listener = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void eventOccurred(Event e) {
|
|
||||||
if (listener == null) throw new IllegalStateException();
|
|
||||||
if (e instanceof BlogPostAddedEvent) {
|
|
||||||
BlogPostAddedEvent b = (BlogPostAddedEvent) e;
|
|
||||||
LOG.info("Blog post added");
|
|
||||||
listener.onBlogPostAdded(b.getHeader(), b.isLocal());
|
|
||||||
} else if (e instanceof GroupAddedEvent) {
|
|
||||||
GroupAddedEvent g = (GroupAddedEvent) e;
|
|
||||||
if (g.getGroup().getClientId().equals(CLIENT_ID)) {
|
|
||||||
LOG.info("Blog added");
|
|
||||||
listener.onBlogAdded();
|
|
||||||
}
|
|
||||||
} else if (e instanceof GroupRemovedEvent) {
|
|
||||||
GroupRemovedEvent g = (GroupRemovedEvent) e;
|
|
||||||
if (g.getGroup().getClientId().equals(CLIENT_ID)) {
|
|
||||||
LOG.info("Blog removed");
|
|
||||||
listener.onBlogRemoved();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadBlogPosts(
|
|
||||||
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {
|
|
||||||
runOnDbThread(() -> {
|
|
||||||
try {
|
|
||||||
long start = now();
|
|
||||||
Collection<BlogPostItem> posts = new ArrayList<>();
|
|
||||||
for (Blog b : blogManager.getBlogs()) {
|
|
||||||
try {
|
|
||||||
posts.addAll(loadItems(b.getId()));
|
|
||||||
} catch (NoSuchGroupException | NoSuchMessageException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logDuration(LOG, "Loading all posts", start);
|
|
||||||
handler.onResult(posts);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
handler.onException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadPersonalBlog(
|
|
||||||
ResultExceptionHandler<Blog, DbException> handler) {
|
|
||||||
runOnDbThread(() -> {
|
|
||||||
try {
|
|
||||||
long start = now();
|
|
||||||
Author a = identityManager.getLocalAuthor();
|
|
||||||
Blog b = blogManager.getPersonalBlog(a);
|
|
||||||
logDuration(LOG, "Loading personal blog", start);
|
|
||||||
handler.onResult(b);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
handler.onException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ package org.briarproject.briar.android.blog;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@@ -10,53 +9,41 @@ import android.view.MenuItem;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.blog.FeedController.FeedListener;
|
import org.briarproject.briar.android.blog.BaseViewModel.ListUpdate;
|
||||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
|
import org.briarproject.briar.android.widget.LinkDialogFragment;
|
||||||
import org.briarproject.briar.api.blog.Blog;
|
import org.briarproject.briar.api.blog.Blog;
|
||||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.UiThread;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG;
|
import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG;
|
||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class FeedFragment extends BaseFragment implements
|
public class FeedFragment extends BaseFragment
|
||||||
OnBlogPostClickListener, FeedListener {
|
implements OnBlogPostClickListener {
|
||||||
|
|
||||||
public final static String TAG = FeedFragment.class.getName();
|
public final static String TAG = FeedFragment.class.getName();
|
||||||
private static final Logger LOG = Logger.getLogger(TAG);
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedController feedController;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
private BlogPostAdapter adapter;
|
private FeedViewModel viewModel;
|
||||||
|
private final BlogPostAdapter adapter = new BlogPostAdapter(true, this);
|
||||||
private LinearLayoutManager layoutManager;
|
private LinearLayoutManager layoutManager;
|
||||||
private BriarRecyclerView list;
|
private BriarRecyclerView list;
|
||||||
@Nullable
|
|
||||||
private Blog personalBlog;
|
|
||||||
@Nullable
|
|
||||||
private Parcelable layoutManagerState;
|
|
||||||
|
|
||||||
public static FeedFragment newInstance() {
|
public static FeedFragment newInstance() {
|
||||||
FeedFragment f = new FeedFragment();
|
FeedFragment f = new FeedFragment();
|
||||||
@@ -70,7 +57,8 @@ public class FeedFragment extends BaseFragment implements
|
|||||||
@Override
|
@Override
|
||||||
public void injectFragment(ActivityComponent component) {
|
public void injectFragment(ActivityComponent component) {
|
||||||
component.inject(this);
|
component.inject(this);
|
||||||
feedController.setFeedListener(this);
|
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||||
|
.get(FeedViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -82,9 +70,6 @@ public class FeedFragment extends BaseFragment implements
|
|||||||
|
|
||||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||||
|
|
||||||
adapter =
|
|
||||||
new BlogPostAdapter(getActivity(), this, getFragmentManager());
|
|
||||||
|
|
||||||
layoutManager = new LinearLayoutManager(getActivity());
|
layoutManager = new LinearLayoutManager(getActivity());
|
||||||
list = v.findViewById(R.id.postList);
|
list = v.findViewById(R.id.postList);
|
||||||
list.setLayoutManager(layoutManager);
|
list.setLayoutManager(layoutManager);
|
||||||
@@ -93,103 +78,38 @@ public class FeedFragment extends BaseFragment implements
|
|||||||
list.setEmptyText(R.string.blogs_feed_empty_state);
|
list.setEmptyText(R.string.blogs_feed_empty_state);
|
||||||
list.setEmptyAction(R.string.blogs_feed_empty_state_action);
|
list.setEmptyAction(R.string.blogs_feed_empty_state_action);
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
viewModel.getBlogPosts().observe(getViewLifecycleOwner(), result ->
|
||||||
layoutManagerState =
|
result.onError(this::handleException)
|
||||||
savedInstanceState.getParcelable("layoutManager");
|
.onSuccess(this::onBlogPostsLoaded)
|
||||||
}
|
);
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode,
|
|
||||||
@Nullable Intent data) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
|
|
||||||
// The BlogPostAddedEvent arrives when the controller is not listening
|
|
||||||
if (requestCode == REQUEST_WRITE_BLOG_POST && resultCode == RESULT_OK) {
|
|
||||||
showSnackBar(R.string.blogs_blog_post_created);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
feedController.onStart();
|
viewModel.blockAndClearAllBlogPostNotifications();
|
||||||
list.startPeriodicUpdate();
|
list.startPeriodicUpdate();
|
||||||
loadPersonalBlog();
|
|
||||||
loadBlogPosts(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
feedController.onStop();
|
viewModel.unblockAllBlogPostNotifications();
|
||||||
adapter.clear();
|
|
||||||
list.showProgressBar();
|
|
||||||
list.stopPeriodicUpdate();
|
list.stopPeriodicUpdate();
|
||||||
// TODO save list position in database/preferences?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void onBlogPostsLoaded(ListUpdate update) {
|
||||||
public void onDestroy() {
|
adapter.submitList(update.getItems(), () -> {
|
||||||
super.onDestroy();
|
Boolean wasLocal = update.getPostAddedWasLocal();
|
||||||
feedController.unsetFeedListener(this);
|
if (wasLocal != null && wasLocal) {
|
||||||
}
|
showSnackBar(R.string.blogs_blog_post_created);
|
||||||
|
} else if (wasLocal != null) {
|
||||||
@Override
|
showSnackBar(R.string.blogs_blog_post_received);
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
}
|
||||||
super.onSaveInstanceState(outState);
|
list.showData();
|
||||||
if (layoutManager != null) {
|
});
|
||||||
layoutManagerState = layoutManager.onSaveInstanceState();
|
|
||||||
outState.putParcelable("layoutManager", layoutManagerState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadPersonalBlog() {
|
|
||||||
feedController.loadPersonalBlog(
|
|
||||||
new UiResultExceptionHandler<Blog, DbException>(this) {
|
|
||||||
@Override
|
|
||||||
public void onResultUi(Blog b) {
|
|
||||||
personalBlog = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleException(exception);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadBlogPosts(boolean clear) {
|
|
||||||
int revision = adapter.getRevision();
|
|
||||||
feedController.loadBlogPosts(
|
|
||||||
new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
|
|
||||||
this) {
|
|
||||||
@Override
|
|
||||||
public void onResultUi(Collection<BlogPostItem> posts) {
|
|
||||||
if (revision == adapter.getRevision()) {
|
|
||||||
adapter.incrementRevision();
|
|
||||||
if (clear) adapter.setItems(posts);
|
|
||||||
else adapter.addAll(posts);
|
|
||||||
if (posts.isEmpty()) list.showData();
|
|
||||||
if (layoutManagerState == null) {
|
|
||||||
list.scrollToPosition(0); // Scroll to the top
|
|
||||||
} else {
|
|
||||||
layoutManager.onRestoreInstanceState(
|
|
||||||
layoutManagerState);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOG.info("Concurrent update, reloading");
|
|
||||||
loadBlogPosts(clear);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleException(exception);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -200,67 +120,48 @@ public class FeedFragment extends BaseFragment implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
if (personalBlog == null) return false;
|
int itemId = item.getItemId();
|
||||||
switch (item.getItemId()) {
|
if (itemId == R.id.action_write_blog_post) {
|
||||||
case R.id.action_write_blog_post:
|
Blog personalBlog = viewModel.getPersonalBlog().getValue();
|
||||||
Intent i1 =
|
if (personalBlog == null) return false;
|
||||||
new Intent(getActivity(), WriteBlogPostActivity.class);
|
Intent i = new Intent(getActivity(), WriteBlogPostActivity.class);
|
||||||
i1.putExtra(GROUP_ID, personalBlog.getId().getBytes());
|
i.putExtra(GROUP_ID, personalBlog.getId().getBytes());
|
||||||
startActivityForResult(i1, REQUEST_WRITE_BLOG_POST);
|
startActivity(i);
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_rss_feeds_import:
|
} else if (itemId == R.id.action_rss_feeds_import) {
|
||||||
Intent i2 =
|
Intent i = new Intent(getActivity(), RssFeedImportActivity.class);
|
||||||
new Intent(getActivity(), RssFeedImportActivity.class);
|
startActivity(i);
|
||||||
startActivity(i2);
|
return true;
|
||||||
return true;
|
} else if (itemId == R.id.action_rss_feeds_manage) {
|
||||||
case R.id.action_rss_feeds_manage:
|
Blog personalBlog = viewModel.getPersonalBlog().getValue();
|
||||||
Intent i3 =
|
if (personalBlog == null) return false;
|
||||||
new Intent(getActivity(), RssFeedManageActivity.class);
|
Intent i = new Intent(getActivity(), RssFeedManageActivity.class);
|
||||||
i3.putExtra(GROUP_ID, personalBlog.getId().getBytes());
|
i.putExtra(GROUP_ID, personalBlog.getId().getBytes());
|
||||||
startActivity(i3);
|
startActivity(i);
|
||||||
return true;
|
return true;
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
}
|
return super.onOptionsItemSelected(item);
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBlogPostAdded(BlogPostHeader header, boolean local) {
|
|
||||||
feedController.loadBlogPost(header,
|
|
||||||
new UiResultExceptionHandler<BlogPostItem, DbException>(
|
|
||||||
this) {
|
|
||||||
@Override
|
|
||||||
public void onResultUi(BlogPostItem post) {
|
|
||||||
adapter.incrementRevision();
|
|
||||||
adapter.add(post);
|
|
||||||
if (local) {
|
|
||||||
showSnackBar(R.string.blogs_blog_post_created);
|
|
||||||
} else {
|
|
||||||
showSnackBar(R.string.blogs_blog_post_received);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleException(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBlogPostClick(BlogPostItem post) {
|
public void onBlogPostClick(BlogPostItem post) {
|
||||||
FeedPostFragment f =
|
BaseFragment f = BlogPostFragment
|
||||||
FeedPostFragment.newInstance(post.getGroupId(), post.getId());
|
.newInstance(post.getGroupId(), post.getId(), true);
|
||||||
showNextFragment(f);
|
showNextFragment(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthorClick(BlogPostItem post) {
|
public void onAuthorClick(BlogPostItem post) {
|
||||||
Intent i = new Intent(getContext(), BlogActivity.class);
|
Intent i = new Intent(requireContext(), BlogActivity.class);
|
||||||
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
|
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
|
||||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
getContext().startActivity(i);
|
requireContext().startActivity(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLinkClick(String url) {
|
||||||
|
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
|
||||||
|
f.show(getParentFragmentManager(), f.getUniqueTag());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -283,14 +184,4 @@ public class FeedFragment extends BaseFragment implements
|
|||||||
sb.make(list, stringRes, LENGTH_LONG).show();
|
sb.make(list, stringRes, LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBlogAdded() {
|
|
||||||
loadBlogPosts(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBlogRemoved() {
|
|
||||||
loadBlogPosts(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
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.android.activity.ActivityComponent;
|
|
||||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
public class FeedPostFragment extends BasePostFragment {
|
|
||||||
|
|
||||||
private static final String TAG = FeedPostFragment.class.getName();
|
|
||||||
|
|
||||||
private GroupId blogId;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
FeedController feedController;
|
|
||||||
|
|
||||||
static FeedPostFragment newInstance(GroupId blogId, MessageId postId) {
|
|
||||||
FeedPostFragment f = new FeedPostFragment();
|
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putByteArray(GROUP_ID, blogId.getBytes());
|
|
||||||
bundle.putByteArray(POST_ID, postId.getBytes());
|
|
||||||
|
|
||||||
f.setArguments(bundle);
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectFragment(ActivityComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater,
|
|
||||||
@Nullable ViewGroup container,
|
|
||||||
@Nullable Bundle savedInstanceState) {
|
|
||||||
Bundle args = requireArguments();
|
|
||||||
byte[] b = args.getByteArray(GROUP_ID);
|
|
||||||
if (b == null) throw new IllegalStateException("No group ID in args");
|
|
||||||
blogId = new GroupId(b);
|
|
||||||
|
|
||||||
return super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUniqueTag() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
feedController.loadBlogPost(blogId, postId,
|
|
||||||
new UiResultExceptionHandler<BlogPostItem, DbException>(
|
|
||||||
this) {
|
|
||||||
@Override
|
|
||||||
public void onResultUi(BlogPostItem post) {
|
|
||||||
onBlogPostLoaded(post);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleException(exception);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
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.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
|
import org.briarproject.briar.android.viewmodel.LiveResult;
|
||||||
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
|
import org.briarproject.briar.api.blog.Blog;
|
||||||
|
import org.briarproject.briar.api.blog.BlogManager;
|
||||||
|
import org.briarproject.briar.api.blog.event.BlogPostAddedEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.annotation.UiThread;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class FeedViewModel extends BaseViewModel {
|
||||||
|
|
||||||
|
private static final Logger LOG = getLogger(FeedViewModel.class.getName());
|
||||||
|
|
||||||
|
private final MutableLiveData<Blog> personalBlog = new MutableLiveData<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FeedViewModel(Application application,
|
||||||
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
|
LifecycleManager lifecycleManager,
|
||||||
|
TransactionManager db,
|
||||||
|
AndroidExecutor androidExecutor,
|
||||||
|
EventBus eventBus,
|
||||||
|
IdentityManager identityManager,
|
||||||
|
AndroidNotificationManager notificationManager,
|
||||||
|
BlogManager blogManager) {
|
||||||
|
super(application, dbExecutor, lifecycleManager, db, androidExecutor,
|
||||||
|
eventBus, identityManager, notificationManager, blogManager);
|
||||||
|
loadPersonalBlog();
|
||||||
|
loadAllBlogPosts();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventOccurred(Event e) {
|
||||||
|
if (e instanceof BlogPostAddedEvent) {
|
||||||
|
BlogPostAddedEvent b = (BlogPostAddedEvent) e;
|
||||||
|
LOG.info("Blog post added");
|
||||||
|
onBlogPostAdded(b.getHeader(), b.isLocal());
|
||||||
|
}
|
||||||
|
if (e instanceof GroupRemovedEvent) {
|
||||||
|
GroupRemovedEvent g = (GroupRemovedEvent) e;
|
||||||
|
if (g.getGroup().getClientId().equals(CLIENT_ID)) {
|
||||||
|
LOG.info("Blog removed");
|
||||||
|
onBlogRemoved(g.getGroup().getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void blockAndClearAllBlogPostNotifications() {
|
||||||
|
notificationManager.blockAllBlogPostNotifications();
|
||||||
|
notificationManager.clearAllBlogPostNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
void unblockAllBlogPostNotifications() {
|
||||||
|
notificationManager.unblockAllBlogPostNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadPersonalBlog() {
|
||||||
|
runOnDbThread(() -> {
|
||||||
|
try {
|
||||||
|
long start = now();
|
||||||
|
Author a = identityManager.getLocalAuthor();
|
||||||
|
Blog b = blogManager.getPersonalBlog(a);
|
||||||
|
logDuration(LOG, "Loading personal blog", start);
|
||||||
|
personalBlog.postValue(b);
|
||||||
|
} catch (DbException e) {
|
||||||
|
handleException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<Blog> getPersonalBlog() {
|
||||||
|
return personalBlog;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAllBlogPosts() {
|
||||||
|
loadList(this::loadAllBlogPosts, blogPosts::setValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DatabaseExecutor
|
||||||
|
private ListUpdate loadAllBlogPosts(Transaction txn)
|
||||||
|
throws DbException {
|
||||||
|
long start = now();
|
||||||
|
List<BlogPostItem> posts = new ArrayList<>();
|
||||||
|
for (GroupId g : blogManager.getBlogIds(txn)) {
|
||||||
|
posts.addAll(loadBlogPosts(txn, g));
|
||||||
|
}
|
||||||
|
Collections.sort(posts);
|
||||||
|
logDuration(LOG, "Loading all posts", start);
|
||||||
|
return new ListUpdate(null, posts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void onBlogRemoved(GroupId g) {
|
||||||
|
List<BlogPostItem> items = removeListItems(getBlogPostItems(),
|
||||||
|
item -> item.getGroupId().equals(g)
|
||||||
|
);
|
||||||
|
if (items != null) {
|
||||||
|
blogPosts.setValue(new LiveResult<>(new ListUpdate(null, items)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,4 +5,6 @@ interface OnBlogPostClickListener {
|
|||||||
void onBlogPostClick(BlogPostItem post);
|
void onBlogPostClick(BlogPostItem post);
|
||||||
|
|
||||||
void onAuthorClick(BlogPostItem post);
|
void onAuthorClick(BlogPostItem post);
|
||||||
|
|
||||||
|
void onLinkClick(String url);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import org.briarproject.briar.android.activity.ActivityComponent;
|
|||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||||
|
|
||||||
import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
|
import static org.briarproject.briar.android.blog.BlogPostFragment.POST_ID;
|
||||||
|
|
||||||
public class ReblogActivity extends BriarActivity implements
|
public class ReblogActivity extends BriarActivity implements
|
||||||
BaseFragmentListener {
|
BaseFragmentListener {
|
||||||
@@ -39,13 +39,11 @@ public class ReblogActivity extends BriarActivity implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == android.R.id.home) {
|
||||||
case android.R.id.home:
|
onBackPressed();
|
||||||
onBackPressed();
|
return true;
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -7,19 +7,17 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.controller.handler.UiExceptionHandler;
|
|
||||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.view.TextInputView;
|
import org.briarproject.briar.android.view.TextInputView;
|
||||||
import org.briarproject.briar.android.view.TextSendController;
|
import org.briarproject.briar.android.view.TextSendController;
|
||||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||||
|
import org.briarproject.briar.android.widget.LinkDialogFragment;
|
||||||
import org.briarproject.briar.api.attachment.AttachmentHeader;
|
import org.briarproject.briar.api.attachment.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -27,13 +25,15 @@ import java.util.List;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import static android.view.View.FOCUS_DOWN;
|
import static android.view.View.FOCUS_DOWN;
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
import static android.view.View.INVISIBLE;
|
import static android.view.View.INVISIBLE;
|
||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||||
import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
|
import static org.briarproject.briar.android.blog.BlogPostFragment.POST_ID;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH;
|
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@@ -42,12 +42,13 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
|||||||
|
|
||||||
public static final String TAG = ReblogFragment.class.getName();
|
public static final String TAG = ReblogFragment.class.getName();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private BlogViewModel viewModel;
|
||||||
private ViewHolder ui;
|
private ViewHolder ui;
|
||||||
private BlogPostItem item;
|
private BlogPostItem item;
|
||||||
|
|
||||||
@Inject
|
|
||||||
FeedController feedController;
|
|
||||||
|
|
||||||
static ReblogFragment newInstance(GroupId groupId, MessageId messageId) {
|
static ReblogFragment newInstance(GroupId groupId, MessageId messageId) {
|
||||||
ReblogFragment f = new ReblogFragment();
|
ReblogFragment f = new ReblogFragment();
|
||||||
|
|
||||||
@@ -67,6 +68,8 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
|||||||
@Override
|
@Override
|
||||||
public void injectFragment(ActivityComponent component) {
|
public void injectFragment(ActivityComponent component) {
|
||||||
component.inject(this);
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||||
|
.get(BlogViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -90,30 +93,20 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
|||||||
ui.input.setMaxTextLength(MAX_BLOG_POST_TEXT_LENGTH);
|
ui.input.setMaxTextLength(MAX_BLOG_POST_TEXT_LENGTH);
|
||||||
showProgressBar();
|
showProgressBar();
|
||||||
|
|
||||||
feedController.loadBlogPost(blogId, postId,
|
viewModel.loadBlogPost(blogId, postId).observe(getViewLifecycleOwner(),
|
||||||
new UiResultExceptionHandler<BlogPostItem, DbException>(
|
result -> result.onError(this::handleException)
|
||||||
this) {
|
.onSuccess(this::bindViewHolder)
|
||||||
@Override
|
);
|
||||||
public void onResultUi(BlogPostItem result) {
|
|
||||||
item = result;
|
|
||||||
bindViewHolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleException(exception);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindViewHolder() {
|
private void bindViewHolder(BlogPostItem item) {
|
||||||
if (item == null) return;
|
this.item = item;
|
||||||
|
|
||||||
hideProgressBar();
|
hideProgressBar();
|
||||||
|
|
||||||
ui.post.bindItem(item);
|
ui.post.bindItem(this.item);
|
||||||
ui.post.hideReblogButton();
|
ui.post.hideReblogButton();
|
||||||
|
|
||||||
ui.input.setReady(true);
|
ui.input.setReady(true);
|
||||||
@@ -124,13 +117,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
|||||||
public void onSendClick(@Nullable String text,
|
public void onSendClick(@Nullable String text,
|
||||||
List<AttachmentHeader> headers) {
|
List<AttachmentHeader> headers) {
|
||||||
ui.input.hideSoftKeyboard();
|
ui.input.hideSoftKeyboard();
|
||||||
feedController.repeatPost(item, text,
|
viewModel.repeatPost(item, text);
|
||||||
new UiExceptionHandler<DbException>(this) {
|
|
||||||
@Override
|
|
||||||
public void onExceptionUi(DbException exception) {
|
|
||||||
handleException(exception);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +131,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
|||||||
ui.input.setVisibility(VISIBLE);
|
ui.input.setVisibility(VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ViewHolder {
|
private class ViewHolder implements OnBlogPostClickListener {
|
||||||
|
|
||||||
private final ScrollView scrollView;
|
private final ScrollView scrollView;
|
||||||
private final ProgressBar progressBar;
|
private final ProgressBar progressBar;
|
||||||
@@ -155,18 +142,25 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
|||||||
scrollView = v.findViewById(R.id.scrollView);
|
scrollView = v.findViewById(R.id.scrollView);
|
||||||
progressBar = v.findViewById(R.id.progressBar);
|
progressBar = v.findViewById(R.id.progressBar);
|
||||||
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout),
|
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout),
|
||||||
true, new OnBlogPostClickListener() {
|
true, this, false);
|
||||||
@Override
|
|
||||||
public void onBlogPostClick(BlogPostItem post) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthorClick(BlogPostItem post) {
|
|
||||||
// probably don't want to allow author clicks here
|
|
||||||
}
|
|
||||||
}, getFragmentManager());
|
|
||||||
input = v.findViewById(R.id.inputText);
|
input = v.findViewById(R.id.inputText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBlogPostClick(BlogPostItem post) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthorClick(BlogPostItem post) {
|
||||||
|
// probably don't want to allow author clicks here
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLinkClick(String url) {
|
||||||
|
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
|
||||||
|
f.show(getParentFragmentManager(), f.getUniqueTag());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.contact;
|
package org.briarproject.briar.android.contact;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import org.briarproject.briar.android.util.BriarAdapter;
|
import org.briarproject.briar.android.util.BriarAdapter;
|
||||||
|
|
||||||
@@ -45,8 +44,4 @@ public abstract class BaseContactListAdapter<I extends ContactItem, VH extends C
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface OnContactClickListener<I> {
|
|
||||||
void onItemClick(View view, I item);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.view.ViewGroup;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.NullSafety;
|
import org.briarproject.bramble.api.nullsafety.NullSafety;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
|
||||||
|
|
||||||
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
|
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
|
||||||
import androidx.recyclerview.widget.ListAdapter;
|
import androidx.recyclerview.widget.ListAdapter;
|
||||||
@@ -16,9 +15,6 @@ import androidx.recyclerview.widget.ListAdapter;
|
|||||||
public class ContactListAdapter extends
|
public class ContactListAdapter extends
|
||||||
ListAdapter<ContactListItem, ContactListItemViewHolder> {
|
ListAdapter<ContactListItem, ContactListItemViewHolder> {
|
||||||
|
|
||||||
// TODO: using the click listener interface from BaseContactListAdapter on
|
|
||||||
// purpose here because it is entangled with ContactListItemViewHolder. At
|
|
||||||
// some point we probably want to change that.
|
|
||||||
protected final OnContactClickListener<ContactListItem> listener;
|
protected final OnContactClickListener<ContactListItem> listener;
|
||||||
|
|
||||||
public ContactListAdapter(
|
public ContactListAdapter(
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
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.AddContactActivity;
|
||||||
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
|
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
|
||||||
import org.briarproject.briar.android.conversation.ConversationActivity;
|
import org.briarproject.briar.android.conversation.ConversationActivity;
|
||||||
@@ -34,7 +33,6 @@ import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
|
|||||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
|
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
|
||||||
|
|
||||||
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE;
|
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE;
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
|
||||||
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@@ -102,7 +100,6 @@ public class ContactListFragment extends BaseFragment
|
|||||||
.observe(getViewLifecycleOwner(), result -> {
|
.observe(getViewLifecycleOwner(), result -> {
|
||||||
result.onError(this::handleException).onSuccess(items -> {
|
result.onError(this::handleException).onSuccess(items -> {
|
||||||
adapter.submitList(items);
|
adapter.submitList(items);
|
||||||
if (requireNonNull(items).size() == 0) list.showData();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
viewModel.getHasPendingContacts()
|
viewModel.getHasPendingContacts()
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
|||||||
@@ -3,70 +3,41 @@ package org.briarproject.briar.android.contact;
|
|||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
|
||||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
|
||||||
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
|
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
|
||||||
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
|
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
|
||||||
import org.briarproject.bramble.api.db.TransactionManager;
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
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.android.AndroidNotificationManager;
|
||||||
import org.briarproject.briar.api.avatar.event.AvatarUpdatedEvent;
|
|
||||||
import org.briarproject.briar.api.client.MessageTracker;
|
|
||||||
import org.briarproject.briar.api.conversation.ConversationManager;
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
|
||||||
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
|
|
||||||
import org.briarproject.briar.api.identity.AuthorInfo;
|
|
||||||
import org.briarproject.briar.api.identity.AuthorManager;
|
import org.briarproject.briar.api.identity.AuthorManager;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.arch.core.util.Function;
|
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ContactListViewModel extends DbViewModel implements EventListener {
|
class ContactListViewModel extends ContactsViewModel {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(ContactListViewModel.class.getName());
|
getLogger(ContactListViewModel.class.getName());
|
||||||
|
|
||||||
private final ContactManager contactManager;
|
|
||||||
private final AuthorManager authorManager;
|
|
||||||
private final ConversationManager conversationManager;
|
|
||||||
private final ConnectionRegistry connectionRegistry;
|
|
||||||
private final EventBus eventBus;
|
|
||||||
private final AndroidNotificationManager notificationManager;
|
private final AndroidNotificationManager notificationManager;
|
||||||
|
|
||||||
private final MutableLiveData<LiveResult<List<ContactListItem>>>
|
|
||||||
contactListItems = new MutableLiveData<>();
|
|
||||||
|
|
||||||
private final MutableLiveData<Boolean> hasPendingContacts =
|
private final MutableLiveData<Boolean> hasPendingContacts =
|
||||||
new MutableLiveData<>();
|
new MutableLiveData<>();
|
||||||
|
|
||||||
@@ -79,99 +50,25 @@ class ContactListViewModel extends DbViewModel implements EventListener {
|
|||||||
ConversationManager conversationManager,
|
ConversationManager conversationManager,
|
||||||
ConnectionRegistry connectionRegistry, EventBus eventBus,
|
ConnectionRegistry connectionRegistry, EventBus eventBus,
|
||||||
AndroidNotificationManager notificationManager) {
|
AndroidNotificationManager notificationManager) {
|
||||||
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
super(application, dbExecutor, lifecycleManager, db, androidExecutor,
|
||||||
this.contactManager = contactManager;
|
contactManager, authorManager, conversationManager,
|
||||||
this.authorManager = authorManager;
|
connectionRegistry, eventBus);
|
||||||
this.conversationManager = conversationManager;
|
|
||||||
this.connectionRegistry = connectionRegistry;
|
|
||||||
this.eventBus = eventBus;
|
|
||||||
this.notificationManager = notificationManager;
|
this.notificationManager = notificationManager;
|
||||||
this.eventBus.addListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCleared() {
|
|
||||||
super.onCleared();
|
|
||||||
eventBus.removeListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loadContacts() {
|
|
||||||
loadList(this::loadContacts, contactListItems::setValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ContactListItem> loadContacts(Transaction txn)
|
|
||||||
throws DbException {
|
|
||||||
long start = now();
|
|
||||||
List<ContactListItem> contacts = new ArrayList<>();
|
|
||||||
for (Contact c : contactManager.getContacts(txn)) {
|
|
||||||
ContactId id = c.getId();
|
|
||||||
AuthorInfo authorInfo = authorManager.getAuthorInfo(txn, c);
|
|
||||||
MessageTracker.GroupCount count =
|
|
||||||
conversationManager.getGroupCount(txn, id);
|
|
||||||
boolean connected = connectionRegistry.isConnected(c.getId());
|
|
||||||
contacts.add(new ContactListItem(c, authorInfo, connected, count));
|
|
||||||
}
|
|
||||||
Collections.sort(contacts);
|
|
||||||
logDuration(LOG, "Full load", start);
|
|
||||||
return contacts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void eventOccurred(Event e) {
|
public void eventOccurred(Event e) {
|
||||||
if (e instanceof ContactAddedEvent) {
|
super.eventOccurred(e);
|
||||||
LOG.info("Contact added, reloading");
|
if (e instanceof PendingContactAddedEvent ||
|
||||||
loadContacts();
|
|
||||||
} else if (e instanceof ContactConnectedEvent) {
|
|
||||||
updateItem(((ContactConnectedEvent) e).getContactId(),
|
|
||||||
item -> new ContactListItem(item, true), false);
|
|
||||||
} else if (e instanceof ContactDisconnectedEvent) {
|
|
||||||
updateItem(((ContactDisconnectedEvent) e).getContactId(),
|
|
||||||
item -> new ContactListItem(item, false), false);
|
|
||||||
} else if (e instanceof ContactRemovedEvent) {
|
|
||||||
LOG.info("Contact removed, removing item");
|
|
||||||
removeItem(((ContactRemovedEvent) e).getContactId());
|
|
||||||
} else if (e instanceof ConversationMessageReceivedEvent) {
|
|
||||||
LOG.info("Conversation message received, updating item");
|
|
||||||
ConversationMessageReceivedEvent<?> p =
|
|
||||||
(ConversationMessageReceivedEvent<?>) e;
|
|
||||||
ConversationMessageHeader h = p.getMessageHeader();
|
|
||||||
updateItem(p.getContactId(), item -> new ContactListItem(item, h),
|
|
||||||
true);
|
|
||||||
} else if (e instanceof PendingContactAddedEvent ||
|
|
||||||
e instanceof PendingContactRemovedEvent) {
|
e instanceof PendingContactRemovedEvent) {
|
||||||
checkForPendingContacts();
|
checkForPendingContacts();
|
||||||
} else if (e instanceof AvatarUpdatedEvent) {
|
|
||||||
AvatarUpdatedEvent a = (AvatarUpdatedEvent) e;
|
|
||||||
updateItem(a.getContactId(), item -> new ContactListItem(item,
|
|
||||||
a.getAttachmentHeader()), false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveData<LiveResult<List<ContactListItem>>> getContactListItems() {
|
|
||||||
return contactListItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
LiveData<Boolean> getHasPendingContacts() {
|
LiveData<Boolean> getHasPendingContacts() {
|
||||||
return hasPendingContacts;
|
return hasPendingContacts;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateItem(ContactId c,
|
|
||||||
Function<ContactListItem, ContactListItem> replacer, boolean sort) {
|
|
||||||
List<ContactListItem> list = updateListItems(contactListItems,
|
|
||||||
itemToTest -> itemToTest.getContact().getId().equals(c),
|
|
||||||
replacer);
|
|
||||||
if (list == null) return;
|
|
||||||
if (sort) Collections.sort(list);
|
|
||||||
contactListItems.setValue(new LiveResult<>(list));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeItem(ContactId c) {
|
|
||||||
List<ContactListItem> list = removeListItems(contactListItems,
|
|
||||||
itemToTest -> itemToTest.getContact().getId().equals(c));
|
|
||||||
if (list == null) return;
|
|
||||||
contactListItems.setValue(new LiveResult<>(list));
|
|
||||||
}
|
|
||||||
|
|
||||||
void checkForPendingContacts() {
|
void checkForPendingContacts() {
|
||||||
runOnDbThread(() -> {
|
runOnDbThread(() -> {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package org.briarproject.briar.android.contact;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
||||||
|
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||||
|
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.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
|
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||||
|
import org.briarproject.briar.android.viewmodel.LiveResult;
|
||||||
|
import org.briarproject.briar.api.avatar.event.AvatarUpdatedEvent;
|
||||||
|
import org.briarproject.briar.api.client.MessageTracker;
|
||||||
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
|
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
||||||
|
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
|
||||||
|
import org.briarproject.briar.api.identity.AuthorInfo;
|
||||||
|
import org.briarproject.briar.api.identity.AuthorManager;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.annotation.UiThread;
|
||||||
|
import androidx.arch.core.util.Function;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class ContactsViewModel extends DbViewModel implements EventListener {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(ContactsViewModel.class.getName());
|
||||||
|
|
||||||
|
protected final ContactManager contactManager;
|
||||||
|
private final AuthorManager authorManager;
|
||||||
|
private final ConversationManager conversationManager;
|
||||||
|
private final ConnectionRegistry connectionRegistry;
|
||||||
|
private final EventBus eventBus;
|
||||||
|
|
||||||
|
private final MutableLiveData<LiveResult<List<ContactListItem>>>
|
||||||
|
contactListItems = new MutableLiveData<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ContactsViewModel(Application application,
|
||||||
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
|
LifecycleManager lifecycleManager, TransactionManager db,
|
||||||
|
AndroidExecutor androidExecutor, ContactManager contactManager,
|
||||||
|
AuthorManager authorManager,
|
||||||
|
ConversationManager conversationManager,
|
||||||
|
ConnectionRegistry connectionRegistry, EventBus eventBus) {
|
||||||
|
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||||
|
this.contactManager = contactManager;
|
||||||
|
this.authorManager = authorManager;
|
||||||
|
this.conversationManager = conversationManager;
|
||||||
|
this.connectionRegistry = connectionRegistry;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.eventBus.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCleared() {
|
||||||
|
super.onCleared();
|
||||||
|
eventBus.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void loadContacts() {
|
||||||
|
loadList(this::loadContacts, contactListItems::setValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ContactListItem> loadContacts(Transaction txn)
|
||||||
|
throws DbException {
|
||||||
|
long start = now();
|
||||||
|
List<ContactListItem> contacts = new ArrayList<>();
|
||||||
|
for (Contact c : contactManager.getContacts(txn)) {
|
||||||
|
ContactId id = c.getId();
|
||||||
|
if (!displayContact(id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
AuthorInfo authorInfo = authorManager.getAuthorInfo(txn, c);
|
||||||
|
MessageTracker.GroupCount count =
|
||||||
|
conversationManager.getGroupCount(txn, id);
|
||||||
|
boolean connected = connectionRegistry.isConnected(c.getId());
|
||||||
|
contacts.add(new ContactListItem(c, authorInfo, connected, count));
|
||||||
|
}
|
||||||
|
Collections.sort(contacts);
|
||||||
|
logDuration(LOG, "Full load", start);
|
||||||
|
return contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override this method to display only a subset of contacts.
|
||||||
|
*/
|
||||||
|
protected boolean displayContact(ContactId contactId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventOccurred(Event e) {
|
||||||
|
if (e instanceof ContactAddedEvent) {
|
||||||
|
LOG.info("Contact added, reloading");
|
||||||
|
loadContacts();
|
||||||
|
} else if (e instanceof ContactConnectedEvent) {
|
||||||
|
updateItem(((ContactConnectedEvent) e).getContactId(),
|
||||||
|
item -> new ContactListItem(item, true), false);
|
||||||
|
} else if (e instanceof ContactDisconnectedEvent) {
|
||||||
|
updateItem(((ContactDisconnectedEvent) e).getContactId(),
|
||||||
|
item -> new ContactListItem(item, false), false);
|
||||||
|
} else if (e instanceof ContactRemovedEvent) {
|
||||||
|
LOG.info("Contact removed, removing item");
|
||||||
|
removeItem(((ContactRemovedEvent) e).getContactId());
|
||||||
|
} else if (e instanceof ConversationMessageReceivedEvent) {
|
||||||
|
LOG.info("Conversation message received, updating item");
|
||||||
|
ConversationMessageReceivedEvent<?> p =
|
||||||
|
(ConversationMessageReceivedEvent<?>) e;
|
||||||
|
ConversationMessageHeader h = p.getMessageHeader();
|
||||||
|
updateItem(p.getContactId(), item -> new ContactListItem(item, h),
|
||||||
|
true);
|
||||||
|
} else if (e instanceof AvatarUpdatedEvent) {
|
||||||
|
AvatarUpdatedEvent a = (AvatarUpdatedEvent) e;
|
||||||
|
updateItem(a.getContactId(), item -> new ContactListItem(item,
|
||||||
|
a.getAttachmentHeader()), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<LiveResult<List<ContactListItem>>> getContactListItems() {
|
||||||
|
return contactListItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void updateItem(ContactId c,
|
||||||
|
Function<ContactListItem, ContactListItem> replacer, boolean sort) {
|
||||||
|
List<ContactListItem> list = updateListItems(getList(contactListItems),
|
||||||
|
itemToTest -> itemToTest.getContact().getId().equals(c),
|
||||||
|
replacer);
|
||||||
|
if (list == null) return;
|
||||||
|
if (sort) Collections.sort(list);
|
||||||
|
contactListItems.setValue(new LiveResult<>(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void removeItem(ContactId c) {
|
||||||
|
removeAndUpdateListItems(contactListItems,
|
||||||
|
itemToTest -> itemToTest.getContact().getId().equals(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package org.briarproject.briar.android.contact;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
public class LegacyContactListAdapter extends
|
|
||||||
BaseContactListAdapter<ContactListItem, ContactListItemViewHolder> {
|
|
||||||
|
|
||||||
public LegacyContactListAdapter(Context context,
|
|
||||||
OnContactClickListener<ContactListItem> listener) {
|
|
||||||
super(context, ContactListItem.class, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ContactListItemViewHolder onCreateViewHolder(ViewGroup viewGroup,
|
|
||||||
int i) {
|
|
||||||
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
|
|
||||||
R.layout.list_item_contact, viewGroup, false);
|
|
||||||
|
|
||||||
return new ContactListItemViewHolder(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean areContentsTheSame(ContactListItem c1, ContactListItem c2) {
|
|
||||||
// check for all properties that influence visual
|
|
||||||
// representation of contact
|
|
||||||
if (c1.isEmpty() != c2.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (c1.getUnreadCount() != c2.getUnreadCount()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (c1.getTimestamp() != c2.getTimestamp()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return c1.isConnected() == c2.isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(ContactListItem c1, ContactListItem c2) {
|
|
||||||
return Long.compare(c2.getTimestamp(), c1.getTimestamp());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.briarproject.briar.android.contact;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
public interface OnContactClickListener<I> {
|
||||||
|
|
||||||
|
void onItemClick(View view, I item);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.contact.ContactId;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.android.contact.BaseContactListAdapter;
|
import org.briarproject.briar.android.contact.BaseContactListAdapter;
|
||||||
import org.briarproject.briar.android.contact.ContactItemViewHolder;
|
import org.briarproject.briar.android.contact.ContactItemViewHolder;
|
||||||
|
import org.briarproject.briar.android.contact.OnContactClickListener;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
|
||||||
import org.briarproject.briar.android.contact.ContactItemViewHolder;
|
import org.briarproject.briar.android.contact.ContactItemViewHolder;
|
||||||
|
import org.briarproject.briar.android.contact.OnContactClickListener;
|
||||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
|
||||||
import org.briarproject.briar.android.contact.ContactItemViewHolder;
|
import org.briarproject.briar.android.contact.ContactItemViewHolder;
|
||||||
|
import org.briarproject.briar.android.contact.OnContactClickListener;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.contact.OnContactClickListener;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ContactSelectorAdapter extends
|
class ContactSelectorAdapter extends
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import android.view.MenuItem;
|
|||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
import org.briarproject.briar.android.contact.OnContactClickListener;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package org.briarproject.briar.android.contactselection;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
import org.briarproject.briar.android.contact.OnContactClickListener;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
package org.briarproject.briar.android.controller;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
@NotNullByDefault
|
|
||||||
public interface SharingController {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the listener that is called when contacts go on or offline.
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
void setSharingListener(SharingListener listener);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsets the listener.
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
void unsetSharingListener(SharingListener listener);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call this when your lifecycle starts,
|
|
||||||
* so the listener will be called when information changes.
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
void onStart();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call this when your lifecycle stops,
|
|
||||||
* so that the controller knows it can stops listening to events.
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
void onStop();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds one contact to be tracked.
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
void add(ContactId c);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a collection of contacts to be tracked.
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
void addAll(Collection<ContactId> contacts);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call this when the contact identified by c is no longer sharing
|
|
||||||
* the given group identified by GroupId g.
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
void remove(ContactId c);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of online contacts.
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
int getOnlineCount();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the total number of contacts that have been added.
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
int getTotalCount();
|
|
||||||
|
|
||||||
interface SharingListener {
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onSharingInfoUpdated(int total, int online);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
package org.briarproject.briar.android.controller;
|
|
||||||
|
|
||||||
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.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
@NotNullByDefault
|
|
||||||
public class SharingControllerImpl implements SharingController, EventListener {
|
|
||||||
|
|
||||||
private final EventBus eventBus;
|
|
||||||
private final ConnectionRegistry connectionRegistry;
|
|
||||||
|
|
||||||
// UI thread
|
|
||||||
private final Set<ContactId> contacts = new HashSet<>();
|
|
||||||
|
|
||||||
// UI thread
|
|
||||||
@Nullable
|
|
||||||
private SharingListener listener;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
SharingControllerImpl(EventBus eventBus,
|
|
||||||
ConnectionRegistry connectionRegistry) {
|
|
||||||
this.eventBus = eventBus;
|
|
||||||
this.connectionRegistry = connectionRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSharingListener(SharingListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unsetSharingListener(SharingListener listener) {
|
|
||||||
if (this.listener == listener) this.listener = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
eventBus.addListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
eventBus.removeListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void eventOccurred(Event e) {
|
|
||||||
if (e instanceof ContactConnectedEvent) {
|
|
||||||
setConnected(((ContactConnectedEvent) e).getContactId());
|
|
||||||
} else if (e instanceof ContactDisconnectedEvent) {
|
|
||||||
setConnected(((ContactDisconnectedEvent) e).getContactId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
private void setConnected(ContactId c) {
|
|
||||||
if (listener == null) throw new IllegalStateException();
|
|
||||||
if (contacts.contains(c)) {
|
|
||||||
int online = getOnlineCount();
|
|
||||||
listener.onSharingInfoUpdated(contacts.size(), online);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAll(Collection<ContactId> c) {
|
|
||||||
contacts.addAll(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(ContactId c) {
|
|
||||||
contacts.add(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove(ContactId c) {
|
|
||||||
contacts.remove(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOnlineCount() {
|
|
||||||
int online = 0;
|
|
||||||
for (ContactId c : contacts) {
|
|
||||||
if (connectionRegistry.isConnected(c)) online++;
|
|
||||||
}
|
|
||||||
return online;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTotalCount() {
|
|
||||||
return contacts.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -24,10 +24,7 @@ import org.briarproject.briar.android.attachment.AttachmentItem;
|
|||||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||||
import org.briarproject.briar.android.view.PullDownLayout;
|
import org.briarproject.briar.android.view.PullDownLayout;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@@ -293,13 +290,10 @@ public class ImageActivity extends BriarActivity
|
|||||||
|
|
||||||
@RequiresApi(api = 19)
|
@RequiresApi(api = 19)
|
||||||
private Intent getCreationIntent() {
|
private Intent getCreationIntent() {
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
|
|
||||||
Locale.getDefault());
|
|
||||||
String fileName = sdf.format(new Date());
|
|
||||||
Intent intent = new Intent(ACTION_CREATE_DOCUMENT);
|
Intent intent = new Intent(ACTION_CREATE_DOCUMENT);
|
||||||
intent.addCategory(CATEGORY_OPENABLE);
|
intent.addCategory(CATEGORY_OPENABLE);
|
||||||
intent.setType(getVisibleAttachment().getMimeType());
|
intent.setType(getVisibleAttachment().getMimeType());
|
||||||
intent.putExtra(EXTRA_TITLE, fileName);
|
intent.putExtra(EXTRA_TITLE, viewModel.getFileName());
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -225,8 +225,8 @@ public class ImageViewModel extends DbViewModel implements EventListener {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFileName() {
|
String getFileName() {
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd",
|
||||||
Locale.getDefault());
|
Locale.getDefault());
|
||||||
return sdf.format(new Date());
|
return sdf.format(new Date());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class ForumListViewModel extends DbViewModel implements EventListener {
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void onForumPostReceived(GroupId g, ForumPostHeader header) {
|
private void onForumPostReceived(GroupId g, ForumPostHeader header) {
|
||||||
List<ForumListItem> list = updateListItems(forumItems,
|
List<ForumListItem> list = updateListItems(getList(forumItems),
|
||||||
itemToTest -> itemToTest.getForum().getId().equals(g),
|
itemToTest -> itemToTest.getForum().getId().equals(g),
|
||||||
itemToUpdate -> new ForumListItem(itemToUpdate, header));
|
itemToUpdate -> new ForumListItem(itemToUpdate, header));
|
||||||
if (list == null) return;
|
if (list == null) return;
|
||||||
@@ -156,11 +156,9 @@ class ForumListViewModel extends DbViewModel implements EventListener {
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void onGroupRemoved(GroupId groupId) {
|
private void onGroupRemoved(GroupId groupId) {
|
||||||
List<ForumListItem> list = removeListItems(forumItems, i ->
|
removeAndUpdateListItems(forumItems, i ->
|
||||||
i.getForum().getId().equals(groupId)
|
i.getForum().getId().equals(groupId)
|
||||||
);
|
);
|
||||||
if (list == null) return;
|
|
||||||
forumItems.setValue(new LiveResult<>(list));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadForumInvitations() {
|
void loadForumInvitations() {
|
||||||
|
|||||||
@@ -10,9 +10,8 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|||||||
import org.briarproject.briar.android.DestroyableContext;
|
import org.briarproject.briar.android.DestroyableContext;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.UiThread;
|
import androidx.annotation.UiThread;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
@@ -47,13 +46,11 @@ public abstract class BaseFragment extends Fragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == android.R.id.home) {
|
||||||
case android.R.id.home:
|
requireActivity().onBackPressed();
|
||||||
listener.onBackPressed();
|
return true;
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@@ -79,6 +76,7 @@ public abstract class BaseFragment extends Fragment
|
|||||||
void handleException(Exception e);
|
void handleException(Exception e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@CallSuper
|
@CallSuper
|
||||||
@Override
|
@Override
|
||||||
public void runOnUiThreadUnlessDestroyed(Runnable r) {
|
public void runOnUiThreadUnlessDestroyed(Runnable r) {
|
||||||
|
|||||||
@@ -5,74 +5,41 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
import org.briarproject.briar.android.contact.ContactListAdapter;
|
||||||
import org.briarproject.briar.android.contact.ContactListItem;
|
import org.briarproject.briar.android.contact.ContactListItem;
|
||||||
import org.briarproject.briar.android.contact.LegacyContactListAdapter;
|
import org.briarproject.briar.android.contact.OnContactClickListener;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
|
||||||
import org.briarproject.briar.api.conversation.ConversationManager;
|
|
||||||
import org.briarproject.briar.api.identity.AuthorInfo;
|
|
||||||
import org.briarproject.briar.api.identity.AuthorManager;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.UiThread;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class ContactChooserFragment extends BaseFragment {
|
public class ContactChooserFragment extends BaseFragment
|
||||||
|
implements OnContactClickListener<ContactListItem> {
|
||||||
|
|
||||||
public static final String TAG = ContactChooserFragment.class.getName();
|
private static final String TAG = ContactChooserFragment.class.getName();
|
||||||
private static final Logger LOG = Logger.getLogger(TAG);
|
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private IntroductionViewModel viewModel;
|
||||||
|
private final ContactListAdapter adapter = new ContactListAdapter(this);
|
||||||
private BriarRecyclerView list;
|
private BriarRecyclerView list;
|
||||||
private LegacyContactListAdapter adapter;
|
|
||||||
private ContactId contactId;
|
|
||||||
|
|
||||||
// Fields that are accessed from background threads must be volatile
|
|
||||||
private volatile Contact c1;
|
|
||||||
@Inject
|
|
||||||
volatile ContactManager contactManager;
|
|
||||||
@Inject
|
|
||||||
volatile AuthorManager authorManager;
|
|
||||||
@Inject
|
|
||||||
volatile ConversationManager conversationManager;
|
|
||||||
@Inject
|
|
||||||
volatile ConnectionRegistry connectionRegistry;
|
|
||||||
|
|
||||||
public static ContactChooserFragment newInstance(ContactId id) {
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
|
|
||||||
ContactChooserFragment fragment = new ContactChooserFragment();
|
|
||||||
args.putInt(CONTACT_ID, id.getInt());
|
|
||||||
fragment.setArguments(args);
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectFragment(ActivityComponent component) {
|
public void injectFragment(ActivityComponent component) {
|
||||||
component.inject(this);
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||||
|
.get(IntroductionViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -80,23 +47,20 @@ public class ContactChooserFragment extends BaseFragment {
|
|||||||
@Nullable ViewGroup container,
|
@Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
|
||||||
View contentView = inflater.inflate(R.layout.list, container, false);
|
// change toolbar text (relevant when navigating back to this fragment)
|
||||||
|
requireActivity().setTitle(R.string.introduction_activity_title);
|
||||||
|
|
||||||
OnContactClickListener<ContactListItem> onContactClickListener =
|
View contentView = inflater.inflate(R.layout.list, container, false);
|
||||||
(view, item) -> {
|
|
||||||
if (c1 == null) throw new IllegalStateException();
|
|
||||||
Contact c2 = item.getContact();
|
|
||||||
showMessageScreen(c1, c2);
|
|
||||||
};
|
|
||||||
adapter = new LegacyContactListAdapter(requireActivity(),
|
|
||||||
onContactClickListener);
|
|
||||||
|
|
||||||
list = contentView.findViewById(R.id.list);
|
list = contentView.findViewById(R.id.list);
|
||||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
list.setEmptyText(R.string.no_contacts);
|
list.setEmptyText(R.string.no_contacts);
|
||||||
|
|
||||||
contactId = new ContactId(requireArguments().getInt(CONTACT_ID));
|
viewModel.getContactListItems().observe(getViewLifecycleOwner(),
|
||||||
|
result -> result.onError(this::handleException)
|
||||||
|
.onSuccess(adapter::submitList)
|
||||||
|
);
|
||||||
|
|
||||||
return contentView;
|
return contentView;
|
||||||
}
|
}
|
||||||
@@ -104,14 +68,13 @@ public class ContactChooserFragment extends BaseFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
loadContacts();
|
list.startPeriodicUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
adapter.clear();
|
list.stopPeriodicUpdate();
|
||||||
list.showProgressBar();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -119,43 +82,9 @@ public class ContactChooserFragment extends BaseFragment {
|
|||||||
return TAG;
|
return TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadContacts() {
|
@Override
|
||||||
listener.runOnDbThread(() -> {
|
public void onItemClick(View view, ContactListItem item) {
|
||||||
try {
|
viewModel.setSecondContactId(item.getContact().getId());
|
||||||
List<ContactListItem> contacts = new ArrayList<>();
|
viewModel.triggerContactSelected();
|
||||||
for (Contact c : contactManager.getContacts()) {
|
|
||||||
if (c.getId().equals(contactId)) {
|
|
||||||
c1 = c;
|
|
||||||
} else {
|
|
||||||
AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
|
|
||||||
ContactId id = c.getId();
|
|
||||||
GroupCount count =
|
|
||||||
conversationManager.getGroupCount(id);
|
|
||||||
boolean connected =
|
|
||||||
connectionRegistry.isConnected(c.getId());
|
|
||||||
contacts.add(new ContactListItem(c, authorInfo,
|
|
||||||
connected, count));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
displayContacts(contacts);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayContacts(List<ContactListItem> contacts) {
|
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
|
||||||
if (contacts.isEmpty()) list.showData();
|
|
||||||
else adapter.addAll(contacts);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showMessageScreen(Contact c1, Contact c2) {
|
|
||||||
IntroductionMessageFragment messageFragment =
|
|
||||||
IntroductionMessageFragment
|
|
||||||
.newInstance(c1.getId().getInt(), c2.getId().getInt());
|
|
||||||
showNextFragment(messageFragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,30 +9,67 @@ import org.briarproject.briar.android.activity.ActivityComponent;
|
|||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
||||||
|
|
||||||
public class IntroductionActivity extends BriarActivity
|
public class IntroductionActivity extends BriarActivity
|
||||||
implements BaseFragmentListener {
|
implements BaseFragmentListener {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private IntroductionViewModel viewModel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectActivity(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||||
|
.get(IntroductionViewModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String BUNDLE_CONTACT2 = "contact2";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
int id = intent.getIntExtra(CONTACT_ID, -1);
|
int contactId1 = intent.getIntExtra(CONTACT_ID, -1);
|
||||||
if (id == -1) throw new IllegalStateException("No ContactId");
|
if (contactId1 == -1)
|
||||||
ContactId contactId = new ContactId(id);
|
throw new IllegalStateException("No ContactId");
|
||||||
|
ContactId firstContactId = new ContactId(contactId1);
|
||||||
|
|
||||||
|
viewModel.setFirstContactId(firstContactId);
|
||||||
|
|
||||||
setContentView(R.layout.activity_fragment_container);
|
setContentView(R.layout.activity_fragment_container);
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
showInitialFragment(ContactChooserFragment.newInstance(contactId));
|
showInitialFragment(new ContactChooserFragment());
|
||||||
|
} else {
|
||||||
|
int contactId2 = savedInstanceState.getInt(BUNDLE_CONTACT2);
|
||||||
|
ContactId secondContactId = new ContactId(contactId2);
|
||||||
|
viewModel.setSecondContactId(secondContactId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.getSecondContactSelected().observeEvent(this, e -> {
|
||||||
|
IntroductionMessageFragment fragment =
|
||||||
|
new IntroductionMessageFragment();
|
||||||
|
showNextFragment(fragment);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectActivity(ActivityComponent component) {
|
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
component.inject(this);
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
|
ContactId secondContactId = viewModel.getSecondContactId();
|
||||||
|
if (secondContactId != null) {
|
||||||
|
outState.putInt(BUNDLE_CONTACT2, secondContactId.getInt());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.briarproject.briar.android.introduction;
|
||||||
|
|
||||||
|
import org.briarproject.briar.android.contact.ContactItem;
|
||||||
|
|
||||||
|
class IntroductionInfo {
|
||||||
|
private final ContactItem c1;
|
||||||
|
private final ContactItem c2;
|
||||||
|
private final boolean possible;
|
||||||
|
|
||||||
|
IntroductionInfo(ContactItem c1, ContactItem c2,
|
||||||
|
boolean possible) {
|
||||||
|
this.c1 = c1;
|
||||||
|
this.c2 = c2;
|
||||||
|
this.possible = possible;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactItem getContact1() {
|
||||||
|
return c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactItem getContact2() {
|
||||||
|
return c2;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isPossible() {
|
||||||
|
return possible;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.briar.android.introduction;
|
package org.briarproject.briar.android.introduction;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -8,12 +7,7 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
@@ -24,25 +18,19 @@ import org.briarproject.briar.android.view.TextInputView;
|
|||||||
import org.briarproject.briar.android.view.TextSendController;
|
import org.briarproject.briar.android.view.TextSendController;
|
||||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||||
import org.briarproject.briar.api.attachment.AttachmentHeader;
|
import org.briarproject.briar.api.attachment.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.identity.AuthorInfo;
|
|
||||||
import org.briarproject.briar.api.identity.AuthorManager;
|
|
||||||
import org.briarproject.briar.api.introduction.IntroductionManager;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import de.hdodenhof.circleimageview.CircleImageView;
|
import de.hdodenhof.circleimageview.CircleImageView;
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
import static android.app.Activity.RESULT_OK;
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||||
import static org.briarproject.briar.android.view.AuthorView.setAvatar;
|
import static org.briarproject.briar.android.view.AuthorView.setAvatar;
|
||||||
@@ -53,45 +41,21 @@ import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_
|
|||||||
public class IntroductionMessageFragment extends BaseFragment
|
public class IntroductionMessageFragment extends BaseFragment
|
||||||
implements SendListener {
|
implements SendListener {
|
||||||
|
|
||||||
public static final String TAG =
|
private static final String TAG =
|
||||||
IntroductionMessageFragment.class.getName();
|
IntroductionMessageFragment.class.getName();
|
||||||
private static final Logger LOG = Logger.getLogger(TAG);
|
|
||||||
|
|
||||||
private final static String CONTACT_ID_1 = "contact1";
|
@Inject
|
||||||
private final static String CONTACT_ID_2 = "contact2";
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private IntroductionViewModel viewModel;
|
||||||
|
|
||||||
private IntroductionActivity introductionActivity;
|
|
||||||
private ViewHolder ui;
|
private ViewHolder ui;
|
||||||
private Contact contact1, contact2;
|
|
||||||
|
|
||||||
// Fields that are accessed from background threads must be volatile
|
|
||||||
@Inject
|
|
||||||
protected volatile ContactManager contactManager;
|
|
||||||
@Inject
|
|
||||||
protected volatile AuthorManager authorManager;
|
|
||||||
@Inject
|
|
||||||
protected volatile IntroductionManager introductionManager;
|
|
||||||
|
|
||||||
public static IntroductionMessageFragment newInstance(int contactId1,
|
|
||||||
int contactId2) {
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putInt(CONTACT_ID_1, contactId1);
|
|
||||||
args.putInt(CONTACT_ID_2, contactId2);
|
|
||||||
IntroductionMessageFragment fragment =
|
|
||||||
new IntroductionMessageFragment();
|
|
||||||
fragment.setArguments(args);
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
introductionActivity = (IntroductionActivity) context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectFragment(ActivityComponent component) {
|
public void injectFragment(ActivityComponent component) {
|
||||||
component.inject(this);
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||||
|
.get(IntroductionViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -100,18 +64,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
|
||||||
// change toolbar text
|
// change toolbar text
|
||||||
ActionBar actionBar = introductionActivity.getSupportActionBar();
|
requireActivity().setTitle(R.string.introduction_message_title);
|
||||||
if (actionBar != null) {
|
|
||||||
actionBar.setTitle(R.string.introduction_message_title);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get contact IDs from fragment arguments
|
|
||||||
Bundle args = requireArguments();
|
|
||||||
int contactId1 = args.getInt(CONTACT_ID_1, -1);
|
|
||||||
int contactId2 = args.getInt(CONTACT_ID_2, -1);
|
|
||||||
if (contactId1 == -1 || contactId2 == -1) {
|
|
||||||
throw new AssertionError("Use newInstance() to instantiate");
|
|
||||||
}
|
|
||||||
|
|
||||||
// inflate view
|
// inflate view
|
||||||
View v = inflater.inflate(R.layout.introduction_message, container,
|
View v = inflater.inflate(R.layout.introduction_message, container,
|
||||||
@@ -123,69 +76,44 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
ui.message.setMaxTextLength(MAX_INTRODUCTION_TEXT_LENGTH);
|
ui.message.setMaxTextLength(MAX_INTRODUCTION_TEXT_LENGTH);
|
||||||
ui.message.setReady(false);
|
ui.message.setReady(false);
|
||||||
|
|
||||||
// get contacts and then show view
|
viewModel.getIntroductionInfo().observe(getViewLifecycleOwner(), ii -> {
|
||||||
prepareToSetUpViews(contactId1, contactId2);
|
if (ii == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setUpViews(ii.getContact1(), ii.getContact2(),
|
||||||
|
ii.isPossible());
|
||||||
|
});
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUniqueTag() {
|
public String getUniqueTag() {
|
||||||
return TAG;
|
return TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareToSetUpViews(int contactId1, int contactId2) {
|
|
||||||
introductionActivity.runOnDbThread(() -> {
|
|
||||||
try {
|
|
||||||
Contact contact1 =
|
|
||||||
contactManager.getContact(new ContactId(contactId1));
|
|
||||||
Contact contact2 =
|
|
||||||
contactManager.getContact(new ContactId(contactId2));
|
|
||||||
AuthorInfo a1 = authorManager.getAuthorInfo(contact1);
|
|
||||||
AuthorInfo a2 = authorManager.getAuthorInfo(contact2);
|
|
||||||
boolean possible =
|
|
||||||
introductionManager.canIntroduce(contact1, contact2);
|
|
||||||
ContactItem c1 = new ContactItem(contact1, a1);
|
|
||||||
ContactItem c2 = new ContactItem(contact2, a2);
|
|
||||||
setUpViews(c1, c2, possible);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUpViews(ContactItem c1, ContactItem c2, boolean possible) {
|
private void setUpViews(ContactItem c1, ContactItem c2, boolean possible) {
|
||||||
introductionActivity.runOnUiThreadUnlessDestroyed(() -> {
|
// set avatars
|
||||||
contact1 = c1.getContact();
|
setAvatar(ui.avatar1, c1);
|
||||||
contact2 = c2.getContact();
|
setAvatar(ui.avatar2, c2);
|
||||||
|
|
||||||
// set avatars
|
// set contact names
|
||||||
setAvatar(ui.avatar1, c1);
|
ui.contactName1.setText(getContactDisplayName(c1.getContact()));
|
||||||
setAvatar(ui.avatar2, c2);
|
ui.contactName2.setText(getContactDisplayName(c2.getContact()));
|
||||||
|
|
||||||
// set contact names
|
// hide progress bar
|
||||||
ui.contactName1.setText(getContactDisplayName(c1.getContact()));
|
ui.progressBar.setVisibility(GONE);
|
||||||
ui.contactName2.setText(getContactDisplayName(c2.getContact()));
|
|
||||||
|
|
||||||
// hide progress bar
|
if (possible) {
|
||||||
ui.progressBar.setVisibility(GONE);
|
// show views
|
||||||
|
ui.notPossible.setVisibility(GONE);
|
||||||
if (possible) {
|
ui.message.setVisibility(VISIBLE);
|
||||||
// show views
|
ui.message.setReady(true);
|
||||||
ui.notPossible.setVisibility(GONE);
|
ui.message.showSoftKeyboard();
|
||||||
ui.message.setVisibility(VISIBLE);
|
} else {
|
||||||
ui.message.setReady(true);
|
ui.notPossible.setVisibility(VISIBLE);
|
||||||
ui.message.showSoftKeyboard();
|
ui.message.setVisibility(GONE);
|
||||||
} else {
|
}
|
||||||
ui.notPossible.setVisibility(VISIBLE);
|
|
||||||
ui.message.setVisibility(GONE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -193,7 +121,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case android.R.id.home:
|
case android.R.id.home:
|
||||||
hideSoftKeyboard(ui.message);
|
hideSoftKeyboard(ui.message);
|
||||||
introductionActivity.onBackPressed();
|
requireActivity().onBackPressed();
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
@@ -206,32 +134,13 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
// disable button to prevent accidental double invitations
|
// disable button to prevent accidental double invitations
|
||||||
ui.message.setReady(false);
|
ui.message.setReady(false);
|
||||||
|
|
||||||
makeIntroduction(contact1, contact2, text);
|
viewModel.makeIntroduction(text);
|
||||||
|
|
||||||
// don't wait for the introduction to be made before finishing activity
|
// don't wait for the introduction to be made before finishing activity
|
||||||
hideSoftKeyboard(ui.message);
|
hideSoftKeyboard(ui.message);
|
||||||
introductionActivity.setResult(RESULT_OK);
|
FragmentActivity activity = requireActivity();
|
||||||
introductionActivity.supportFinishAfterTransition();
|
activity.setResult(RESULT_OK);
|
||||||
}
|
activity.supportFinishAfterTransition();
|
||||||
|
|
||||||
private void makeIntroduction(Contact c1, Contact c2,
|
|
||||||
@Nullable String text) {
|
|
||||||
introductionActivity.runOnDbThread(() -> {
|
|
||||||
// actually make the introduction
|
|
||||||
try {
|
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
introductionManager.makeIntroduction(c1, c2, text, timestamp);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
introductionError();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void introductionError() {
|
|
||||||
introductionActivity.runOnUiThreadUnlessDestroyed(
|
|
||||||
() -> Toast.makeText(introductionActivity,
|
|
||||||
R.string.introduction_error, LENGTH_SHORT).show());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ViewHolder {
|
private static class ViewHolder {
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.briarproject.briar.android.introduction;
|
||||||
|
|
||||||
|
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 IntroductionModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(IntroductionViewModel.class)
|
||||||
|
abstract ViewModel bindIntroductionViewModel(
|
||||||
|
IntroductionViewModel introductionViewModel);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
package org.briarproject.briar.android.introduction;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
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.TransactionManager;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
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.R;
|
||||||
|
import org.briarproject.briar.android.contact.ContactItem;
|
||||||
|
import org.briarproject.briar.android.contact.ContactsViewModel;
|
||||||
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
|
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||||
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
|
import org.briarproject.briar.api.identity.AuthorInfo;
|
||||||
|
import org.briarproject.briar.api.identity.AuthorManager;
|
||||||
|
import org.briarproject.briar.api.introduction.IntroductionManager;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
|
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.logException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class IntroductionViewModel extends ContactsViewModel {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(IntroductionViewModel.class.getName());
|
||||||
|
|
||||||
|
private final ContactManager contactManager;
|
||||||
|
private final AuthorManager authorManager;
|
||||||
|
private final IntroductionManager introductionManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
IntroductionViewModel(Application application,
|
||||||
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
|
LifecycleManager lifecycleManager, TransactionManager db,
|
||||||
|
AndroidExecutor androidExecutor, ContactManager contactManager,
|
||||||
|
AuthorManager authorManager,
|
||||||
|
ConversationManager conversationManager,
|
||||||
|
ConnectionRegistry connectionRegistry, EventBus eventBus,
|
||||||
|
IntroductionManager introductionManager) {
|
||||||
|
super(application, dbExecutor, lifecycleManager, db, androidExecutor,
|
||||||
|
contactManager, authorManager, conversationManager,
|
||||||
|
connectionRegistry, eventBus);
|
||||||
|
this.contactManager = contactManager;
|
||||||
|
this.authorManager = authorManager;
|
||||||
|
this.introductionManager = introductionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the contact from whose conversation we started the introduction
|
||||||
|
* using the menu item.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private ContactId firstContactId;
|
||||||
|
/*
|
||||||
|
* This is the contact we selected from the list of contacts as a second
|
||||||
|
* contact for the introduction.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private ContactId secondContactId;
|
||||||
|
|
||||||
|
private final MutableLiveEvent<Boolean> secondContactSelected =
|
||||||
|
new MutableLiveEvent<>();
|
||||||
|
|
||||||
|
private final MutableLiveData<IntroductionInfo> introductionInfo =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
|
||||||
|
void setFirstContactId(ContactId contactId) {
|
||||||
|
this.firstContactId = contactId;
|
||||||
|
loadContacts();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
ContactId getSecondContactId() {
|
||||||
|
return secondContactId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSecondContactId(ContactId contactId) {
|
||||||
|
secondContactId = contactId;
|
||||||
|
// Setting this to null here so that IntroductionMessageFragment can
|
||||||
|
// tell whether the correct value has been loaded from the database when
|
||||||
|
// selecting a second contact repeatedly.
|
||||||
|
introductionInfo.setValue(null);
|
||||||
|
loadIntroductionInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger the event that the second contact has been selected from the
|
||||||
|
* contact list by the user.
|
||||||
|
*/
|
||||||
|
void triggerContactSelected() {
|
||||||
|
secondContactSelected.setEvent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event will be triggered once the second contact has been selected
|
||||||
|
* from the list of contacts displayed. It is not fired when the second
|
||||||
|
* contact gets restored from the saved instance state.
|
||||||
|
*/
|
||||||
|
LiveEvent<Boolean> getSecondContactSelected() {
|
||||||
|
return secondContactSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holder for the introduction info object with data about both contacts
|
||||||
|
* and whether the introduction is possible. May wrap null if the data
|
||||||
|
* is not available yet. This happens when it is reset by selecting a
|
||||||
|
* contact with the same view model instance more than once.
|
||||||
|
*/
|
||||||
|
LiveData<IntroductionInfo> getIntroductionInfo() {
|
||||||
|
return introductionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean displayContact(ContactId contactId) {
|
||||||
|
return !requireNonNull(firstContactId).equals(contactId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadIntroductionInfo() {
|
||||||
|
final ContactId firstContactId = requireNonNull(this.firstContactId);
|
||||||
|
final ContactId secondContactId = requireNonNull(this.secondContactId);
|
||||||
|
runOnDbThread(() -> {
|
||||||
|
try {
|
||||||
|
Contact firstContact =
|
||||||
|
contactManager.getContact(firstContactId);
|
||||||
|
Contact secondContact =
|
||||||
|
contactManager.getContact(secondContactId);
|
||||||
|
AuthorInfo a1 = authorManager.getAuthorInfo(firstContact);
|
||||||
|
AuthorInfo a2 = authorManager.getAuthorInfo(secondContact);
|
||||||
|
boolean possible = introductionManager
|
||||||
|
.canIntroduce(firstContact, secondContact);
|
||||||
|
ContactItem c1 = new ContactItem(firstContact, a1);
|
||||||
|
ContactItem c2 = new ContactItem(secondContact, a2);
|
||||||
|
introductionInfo.postValue(
|
||||||
|
new IntroductionInfo(c1, c2, possible));
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void makeIntroduction(@Nullable String text) {
|
||||||
|
final IntroductionInfo info =
|
||||||
|
requireNonNull(introductionInfo.getValue());
|
||||||
|
runOnDbThread(() -> {
|
||||||
|
// actually make the introduction
|
||||||
|
try {
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
introductionManager.makeIntroduction(
|
||||||
|
info.getContact1().getContact(),
|
||||||
|
info.getContact2().getContact(), text, timestamp);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
androidExecutor.runOnUiThread(() -> Toast.makeText(
|
||||||
|
getApplication(), R.string.introduction_error,
|
||||||
|
LENGTH_SHORT).show());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -411,6 +411,8 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
@UiThread
|
@UiThread
|
||||||
public void onRequestPermissionsResult(int requestCode,
|
public void onRequestPermissionsResult(int requestCode,
|
||||||
String[] permissions, int[] grantResults) {
|
String[] permissions, int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions,
|
||||||
|
grantResults);
|
||||||
if (requestCode != REQUEST_PERMISSION_CAMERA_LOCATION)
|
if (requestCode != REQUEST_PERMISSION_CAMERA_LOCATION)
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
if (gotPermission(CAMERA, permissions, grantResults)) {
|
if (gotPermission(CAMERA, permissions, grantResults)) {
|
||||||
|
|||||||
@@ -49,14 +49,12 @@ import androidx.annotation.DrawableRes;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.drawerlayout.widget.DrawerLayout;
|
import androidx.drawerlayout.widget.DrawerLayout;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
@@ -67,9 +65,7 @@ import static android.view.View.GONE;
|
|||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
import static androidx.core.view.GravityCompat.START;
|
import static androidx.core.view.GravityCompat.START;
|
||||||
import static androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
import static androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
||||||
import static androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
|
|
||||||
import static androidx.lifecycle.Lifecycle.State.STARTED;
|
import static androidx.lifecycle.Lifecycle.State.STARTED;
|
||||||
import static java.util.Objects.requireNonNull;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||||
@@ -146,7 +142,7 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
if (ask) showDozeDialog(getString(R.string.setup_doze_intro));
|
if (ask) showDozeDialog(getString(R.string.setup_doze_intro));
|
||||||
});
|
});
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = setUpCustomToolbar(false);
|
||||||
drawerLayout = findViewById(R.id.drawer_layout);
|
drawerLayout = findViewById(R.id.drawer_layout);
|
||||||
navigation = findViewById(R.id.navigation);
|
navigation = findViewById(R.id.navigation);
|
||||||
GridView transportsView = findViewById(R.id.transportsView);
|
GridView transportsView = findViewById(R.id.transportsView);
|
||||||
@@ -156,11 +152,6 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
startActivity(new Intent(this, TransportsActivity.class));
|
startActivity(new Intent(this, TransportsActivity.class));
|
||||||
});
|
});
|
||||||
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
ActionBar actionBar = requireNonNull(getSupportActionBar());
|
|
||||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
|
||||||
actionBar.setHomeButtonEnabled(true);
|
|
||||||
|
|
||||||
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar,
|
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar,
|
||||||
R.string.nav_drawer_open_description,
|
R.string.nav_drawer_open_description,
|
||||||
R.string.nav_drawer_close_description) {
|
R.string.nav_drawer_close_description) {
|
||||||
@@ -184,9 +175,6 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
|
|
||||||
if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) {
|
if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) {
|
||||||
showSignOutFragment();
|
showSignOutFragment();
|
||||||
} else if (state == null) {
|
|
||||||
startFragment(ContactListFragment.newInstance(),
|
|
||||||
R.id.nav_btn_contacts);
|
|
||||||
}
|
}
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
// do not call this again when there's existing state
|
// do not call this again when there's existing state
|
||||||
@@ -276,7 +264,6 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
@Override
|
@Override
|
||||||
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
||||||
drawerLayout.closeDrawer(START);
|
drawerLayout.closeDrawer(START);
|
||||||
clearBackStack();
|
|
||||||
if (item.getItemId() == R.id.nav_btn_lock) {
|
if (item.getItemId() == R.id.nav_btn_lock) {
|
||||||
lockManager.setLocked(true);
|
lockManager.setLocked(true);
|
||||||
ActivityCompat.finishAfterTransition(this);
|
ActivityCompat.finishAfterTransition(this);
|
||||||
@@ -296,8 +283,8 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
FragmentManager fm = getSupportFragmentManager();
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
if (fm.findFragmentByTag(SignOutFragment.TAG) != null) {
|
if (fm.findFragmentByTag(SignOutFragment.TAG) != null) {
|
||||||
finish();
|
finish();
|
||||||
} else if (fm.getBackStackEntryCount() == 0
|
} else if (fm.getBackStackEntryCount() == 0 &&
|
||||||
&& fm.findFragmentByTag(ContactListFragment.TAG) == null) {
|
fm.findFragmentByTag(ContactListFragment.TAG) == null) {
|
||||||
// don't start fragments in the wrong part of lifecycle (#1904)
|
// don't start fragments in the wrong part of lifecycle (#1904)
|
||||||
if (!getLifecycle().getCurrentState().isAtLeast(STARTED)) {
|
if (!getLifecycle().getCurrentState().isAtLeast(STARTED)) {
|
||||||
LOG.warning("Tried to start contacts fragment in state " +
|
LOG.warning("Tried to start contacts fragment in state " +
|
||||||
@@ -346,30 +333,12 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
startFragment(fragment);
|
startFragment(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startFragment(BaseFragment fragment) {
|
private void startFragment(BaseFragment f) {
|
||||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
|
getSupportFragmentManager().beginTransaction()
|
||||||
startFragment(fragment, false);
|
.setCustomAnimations(R.anim.fade_in, R.anim.fade_out,
|
||||||
else startFragment(fragment, true);
|
R.anim.fade_in, R.anim.fade_out)
|
||||||
}
|
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
||||||
|
.commit();
|
||||||
private void startFragment(BaseFragment fragment,
|
|
||||||
boolean isAddedToBackStack) {
|
|
||||||
FragmentTransaction trans =
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
|
||||||
.setCustomAnimations(R.anim.fade_in,
|
|
||||||
R.anim.fade_out, R.anim.fade_in,
|
|
||||||
R.anim.fade_out)
|
|
||||||
.replace(R.id.fragmentContainer, fragment,
|
|
||||||
fragment.getUniqueTag());
|
|
||||||
if (isAddedToBackStack) {
|
|
||||||
trans.addToBackStack(fragment.getUniqueTag());
|
|
||||||
}
|
|
||||||
trans.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearBackStack() {
|
|
||||||
getSupportFragmentManager().popBackStackImmediate(null,
|
|
||||||
POP_BACK_STACK_INCLUSIVE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.briarproject.briar.android.privategroup.list;
|
|||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
@@ -173,7 +172,7 @@ class GroupListViewModel extends DbViewModel implements EventListener {
|
|||||||
@UiThread
|
@UiThread
|
||||||
private void onGroupMessageAdded(GroupMessageHeader header) {
|
private void onGroupMessageAdded(GroupMessageHeader header) {
|
||||||
GroupId g = header.getGroupId();
|
GroupId g = header.getGroupId();
|
||||||
List<GroupItem> list = updateListItems(groupItems,
|
List<GroupItem> list = updateListItems(getList(groupItems),
|
||||||
itemToTest -> itemToTest.getId().equals(g),
|
itemToTest -> itemToTest.getId().equals(g),
|
||||||
itemToUpdate -> new GroupItem(itemToUpdate, header));
|
itemToUpdate -> new GroupItem(itemToUpdate, header));
|
||||||
if (list == null) return;
|
if (list == null) return;
|
||||||
@@ -184,7 +183,7 @@ class GroupListViewModel extends DbViewModel implements EventListener {
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void onGroupDissolved(GroupId groupId) {
|
private void onGroupDissolved(GroupId groupId) {
|
||||||
List<GroupItem> list = updateListItems(groupItems,
|
List<GroupItem> list = updateListItems(getList(groupItems),
|
||||||
itemToTest -> itemToTest.getId().equals(groupId),
|
itemToTest -> itemToTest.getId().equals(groupId),
|
||||||
itemToUpdate -> new GroupItem(itemToUpdate, true));
|
itemToUpdate -> new GroupItem(itemToUpdate, true));
|
||||||
if (list == null) return;
|
if (list == null) return;
|
||||||
@@ -193,10 +192,7 @@ class GroupListViewModel extends DbViewModel implements EventListener {
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void onGroupRemoved(GroupId groupId) {
|
private void onGroupRemoved(GroupId groupId) {
|
||||||
List<GroupItem> list =
|
removeAndUpdateListItems(groupItems, i -> i.getId().equals(groupId));
|
||||||
removeListItems(groupItems, i -> i.getId().equals(groupId));
|
|
||||||
if (list == null) return;
|
|
||||||
groupItems.setValue(new LiveResult<>(list));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeGroup(GroupId g) {
|
void removeGroup(GroupId g) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
import org.briarproject.briar.android.contact.OnContactClickListener;
|
||||||
import org.briarproject.briar.android.contactselection.BaseContactSelectorFragment;
|
import org.briarproject.briar.android.contactselection.BaseContactSelectorFragment;
|
||||||
import org.briarproject.briar.android.contactselection.ContactSelectorController;
|
import org.briarproject.briar.android.contactselection.ContactSelectorController;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.view.ViewGroup;
|
|||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.contact.OnContactClickListener;
|
||||||
import org.briarproject.briar.android.contactselection.BaseContactSelectorAdapter;
|
import org.briarproject.briar.android.contactselection.BaseContactSelectorAdapter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import android.widget.ImageView;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
import org.briarproject.briar.android.contact.OnContactClickListener;
|
||||||
import org.briarproject.briar.android.contactselection.BaseSelectableContactHolder;
|
import org.briarproject.briar.android.contactselection.BaseSelectableContactHolder;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|||||||
@@ -9,10 +9,13 @@ import android.view.View;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.BaseActivity;
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
|
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@@ -32,7 +35,7 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
private SettingsViewModel settingsViewModel;
|
private SettingsViewModel viewModel;
|
||||||
|
|
||||||
private static final String ARG_URI = "uri";
|
private static final String ARG_URI = "uri";
|
||||||
private Uri uri;
|
private Uri uri;
|
||||||
@@ -51,6 +54,9 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
|
|||||||
public void onAttach(Context ctx) {
|
public void onAttach(Context ctx) {
|
||||||
super.onAttach(ctx);
|
super.onAttach(ctx);
|
||||||
((BaseActivity) requireActivity()).getActivityComponent().inject(this);
|
((BaseActivity) requireActivity()).getActivityComponent().inject(this);
|
||||||
|
ViewModelProvider provider =
|
||||||
|
new ViewModelProvider(requireActivity(), viewModelFactory);
|
||||||
|
viewModel = provider.get(SettingsViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -60,32 +66,34 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
|
|||||||
uri = Uri.parse(argUri);
|
uri = Uri.parse(argUri);
|
||||||
|
|
||||||
FragmentActivity activity = requireActivity();
|
FragmentActivity activity = requireActivity();
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(activity);
|
||||||
ViewModelProvider provider =
|
|
||||||
new ViewModelProvider(activity, viewModelFactory);
|
|
||||||
settingsViewModel = provider.get(SettingsViewModel.class);
|
|
||||||
|
|
||||||
AlertDialog.Builder builder =
|
|
||||||
new AlertDialog.Builder(activity, R.style.BriarDialogTheme);
|
|
||||||
|
|
||||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
|
||||||
final View view =
|
final View view =
|
||||||
inflater.inflate(R.layout.fragment_confirm_avatar_dialog, null);
|
inflater.inflate(R.layout.fragment_confirm_avatar_dialog, null);
|
||||||
builder.setView(view);
|
|
||||||
|
|
||||||
builder.setTitle(R.string.dialog_confirm_profile_picture_title);
|
|
||||||
builder.setNegativeButton(R.string.cancel, null);
|
|
||||||
builder.setPositiveButton(R.string.change,
|
|
||||||
(dialog, id) -> settingsViewModel.setAvatar(uri));
|
|
||||||
|
|
||||||
ImageView imageView = view.findViewById(R.id.image);
|
ImageView imageView = view.findViewById(R.id.image);
|
||||||
imageView.setImageURI(uri);
|
|
||||||
|
|
||||||
TextView textViewUserName = view.findViewById(R.id.username);
|
TextView textViewUserName = view.findViewById(R.id.username);
|
||||||
settingsViewModel.getOwnIdentityInfo().observe(activity,
|
|
||||||
us -> textViewUserName.setText(us.getLocalAuthor().getName()));
|
|
||||||
|
|
||||||
return builder.create();
|
GlideApp.with(imageView)
|
||||||
|
.load(uri)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.error(R.drawable.ic_image_broken)
|
||||||
|
.into(imageView)
|
||||||
|
.waitForLayout();
|
||||||
|
|
||||||
|
// we can't use getViewLifecycleOwner() here
|
||||||
|
// as this fragment technically doesn't have a view
|
||||||
|
viewModel.getOwnIdentityInfo().observe(activity, us ->
|
||||||
|
textViewUserName.setText(us.getLocalAuthor().getName())
|
||||||
|
);
|
||||||
|
|
||||||
|
int theme = R.style.BriarDialogTheme;
|
||||||
|
return new AlertDialog.Builder(activity, theme)
|
||||||
|
.setView(view)
|
||||||
|
.setTitle(R.string.dialog_confirm_profile_picture_title)
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.setPositiveButton(R.string.change, (d, id) ->
|
||||||
|
viewModel.setAvatar(uri)
|
||||||
|
)
|
||||||
|
.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class SettingsViewModel extends AndroidViewModel {
|
|||||||
return ownIdentityInfo;
|
return ownIdentityInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveEvent<Boolean> getSetAvatarFailed() {
|
LiveEvent<Boolean> getSetAvatarFailed() {
|
||||||
return setAvatarFailed;
|
return setAvatarFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,6 +197,10 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
|
|||||||
*/
|
*/
|
||||||
@UiThread
|
@UiThread
|
||||||
protected void addItem(I item, boolean scrollToItem) {
|
protected void addItem(I item, boolean scrollToItem) {
|
||||||
|
// If items haven't loaded, we need to wait until they have.
|
||||||
|
// Since this was a R/W DB transaction, the load will pick up this item.
|
||||||
|
if (items.getValue() == null) return;
|
||||||
|
|
||||||
messageTree.add(item);
|
messageTree.add(item);
|
||||||
if (scrollToItem) this.scrollToItem.set(item.getId());
|
if (scrollToItem) this.scrollToItem.set(item.getId());
|
||||||
items.setValue(new LiveResult<>(messageTree.depthFirstOrder()));
|
items.setValue(new LiveResult<>(messageTree.depthFirstOrder()));
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.reporting.FeedbackActivity;
|
import org.briarproject.briar.android.reporting.FeedbackActivity;
|
||||||
import org.briarproject.briar.android.view.ArticleMovementMethod;
|
import org.briarproject.briar.android.view.ArticleMovementMethod;
|
||||||
import org.briarproject.briar.android.widget.LinkDialogFragment;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
@@ -49,8 +48,8 @@ import androidx.appcompat.app.AlertDialog;
|
|||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
||||||
import androidx.core.text.HtmlCompat;
|
import androidx.core.text.HtmlCompat;
|
||||||
|
import androidx.core.util.Consumer;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
@@ -198,8 +197,7 @@ public class UiUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void makeLinksClickable(TextView v,
|
public static void makeLinksClickable(TextView v,
|
||||||
@Nullable FragmentManager fm) {
|
Consumer<String> onLinkClicked) {
|
||||||
if (fm == null) return;
|
|
||||||
SpannableStringBuilder ssb = new SpannableStringBuilder(v.getText());
|
SpannableStringBuilder ssb = new SpannableStringBuilder(v.getText());
|
||||||
URLSpan[] spans = ssb.getSpans(0, ssb.length(), URLSpan.class);
|
URLSpan[] spans = ssb.getSpans(0, ssb.length(), URLSpan.class);
|
||||||
for (URLSpan span : spans) {
|
for (URLSpan span : spans) {
|
||||||
@@ -210,8 +208,7 @@ public class UiUtils {
|
|||||||
ClickableSpan cSpan = new ClickableSpan() {
|
ClickableSpan cSpan = new ClickableSpan() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v2) {
|
public void onClick(View v2) {
|
||||||
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
|
onLinkClicked.accept(url);
|
||||||
f.show(fm, f.getUniqueTag());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ssb.setSpan(cSpan, start, end, 0);
|
ssb.setSpan(cSpan, start, end, 0);
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ public class BriarRecyclerView extends FrameLayout {
|
|||||||
R.styleable.BriarRecyclerView);
|
R.styleable.BriarRecyclerView);
|
||||||
isScrollingToEnd = attributes
|
isScrollingToEnd = attributes
|
||||||
.getBoolean(R.styleable.BriarRecyclerView_scrollToEnd, true);
|
.getBoolean(R.styleable.BriarRecyclerView_scrollToEnd, true);
|
||||||
int drawableRes = attributes.getResourceId(R.styleable.BriarRecyclerView_emptyImage, -1);
|
int drawableRes = attributes
|
||||||
|
.getResourceId(R.styleable.BriarRecyclerView_emptyImage, -1);
|
||||||
if (drawableRes != -1) setEmptyImage(drawableRes);
|
if (drawableRes != -1) setEmptyImage(drawableRes);
|
||||||
String emtpyText =
|
String emtpyText =
|
||||||
attributes.getString(R.styleable.BriarRecyclerView_emptyText);
|
attributes.getString(R.styleable.BriarRecyclerView_emptyText);
|
||||||
@@ -87,10 +88,30 @@ public class BriarRecyclerView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
emptyObserver = new RecyclerView.AdapterDataObserver() {
|
emptyObserver = new RecyclerView.AdapterDataObserver() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChanged() {
|
||||||
|
super.onChanged();
|
||||||
|
showData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemRangeChanged(int positionStart, int itemCount) {
|
||||||
|
super.onItemRangeChanged(positionStart, itemCount);
|
||||||
|
if (itemCount > 0) showData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemRangeMoved(int fromPosition, int toPosition,
|
||||||
|
int itemCount) {
|
||||||
|
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
|
||||||
|
if (itemCount > 0) showData();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||||
super.onItemRangeInserted(positionStart, itemCount);
|
super.onItemRangeInserted(positionStart, itemCount);
|
||||||
if (itemCount > 0) showData();
|
showData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.briar.android.viewmodel;
|
package org.briarproject.briar.android.viewmodel;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbCallable;
|
import org.briarproject.bramble.api.db.DbCallable;
|
||||||
@@ -11,8 +12,10 @@ import org.briarproject.bramble.api.db.TransactionManager;
|
|||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -20,6 +23,7 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import androidx.annotation.AnyThread;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.UiThread;
|
import androidx.annotation.UiThread;
|
||||||
@@ -27,8 +31,10 @@ import androidx.arch.core.util.Function;
|
|||||||
import androidx.core.util.Consumer;
|
import androidx.core.util.Consumer;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import static android.widget.Toast.LENGTH_LONG;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
@@ -43,7 +49,7 @@ public abstract class DbViewModel extends AndroidViewModel {
|
|||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
private final LifecycleManager lifecycleManager;
|
private final LifecycleManager lifecycleManager;
|
||||||
private final TransactionManager db;
|
private final TransactionManager db;
|
||||||
private final AndroidExecutor androidExecutor;
|
protected final AndroidExecutor androidExecutor;
|
||||||
|
|
||||||
public DbViewModel(
|
public DbViewModel(
|
||||||
@NonNull Application application,
|
@NonNull Application application,
|
||||||
@@ -114,9 +120,11 @@ public abstract class DbViewModel extends AndroidViewModel {
|
|||||||
* This method ensures that those operations can be processed on the
|
* This method ensures that those operations can be processed on the
|
||||||
* UiThread in the correct order so that the removed message will not be
|
* UiThread in the correct order so that the removed message will not be
|
||||||
* re-added when the re-load completes.
|
* re-added when the re-load completes.
|
||||||
|
*
|
||||||
|
* TODO: Rename this method and update javadoc, as it's not restricted to
|
||||||
|
* lists
|
||||||
*/
|
*/
|
||||||
protected <T extends List<?>> void loadList(
|
protected <T> void loadList(DbCallable<T, DbException> task,
|
||||||
DbCallable<T, DbException> task,
|
|
||||||
UiConsumer<LiveResult<T>> uiConsumer) {
|
UiConsumer<LiveResult<T>> uiConsumer) {
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -143,25 +151,46 @@ public abstract class DbViewModel extends AndroidViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a copy of the list available in the given LiveData
|
* Creates a copy of the given list and adds the given item to the copy.
|
||||||
* and replaces items where the given test function returns true.
|
|
||||||
*
|
*
|
||||||
* @return a copy of the list in the LiveData with item(s) replaced
|
* @return an updated copy of the list, or null if the list is null
|
||||||
* or null when the
|
|
||||||
* <ul>
|
|
||||||
* <li> LiveData does not have a value
|
|
||||||
* <li> LiveResult in the LiveData has an error
|
|
||||||
* <li> test function did return false for all items in the list
|
|
||||||
* </ul>
|
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
protected <T> List<T> updateListItems(
|
protected <T> List<T> addListItem(@Nullable List<T> list, T item) {
|
||||||
LiveData<LiveResult<List<T>>> liveData, Function<T, Boolean> test,
|
if (list == null) return null;
|
||||||
Function<T, T> replacer) {
|
List<T> copy = new ArrayList<>(list);
|
||||||
List<T> items = getListCopy(liveData);
|
copy.add(item);
|
||||||
if (items == null) return null;
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
ListIterator<T> iterator = items.listIterator();
|
/**
|
||||||
|
* Creates a copy of the given list and adds the given items to the copy.
|
||||||
|
*
|
||||||
|
* @return an updated copy of the list, or null if the list is null
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
protected <T> List<T> addListItems(@Nullable List<T> list,
|
||||||
|
Collection<T> items) {
|
||||||
|
if (list == null) return null;
|
||||||
|
List<T> copy = new ArrayList<>(list);
|
||||||
|
copy.addAll(items);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a copy of the given list, replacing items where the given test
|
||||||
|
* function returns true.
|
||||||
|
*
|
||||||
|
* @return an updated copy of the list, or null if either the list is null
|
||||||
|
* or the test function returns false for all items
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
protected <T> List<T> updateListItems(@Nullable List<T> list,
|
||||||
|
Function<T, Boolean> test, Function<T, T> replacer) {
|
||||||
|
if (list == null) return null;
|
||||||
|
List<T> copy = new ArrayList<>(list);
|
||||||
|
|
||||||
|
ListIterator<T> iterator = copy.listIterator();
|
||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
T item = iterator.next();
|
T item = iterator.next();
|
||||||
@@ -170,28 +199,23 @@ public abstract class DbViewModel extends AndroidViewModel {
|
|||||||
iterator.set(replacer.apply(item));
|
iterator.set(replacer.apply(item));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return changed ? items : null;
|
return changed ? copy : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a copy of the list available in the given LiveData
|
* Creates a copy of the given list, removing items from it where the given
|
||||||
* and removes the items from it where the given test function returns true.
|
* test function returns true.
|
||||||
*
|
*
|
||||||
* @return a copy of the list in the LiveData with item(s) removed
|
* @return an updated copy of the list, or null if either the list is null
|
||||||
* or null when the
|
* or the test function returns false for all items
|
||||||
* <ul>
|
|
||||||
* <li> LiveData does not have a value
|
|
||||||
* <li> LiveResult in the LiveData has an error
|
|
||||||
* <li> test function did return false for all items in the list
|
|
||||||
* </ul>
|
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
protected <T> List<T> removeListItems(
|
protected <T> List<T> removeListItems(@Nullable List<T> list,
|
||||||
LiveData<LiveResult<List<T>>> liveData, Function<T, Boolean> test) {
|
Function<T, Boolean> test) {
|
||||||
List<T> items = getListCopy(liveData);
|
if (list == null) return null;
|
||||||
if (items == null) return null;
|
List<T> copy = new ArrayList<>(list);
|
||||||
|
|
||||||
ListIterator<T> iterator = items.listIterator();
|
ListIterator<T> iterator = copy.listIterator();
|
||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
T item = iterator.next();
|
T item = iterator.next();
|
||||||
@@ -200,21 +224,58 @@ public abstract class DbViewModel extends AndroidViewModel {
|
|||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return changed ? items : null;
|
return changed ? copy : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a copy of the list of items from the given LiveData
|
* Updates the given LiveData with a copy of its list
|
||||||
* or null if it is not available.
|
* with the items removed where the given test function returns true.
|
||||||
* The list copy can be safely mutated.
|
* <p>
|
||||||
|
* Nothing is updated, if the
|
||||||
|
* <ul>
|
||||||
|
* <li> LiveData does not have a value
|
||||||
|
* <li> LiveResult in the LiveData has an error
|
||||||
|
* <li> test function returned false for all items in the list
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@UiThread
|
||||||
|
protected <T> void removeAndUpdateListItems(
|
||||||
|
MutableLiveData<LiveResult<List<T>>> liveData,
|
||||||
|
Function<T, Boolean> test) {
|
||||||
|
List<T> copy = removeListItems(getList(liveData), test);
|
||||||
|
if (copy != null) liveData.setValue(new LiveResult<>(copy));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of items from the given LiveData, or null if no list is
|
||||||
|
* available.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private <T> List<T> getListCopy(LiveData<LiveResult<List<T>>> liveData) {
|
protected <T> List<T> getList(LiveData<LiveResult<List<T>>> liveData) {
|
||||||
LiveResult<List<T>> value = liveData.getValue();
|
LiveResult<List<T>> value = liveData.getValue();
|
||||||
if (value == null) return null;
|
if (value == null) return null;
|
||||||
List<T> list = value.getResultOrNull();
|
return value.getResultOrNull();
|
||||||
if (list == null) return null;
|
}
|
||||||
return new ArrayList<>(list);
|
|
||||||
|
/**
|
||||||
|
* Logs the exception and shows a Toast to the user.
|
||||||
|
* <p>
|
||||||
|
* Errors that are likely or expected to happen should not use this method
|
||||||
|
* and show proper error states in UI.
|
||||||
|
*/
|
||||||
|
@AnyThread
|
||||||
|
protected void handleException(Exception e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
androidExecutor.runOnUiThread(() -> {
|
||||||
|
String msg = "Error: " + e.getClass().getSimpleName();
|
||||||
|
if (!StringUtils.isNullOrEmpty(e.getMessage())) {
|
||||||
|
msg += " " + e.getMessage();
|
||||||
|
}
|
||||||
|
if (e.getCause() != null) {
|
||||||
|
msg += " caused by " + e.getCause().getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
Toast.makeText(getApplication(), msg, LENGTH_LONG).show();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ public interface AndroidNotificationManager {
|
|||||||
String BLOG_CHANNEL_ID = "blogs";
|
String BLOG_CHANNEL_ID = "blogs";
|
||||||
// Channels are sorted by channel ID in the Settings app, so use IDs
|
// Channels are sorted by channel ID in the Settings app, so use IDs
|
||||||
// that will sort below the main channels such as contacts
|
// that will sort below the main channels such as contacts
|
||||||
String ONGOING_CHANNEL_ID = "zForegroundService";
|
String ONGOING_CHANNEL_OLD_ID = "zForegroundService";
|
||||||
|
String ONGOING_CHANNEL_ID = "zForegroundService2";
|
||||||
String FAILURE_CHANNEL_ID = "zStartupFailure";
|
String FAILURE_CHANNEL_ID = "zStartupFailure";
|
||||||
String REMINDER_CHANNEL_ID = "zSignInReminder";
|
String REMINDER_CHANNEL_ID = "zSignInReminder";
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData;
|
|||||||
public interface LockManager {
|
public interface LockManager {
|
||||||
|
|
||||||
String ACTION_LOCK = "lock";
|
String ACTION_LOCK = "lock";
|
||||||
|
String EXTRA_PID = "PID";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the inactivity timer when the user interacts with the app.
|
* Stops the inactivity timer when the user interacts with the app.
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<alpha
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
android:duration="@integer/animationSpeed"
|
||||||
android:duration="@android:integer/config_longAnimTime"
|
|
||||||
android:fromAlpha="0.0"
|
android:fromAlpha="0.0"
|
||||||
android:interpolator="@android:interpolator/decelerate_quad"
|
android:interpolator="@android:interpolator/decelerate_quad"
|
||||||
android:toAlpha="1.0"/>
|
android:toAlpha="1.0" />
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<alpha
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
android:duration="@integer/animationSpeed"
|
||||||
android:duration="@android:integer/config_mediumAnimTime"
|
|
||||||
android:fromAlpha="1.0"
|
android:fromAlpha="1.0"
|
||||||
android:interpolator="@android:interpolator/accelerate_quad"
|
android:interpolator="@android:interpolator/accelerate_quad"
|
||||||
android:toAlpha="0.0"/>
|
android:toAlpha="0.0" />
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<set
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<!-- slide in from right -->
|
<!-- slide in from right -->
|
||||||
<translate
|
<translate
|
||||||
android:duration="@android:integer/config_mediumAnimTime"
|
android:duration="@integer/animationSpeed"
|
||||||
android:fromXDelta="100%p"
|
android:fromXDelta="100%p"
|
||||||
android:interpolator="@android:interpolator/decelerate_quad"
|
android:interpolator="@android:interpolator/decelerate_quad"
|
||||||
android:toXDelta="0"/>
|
android:toXDelta="0" />
|
||||||
|
|
||||||
</set>
|
</set>
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<set
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<!-- slide out to right -->
|
<!-- slide out to right -->
|
||||||
<translate
|
<translate
|
||||||
android:duration="@android:integer/config_mediumAnimTime"
|
android:duration="@integer/animationSpeed"
|
||||||
android:fromXDelta="0"
|
android:fromXDelta="0"
|
||||||
android:interpolator="@android:interpolator/accelerate_quad"
|
android:interpolator="@android:interpolator/accelerate_quad"
|
||||||
android:toXDelta="100%p"/>
|
android:toXDelta="100%p" />
|
||||||
|
|
||||||
</set>
|
</set>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<alpha
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
android:duration="@integer/animationSpeed"
|
||||||
android:duration="@android:integer/config_mediumAnimTime"
|
|
||||||
android:fromAlpha="0.0"
|
android:fromAlpha="0.0"
|
||||||
android:interpolator="@android:interpolator/decelerate_quad"
|
android:interpolator="@android:interpolator/decelerate_quad"
|
||||||
android:toAlpha="1.0"/>
|
android:toAlpha="1.0" />
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<alpha
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
android:duration="@integer/animationSpeed"
|
||||||
android:duration="@android:integer/config_mediumAnimTime"
|
|
||||||
android:fromAlpha="1.0"
|
android:fromAlpha="1.0"
|
||||||
android:interpolator="@android:interpolator/accelerate_quad"
|
android:interpolator="@android:interpolator/accelerate_quad"
|
||||||
android:toAlpha="0.0"/>
|
android:toAlpha="0.0" />
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<!-- slide in from right -->
|
<!-- slide in from right -->
|
||||||
<translate
|
<translate
|
||||||
android:duration="@android:integer/config_mediumAnimTime"
|
android:duration="@integer/animationSpeed"
|
||||||
android:fromXDelta="100%p"
|
android:fromXDelta="100%p"
|
||||||
android:interpolator="@android:interpolator/decelerate_quad"
|
android:interpolator="@android:interpolator/decelerate_quad"
|
||||||
android:toXDelta="0"/>
|
android:toXDelta="0"/>
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<set
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<!-- slide out to right -->
|
<!-- slide out to right -->
|
||||||
<translate
|
<translate
|
||||||
android:duration="@android:integer/config_mediumAnimTime"
|
android:duration="@integer/animationSpeed"
|
||||||
android:fromXDelta="0"
|
android:fromXDelta="0"
|
||||||
android:interpolator="@android:interpolator/accelerate_quad"
|
android:interpolator="@android:interpolator/accelerate_quad"
|
||||||
android:toXDelta="100%p"/>
|
android:toXDelta="100%p" />
|
||||||
|
|
||||||
</set>
|
</set>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<alpha
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
android:duration="@integer/animationSpeed"
|
||||||
android:duration="@android:integer/config_mediumAnimTime"
|
|
||||||
android:fromAlpha="0.0"
|
android:fromAlpha="0.0"
|
||||||
android:interpolator="@android:interpolator/decelerate_quad"
|
android:interpolator="@android:interpolator/decelerate_quad"
|
||||||
android:toAlpha="1.0"/>
|
android:toAlpha="1.0" />
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<alpha
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
android:duration="@integer/animationSpeed"
|
||||||
android:duration="@android:integer/config_mediumAnimTime"
|
|
||||||
android:fromAlpha="1.0"
|
android:fromAlpha="1.0"
|
||||||
android:interpolator="@android:interpolator/accelerate_quad"
|
android:interpolator="@android:interpolator/accelerate_quad"
|
||||||
android:toAlpha="0.0"/>
|
android:toAlpha="0.0" />
|
||||||
|
|||||||
@@ -52,16 +52,17 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:contentDescription="@string/close"
|
android:contentDescription="@string/close"
|
||||||
android:scaleType="center"
|
android:scaleType="center"
|
||||||
app:srcCompat="@drawable/ic_close"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_close"
|
||||||
app:tint="@color/briar_text_tertiary_inverse" />
|
app:tint="@color/briar_text_tertiary_inverse" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/fragmentContainer"
|
android:id="@+id/fragmentContainer"
|
||||||
|
android:name="org.briarproject.briar.android.contact.ContactListFragment"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
|||||||
@@ -5,27 +5,28 @@
|
|||||||
<group android:checkableBehavior="single">
|
<group android:checkableBehavior="single">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/nav_btn_contacts"
|
android:id="@+id/nav_btn_contacts"
|
||||||
|
android:checked="true"
|
||||||
android:icon="@drawable/ic_contacts"
|
android:icon="@drawable/ic_contacts"
|
||||||
android:title="@string/contact_list_button"/>
|
android:title="@string/contact_list_button" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/nav_btn_groups"
|
android:id="@+id/nav_btn_groups"
|
||||||
android:icon="@drawable/ic_group"
|
android:icon="@drawable/ic_group"
|
||||||
android:title="@string/groups_button"/>
|
android:title="@string/groups_button" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/nav_btn_forums"
|
android:id="@+id/nav_btn_forums"
|
||||||
android:icon="@drawable/ic_forums_black_24dp"
|
android:icon="@drawable/ic_forums_black_24dp"
|
||||||
android:title="@string/forums_button"/>
|
android:title="@string/forums_button" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/nav_btn_blogs"
|
android:id="@+id/nav_btn_blogs"
|
||||||
android:icon="@drawable/blogs"
|
android:icon="@drawable/blogs"
|
||||||
android:title="@string/blogs_button"/>
|
android:title="@string/blogs_button" />
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<group android:checkableBehavior="single">
|
<group android:checkableBehavior="single">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/nav_btn_settings"
|
android:id="@+id/nav_btn_settings"
|
||||||
android:icon="@drawable/ic_settings_black"
|
android:icon="@drawable/ic_settings_black"
|
||||||
android:title="@string/settings_button"/>
|
android:title="@string/settings_button" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/nav_btn_lock"
|
android:id="@+id/nav_btn_lock"
|
||||||
android:icon="@drawable/startup_lock"
|
android:icon="@drawable/startup_lock"
|
||||||
@@ -35,7 +36,7 @@
|
|||||||
<item
|
<item
|
||||||
android:id="@+id/nav_btn_signout"
|
android:id="@+id/nav_btn_signout"
|
||||||
android:icon="@drawable/ic_signout"
|
android:icon="@drawable/ic_signout"
|
||||||
android:title="@string/sign_out_button"/>
|
android:title="@string/sign_out_button" />
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
@@ -67,8 +67,11 @@
|
|||||||
<string name="lock_button">قفل کردن برنامه</string>
|
<string name="lock_button">قفل کردن برنامه</string>
|
||||||
<string name="settings_button">تنظیمات</string>
|
<string name="settings_button">تنظیمات</string>
|
||||||
<string name="sign_out_button">خروج</string>
|
<string name="sign_out_button">خروج</string>
|
||||||
|
<string name="transports_onboarding_text">برای کنترل چگونگی اتصال Briar (برایر) به مخاطبین خود، اینجا را لمس کنید.</string>
|
||||||
<!--Transports: Tor-->
|
<!--Transports: Tor-->
|
||||||
<string name="transport_tor">اینترنت</string>
|
<string name="transport_tor">اینترنت</string>
|
||||||
|
<string name="tor_device_status_online_wifi">تلفن شما از طریق Wi-Fi به اینترنت دسترسی دارد.</string>
|
||||||
|
<string name="tor_device_status_online_mobile">تلفن شما از طریق دیتا سیمکارت به اینترنت دسترسی دارد.</string>
|
||||||
<string name="tor_device_status_offline">تلفن شما دارای دسترسی اینترنتی نیست</string>
|
<string name="tor_device_status_offline">تلفن شما دارای دسترسی اینترنتی نیست</string>
|
||||||
<string name="tor_plugin_status_enabling">Briar در حال اتصال به اینترنت می باشد</string>
|
<string name="tor_plugin_status_enabling">Briar در حال اتصال به اینترنت می باشد</string>
|
||||||
<string name="tor_plugin_status_active">Briar به اینترنت متصل شد</string>
|
<string name="tor_plugin_status_active">Briar به اینترنت متصل شد</string>
|
||||||
@@ -462,6 +465,10 @@
|
|||||||
برای وارد کردن خوراک روی آیکون + ضربه بزنید</string>
|
برای وارد کردن خوراک روی آیکون + ضربه بزنید</string>
|
||||||
<string name="blogs_rss_feeds_manage_error">مشکلی با بارگذاری فیدهای شما وجود داشت. لطفا بعدا امتحان کنید.</string>
|
<string name="blogs_rss_feeds_manage_error">مشکلی با بارگذاری فیدهای شما وجود داشت. لطفا بعدا امتحان کنید.</string>
|
||||||
<!--Settings Profile Picture-->
|
<!--Settings Profile Picture-->
|
||||||
|
<string name="change_profile_picture">برای تغییر تصویر نمایه خود اینجا را لمس کنید.</string>
|
||||||
|
<string name="dialog_confirm_profile_picture_title">تغییر تصویر نمایه</string>
|
||||||
|
<string name="dialog_confirm_profile_picture_remark">تنها مخاطبین شما میتوانند تصویر نمایه شما را مشاهده کنند.</string>
|
||||||
|
<string name="change_profile_picture_failed_message">تاسفیم اما هنگام بروزرسانی تصویر نمایه شما مشکلی رخ داد.</string>
|
||||||
<!--Settings Display-->
|
<!--Settings Display-->
|
||||||
<string name="pref_language_title">زبان و منطقه</string>
|
<string name="pref_language_title">زبان و منطقه</string>
|
||||||
<string name="pref_language_changed">این تنظیمات زمانی که Briar (برایر) را ری استارت کنید تاثیر خود را می گذارند. لطفا خارج شوید و Briar (برایر) را دوباره راه اندازی کنید.</string>
|
<string name="pref_language_changed">این تنظیمات زمانی که Briar (برایر) را ری استارت کنید تاثیر خود را می گذارند. لطفا خارج شوید و Briar (برایر) را دوباره راه اندازی کنید.</string>
|
||||||
@@ -571,17 +578,20 @@
|
|||||||
<string name="include_debug_report_feedback">قرار دادن داده های ناشناس درباره این دستگاه</string>
|
<string name="include_debug_report_feedback">قرار دادن داده های ناشناس درباره این دستگاه</string>
|
||||||
<string name="dev_report_basic_info">اطلاعات پایه</string>
|
<string name="dev_report_basic_info">اطلاعات پایه</string>
|
||||||
<string name="dev_report_device_info">اطلاعات دستگاه</string>
|
<string name="dev_report_device_info">اطلاعات دستگاه</string>
|
||||||
|
<string name="dev_report_stacktrace">Stacktrace</string>
|
||||||
<string name="dev_report_time_info">اطلاعات زمانی</string>
|
<string name="dev_report_time_info">اطلاعات زمانی</string>
|
||||||
<string name="dev_report_memory">حافظه</string>
|
<string name="dev_report_memory">حافظه</string>
|
||||||
<string name="dev_report_storage">حافظه</string>
|
<string name="dev_report_storage">حافظه</string>
|
||||||
<string name="dev_report_connectivity">اتصال</string>
|
<string name="dev_report_connectivity">اتصال</string>
|
||||||
<string name="dev_report_build_config">پیکربندی ساخت</string>
|
<string name="dev_report_build_config">پیکربندی ساخت</string>
|
||||||
|
<string name="dev_report_logcat">لاگ برنامه</string>
|
||||||
<string name="dev_report_device_features">ویژگیهای دستگاه</string>
|
<string name="dev_report_device_features">ویژگیهای دستگاه</string>
|
||||||
<string name="send_report">ارسال گزارش</string>
|
<string name="send_report">ارسال گزارش</string>
|
||||||
<string name="close">بستن</string>
|
<string name="close">بستن</string>
|
||||||
<string name="dev_report_sending">در حال فرستادن نظر...</string>
|
<string name="dev_report_sending">در حال فرستادن نظر...</string>
|
||||||
<string name="dev_report_sent">بازخورد ارسال شد</string>
|
<string name="dev_report_sent">بازخورد ارسال شد</string>
|
||||||
<string name="dev_report_saved">گزارش ذخیره شد. دفعه بعدی که وارد Briar (برایر) شدید فرستاده خواهد شد.</string>
|
<string name="dev_report_saved">گزارش ذخیره شد. دفعه بعدی که وارد Briar (برایر) شدید فرستاده خواهد شد.</string>
|
||||||
|
<string name="dev_report_error">خطا در ارسال گزارش</string>
|
||||||
<!--Sign Out-->
|
<!--Sign Out-->
|
||||||
<string name="progress_title_logout">خروج از Briar (برایر)...</string>
|
<string name="progress_title_logout">خروج از Briar (برایر)...</string>
|
||||||
<!--Screen Filters & Tapjacking-->
|
<!--Screen Filters & Tapjacking-->
|
||||||
@@ -591,7 +601,9 @@
|
|||||||
این برنامه ها ممکن است روی Briar (برایر) قرار گرفته باشند:
|
این برنامه ها ممکن است روی Briar (برایر) قرار گرفته باشند:
|
||||||
|
|
||||||
%1$s</string>
|
%1$s</string>
|
||||||
|
<string name="screen_filter_body_api_30">برنامه دیگری بر روی برنامه Briar (برایر) قرار دارد. برای محافظت از امنیت شما، Briar (برایر) هنگامی که برنامه دیگری روی آن باز است، به لمس پاسخ نخواهد داد. \n\nبرای یافتن برنامه مذکور، برنامههای زیر را بررسی کنید.</string>
|
||||||
<string name="screen_filter_allow">به این برنامه ها اجازه بده تا روی Briar (برایر) قرار بگیرند</string>
|
<string name="screen_filter_allow">به این برنامه ها اجازه بده تا روی Briar (برایر) قرار بگیرند</string>
|
||||||
|
<string name="screen_filter_review_apps">بررسی برنامهها</string>
|
||||||
<!--Permission Requests-->
|
<!--Permission Requests-->
|
||||||
<string name="permission_camera_title">دسترسی به دوربین</string>
|
<string name="permission_camera_title">دسترسی به دوربین</string>
|
||||||
<string name="permission_camera_request_body">برای اسکن کردن کد کیوآر دسترسی به دوربین لازم است.</string>
|
<string name="permission_camera_request_body">برای اسکن کردن کد کیوآر دسترسی به دوربین لازم است.</string>
|
||||||
@@ -608,6 +620,7 @@ Briar (برایر) موقعیت شما را ذخیره نمیکند و آن
|
|||||||
<string name="permission_camera_denied_body">شما دسترسی به دوربین را رد کرده اید، اما افزودن مخاطب نیاز به دوربین دارد.
|
<string name="permission_camera_denied_body">شما دسترسی به دوربین را رد کرده اید، اما افزودن مخاطب نیاز به دوربین دارد.
|
||||||
|
|
||||||
لطفا اجازه دسترسی را بدهید.</string>
|
لطفا اجازه دسترسی را بدهید.</string>
|
||||||
|
<string name="permission_location_denied_body">شما دسترسی به موقعیت خود را ندادهاید اما Briar (برایر) برای یافتن دستگاههای بلوتوث نیاز به این دسترسی دارد.\n\nلطفا این دسترسی را فراهم کنید.</string>
|
||||||
<string name="qr_code">کد کیوآر</string>
|
<string name="qr_code">کد کیوآر</string>
|
||||||
<string name="show_qr_code_fullscreen">نمایش کد کیوآر به صورت فول اسکرین</string>
|
<string name="show_qr_code_fullscreen">نمایش کد کیوآر به صورت فول اسکرین</string>
|
||||||
<!--App Locking-->
|
<!--App Locking-->
|
||||||
@@ -618,6 +631,7 @@ Briar (برایر) موقعیت شما را ذخیره نمیکند و آن
|
|||||||
<string name="lock_is_locked">Briar (برایر) قفل می باشد</string>
|
<string name="lock_is_locked">Briar (برایر) قفل می باشد</string>
|
||||||
<string name="lock_tap_to_unlock">برای آنلاک کردن کلیک کنید</string>
|
<string name="lock_tap_to_unlock">برای آنلاک کردن کلیک کنید</string>
|
||||||
<!--Connections Screen-->
|
<!--Connections Screen-->
|
||||||
|
<string name="transports_help_text">Briar (برایر) میتواند از طریق اینترنت، Wi-Fi و یا بلوتوث به مخاطبین شما متصل گردد.\n\nارتباط با اینترنت از طریق شبکهی تور صورت میپذیرد.\n\nاگر دسترسی به مخاطب شما از روشهای مختلفی ممکن باشد، Briar (برایر) به صورت موازی از آنها استفاده خواهد کرد.</string>
|
||||||
<!--Screenshots-->
|
<!--Screenshots-->
|
||||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||||
<string name="screenshot_alice">آلیس</string>
|
<string name="screenshot_alice">آلیس</string>
|
||||||
|
|||||||
@@ -425,6 +425,10 @@
|
|||||||
<string name="blogs_rss_feeds_manage_empty_state">Sen fontes RSS que mostrar\n\nToque na icona + para importar unha fonte</string>
|
<string name="blogs_rss_feeds_manage_empty_state">Sen fontes RSS que mostrar\n\nToque na icona + para importar unha fonte</string>
|
||||||
<string name="blogs_rss_feeds_manage_error">Aconteceu un problema ao cargar as súas fontes. Por favor, inténteo máis tarde.</string>
|
<string name="blogs_rss_feeds_manage_error">Aconteceu un problema ao cargar as súas fontes. Por favor, inténteo máis tarde.</string>
|
||||||
<!--Settings Profile Picture-->
|
<!--Settings Profile Picture-->
|
||||||
|
<string name="change_profile_picture">Toca para cambiar a túa imaxe de perfil</string>
|
||||||
|
<string name="dialog_confirm_profile_picture_title">Mudar imaxe de perfil</string>
|
||||||
|
<string name="dialog_confirm_profile_picture_remark">Só os teus contactos poden ver a túa imaxe de perfil</string>
|
||||||
|
<string name="change_profile_picture_failed_message">Lamentámolo, pero algo fallou cando intentamos actualizar a túa imaxe de pefil</string>
|
||||||
<!--Settings Display-->
|
<!--Settings Display-->
|
||||||
<string name="pref_language_title">Idioma & rexión</string>
|
<string name="pref_language_title">Idioma & rexión</string>
|
||||||
<string name="pref_language_changed">Este axuste terá efecto cando reinicie Briar. Por favor desconecte e volte a iniciar Briar.</string>
|
<string name="pref_language_changed">Este axuste terá efecto cando reinicie Briar. Por favor desconecte e volte a iniciar Briar.</string>
|
||||||
|
|||||||
@@ -449,6 +449,10 @@
|
|||||||
<string name="blogs_rss_feeds_manage_empty_state">אין הזנות RSS להראות\n\nהקש על הצלמית + כדי לייבא הזנה</string>
|
<string name="blogs_rss_feeds_manage_empty_state">אין הזנות RSS להראות\n\nהקש על הצלמית + כדי לייבא הזנה</string>
|
||||||
<string name="blogs_rss_feeds_manage_error">הייתה בעיה בטעינת ההזנות שלך. אנא נסה שוב מאוחר יותר.</string>
|
<string name="blogs_rss_feeds_manage_error">הייתה בעיה בטעינת ההזנות שלך. אנא נסה שוב מאוחר יותר.</string>
|
||||||
<!--Settings Profile Picture-->
|
<!--Settings Profile Picture-->
|
||||||
|
<string name="change_profile_picture">הקש כדי לשנות את תמונת הפרופיל שלך</string>
|
||||||
|
<string name="dialog_confirm_profile_picture_title">שנה תמונת פרופיל</string>
|
||||||
|
<string name="dialog_confirm_profile_picture_remark">רק אנשי הקשר שלך יכולים לראות את תמונת הפרופיל שלך</string>
|
||||||
|
<string name="change_profile_picture_failed_message">אנו מצטערים משהו השתבש בעת עדכון תמונת הפרופיל שלך</string>
|
||||||
<!--Settings Display-->
|
<!--Settings Display-->
|
||||||
<string name="pref_language_title">שפה ואזור</string>
|
<string name="pref_language_title">שפה ואזור</string>
|
||||||
<string name="pref_language_changed">הגדרה זו תיכנס לתוקף כשתפעיל מחדש את Briar. אנא התנתק והפעל מחדש את Briar.</string>
|
<string name="pref_language_changed">הגדרה זו תיכנס לתוקף כשתפעיל מחדש את Briar. אנא התנתק והפעל מחדש את Briar.</string>
|
||||||
@@ -558,6 +562,7 @@
|
|||||||
<string name="include_debug_report_feedback">כלול נתונים אלמוניים לגבי מכשיר זה</string>
|
<string name="include_debug_report_feedback">כלול נתונים אלמוניים לגבי מכשיר זה</string>
|
||||||
<string name="dev_report_basic_info">מידע בסיסי</string>
|
<string name="dev_report_basic_info">מידע בסיסי</string>
|
||||||
<string name="dev_report_device_info">מידע מכשיר</string>
|
<string name="dev_report_device_info">מידע מכשיר</string>
|
||||||
|
<string name="dev_report_stacktrace">מחסנית עקיבה (Stacktrace)</string>
|
||||||
<string name="dev_report_time_info">מידע זמן</string>
|
<string name="dev_report_time_info">מידע זמן</string>
|
||||||
<string name="dev_report_memory">זיכרון</string>
|
<string name="dev_report_memory">זיכרון</string>
|
||||||
<string name="dev_report_storage">אחסון</string>
|
<string name="dev_report_storage">אחסון</string>
|
||||||
|
|||||||
@@ -425,6 +425,10 @@
|
|||||||
<string name="blogs_rss_feeds_manage_empty_state">Engin RSS-streymi til að birta\n\nÝttu á + táknið til að flytja inn streymi</string>
|
<string name="blogs_rss_feeds_manage_empty_state">Engin RSS-streymi til að birta\n\nÝttu á + táknið til að flytja inn streymi</string>
|
||||||
<string name="blogs_rss_feeds_manage_error">Vandamál hefur komið upp með að hlaða inn streymunum þínum. Reyndu aftur síðar.</string>
|
<string name="blogs_rss_feeds_manage_error">Vandamál hefur komið upp með að hlaða inn streymunum þínum. Reyndu aftur síðar.</string>
|
||||||
<!--Settings Profile Picture-->
|
<!--Settings Profile Picture-->
|
||||||
|
<string name="change_profile_picture">Ýttu til að skipta um auðkennismyndina þína</string>
|
||||||
|
<string name="dialog_confirm_profile_picture_title">Skipta um auðkennismynd</string>
|
||||||
|
<string name="dialog_confirm_profile_picture_remark">Einungis tengiliðirnir þínir geta séð auðkennismyndina þína</string>
|
||||||
|
<string name="change_profile_picture_failed_message">Því miður, eitthvað fór úrskeiðis við að uppfæra auðkennismyndina þína.</string>
|
||||||
<!--Settings Display-->
|
<!--Settings Display-->
|
||||||
<string name="pref_language_title">Tungumál og landsvæði</string>
|
<string name="pref_language_title">Tungumál og landsvæði</string>
|
||||||
<string name="pref_language_changed">Þessi stilling tekur gildi í næst þegar þú skráir þig inn í Briar. Skráðu þig út og endurræstu Briar.</string>
|
<string name="pref_language_changed">Þessi stilling tekur gildi í næst þegar þú skráir þig inn í Briar. Skráðu þig út og endurræstu Briar.</string>
|
||||||
|
|||||||
@@ -425,6 +425,10 @@
|
|||||||
<string name="blogs_rss_feeds_manage_empty_state">Nessun feed RSS da mostrare\n\nClicca l\'icona + per importare un feed</string>
|
<string name="blogs_rss_feeds_manage_empty_state">Nessun feed RSS da mostrare\n\nClicca l\'icona + per importare un feed</string>
|
||||||
<string name="blogs_rss_feeds_manage_error">C\'è stato un problema nel caricare i tuoi feeds. Per favore riprova fra poco.</string>
|
<string name="blogs_rss_feeds_manage_error">C\'è stato un problema nel caricare i tuoi feeds. Per favore riprova fra poco.</string>
|
||||||
<!--Settings Profile Picture-->
|
<!--Settings Profile Picture-->
|
||||||
|
<string name="change_profile_picture">Tocca per cambiare l\'immagine del profilo</string>
|
||||||
|
<string name="dialog_confirm_profile_picture_title">Cambia immagine profilo</string>
|
||||||
|
<string name="dialog_confirm_profile_picture_remark">Solo i tuoi contatti possono vedere l\'immagine del profilo</string>
|
||||||
|
<string name="change_profile_picture_failed_message">Spiacenti, qualcosa è andato storto aggiornando la tua foto del profilo.</string>
|
||||||
<!--Settings Display-->
|
<!--Settings Display-->
|
||||||
<string name="pref_language_title">Lingua & regione</string>
|
<string name="pref_language_title">Lingua & regione</string>
|
||||||
<string name="pref_language_changed">Questa impostazione avrà effetto quando riavvierai Briar. Per favore, esci e riavvia Briar.</string>
|
<string name="pref_language_changed">Questa impostazione avrà effetto quando riavvierai Briar. Per favore, esci e riavvia Briar.</string>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user