mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
59 Commits
integratio
...
alpha-1.2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2a4b5e26a | ||
|
|
feac0ad802 | ||
|
|
60478eba3f | ||
|
|
3639952612 | ||
|
|
c4a654b267 | ||
|
|
ecb31a4d32 | ||
|
|
76f201bb2f | ||
|
|
87799b743c | ||
|
|
b898a7c370 | ||
|
|
f3210e3af2 | ||
|
|
225fd6fd49 | ||
|
|
400d259a60 | ||
|
|
4074ac8578 | ||
|
|
b2e6dd4138 | ||
|
|
b608b42174 | ||
|
|
f603254153 | ||
|
|
c851dd228b | ||
|
|
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 | ||
|
|
6b61725c6a |
@@ -1,30 +1,52 @@
|
|||||||
image: briar/ci-image-android:latest
|
image: briar/ci-image-android:latest
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- test
|
- test
|
||||||
- optional_tests
|
- optional_tests
|
||||||
- check_reproducibility
|
- check_reproducibility
|
||||||
|
|
||||||
test:
|
.base-test:
|
||||||
stage: test
|
|
||||||
before_script:
|
before_script:
|
||||||
- set -e
|
- set -e
|
||||||
- export GRADLE_USER_HOME=$PWD/.gradle
|
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
|
key: "$CI_COMMIT_REF_SLUG"
|
||||||
paths:
|
paths:
|
||||||
- .gradle/wrapper
|
- .gradle/wrapper
|
||||||
- .gradle/caches
|
- .gradle/caches
|
||||||
|
|
||||||
script:
|
|
||||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
|
|
||||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources
|
|
||||||
|
|
||||||
after_script:
|
after_script:
|
||||||
# these file change every time but should not be cached
|
# these file change every time and should not be cached
|
||||||
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
||||||
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
|
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
|
||||||
|
|
||||||
|
test:
|
||||||
|
extends: .base-test
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
|
||||||
|
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check
|
||||||
|
|
||||||
|
android test:
|
||||||
|
extends: .base-test
|
||||||
|
stage: optional_tests
|
||||||
|
image: briar/ci-image-android-emulator:latest
|
||||||
|
script:
|
||||||
|
# start emulator first, so it can fail early
|
||||||
|
- start-emulator.sh
|
||||||
|
# run normal and screenshot tests together (exclude Large tests)
|
||||||
|
- ./gradlew -Djava.security.egd=file:/dev/urandom connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest
|
||||||
|
artifacts:
|
||||||
|
name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}"
|
||||||
|
paths:
|
||||||
|
- kernel.log
|
||||||
|
- logcat.txt
|
||||||
|
expire_in: 3 days
|
||||||
|
when: on_failure
|
||||||
|
when: manual
|
||||||
|
except:
|
||||||
|
- tags
|
||||||
|
tags:
|
||||||
|
- kvm
|
||||||
|
|
||||||
test_reproducible:
|
test_reproducible:
|
||||||
stage: check_reproducibility
|
stage: check_reproducibility
|
||||||
@@ -40,6 +62,7 @@ test_reproducible:
|
|||||||
- export GRADLE_USER_HOME=$PWD/.gradle
|
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
|
key: "$CI_COMMIT_REF_SLUG"
|
||||||
paths:
|
paths:
|
||||||
- .gradle/wrapper
|
- .gradle/wrapper
|
||||||
- .gradle/caches
|
- .gradle/caches
|
||||||
|
|||||||
51
.idea/runConfigurations/Instrumentation_Tests__destroys_DB_.xml
generated
Normal file
51
.idea/runConfigurations/Instrumentation_Tests__destroys_DB_.xml
generated
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Instrumentation Tests (destroys DB)" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
|
||||||
|
<module name="briar.briar-android" />
|
||||||
|
<option name="TESTING_TYPE" value="1" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="CLASS_NAME" value="" />
|
||||||
|
<option name="PACKAGE_NAME" value="org.briarproject.briar.android" />
|
||||||
|
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
|
||||||
|
<option name="EXTRA_OPTIONS" value="-e notAnnotation androidx.test.filters.LargeTest" />
|
||||||
|
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" />
|
||||||
|
<option name="CLEAR_LOGCAT" value="false" />
|
||||||
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||||
|
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
|
||||||
|
<option name="FORCE_STOP_RUNNING_APP" value="true" />
|
||||||
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
|
<Auto>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
</Auto>
|
||||||
|
<Hybrid>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
</Hybrid>
|
||||||
|
<Java />
|
||||||
|
<Native>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
</Native>
|
||||||
|
<Profilers>
|
||||||
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Sample Java Methods" />
|
||||||
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
|
</Profilers>
|
||||||
|
<method v="2">
|
||||||
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -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 10216
|
versionCode 10219
|
||||||
versionName "1.2.16"
|
versionName "1.2.19"
|
||||||
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 10216
|
versionCode 10219
|
||||||
versionName "1.2.16"
|
versionName "1.2.19"
|
||||||
applicationId "org.briarproject.briar.android"
|
applicationId "org.briarproject.briar.android"
|
||||||
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|||||||
@@ -4,16 +4,20 @@ import android.app.Activity;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.account.AccountManager;
|
import org.briarproject.bramble.api.account.AccountManager;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.test.espresso.intent.rule.IntentsTestRule;
|
import androidx.test.espresso.intent.rule.IntentsTestRule;
|
||||||
|
|
||||||
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
||||||
|
import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN;
|
||||||
|
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
@@ -27,6 +31,8 @@ public abstract class UiTest {
|
|||||||
protected AccountManager accountManager;
|
protected AccountManager accountManager;
|
||||||
@Inject
|
@Inject
|
||||||
protected LifecycleManager lifecycleManager;
|
protected LifecycleManager lifecycleManager;
|
||||||
|
@Inject
|
||||||
|
protected SettingsManager settingsManager;
|
||||||
|
|
||||||
public UiTest() {
|
public UiTest() {
|
||||||
BriarTestComponentApplication app = getApplicationContext();
|
BriarTestComponentApplication app = getApplicationContext();
|
||||||
@@ -39,22 +45,8 @@ public abstract class UiTest {
|
|||||||
protected class CleanAccountTestRule<A extends Activity>
|
protected class CleanAccountTestRule<A extends Activity>
|
||||||
extends IntentsTestRule<A> {
|
extends IntentsTestRule<A> {
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final Runnable runnable;
|
|
||||||
|
|
||||||
public CleanAccountTestRule(Class<A> activityClass) {
|
public CleanAccountTestRule(Class<A> activityClass) {
|
||||||
super(activityClass);
|
super(activityClass);
|
||||||
this.runnable = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use this if you need to run code before launching the activity.
|
|
||||||
* Note: You need to use {@link #launchActivity(Intent)} yourself
|
|
||||||
* to start the activity.
|
|
||||||
*/
|
|
||||||
public CleanAccountTestRule(Class<A> activityClass, Runnable runnable) {
|
|
||||||
super(activityClass, false, false);
|
|
||||||
this.runnable = runnable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -62,16 +54,17 @@ public abstract class UiTest {
|
|||||||
super.beforeActivityLaunched();
|
super.beforeActivityLaunched();
|
||||||
accountManager.deleteAccount();
|
accountManager.deleteAccount();
|
||||||
accountManager.createAccount(USERNAME, PASSWORD);
|
accountManager.createAccount(USERNAME, PASSWORD);
|
||||||
if (runnable != null) {
|
Intent serviceIntent =
|
||||||
Intent serviceIntent =
|
new Intent(getApplicationContext(), BriarService.class);
|
||||||
new Intent(getApplicationContext(), BriarService.class);
|
getApplicationContext().startService(serviceIntent);
|
||||||
getApplicationContext().startService(serviceIntent);
|
try {
|
||||||
try {
|
lifecycleManager.waitForStartup();
|
||||||
lifecycleManager.waitForStartup();
|
// do not show doze white-listing dialog
|
||||||
} catch (InterruptedException e) {
|
Settings settings = new Settings();
|
||||||
throw new AssertionError(e);
|
settings.putBoolean(DOZE_ASK_AGAIN, false);
|
||||||
}
|
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
|
||||||
runnable.run();
|
} catch (InterruptedException | DbException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,15 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import androidx.test.filters.LargeTest;
|
||||||
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
|
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import static org.junit.Assume.assumeTrue;
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
@RunWith(Parameterized.class)
|
@RunWith(Parameterized.class)
|
||||||
public class PngSuiteImageCompressorTest
|
public class PngSuiteImageCompressorTest
|
||||||
extends AbstractImageCompressorTest {
|
extends AbstractImageCompressorTest {
|
||||||
|
|||||||
@@ -12,11 +12,14 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import androidx.test.filters.LargeTest;
|
||||||
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
|
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
|
||||||
import static org.junit.Assume.assumeTrue;
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
@RunWith(Parameterized.class)
|
@RunWith(Parameterized.class)
|
||||||
public class PngSuiteImageSizeCalculatorTest
|
public class PngSuiteImageSizeCalculatorTest
|
||||||
extends AbstractImageSizeCalculatorTest {
|
extends AbstractImageSizeCalculatorTest {
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
import androidx.test.rule.ActivityTestRule;
|
|
||||||
|
|
||||||
import static androidx.test.espresso.Espresso.onView;
|
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
|
||||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
|
||||||
import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
|
|
||||||
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class ConversationActivityNotSignedInTest {
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public ActivityTestRule<ConversationActivity> testRule =
|
|
||||||
new ActivityTestRule<>(ConversationActivity.class, false, false);
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void openWithoutSignedIn() {
|
|
||||||
Context targetContext = getInstrumentation().getTargetContext();
|
|
||||||
Intent intent = new Intent(targetContext, ConversationActivity.class);
|
|
||||||
intent.putExtra(CONTACT_ID, 1);
|
|
||||||
testRule.launchActivity(intent);
|
|
||||||
|
|
||||||
onView(withText(R.string.sign_in_button))
|
|
||||||
.perform(waitUntilMatches(isDisplayed()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -16,6 +16,7 @@ import androidx.test.espresso.contrib.DrawerActions;
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.rule.ActivityTestRule;
|
import androidx.test.rule.ActivityTestRule;
|
||||||
|
|
||||||
|
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
||||||
import static androidx.test.espresso.Espresso.onView;
|
import static androidx.test.espresso.Espresso.onView;
|
||||||
import static androidx.test.espresso.action.ViewActions.click;
|
import static androidx.test.espresso.action.ViewActions.click;
|
||||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||||
@@ -29,7 +30,9 @@ import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
|
|||||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||||
import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
|
import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.hasScreenLock;
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class SettingsActivityScreenshotTest extends ScreenshotTest {
|
public class SettingsActivityScreenshotTest extends ScreenshotTest {
|
||||||
@@ -76,6 +79,8 @@ public class SettingsActivityScreenshotTest extends ScreenshotTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void appLock() {
|
public void appLock() {
|
||||||
|
assumeTrue("device has no screen lock",
|
||||||
|
hasScreenLock(getApplicationContext()));
|
||||||
// scroll down
|
// scroll down
|
||||||
onView(withClassName(is(RecyclerView.class.getName())))
|
onView(withClassName(is(RecyclerView.class.getName())))
|
||||||
.perform(scrollTo(hasDescendant(
|
.perform(scrollTo(hasDescendant(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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,204 @@
|
|||||||
|
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
|
||||||
|
@Nullable
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null when not a single post was added with this update.
|
||||||
|
* true when a single post was added locally and false if remotely.
|
||||||
|
*/
|
||||||
|
@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) {
|
||||||
|
loadFromDb(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,133 @@
|
|||||||
|
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());
|
||||||
|
} else 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() {
|
||||||
|
loadFromDb(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;
|
||||||
@@ -101,8 +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);
|
||||||
// TODO remove when BriarRecyclerView was adapted
|
|
||||||
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() {
|
||||||
|
loadFromDb(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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ class ForumListViewModel extends DbViewModel implements EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadForums() {
|
public void loadForums() {
|
||||||
loadList(this::loadForums, forumItems::setValue);
|
loadFromDb(this::loadForums, forumItems::setValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
@@ -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() {
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ class ForumViewModel extends ThreadListViewModel<ForumPostItem> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadItems() {
|
public void loadItems() {
|
||||||
loadList(txn -> {
|
loadFromDb(txn -> {
|
||||||
long start = now();
|
long start = now();
|
||||||
List<ForumPostHeader> headers =
|
List<ForumPostHeader> headers =
|
||||||
forumManager.getPostHeaders(txn, groupId);
|
forumManager.getPostHeaders(txn, groupId);
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -159,7 +159,7 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadItems() {
|
public void loadItems() {
|
||||||
loadList(txn -> {
|
loadFromDb(txn -> {
|
||||||
// check first if group is dissolved
|
// check first if group is dissolved
|
||||||
isDissolved
|
isDissolved
|
||||||
.postValue(privateGroupManager.isDissolved(txn, groupId));
|
.postValue(privateGroupManager.isDissolved(txn, groupId));
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -142,7 +141,7 @@ class GroupListViewModel extends DbViewModel implements EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void loadGroups() {
|
void loadGroups() {
|
||||||
loadList(this::loadGroups, groupItems::setValue);
|
loadFromDb(this::loadGroups, groupItems::setValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -51,15 +51,15 @@ class ReportData {
|
|||||||
final boolean isOptional;
|
final boolean isOptional;
|
||||||
boolean isIncluded = true;
|
boolean isIncluded = true;
|
||||||
|
|
||||||
ReportItem(String name, int nameRes, ReportInfo info) {
|
ReportItem(String name, @StringRes int nameRes, ReportInfo info) {
|
||||||
this(name, nameRes, info, true);
|
this(name, nameRes, info, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReportItem(String name, int nameRes, String info) {
|
ReportItem(String name, @StringRes int nameRes, String info) {
|
||||||
this(name, nameRes, new SingleReportInfo(info), true);
|
this(name, nameRes, new SingleReportInfo(info), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReportItem(String name, int nameRes, ReportInfo info,
|
ReportItem(String name, @StringRes int nameRes, ReportInfo info,
|
||||||
boolean isOptional) {
|
boolean isOptional) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.nameRes = nameRes;
|
this.nameRes = nameRes;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.briarproject.briar.android.logging.BriefLogFormatter;
|
|||||||
import org.briarproject.briar.android.logging.CachingLogHandler;
|
import org.briarproject.briar.android.logging.CachingLogHandler;
|
||||||
import org.briarproject.briar.android.logging.LogDecrypter;
|
import org.briarproject.briar.android.logging.LogDecrypter;
|
||||||
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
|
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
|
||||||
|
import org.briarproject.briar.android.reporting.ReportData.ReportItem;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
@@ -156,7 +157,8 @@ class ReportViewModel extends AndroidViewModel {
|
|||||||
MultiReportInfo userInfo = new MultiReportInfo();
|
MultiReportInfo userInfo = new MultiReportInfo();
|
||||||
if (!isNullOrEmpty(comment)) userInfo.add("Comment", comment);
|
if (!isNullOrEmpty(comment)) userInfo.add("Comment", comment);
|
||||||
if (!isNullOrEmpty(email)) userInfo.add("Email", email);
|
if (!isNullOrEmpty(email)) userInfo.add("Email", email);
|
||||||
data.add(new ReportData.ReportItem("UserInfo", 0, userInfo, false));
|
data.add(new ReportItem("UserInfo", R.string.dev_report_user_info,
|
||||||
|
userInfo, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the state of the TorPlugin, if this is feedback
|
// check the state of the TorPlugin, if this is feedback
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -64,7 +70,7 @@ public abstract class DbViewModel extends AndroidViewModel {
|
|||||||
* <p>
|
* <p>
|
||||||
* If you need a list of items to be displayed in a
|
* If you need a list of items to be displayed in a
|
||||||
* {@link RecyclerView.Adapter},
|
* {@link RecyclerView.Adapter},
|
||||||
* use {@link #loadList(DbCallable, UiConsumer)} instead.
|
* use {@link #loadFromDb(DbCallable, UiConsumer)} instead.
|
||||||
*/
|
*/
|
||||||
protected void runOnDbThread(Runnable task) {
|
protected void runOnDbThread(Runnable task) {
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
@@ -84,7 +90,7 @@ public abstract class DbViewModel extends AndroidViewModel {
|
|||||||
* <p>
|
* <p>
|
||||||
* If you need a list of items to be displayed in a
|
* If you need a list of items to be displayed in a
|
||||||
* {@link RecyclerView.Adapter},
|
* {@link RecyclerView.Adapter},
|
||||||
* use {@link #loadList(DbCallable, UiConsumer)} instead.
|
* use {@link #loadFromDb(DbCallable, UiConsumer)} instead.
|
||||||
*/
|
*/
|
||||||
protected void runOnDbThread(boolean readOnly,
|
protected void runOnDbThread(boolean readOnly,
|
||||||
DbRunnable<Exception> task, Consumer<Exception> err) {
|
DbRunnable<Exception> task, Consumer<Exception> err) {
|
||||||
@@ -102,21 +108,20 @@ public abstract class DbViewModel extends AndroidViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a list of items on the {@link DatabaseExecutor} within a single
|
* Loads a data on the {@link DatabaseExecutor} within a single
|
||||||
* {@link Transaction} and publishes it as a {@link LiveResult}
|
* {@link Transaction} and publishes it as a {@link LiveResult}
|
||||||
* to the {@link UiThread}.
|
* to the {@link UiThread}.
|
||||||
* <p>
|
* <p>
|
||||||
* Use this to ensure that modifications to your local list do not get
|
* Use this to ensure that modifications to your local UI data do not get
|
||||||
* overridden by database loads that were in progress while the modification
|
* overridden by database loads that were in progress while the modification
|
||||||
* was made.
|
* was made.
|
||||||
* E.g. An event about the removal of a message causes the message item to
|
* E.g. An event about the removal of a message causes the message item to
|
||||||
* be removed from the local list while all messages are reloaded.
|
* be removed from the local data set while all messages are reloaded.
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
protected <T extends List<?>> void loadList(
|
protected <T> void loadFromDb(DbCallable<T, DbException> task,
|
||||||
DbCallable<T, DbException> task,
|
|
||||||
UiConsumer<LiveResult<T>> uiConsumer) {
|
UiConsumer<LiveResult<T>> uiConsumer) {
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -143,25 +148,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 +196,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 +221,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();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -171,7 +171,7 @@
|
|||||||
<string name="you">Du</string>
|
<string name="you">Du</string>
|
||||||
<string name="save_image">Bild speichern</string>
|
<string name="save_image">Bild speichern</string>
|
||||||
<string name="dialog_title_save_image">Bild speichern?</string>
|
<string name="dialog_title_save_image">Bild speichern?</string>
|
||||||
<string name="dialog_message_save_image">Gespeicherte Bilder können von vielen anderen Apps eingesehen werden.\n\nBist du sicher, dass du das Bild speichern möchtest?</string>
|
<string name="dialog_message_save_image">Durch das Speichern dieses Bildes können andere Apps auf das Bild zugreifen.\n\nBist du sicher, dass du das Bild speichern möchtest?</string>
|
||||||
<string name="save_image_success">Bild wurde gespeichert</string>
|
<string name="save_image_success">Bild wurde gespeichert</string>
|
||||||
<string name="save_image_error">Bild konnte nicht gespeichert werden</string>
|
<string name="save_image_error">Bild konnte nicht gespeichert werden</string>
|
||||||
<string name="dialog_title_no_image_support">Bilder nicht verfügbar</string>
|
<string name="dialog_title_no_image_support">Bilder nicht verfügbar</string>
|
||||||
@@ -427,7 +427,7 @@
|
|||||||
<!--Settings Profile Picture-->
|
<!--Settings Profile Picture-->
|
||||||
<string name="change_profile_picture">Tippe, um dein Profilbild zu ändern</string>
|
<string name="change_profile_picture">Tippe, um dein Profilbild zu ändern</string>
|
||||||
<string name="dialog_confirm_profile_picture_title">Profilbild ändern</string>
|
<string name="dialog_confirm_profile_picture_title">Profilbild ändern</string>
|
||||||
<string name="dialog_confirm_profile_picture_remark">Nur deine Kontakte können dein Profilbild sehen</string>
|
<string name="dialog_confirm_profile_picture_remark">Nur deine Kontakte können dieses Bild sehen</string>
|
||||||
<string name="change_profile_picture_failed_message">Es tut uns leid, aber beim Aktualisieren deines Profilbildes ist ein Fehler aufgetreten</string>
|
<string name="change_profile_picture_failed_message">Es tut uns leid, aber beim Aktualisieren deines Profilbildes ist ein Fehler aufgetreten</string>
|
||||||
<!--Settings Display-->
|
<!--Settings Display-->
|
||||||
<string name="pref_language_title">Sprache & Region</string>
|
<string name="pref_language_title">Sprache & Region</string>
|
||||||
|
|||||||
@@ -427,7 +427,7 @@
|
|||||||
<!--Settings Profile Picture-->
|
<!--Settings Profile Picture-->
|
||||||
<string name="change_profile_picture">Pulsar para cambiar la imagen de tu perfil</string>
|
<string name="change_profile_picture">Pulsar para cambiar la imagen de tu perfil</string>
|
||||||
<string name="dialog_confirm_profile_picture_title">Cambiar imagen de perfil</string>
|
<string name="dialog_confirm_profile_picture_title">Cambiar imagen de perfil</string>
|
||||||
<string name="dialog_confirm_profile_picture_remark">Solo tus contactos pueden ver la imagen de tu perfil</string>
|
<string name="dialog_confirm_profile_picture_remark">Solo tus contactos pueden ver esta imagen</string>
|
||||||
<string name="change_profile_picture_failed_message">Lo sentimos, pero algo falló mientras se estaba actualizando la imagen de tu perfil</string>
|
<string name="change_profile_picture_failed_message">Lo sentimos, pero algo falló mientras se estaba actualizando la imagen de tu perfil</string>
|
||||||
<!--Settings Display-->
|
<!--Settings Display-->
|
||||||
<string name="pref_language_title">Lenguaje & región</string>
|
<string name="pref_language_title">Lenguaje & región</string>
|
||||||
|
|||||||
@@ -467,7 +467,6 @@
|
|||||||
<!--Settings Profile Picture-->
|
<!--Settings Profile Picture-->
|
||||||
<string name="change_profile_picture">برای تغییر تصویر نمایه خود اینجا را لمس کنید.</string>
|
<string name="change_profile_picture">برای تغییر تصویر نمایه خود اینجا را لمس کنید.</string>
|
||||||
<string name="dialog_confirm_profile_picture_title">تغییر تصویر نمایه</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>
|
<string name="change_profile_picture_failed_message">تاسفیم اما هنگام بروزرسانی تصویر نمایه شما مشکلی رخ داد.</string>
|
||||||
<!--Settings Display-->
|
<!--Settings Display-->
|
||||||
<string name="pref_language_title">زبان و منطقه</string>
|
<string name="pref_language_title">زبان و منطقه</string>
|
||||||
|
|||||||
@@ -213,7 +213,7 @@
|
|||||||
<string name="your_link">Donnez ce lien au contact que vous souhaitez ajouter</string>
|
<string name="your_link">Donnez ce lien au contact que vous souhaitez ajouter</string>
|
||||||
<string name="link_clip_label">Lien Briar</string>
|
<string name="link_clip_label">Lien Briar</string>
|
||||||
<string name="link_copied_toast">Le lien a été copié</string>
|
<string name="link_copied_toast">Le lien a été copié</string>
|
||||||
<string name="adding_contact_error">Une erreur est survenue lors de l’ajout du contact</string>
|
<string name="adding_contact_error">Une erreur est survenue lors de l’ajout du contact.</string>
|
||||||
<string name="pending_contact_requests_snackbar">Des demandes de contact sont en attente</string>
|
<string name="pending_contact_requests_snackbar">Des demandes de contact sont en attente</string>
|
||||||
<string name="pending_contact_requests">Demandes de contact en attente</string>
|
<string name="pending_contact_requests">Demandes de contact en attente</string>
|
||||||
<string name="no_pending_contacts">Il n’y a aucun contact en attente</string>
|
<string name="no_pending_contacts">Il n’y a aucun contact en attente</string>
|
||||||
@@ -427,7 +427,7 @@
|
|||||||
<!--Settings Profile Picture-->
|
<!--Settings Profile Picture-->
|
||||||
<string name="change_profile_picture">Touchez pour changer votre photo de profil</string>
|
<string name="change_profile_picture">Touchez pour changer votre photo de profil</string>
|
||||||
<string name="dialog_confirm_profile_picture_title">Changer la photo de profil</string>
|
<string name="dialog_confirm_profile_picture_title">Changer la photo de profil</string>
|
||||||
<string name="dialog_confirm_profile_picture_remark">Seuls vos contacts peuvent voir votre photo de profil</string>
|
<string name="dialog_confirm_profile_picture_remark">Seuls vos contacts peuvent voir cette photo</string>
|
||||||
<string name="change_profile_picture_failed_message">Nous sommes désolés, mais un problème est survenu lors de la mise à jour de votre photo de profil</string>
|
<string name="change_profile_picture_failed_message">Nous sommes désolés, mais un problème est survenu lors de la mise à jour de votre photo de profil</string>
|
||||||
<!--Settings Display-->
|
<!--Settings Display-->
|
||||||
<string name="pref_language_title">Langue et région</string>
|
<string name="pref_language_title">Langue et région</string>
|
||||||
|
|||||||
@@ -2,50 +2,50 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<!--Setup-->
|
<!--Setup-->
|
||||||
<string name="setup_title">Benvida a Briar</string>
|
<string name="setup_title">Benvida a Briar</string>
|
||||||
<string name="setup_name_explanation">O seu alcume mostrarase xunto a todas as mensaxes que publique. Pode cambialo tras crear a súa conta.</string>
|
<string name="setup_name_explanation">O teu alcume mostrarase xunto a todas as mensaxes que publiques. Podes cambialo tras crear a túa conta.</string>
|
||||||
<string name="setup_next">Seguinte</string>
|
<string name="setup_next">Seguinte</string>
|
||||||
<string name="setup_password_intro">Escolla unha Clave</string>
|
<string name="setup_password_intro">Elixe un Contrasinal</string>
|
||||||
<string name="setup_password_explanation">A súa conta en Briar gárdase cifrada no seu dispositivo, non na nube. Si esquece o contrasinal ou desinstala Briar, non haberá xeito de recuperar a súa conta.\n\nEscolla unha clave longa que sexa difícil de adiviñar, algo como catro palabras ao chou, ou dez letras aleatorias, números e símbolos.</string>
|
<string name="setup_password_explanation">A túa conta en Briar gárdase cifrada no teu dispositivo, non na nube. Se esqueces o contrasinal ou desinstalas Briar, non haberá xeito de recuperar a túa conta.\n\nEscolle un contrasinal longo que sexa difícil de adiviñar, algo como catro palabras ao chou, ou dez letras aleatorias, números e símbolos.</string>
|
||||||
<string name="setup_doze_title">Conexións en segundo plano</string>
|
<string name="setup_doze_title">Conexións en segundo plano</string>
|
||||||
<string name="setup_doze_intro">Para recibir mensaxes, Briar precisa estar conectada en segundo plano.</string>
|
<string name="setup_doze_intro">Para recibir mensaxes, Briar precisa estar conectada en segundo plano.</string>
|
||||||
<string name="setup_doze_explanation">Para recibir mensaxes, Briar precisa estar conectada en segundo plano. Por favor desactive as optimizacións de batería para que Briar poida permanecer conectada.</string>
|
<string name="setup_doze_explanation">Para recibir mensaxes, Briar precisa estar conectada en segundo plano. Por favor desactiva as optimizacións de batería para que Briar poida permanecer conectada.</string>
|
||||||
<string name="setup_doze_button">Permitir conexións</string>
|
<string name="setup_doze_button">Permitir conexións</string>
|
||||||
<string name="choose_nickname">Escolle o teu alcume</string>
|
<string name="choose_nickname">Escolle o teu alcume</string>
|
||||||
<string name="choose_password">Escolle a túa clave</string>
|
<string name="choose_password">Elixe o teu contrasinal</string>
|
||||||
<string name="confirm_password">Confirma a túa clave</string>
|
<string name="confirm_password">Confirma o teu contrasinal</string>
|
||||||
<string name="name_too_long">O nome é demasiado longo</string>
|
<string name="name_too_long">O nome é demasiado longo</string>
|
||||||
<string name="password_too_weak">A clave é demasiado débil</string>
|
<string name="password_too_weak">O contrasinal é demasiado débil</string>
|
||||||
<string name="passwords_do_not_match">As claves non coinciden</string>
|
<string name="passwords_do_not_match">Os contrasinais non concordan</string>
|
||||||
<string name="create_account_button">Crea a conta</string>
|
<string name="create_account_button">Crea unha conta</string>
|
||||||
<string name="more_info">Máis información</string>
|
<string name="more_info">Máis información</string>
|
||||||
<string name="don_t_ask_again">Non preguntar de novo</string>
|
<string name="don_t_ask_again">Non preguntar de novo</string>
|
||||||
<string name="setup_huawei_text">Por favor toque o botón inferior e asegúrese de que Briar está protexida na pantalla \"Apps Protexidas\"</string>
|
<string name="setup_huawei_text">Por favor toca o botón inferior e asegúrate de que Briar está protexida na pantalla \"Apps Protexidas\"</string>
|
||||||
<string name="setup_huawei_button">Protexer Briar</string>
|
<string name="setup_huawei_button">Protexer Briar</string>
|
||||||
<string name="setup_huawei_help">Si Briar non se engade ao listado de apps protexidas, non poderá funcionar en segundo plano.</string>
|
<string name="setup_huawei_help">Se Briar non se engade ao listado de apps protexidas, non poderá funcionar en segundo plano.</string>
|
||||||
<string name="warning_dozed">%s non foi quen de funcionar en segundo plano</string>
|
<string name="warning_dozed">%s non foi quen de funcionar en segundo plano</string>
|
||||||
<!--Login-->
|
<!--Login-->
|
||||||
<string name="enter_password">Clave</string>
|
<string name="enter_password">Contrasinal</string>
|
||||||
<string name="try_again">Clave incorrecta, tenteo de novo</string>
|
<string name="try_again">Contrasinal incorrecto, tenteo de novo</string>
|
||||||
<string name="dialog_title_cannot_check_password">Non se comprobou o contrasinal</string>
|
<string name="dialog_title_cannot_check_password">Non se comprobou o contrasinal</string>
|
||||||
<string name="dialog_message_cannot_check_password">Briar non pode comprobar o contrasinal. Intenta reiniciar o dispositivo para solucionar o problema.</string>
|
<string name="dialog_message_cannot_check_password">Briar non pode comprobar o contrasinal. Intenta reiniciar o dispositivo para solucionar o problema.</string>
|
||||||
<string name="sign_in_button">Iniciar sesión</string>
|
<string name="sign_in_button">Iniciar sesión</string>
|
||||||
<string name="forgotten_password">Esquecín a miña clave</string>
|
<string name="forgotten_password">Esquecín a miña clave</string>
|
||||||
<string name="dialog_title_lost_password">Clave perdida</string>
|
<string name="dialog_title_lost_password">Contrasinal perdido</string>
|
||||||
<string name="dialog_message_lost_password">Briar almacena a súa configuración encriptada no dispositivo, non na nube, así que non podemos restabelecer a súa clave. Quererías borrar a túa conta e empezar de novo?\n\nPrecaución: As túas identidades, contactos e mensaxes serán eliminadas de forma permanente.</string>
|
<string name="dialog_message_lost_password">Briar almacena a túa configuración cifrada no dispositivo, non na nube, así que non podemos restabelecer o teu contrasinal. Quererías borrar a túa conta e empezar de novo?\n\nPrecaución: As túas identidades, contactos e mensaxes serán eliminadas de forma permanente.</string>
|
||||||
<string name="startup_failed_notification_title">Briar non puido iniciarse</string>
|
<string name="startup_failed_notification_title">Briar non puido iniciarse</string>
|
||||||
<string name="startup_failed_notification_text">Toque para máis información.</string>
|
<string name="startup_failed_notification_text">Toca para máis información.</string>
|
||||||
<string name="startup_failed_activity_title">Fallo de Inicio de Briar</string>
|
<string name="startup_failed_activity_title">Fallo de Inicio de Briar</string>
|
||||||
<string name="startup_failed_db_error">Por algún motivo, a súa base de datos de Briar está defectuosa sen remedio. A súa conta, os seus datos e contactos perdéronse. Desgraciadamente, debe reinstalar Briar ou crear unha nova conta escollendo \'Esquecín o meu contrasinal\' cando se lle solicite a clave.</string>
|
<string name="startup_failed_db_error">Por algún motivo, a túa base de datos de Briar está defectuosa sen remedio. A túa conta, os teus datos e contactos perdéronse. Desgraciadamente, debes reinstalar Briar ou crear unha nova conta escollendo \'Esquecín o meu contrasinal\' cando se che solicite o contrasinal.</string>
|
||||||
<string name="startup_failed_data_too_old_error">A súa conta foi creada cunha versión anterior da aplicación e non se pode abrir con esta versión. Deberá reinstalar a versión anterior ou ben crear unha nova conta escollendo \'Esquecín o meu contrasinal\' cando se lle solicite o contrasinal.</string>
|
<string name="startup_failed_data_too_old_error">A túa conta foi creada cunha versión anterior da aplicación e non se pode abrir con esta versión. Deberás reinstalar a versión anterior ou ben crear unha nova conta escollendo \'Esquecín o meu contrasinal\' cando se che solicite o contrasinal.</string>
|
||||||
<string name="startup_failed_data_too_new_error">Esta versión da app é moi antiga. Actualice por favor á última versión e inténteo de novo.</string>
|
<string name="startup_failed_data_too_new_error">Esta versión da app é moi antiga. Actualiza por favor á última versión e inténtao de novo.</string>
|
||||||
<string name="startup_failed_service_error"> Briar non puido iniciar un complemento necesario. Xeralmente reinstalar Briar resolve este problema. Teña en conta que entón perderá a súa conta e todos os datos asociados a esta pois Briar non está a utilizar servidores centrais para almacenar os seus datos.</string>
|
<string name="startup_failed_service_error">Briar non puido iniciar un complemento necesario. Normalmente ao reinstalar Briar solucionase este problema. Ten en conta que entón perderás a túa conta e todos os datos asociados a esta pois Briar non está a utilizar servidores centrais para almacenar os teus datos.</string>
|
||||||
<plurals name="expiry_warning">
|
<plurals name="expiry_warning">
|
||||||
<item quantity="one">Esta versión de Briar é para probas. A conta caducará en %d día e non se pode anovar.</item>
|
<item quantity="one">Esta versión de Briar é para probas. A conta caducará en %d día e non se pode anovar.</item>
|
||||||
<item quantity="other">Esta é unha versión de proba de Briar. A conta caducará en %d días e non se pode anovar.</item>
|
<item quantity="other">Esta é unha versión de proba de Briar. A conta caducará en %d días e non se pode anovar.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="expiry_date_reached">Este software caducou.\nGrazas por probalo!</string>
|
<string name="expiry_date_reached">Este software caducou.\nGrazas por probalo!</string>
|
||||||
<string name="download_briar">Para seguir utilizando Briar, descarga por favor a última versión.</string>
|
<string name="download_briar">Para seguir utilizando Briar, descarga por favor a última versión.</string>
|
||||||
<string name="create_new_account">Precisa crear unha nova conta, pero pode utilizar o mesmo alcume.</string>
|
<string name="create_new_account">Precisas crear unha nova conta, pero podes utilizar o mesmo alcume.</string>
|
||||||
<string name="download_briar_button">Descargar Última Versión</string>
|
<string name="download_briar_button">Descargar Última Versión</string>
|
||||||
<string name="startup_open_database">Descifrando a Base de datos...</string>
|
<string name="startup_open_database">Descifrando a Base de datos...</string>
|
||||||
<string name="startup_migrate_database">Actualizando a Base de datos...</string>
|
<string name="startup_migrate_database">Actualizando a Base de datos...</string>
|
||||||
@@ -92,12 +92,12 @@
|
|||||||
<string name="bt_plugin_status_inactive">Briar non pode conectar por Bluetooth</string>
|
<string name="bt_plugin_status_inactive">Briar non pode conectar por Bluetooth</string>
|
||||||
<string name="bt_plugin_status_disabled">Briar está configurada para non usar Bluetooth</string>
|
<string name="bt_plugin_status_disabled">Briar está configurada para non usar Bluetooth</string>
|
||||||
<!--Notifications-->
|
<!--Notifications-->
|
||||||
<string name="reminder_notification_title">Desconectou de Briar</string>
|
<string name="reminder_notification_title">Desconectaches de Briar</string>
|
||||||
<string name="reminder_notification_text">Toque para voltar a conectar</string>
|
<string name="reminder_notification_text">Toca para volver a conectar</string>
|
||||||
<string name="reminder_notification_channel_title">Recordatorio para conectar a Briar</string>
|
<string name="reminder_notification_channel_title">Recordatorio para conectar a Briar</string>
|
||||||
<string name="reminder_notification_dismiss">Desbotar</string>
|
<string name="reminder_notification_dismiss">Desbotar</string>
|
||||||
<string name="ongoing_notification_title">Conectado a Briar</string>
|
<string name="ongoing_notification_title">Conectada a Briar</string>
|
||||||
<string name="ongoing_notification_text">Toque para abrir Briar</string>
|
<string name="ongoing_notification_text">Toca para abrir Briar</string>
|
||||||
<plurals name="private_message_notification_text">
|
<plurals name="private_message_notification_text">
|
||||||
<item quantity="one">Nova mensaxe privada.</item>
|
<item quantity="one">Nova mensaxe privada.</item>
|
||||||
<item quantity="other">%d novas mensaxes privadas.</item>
|
<item quantity="other">%d novas mensaxes privadas.</item>
|
||||||
@@ -136,15 +136,15 @@
|
|||||||
<string name="show_onboarding">Amosar xanela de axuda</string>
|
<string name="show_onboarding">Amosar xanela de axuda</string>
|
||||||
<string name="fix">Arranxar</string>
|
<string name="fix">Arranxar</string>
|
||||||
<string name="help">Axuda</string>
|
<string name="help">Axuda</string>
|
||||||
<string name="sorry">Desculpe</string>
|
<string name="sorry">Desculpa</string>
|
||||||
<string name="error_start_activity">Non dispoñible para o teu sistema</string>
|
<string name="error_start_activity">Non dispoñible para o teu sistema</string>
|
||||||
<string name="status_heading">Estado</string>
|
<string name="status_heading">Estado:</string>
|
||||||
<!--Contacts and Private Conversations-->
|
<!--Contacts and Private Conversations-->
|
||||||
<string name="no_contacts">Sen contactos para amosar</string>
|
<string name="no_contacts">Sen contactos que amosar</string>
|
||||||
<string name="no_contacts_action">Toque a icona + para engadir un contacto</string>
|
<string name="no_contacts_action">Toca na icona + para engadir un contacto</string>
|
||||||
<string name="date_no_private_messages">Sen mensaxes</string>
|
<string name="date_no_private_messages">Sen mensaxes</string>
|
||||||
<string name="no_private_messages">Sen mensaxes que amosar</string>
|
<string name="no_private_messages">Sen mensaxes que amosar</string>
|
||||||
<string name="message_hint">Esciba unha mensaxe</string>
|
<string name="message_hint">Escibe unha mensaxe</string>
|
||||||
<string name="image_caption_hint">Engadir un comentario (optativo)</string>
|
<string name="image_caption_hint">Engadir un comentario (optativo)</string>
|
||||||
<string name="image_attach">Anexar imaxe</string>
|
<string name="image_attach">Anexar imaxe</string>
|
||||||
<string name="image_attach_error">Non se anexaron imaxe(s)</string>
|
<string name="image_attach_error">Non se anexaron imaxe(s)</string>
|
||||||
@@ -152,11 +152,11 @@
|
|||||||
<string name="image_attach_error_invalid_mime_type">Formato non admitido: %s</string>
|
<string name="image_attach_error_invalid_mime_type">Formato non admitido: %s</string>
|
||||||
<string name="set_contact_alias">Cambiar o nome do contacto</string>
|
<string name="set_contact_alias">Cambiar o nome do contacto</string>
|
||||||
<string name="set_contact_alias_hint">Nome do contacto</string>
|
<string name="set_contact_alias_hint">Nome do contacto</string>
|
||||||
<string name="delete_all_messages">Borrar todas as mensaxes</string>
|
<string name="delete_all_messages">Borrar tódalas mensaxes</string>
|
||||||
<string name="dialog_title_delete_all_messages">Confirmar borrado de mensaxes</string>
|
<string name="dialog_title_delete_all_messages">Confirmar borrado de mensaxes</string>
|
||||||
<string name="dialog_message_delete_all_messages">Seguro que queres borrar todas as mensaxes?</string>
|
<string name="dialog_message_delete_all_messages">Seguro que queres borrar tódalas mensaxes?</string>
|
||||||
<string name="dialog_title_not_all_messages_deleted">Non se puideron borrar todas as mensaxes</string>
|
<string name="dialog_title_not_all_messages_deleted">Non se puideron borrar todalas mensaxes</string>
|
||||||
<string name="dialog_message_not_deleted_ongoing_both">As mensaxes relacionadas con convites actuais e presentacións non se poden borrar ata que conclúan.</string>
|
<string name="dialog_message_not_deleted_ongoing_both">As mensaxes relacionadas con convites e presentacións actuais non se poden borrar ata que conclúan.</string>
|
||||||
<string name="dialog_message_not_deleted_ongoing_introductions">As mensaxes relacionadas con presentacións en curso non se poden borrar ata que conclúan.</string>
|
<string name="dialog_message_not_deleted_ongoing_introductions">As mensaxes relacionadas con presentacións en curso non se poden borrar ata que conclúan.</string>
|
||||||
<string name="dialog_message_not_deleted_ongoing_invitations">As mensaxes relacionadas con convites en curso non se poden borrar ata que conclúan.</string>
|
<string name="dialog_message_not_deleted_ongoing_invitations">As mensaxes relacionadas con convites en curso non se poden borrar ata que conclúan.</string>
|
||||||
<string name="dialog_message_not_deleted_partly_downloaded">As mensaxes parcialmente descargadas non se poden borrar ata que rematen de descargarse.</string>
|
<string name="dialog_message_not_deleted_partly_downloaded">As mensaxes parcialmente descargadas non se poden borrar ata que rematen de descargarse.</string>
|
||||||
@@ -164,71 +164,71 @@
|
|||||||
<string name="dialog_message_not_deleted_not_all_selected_introductions">Para borrar unha presentación, debes seleccionar a solicitude e a resposta.</string>
|
<string name="dialog_message_not_deleted_not_all_selected_introductions">Para borrar unha presentación, debes seleccionar a solicitude e a resposta.</string>
|
||||||
<string name="dialog_message_not_deleted_not_all_selected_invitations">Para borrar un convite, debes seleccionar a solicitude e a resposta.</string>
|
<string name="dialog_message_not_deleted_not_all_selected_invitations">Para borrar un convite, debes seleccionar a solicitude e a resposta.</string>
|
||||||
<string name="delete_contact">Eliminar contacto</string>
|
<string name="delete_contact">Eliminar contacto</string>
|
||||||
<string name="dialog_title_delete_contact">Confirme a eliminación do contacto</string>
|
<string name="dialog_title_delete_contact">Confirma a eliminación do contacto</string>
|
||||||
<string name="dialog_message_delete_contact">Segura de querer eliminar este contacto e todas as mensaxes que intercambiaron?</string>
|
<string name="dialog_message_delete_contact">Segura de querer eliminar este contacto e todas as mensaxes que intercambiaron?</string>
|
||||||
<string name="contact_deleted_toast">Contacto eliminado</string>
|
<string name="contact_deleted_toast">Contacto eliminado</string>
|
||||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||||
<string name="you">Vostede</string>
|
<string name="you">Ti</string>
|
||||||
<string name="save_image">Gardar imaxe</string>
|
<string name="save_image">Gardar imaxe</string>
|
||||||
<string name="dialog_title_save_image">Gardar imaxe?</string>
|
<string name="dialog_title_save_image">Gardar imaxe?</string>
|
||||||
<string name="dialog_message_save_image">Ao gardar esta imaxe permitirá que outras apps teñan acceso a ela.\n\nSeguro que a quere gardar?</string>
|
<string name="dialog_message_save_image">Ao gardar esta imaxe permitirás que outras apps teñan acceso a ela.\n\nSeguro que a queres gardar?</string>
|
||||||
<string name="save_image_success">Gardouse a imaxe</string>
|
<string name="save_image_success">Gardouse a imaxe</string>
|
||||||
<string name="save_image_error">Non se gardou a imaxe</string>
|
<string name="save_image_error">Non se gardou a imaxe</string>
|
||||||
<string name="dialog_title_no_image_support">Imaxes non dispoñibles</string>
|
<string name="dialog_title_no_image_support">Imaxes non dispoñibles</string>
|
||||||
<string name="dialog_message_no_image_support">O cliente do seu contacto Briar non admite anexos de imaxes. Unha vez actualice verá unha icona diferente.</string>
|
<string name="dialog_message_no_image_support">O cliente do teu contacto Briar non admite anexos de imaxes. Unha vez actualice verá unha icona diferente.</string>
|
||||||
<string name="dialog_title_image_support">Xa pode enviar imaxes a este contacto</string>
|
<string name="dialog_title_image_support">Xa podes enviar imaxes a este contacto</string>
|
||||||
<string name="dialog_message_image_support">Toque en esta icona para anexar imaxes.</string>
|
<string name="dialog_message_image_support">Toque nesta icona para anexar imaxes.</string>
|
||||||
<string name="messaging_too_many_attachments_toast">Só se enviarán as primeiras %d imaxes</string>
|
<string name="messaging_too_many_attachments_toast">Só se enviarán as primeiras %d imaxes</string>
|
||||||
<!--Adding Contacts-->
|
<!--Adding Contacts-->
|
||||||
<string name="add_contact_title">Engadir contacto próximo</string>
|
<string name="add_contact_title">Engadir contacto próximo</string>
|
||||||
<string name="face_to_face">Debe atoparse coa persoa que quere engadir como contacto.\n\Isto evitará que calquera poida suplantala ou ler as súas mensaxes no futuro.</string>
|
<string name="face_to_face">Debes verte coa persoa que queres engadir como contacto.\n\Isto evitará que calquera poida suplantarte ou ler as túas mensaxes no futuro.</string>
|
||||||
<string name="continue_button">Continuar</string>
|
<string name="continue_button">Continuar</string>
|
||||||
<string name="try_again_button">Tenteo de novo</string>
|
<string name="try_again_button">Inténtao de novo</string>
|
||||||
<string name="waiting_for_contact_to_scan">Agardando polo contacto para escanear e conectar\u2026</string>
|
<string name="waiting_for_contact_to_scan">Agardando polo contacto para escanear e conectar\u2026</string>
|
||||||
<string name="exchanging_contact_details">Intercambiando detalles do contacto\u2026</string>
|
<string name="exchanging_contact_details">Intercambiando detalles do contacto\u2026</string>
|
||||||
<string name="contact_added_toast">Contacto engadido: %s</string>
|
<string name="contact_added_toast">Contacto engadido: %s</string>
|
||||||
<string name="contact_already_exists">O contacto %s xa existe</string>
|
<string name="contact_already_exists">O contacto %s xa existe</string>
|
||||||
<string name="qr_code_invalid">O código QR non é válido</string>
|
<string name="qr_code_invalid">O código QR non é válido</string>
|
||||||
<string name="qr_code_too_old">O código QR que escaneou procede de unha versión anterior de %s.\n\nPor favor, solicite ao seu contacto actualizar a nova versión e inténteo de novo.</string>
|
<string name="qr_code_too_old">O código QR que escaneaches procede dunha versión anterior de %s.\n\nPor favor, solicite ao teu contacto actualizar a nova versión e inténtao de novo.</string>
|
||||||
<string name="qr_code_too_new">O código QR que escaneou procede de unha nova versión de %s.\n\nPor favor, actualice a última versión e inténteo de novo.</string>
|
<string name="qr_code_too_new">O código QR que escaneaches procede dunha nova versión de %s.\n\nPor favor, actualiza a última versión e inténteo de novo.</string>
|
||||||
<string name="camera_error">Fallo na cámara</string>
|
<string name="camera_error">Fallo na cámara</string>
|
||||||
<string name="connecting_to_device">Conectando co dispositivo\u2026</string>
|
<string name="connecting_to_device">Conectando co dispositivo\u2026</string>
|
||||||
<string name="authenticating_with_device">Autenticándose co dispositivo\u2026</string>
|
<string name="authenticating_with_device">Autenticándose co dispositivo\u2026</string>
|
||||||
<string name="connection_error_title">Non se puido conectar co contacto</string>
|
<string name="connection_error_title">Non se puido conectar co contacto</string>
|
||||||
<string name="connection_error_feedback">Si persiste o problema,<a href="feedback">envíe un informe</a> para axudarnos a mellorar a app.</string>
|
<string name="connection_error_feedback">Se persiste o problema,<a href="feedback">envía un informe</a> para axudarnos a mellorar a app.</string>
|
||||||
<!--Adding Contacts Remotely-->
|
<!--Adding Contacts Remotely-->
|
||||||
<string name="add_contact_remotely_title_case">Engadir contacto a distancia</string>
|
<string name="add_contact_remotely_title_case">Engadir contacto a distancia</string>
|
||||||
<string name="add_contact_nearby_title">Engadir contacto próximo</string>
|
<string name="add_contact_nearby_title">Engadir contacto próximo</string>
|
||||||
<string name="add_contact_remotely_title">Engadir contacto a distancia</string>
|
<string name="add_contact_remotely_title">Engadir contacto a distancia</string>
|
||||||
<string name="contact_link_intro">Introducir aquí a ligazón do seu contacto</string>
|
<string name="contact_link_intro">Introducir aquí a ligazón do teu contacto</string>
|
||||||
<string name="contact_link_hint">Ligazón do contacto</string>
|
<string name="contact_link_hint">Ligazón do contacto</string>
|
||||||
<string name="paste_button">Pegar</string>
|
<string name="paste_button">Pegar</string>
|
||||||
<string name="add_contact_button">Engadir contacto</string>
|
<string name="add_contact_button">Engadir contacto</string>
|
||||||
<string name="copy_button">Copiar</string>
|
<string name="copy_button">Copiar</string>
|
||||||
<string name="share_button">Compartir</string>
|
<string name="share_button">Compartir</string>
|
||||||
<string name="send_link_title">Intercambiar ligazóns</string>
|
<string name="send_link_title">Intercambiar ligazóns</string>
|
||||||
<string name="add_contact_choose_nickname">Escoller alcume</string>
|
<string name="add_contact_choose_nickname">Elexir alcume</string>
|
||||||
<string name="add_contact_choose_a_nickname">Introducir alcume</string>
|
<string name="add_contact_choose_a_nickname">Escribe un alcume</string>
|
||||||
<string name="nickname_intro">Déalle un alcume ao contacto. Só vostede pode velo.</string>
|
<string name="nickname_intro">Dálle un alcume ao contacto. Só ti pode velo.</string>
|
||||||
<string name="your_link">Envíe esta ligazón ao contacto que quere engadir</string>
|
<string name="your_link">Envía esta ligazón ao contacto que queres engadir</string>
|
||||||
<string name="link_clip_label">Ligazón Briar</string>
|
<string name="link_clip_label">Ligazón Briar</string>
|
||||||
<string name="link_copied_toast">Ligazón copiada</string>
|
<string name="link_copied_toast">Ligazón copiada</string>
|
||||||
<string name="adding_contact_error">Algo fallou ao engadir o contacto.</string>
|
<string name="adding_contact_error">Algo fallou ao engadir o contacto.</string>
|
||||||
<string name="pending_contact_requests_snackbar">Existen solicitudes de contactos pendentes</string>
|
<string name="pending_contact_requests_snackbar">Existen solicitudes de contactos pendentes</string>
|
||||||
<string name="pending_contact_requests">Solicitudes Pendentes de Contactos</string>
|
<string name="pending_contact_requests">Solicitudes de Contacto pendentes</string>
|
||||||
<string name="no_pending_contacts">Sen contactos pendentes</string>
|
<string name="no_pending_contacts">Sen contactos pendentes</string>
|
||||||
<string name="waiting_for_contact_to_come_online">Agardando a que o contacto se conecte...</string>
|
<string name="waiting_for_contact_to_come_online">Agardando a que o contacto se conecte...</string>
|
||||||
<string name="connecting">Conectando...</string>
|
<string name="connecting">Conectando...</string>
|
||||||
<string name="adding_contact">Engadindo contacto...</string>
|
<string name="adding_contact">Engadindo contacto...</string>
|
||||||
<string name="adding_contact_failed">Fallou engadir contacto</string>
|
<string name="adding_contact_failed">Fallou engadir contacto</string>
|
||||||
<string name="dialog_title_remove_pending_contact">Confirme a eliminación</string>
|
<string name="dialog_title_remove_pending_contact">Confirma a eliminación</string>
|
||||||
<string name="dialog_message_remove_pending_contact">Este contacto aínda está a ser engadido. Se o elimina agora, non será engadido.</string>
|
<string name="dialog_message_remove_pending_contact">Este contacto aínda está a ser engadido. Se o eliminas agora, non será engadido.</string>
|
||||||
<string name="own_link_error">Introduza a ligazón do contacto, non o propio</string>
|
<string name="own_link_error">Introduza a ligazón do contacto, non o propio</string>
|
||||||
<string name="nickname_missing">Por favor introduza un alcume</string>
|
<string name="nickname_missing">Por favor escribe un alcume</string>
|
||||||
<string name="invalid_link">Ligazón non válida</string>
|
<string name="invalid_link">Ligazón non válida</string>
|
||||||
<string name="unsupported_link">Esta ligazón chega desde unha versión nova de Briar. Por favor, actualice a nova versión e inténteo de novo.</string>
|
<string name="unsupported_link">Esta ligazón chega desde unha versión nova de Briar. Por favor, actualiza a nova versión e inténtao de novo.</string>
|
||||||
<string name="intent_own_link">Abreu a súa propia ligazón. Utilice a do contacto que quere engadir!</string>
|
<string name="intent_own_link">Abreu a súa propia ligazón. Utilice a do contacto que quere engadir!</string>
|
||||||
<string name="missing_link">Por favor introduza ligazón</string>
|
<string name="missing_link">Por favor escribe unha ligazón</string>
|
||||||
<!--This is a numeral indicating the first step in a series of screens-->
|
<!--This is a numeral indicating the first step in a series of screens-->
|
||||||
<string name="step_1">1</string>
|
<string name="step_1">1</string>
|
||||||
<!--This is a numeral indicating the second step in a series of screens-->
|
<!--This is a numeral indicating the second step in a series of screens-->
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="offline_state">Sen conexión a internet</string>
|
<string name="offline_state">Sen conexión a internet</string>
|
||||||
<string name="duplicate_link_dialog_title">Duplicar Ligazón</string>
|
<string name="duplicate_link_dialog_title">Duplicar Ligazón</string>
|
||||||
<string name="duplicate_link_dialog_text_1">Xa ten un contacto pendente con esta ligazón: %s</string>
|
<string name="duplicate_link_dialog_text_1">Xa tes un contacto pendente con esta ligazón: %s</string>
|
||||||
<string name="duplicate_link_dialog_text_1_contact">Xa tes un contacto con esta ligazón: %s</string>
|
<string name="duplicate_link_dialog_text_1_contact">Xa tes un contacto con esta ligazón: %s</string>
|
||||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||||
<string name="duplicate_link_dialog_text_2">Son %s e %s a mesma persoa?</string>
|
<string name="duplicate_link_dialog_text_2">Son %s e %s a mesma persoa?</string>
|
||||||
@@ -251,65 +251,65 @@
|
|||||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||||
<string name="different_person_button">Diferente Persoa</string>
|
<string name="different_person_button">Diferente Persoa</string>
|
||||||
<string name="duplicate_link_dialog_text_3">%s e %s enviaronlle a mesma ligazón.\n\nUnha delas podería estar a intentar descubrir os seus contactos.\n\nNon lles diga que recibeu a mesma ligazón de alguén máis.</string>
|
<string name="duplicate_link_dialog_text_3">%s e %s enviáronche a mesma ligazón.\n\nUnha delas podería estar a intentar descubrir os teus contactos.\n\nNon lles digas que recibiches a mesma ligazón de alguén máis.</string>
|
||||||
<string name="pending_contact_updated_toast">Contacto pendente actualizado</string>
|
<string name="pending_contact_updated_toast">Contacto pendente actualizado</string>
|
||||||
<!--Introductions-->
|
<!--Introductions-->
|
||||||
<string name="introduction_onboarding_title">Presente aos seus contactos</string>
|
<string name="introduction_onboarding_title">Presenta aos seus contactos</string>
|
||||||
<string name="introduction_onboarding_text">Pode presentar aos seus contactos, así non precisan encontrarse en persoa para conectar a través de Briar.</string>
|
<string name="introduction_onboarding_text">Podes presentar aos teus contactos, así non precisan encontrarse en persoa para conectar a través de Briar.</string>
|
||||||
<string name="introduction_menu_item">Preséntese</string>
|
<string name="introduction_menu_item">Preséntate</string>
|
||||||
<string name="introduction_activity_title">Escolla contacto</string>
|
<string name="introduction_activity_title">Elixe Contacto</string>
|
||||||
<string name="introduction_not_possible">Xa ten unha presentación en progreso con estes contactos. Por favor, deixe que remate o proceso. Isto podería levar algún tempo se vostede ou os contactos raramente se conectan.</string>
|
<string name="introduction_not_possible">Xa tes unha presentación en progreso con estes contactos. Por favor, deixa que remate o proceso. Isto podería levar algún tempo se ti ou os contactos raramente se conectan.</string>
|
||||||
<string name="introduction_message_title">Introducir Contactos</string>
|
<string name="introduction_message_title">Presentar Contactos</string>
|
||||||
<string name="introduction_message_hint">Engadir unha mensaxe (opcional)</string>
|
<string name="introduction_message_hint">Engadir unha mensaxe (opcional)</string>
|
||||||
<string name="introduction_button">Preséntese</string>
|
<string name="introduction_button">Preséntate</string>
|
||||||
<string name="introduction_sent">Enviouse a súa presentación.</string>
|
<string name="introduction_sent">Enviouse a túa presentación.</string>
|
||||||
<string name="introduction_error">Algo fallou ao enviar a presentación.</string>
|
<string name="introduction_error">Algo fallou ao enviar a presentación.</string>
|
||||||
<string name="introduction_request_sent">Solicitou presentar %1$s a %2$s.</string>
|
<string name="introduction_request_sent">Solicitaches presentar %1$s a %2$s.</string>
|
||||||
<string name="introduction_request_received">%1$s solicitou presentala a %2$s. Quere engadir a %2$s ao seu listado de contactos?</string>
|
<string name="introduction_request_received">%1$s solicitou presentarche a %2$s. Queres engadir a %2$s á túa lista de contactos?</string>
|
||||||
<string name="introduction_request_exists_received">%1$s solicitou presentala a %2$s, pero %2$s xa está no seu listado de contactos. Xa que %1$s podería non sabelo, pode responder igualmente:</string>
|
<string name="introduction_request_exists_received">%1$s solicitou presentarte a %2$s, pero %2$s xa estás na súa lista de contactos. Xa que %1$s podería non sabelo, podes responder igualmente:</string>
|
||||||
<string name="introduction_request_answered_received">%1$s solicitou presentala a %2$s.</string>
|
<string name="introduction_request_answered_received">%1$s solicitou presentarte a %2$s.</string>
|
||||||
<string name="introduction_response_accepted_sent">Aceptou a presentación a %1$s.</string>
|
<string name="introduction_response_accepted_sent">Aceptaches a presentación a %1$s.</string>
|
||||||
<string name="introduction_response_accepted_sent_info">Antes de engadir %1$s aos seus contactos, eles precisan aceptar a presentación tamén. Esto podería levar algún tempo.</string>
|
<string name="introduction_response_accepted_sent_info">Antes de engadir a %1$s aos teus contactos, eles precisan aceptar a presentación tamén. Esto podería levar algún tempo.</string>
|
||||||
<string name="introduction_response_declined_sent">Vostede rexeitou a presentación a %1$s.</string>
|
<string name="introduction_response_declined_sent">Rexeitaches a presentación a %1$s.</string>
|
||||||
<string name="introduction_response_accepted_received">%1$s aceptou a presentación a %2$s.</string>
|
<string name="introduction_response_accepted_received">%1$s aceptou a presentación a %2$s.</string>
|
||||||
<string name="introduction_response_declined_received">%1$s rexeitou a presentación a %2$s.</string>
|
<string name="introduction_response_declined_received">%1$s rexeitou a presentación a %2$s.</string>
|
||||||
<string name="introduction_response_declined_received_by_introducee">%1$s di que %2$srexeitou a presentación.</string>
|
<string name="introduction_response_declined_received_by_introducee">%1$s di que %2$srexeitou a presentación.</string>
|
||||||
<!--Private Groups-->
|
<!--Private Groups-->
|
||||||
<string name="groups_list_empty">Sen grupos que amosar</string>
|
<string name="groups_list_empty">Sen grupos que amosar</string>
|
||||||
<string name="groups_list_empty_action">Toque a icona + para crear un grupo, ou solicite aos contactos que compartan grupos con vostede</string>
|
<string name="groups_list_empty_action">Toca a icona + para crear un grupo, ou solicita aos contactos que compartan grupos contigo</string>
|
||||||
<string name="groups_created_by">Creado por %s</string>
|
<string name="groups_created_by">Creado por %s</string>
|
||||||
<plurals name="messages">
|
<plurals name="messages">
|
||||||
<item quantity="one">%d mensaxe</item>
|
<item quantity="one">%d mensaxe</item>
|
||||||
<item quantity="other">%d mensaxes</item>
|
<item quantity="other">%d mensaxes</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="groups_group_is_empty">Este grupo está valeiro</string>
|
<string name="groups_group_is_empty">Este grupo está baleiro</string>
|
||||||
<string name="groups_group_is_dissolved">Este grupo foi disolto</string>
|
<string name="groups_group_is_dissolved">Este grupo foi disolto</string>
|
||||||
<string name="groups_remove">Eliminar</string>
|
<string name="groups_remove">Eliminar</string>
|
||||||
<string name="groups_create_group_title">Crear Grupo Privado</string>
|
<string name="groups_create_group_title">Crear Grupo Privado</string>
|
||||||
<string name="groups_create_group_button">Crear Grupo</string>
|
<string name="groups_create_group_button">Crear Grupo</string>
|
||||||
<string name="groups_create_group_invitation_button">Enviar Convite</string>
|
<string name="groups_create_group_invitation_button">Enviar Convite</string>
|
||||||
<string name="groups_create_group_hint">Escolla un nome para o seu grupo privado</string>
|
<string name="groups_create_group_hint">Escolle un nome para o teu grupo privado</string>
|
||||||
<string name="groups_invitation_sent">Enviouse o convite de grupo</string>
|
<string name="groups_invitation_sent">Enviouse o convite de grupo</string>
|
||||||
<string name="groups_member_list">Lista de Membros</string>
|
<string name="groups_member_list">Lista de Membros</string>
|
||||||
<string name="groups_invite_members">Convidar a Membros</string>
|
<string name="groups_invite_members">Convidar a Membros</string>
|
||||||
<string name="groups_member_created_you">Vostede creou o grupo</string>
|
<string name="groups_member_created_you">Creaches o grupo</string>
|
||||||
<string name="groups_member_created">%s creou o grupo</string>
|
<string name="groups_member_created">%s creou o grupo</string>
|
||||||
<string name="groups_member_joined_you">Vostede ingresou no grupo</string>
|
<string name="groups_member_joined_you">Entraches no grupo</string>
|
||||||
<string name="groups_member_joined">%s uníuse ao grupo</string>
|
<string name="groups_member_joined">%s uníuse ao grupo</string>
|
||||||
<string name="groups_leave">Deixar Grupo</string>
|
<string name="groups_leave">Deixar Grupo</string>
|
||||||
<string name="groups_leave_dialog_title">Confirme que deixa o Grupo</string>
|
<string name="groups_leave_dialog_title">Confirma que deixas o Grupo</string>
|
||||||
<string name="groups_leave_dialog_message">Está certo de que quere deixar este grupo?</string>
|
<string name="groups_leave_dialog_message">Tes a certeza de querer deixar este grupo?</string>
|
||||||
<string name="groups_dissolve">Desfacer o grupo</string>
|
<string name="groups_dissolve">Desfacer o grupo</string>
|
||||||
<string name="groups_dissolve_dialog_title">Confirme a disolución do grupo</string>
|
<string name="groups_dissolve_dialog_title">Confirma a disolución do grupo</string>
|
||||||
<string name="groups_dissolve_dialog_message">Segura de querer desfacer o grupo?\n\nOs restantes membros non poderán continuar as súas conversas e poderían non recibir as últimas mensaxes.</string>
|
<string name="groups_dissolve_dialog_message">Segura de querer desfacer o grupo?\n\nOs restantes membros non poderán continuar as súas conversas e poderían non recibir as últimas mensaxes.</string>
|
||||||
<string name="groups_dissolve_button">Desfacer</string>
|
<string name="groups_dissolve_button">Desfacer</string>
|
||||||
<string name="groups_dissolved_dialog_title">O grupo foi desfeito</string>
|
<string name="groups_dissolved_dialog_title">O grupo foi destruído</string>
|
||||||
<string name="groups_dissolved_dialog_message">A persoa creadora do grupo desfíxoo.\n\nXa non poderá escribir mensaxes ao grupo e podería non ter recibido todas as mensaxes que foran escritas.</string>
|
<string name="groups_dissolved_dialog_message">A persoa creadora do grupo desfíxoo.\n\nXa non poderás escribir mensaxes ao grupo e poderías non ter recibido tódalas mensaxes que foran escritas.</string>
|
||||||
<!--Private Group Invitations-->
|
<!--Private Group Invitations-->
|
||||||
<string name="groups_invitations_title">Convites de Grupo</string>
|
<string name="groups_invitations_title">Convites de Grupo</string>
|
||||||
<string name="groups_invitations_invitation_sent">Convidou a %1$s a unirse ao grupo \"%2$s\".</string>
|
<string name="groups_invitations_invitation_sent">Convidaches a %1$s a unirse ao grupo \"%2$s\".</string>
|
||||||
<string name="groups_invitations_invitation_received">%1$s convidouna a unirse ao grupo \"%2$s\".</string>
|
<string name="groups_invitations_invitation_received">%1$s convidoute a unirte ao grupo \"%2$s\".</string>
|
||||||
<string name="groups_invitations_joined">Xa está no grupo</string>
|
<string name="groups_invitations_joined">Xa estás no grupo</string>
|
||||||
<string name="groups_invitations_declined">Rexeitou o convite ao grupo</string>
|
<string name="groups_invitations_declined">Rexeitou o convite ao grupo</string>
|
||||||
<plurals name="groups_invitations_open">
|
<plurals name="groups_invitations_open">
|
||||||
<item quantity="one">%d convite de grupo aberto</item>
|
<item quantity="one">%d convite de grupo aberto</item>
|
||||||
@@ -322,16 +322,16 @@
|
|||||||
<string name="sharing_status_groups">Só a persoa creadora poder convidar a novos membros ao grupo. Abaixo está a membresía do grupo.</string>
|
<string name="sharing_status_groups">Só a persoa creadora poder convidar a novos membros ao grupo. Abaixo está a membresía do grupo.</string>
|
||||||
<!--Private Groups Revealing Contacts-->
|
<!--Private Groups Revealing Contacts-->
|
||||||
<string name="groups_reveal_contacts">Revelar contactos</string>
|
<string name="groups_reveal_contacts">Revelar contactos</string>
|
||||||
<string name="groups_reveal_dialog_message">Pode escoller si revela os contactos a todos os compoñentes actuais e futuros do grupo.\n\Si revela os contactos a conexión do grupo será máis rápida e fiable, xa que poderá comunicarse cos contactos revelados incluso si a creadora do grupo non está en liña.</string>
|
<string name="groups_reveal_dialog_message">Podes escoller se revelas os contactos a todos os compoñentes actuais e futuros do grupo.\n\Se revelas os contactos a conexión do grupo será máis rápida e fiable, xa que poderá comunicarse cos contactos revelados incluso se a creadora do grupo non está en liña.</string>
|
||||||
<string name="groups_reveal_visible">A relación do contacto é visible para o grupo</string>
|
<string name="groups_reveal_visible">As relacións dos contactos son visibles para o grupo</string>
|
||||||
<string name="groups_reveal_visible_revealed_by_us">A relación co contacto é visible para o grupo (revelada por vostede)</string>
|
<string name="groups_reveal_visible_revealed_by_us">A relación co contacto é visible para o grupo (revelada por ti)</string>
|
||||||
<string name="groups_reveal_visible_revealed_by_contact">A relación con contacto é visible para o grupo (revelada por %s)</string>
|
<string name="groups_reveal_visible_revealed_by_contact">A relación con contacto é visible para o grupo (revelada por %s)</string>
|
||||||
<string name="groups_reveal_invisible">A relación co contacto non é visible para o grupo</string>
|
<string name="groups_reveal_invisible">A relación co contacto non é visible para o grupo</string>
|
||||||
<!--Forums-->
|
<!--Forums-->
|
||||||
<string name="no_forums">Sen foros que mostrar</string>
|
<string name="no_forums">Sen foros que mostrar</string>
|
||||||
<string name="no_forums_action">Toque na icona + para crear un foro, ou pida aos contactos que compartan foros con vostede</string>
|
<string name="no_forums_action">Toca na icona + para crear un foro, ou pídelle aos contactos que compartan foros contigo</string>
|
||||||
<string name="create_forum_title">Crear Foro</string>
|
<string name="create_forum_title">Crear Foro</string>
|
||||||
<string name="choose_forum_hint">Escolla un nome para o seu foro</string>
|
<string name="choose_forum_hint">Escolle un nome para o teu foro</string>
|
||||||
<string name="create_forum_button">Crear Foro</string>
|
<string name="create_forum_button">Crear Foro</string>
|
||||||
<string name="forum_created_toast">Foro creado</string>
|
<string name="forum_created_toast">Foro creado</string>
|
||||||
<string name="no_forum_posts">Sen publicacións que mostrar</string>
|
<string name="no_forum_posts">Sen publicacións que mostrar</string>
|
||||||
@@ -342,35 +342,35 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="forum_new_message_hint">Nova publicación</string>
|
<string name="forum_new_message_hint">Nova publicación</string>
|
||||||
<string name="forum_message_reply_hint">Nova Resposta</string>
|
<string name="forum_message_reply_hint">Nova Resposta</string>
|
||||||
<string name="btn_reply">Respostar</string>
|
<string name="btn_reply">Responder</string>
|
||||||
<string name="forum_leave">Deixar foro</string>
|
<string name="forum_leave">Deixar foro</string>
|
||||||
<string name="dialog_title_leave_forum">Confirme a saída do foro</string>
|
<string name="dialog_title_leave_forum">Confirma a saída do foro</string>
|
||||||
<string name="dialog_message_leave_forum">Segura de querer deixar este foro?\n\nTodos os contactos cos que comparteu este foro poderían deixar de recibir actualizacións.</string>
|
<string name="dialog_message_leave_forum">Segura de querer deixar este foro?\n\nTodos os contactos cos que compartiches este foro poderían deixar de recibir actualizacións.</string>
|
||||||
<string name="dialog_button_leave">Saír</string>
|
<string name="dialog_button_leave">Saír</string>
|
||||||
<string name="forum_left_toast">Saír do foro</string>
|
<string name="forum_left_toast">Saír do foro</string>
|
||||||
<!--Forum Sharing-->
|
<!--Forum Sharing-->
|
||||||
<string name="forum_share_button">Compartir Foro</string>
|
<string name="forum_share_button">Compartir Foro</string>
|
||||||
<string name="contacts_selected">Contactos selecionados</string>
|
<string name="contacts_selected">Contactos selecionados</string>
|
||||||
<string name="activity_share_toolbar_header">Escolla Contactos</string>
|
<string name="activity_share_toolbar_header">Elixe Contactos</string>
|
||||||
<string name="no_contacts_selector">Sen contactos a mostrar</string>
|
<string name="no_contacts_selector">Sen contactos que amosar</string>
|
||||||
<string name="no_contacts_selector_action">Volte aquí tras engadir un contacto</string>
|
<string name="no_contacts_selector_action">Volve aquí tras engadir un contacto</string>
|
||||||
<string name="forum_shared_snackbar">Foro compartido cos contactos escollidos</string>
|
<string name="forum_shared_snackbar">Foro compartido cos contactos escollidos</string>
|
||||||
<string name="forum_share_message">Engadir unha mensaxe (opcional)</string>
|
<string name="forum_share_message">Engadir unha mensaxe (opcional)</string>
|
||||||
<string name="forum_share_error">Algo fallou ao compartir este foro.</string>
|
<string name="forum_share_error">Algo fallou ao compartir este foro.</string>
|
||||||
<string name="forum_invitation_received">%1$s compartiu este foro \"%2$s\" con vostede.</string>
|
<string name="forum_invitation_received">%1$s compartiu este foro \"%2$s\" contigo.</string>
|
||||||
<string name="forum_invitation_sent">Compartiu este foro \"%1$s\" con %2$s.</string>
|
<string name="forum_invitation_sent">Compartiu este foro \"%1$s\" con %2$s.</string>
|
||||||
<string name="forum_invitations_title">Convites a foros</string>
|
<string name="forum_invitations_title">Convites a foros</string>
|
||||||
<string name="forum_invitation_exists">Xa aceptara o convite a este foro\n\nAceptando máis convites fará a súa conexión ao foro máis rápida e fiable.</string>
|
<string name="forum_invitation_exists">Xa aceptaras o convite a este foro\n\nAceptando máis convites farás a túa conexión ao foro máis rápida e fiable.</string>
|
||||||
<string name="forum_joined_toast">Uniuse ao foro</string>
|
<string name="forum_joined_toast">Unícheste ao foro</string>
|
||||||
<string name="forum_declined_toast">Rexeitou o convite</string>
|
<string name="forum_declined_toast">Rexeitaches o convite</string>
|
||||||
<string name="shared_by_format">Compartido por %s</string>
|
<string name="shared_by_format">Compartido por %s</string>
|
||||||
<string name="forum_invitation_already_sharing">Xa compartindo</string>
|
<string name="forum_invitation_already_sharing">Xa compartindo</string>
|
||||||
<string name="forum_invitation_response_accepted_sent">Aceptou o convite de %s ao foro.</string>
|
<string name="forum_invitation_response_accepted_sent">Aceptaches o convite de %s ao foro.</string>
|
||||||
<string name="forum_invitation_response_declined_sent">Rexeitou o convite de %s ao foro.</string>
|
<string name="forum_invitation_response_declined_sent">Rexeitaches o convite de %s ao foro.</string>
|
||||||
<string name="forum_invitation_response_accepted_received">%s aceptou o convite ao foro.</string>
|
<string name="forum_invitation_response_accepted_received">%s aceptou o convite ao foro.</string>
|
||||||
<string name="forum_invitation_response_declined_received">%s rexeitou o convite ao foro.</string>
|
<string name="forum_invitation_response_declined_received">%s rexeitou o convite ao foro.</string>
|
||||||
<string name="sharing_status">Estado do compartido</string>
|
<string name="sharing_status">Estado do compartido</string>
|
||||||
<string name="sharing_status_forum">Calquera compoñente do foro pode compartilo cos seus contactos. Vostede pode compartir este foro cos seguintes contactos. Pode haber outras persoas que vostede non pode ver.</string>
|
<string name="sharing_status_forum">Calquera compoñente do foro pode compartilo cos seus contactos. Tie pode compartir este foro cos seguintes contactos. Pode haber outras persoas que non podes ver.</string>
|
||||||
<string name="shared_with">Compartido con %1$d (%2$d en liña)</string>
|
<string name="shared_with">Compartido con %1$d (%2$d en liña)</string>
|
||||||
<plurals name="forums_shared">
|
<plurals name="forums_shared">
|
||||||
<item quantity="one">%d foro compartido por contactos</item>
|
<item quantity="one">%d foro compartido por contactos</item>
|
||||||
@@ -381,15 +381,15 @@
|
|||||||
<string name="blogs_other_blog_empty_state">Sen publicacións que mostrar</string>
|
<string name="blogs_other_blog_empty_state">Sen publicacións que mostrar</string>
|
||||||
<string name="read_more">ler mais</string>
|
<string name="read_more">ler mais</string>
|
||||||
<string name="blogs_write_blog_post">Escribir entrada de Blog</string>
|
<string name="blogs_write_blog_post">Escribir entrada de Blog</string>
|
||||||
<string name="blogs_write_blog_post_body_hint">Escriba a súa entrada no blog</string>
|
<string name="blogs_write_blog_post_body_hint">Escribe o artigo no blog</string>
|
||||||
<string name="blogs_publish_blog_post">Publicar</string>
|
<string name="blogs_publish_blog_post">Publicar</string>
|
||||||
<string name="blogs_blog_post_created">Entrada no blog creada</string>
|
<string name="blogs_blog_post_created">Entrada no blog creada</string>
|
||||||
<string name="blogs_blog_post_received">Nova entrada de blog recibida</string>
|
<string name="blogs_blog_post_received">Nova entrada de blog recibida</string>
|
||||||
<string name="blogs_blog_post_scroll_to">Desplazarse a</string>
|
<string name="blogs_blog_post_scroll_to">Desprazarse a</string>
|
||||||
<string name="blogs_feed_empty_state">Sen publicacións que mostrar</string>
|
<string name="blogs_feed_empty_state">Sen publicacións que mostrar</string>
|
||||||
<string name="blogs_feed_empty_state_action">Publicacións dos contactos e blogs aos que se suscriba aparecerán aquí\n\nToque na icona do lápis para encribir unha entrada</string>
|
<string name="blogs_feed_empty_state_action">Publicacións dos contactos e blogs aos que te suscribas aparecerán aquí\n\nToca na icona do lapis para encribir unha entrada</string>
|
||||||
<string name="blogs_remove_blog">Eliminar Blog</string>
|
<string name="blogs_remove_blog">Eliminar Blog</string>
|
||||||
<string name="blogs_remove_blog_dialog_message">Segura de que quere eliminar este blog?\n\nAs entradas eliminaranse do seu dispositivo pero non dos dispositivos de outras persoas.\n\nCalquera contacto co que compartira este blog podería deixar de recibir actualizacións.</string>
|
<string name="blogs_remove_blog_dialog_message">Tes a certeza de querer eliminar este blog?\n\nAs entradas eliminaranse do teu dispositivo pero non dos dispositivos doutras persoas.\n\nCalquera contacto co que compartira este blog podería deixar de recibir actualizacións.</string>
|
||||||
<string name="blogs_remove_blog_ok">Eliminar</string>
|
<string name="blogs_remove_blog_ok">Eliminar</string>
|
||||||
<string name="blogs_blog_removed">Blog eliminado</string>
|
<string name="blogs_blog_removed">Blog eliminado</string>
|
||||||
<string name="blogs_reblog_comment_hint">Engadir un comentario (optativo)</string>
|
<string name="blogs_reblog_comment_hint">Engadir un comentario (optativo)</string>
|
||||||
@@ -399,46 +399,46 @@
|
|||||||
<string name="blogs_sharing_error">Algo fallou ao compartir o blog</string>
|
<string name="blogs_sharing_error">Algo fallou ao compartir o blog</string>
|
||||||
<string name="blogs_sharing_button">Compartir blog</string>
|
<string name="blogs_sharing_button">Compartir blog</string>
|
||||||
<string name="blogs_sharing_snackbar">Blog compartido cos contactos escollidos</string>
|
<string name="blogs_sharing_snackbar">Blog compartido cos contactos escollidos</string>
|
||||||
<string name="blogs_sharing_response_accepted_sent">Aceptou o convite ao blog de %s.</string>
|
<string name="blogs_sharing_response_accepted_sent">Aceptaches o convite ao blog de %s.</string>
|
||||||
<string name="blogs_sharing_response_declined_sent">Rexeitou o convite ao blog de %s.</string>
|
<string name="blogs_sharing_response_declined_sent">Rexeitaches o convite ao blog de %s.</string>
|
||||||
<string name="blogs_sharing_response_accepted_received">%s aceptou o convite ao blog.</string>
|
<string name="blogs_sharing_response_accepted_received">%s aceptou o convite ao blog.</string>
|
||||||
<string name="blogs_sharing_response_declined_received">%s rexeitou o convite ao blog.</string>
|
<string name="blogs_sharing_response_declined_received">%s rexeitou o convite ao blog.</string>
|
||||||
<string name="blogs_sharing_invitation_received">%1$s compartiu o blog \"%2$s\" con vostede.</string>
|
<string name="blogs_sharing_invitation_received">%1$s compartiu o blog \"%2$s\" contigo.</string>
|
||||||
<string name="blogs_sharing_invitation_sent">Vostede compartiu o blog \"%1$s\" con %2$s.</string>
|
<string name="blogs_sharing_invitation_sent">Compartiches o blog \"%1$s\" con %2$s.</string>
|
||||||
<string name="blogs_sharing_invitations_title">Convites a Blog</string>
|
<string name="blogs_sharing_invitations_title">Convites a Blog</string>
|
||||||
<string name="blogs_sharing_joined_toast">Subscrita ao blog</string>
|
<string name="blogs_sharing_joined_toast">Subscrita ao blog</string>
|
||||||
<string name="blogs_sharing_declined_toast">Rexeitou o convite</string>
|
<string name="blogs_sharing_declined_toast">Rexeitaches o convite</string>
|
||||||
<string name="sharing_status_blog">Calquera que se subscribe a un blog pode compartilo cos seus contactos. Pode compartir este blog cos seguintes contactos. Podería haber outras subscritoras que vostede non pode ver.</string>
|
<string name="sharing_status_blog">Calquera que se subscribe a un blog pode compartilo cos seus contactos. Podes compartir este blog cos seguintes contactos. Podería haber outras subscritoras que ti non podes ver.</string>
|
||||||
<!--RSS Feeds-->
|
<!--RSS Feeds-->
|
||||||
<string name="blogs_rss_feeds_import">Importar fonte RSS</string>
|
<string name="blogs_rss_feeds_import">Importar fonte RSS</string>
|
||||||
<string name="blogs_rss_feeds_import_button">Importar</string>
|
<string name="blogs_rss_feeds_import_button">Importar</string>
|
||||||
<string name="blogs_rss_feeds_import_hint">Introduza o URL da fonte RSS</string>
|
<string name="blogs_rss_feeds_import_hint">Escribe o URL da fonte RSS</string>
|
||||||
<string name="blogs_rss_feeds_import_error">Lamentámolo! Algo fallou ao importar a fonte.</string>
|
<string name="blogs_rss_feeds_import_error">Lamentámolo! Algo fallou ao importar a fonte.</string>
|
||||||
<string name="blogs_rss_feeds_manage">Xestionar Fontes RSS</string>
|
<string name="blogs_rss_feeds_manage">Xestionar Fontes RSS</string>
|
||||||
<string name="blogs_rss_feeds_manage_imported">Importado:</string>
|
<string name="blogs_rss_feeds_manage_imported">Importado:</string>
|
||||||
<string name="blogs_rss_feeds_manage_author">Autor/a:</string>
|
<string name="blogs_rss_feeds_manage_author">Autor/a:</string>
|
||||||
<string name="blogs_rss_feeds_manage_updated">Última actualización:</string>
|
<string name="blogs_rss_feeds_manage_updated">Última actualización:</string>
|
||||||
<string name="blogs_rss_remove_feed">Eliminar fonte</string>
|
<string name="blogs_rss_remove_feed">Eliminar fonte</string>
|
||||||
<string name="blogs_rss_remove_feed_dialog_message">Está segura de que quere eliminar esta fonte?\n\nAs entradas eliminaranse do seu dispositivo pero non dos dispositivos de outras persoas\n\nTodas as persoas coas que compartiu esta fonte poderían deixar de recibir actualizacións.</string>
|
<string name="blogs_rss_remove_feed_dialog_message">Tes a certeza de querer eliminar esta fonte?\n\nAs entradas eliminaranse do teu dispositivo pero non dos dispositivos doutras persoas\n\nTodas as persoas coas que compartiches esta fonte poderían deixar de recibir actualizacións.</string>
|
||||||
<string name="blogs_rss_remove_feed_ok">Eliminar</string>
|
<string name="blogs_rss_remove_feed_ok">Eliminar</string>
|
||||||
<string name="blogs_rss_feeds_manage_delete_error">Non se puido eliminar a fonte!</string>
|
<string name="blogs_rss_feeds_manage_delete_error">Non se puido eliminar a 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_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 túas fontes. Por favor, inténtao 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="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_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="dialog_confirm_profile_picture_remark">Só os teus contactos poden ver esta foto</string>
|
||||||
<string name="change_profile_picture_failed_message">Lamentámolo, pero algo fallou cando intentamos actualizar a túa imaxe de pefil</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 reinicies Briar. Pecha e reinicia Briar.</string>
|
||||||
<string name="pref_language_default">Por omisión do sistema</string>
|
<string name="pref_language_default">Por defecto no sistema</string>
|
||||||
<string name="display_settings_title">Pantalla</string>
|
<string name="display_settings_title">Pantalla</string>
|
||||||
<string name="pref_theme_title">Decorado</string>
|
<string name="pref_theme_title">Decorado</string>
|
||||||
<string name="pref_theme_light">Claro</string>
|
<string name="pref_theme_light">Claro</string>
|
||||||
<string name="pref_theme_dark">Oscuro</string>
|
<string name="pref_theme_dark">Escuro</string>
|
||||||
<string name="pref_theme_auto">Automático (no día)</string>
|
<string name="pref_theme_auto">Automático (no día)</string>
|
||||||
<string name="pref_theme_system">Por omisión do sistema</string>
|
<string name="pref_theme_system">Por defecto no sistema</string>
|
||||||
<!--Settings Connections-->
|
<!--Settings Connections-->
|
||||||
<string name="network_settings_title">Conexións</string>
|
<string name="network_settings_title">Conexións</string>
|
||||||
<string name="bluetooth_setting">Conectar cos contactos vía Bluetooth</string>
|
<string name="bluetooth_setting">Conectar cos contactos vía Bluetooth</string>
|
||||||
@@ -458,11 +458,11 @@
|
|||||||
<!--Settings Security and Panic-->
|
<!--Settings Security and Panic-->
|
||||||
<string name="security_settings_title">Seguridade</string>
|
<string name="security_settings_title">Seguridade</string>
|
||||||
<string name="pref_lock_title">Bloquear app</string>
|
<string name="pref_lock_title">Bloquear app</string>
|
||||||
<string name="pref_lock_summary">Utilizar o bloqueo de pantalla do dispositiov para protexer Briar mentras está conectada</string>
|
<string name="pref_lock_summary">Utilizar o bloqueo de pantalla do dispositio para protexer Briar mentras estás conectada</string>
|
||||||
<string name="pref_lock_disabled_summary">Para utilizar esta función, axuste o bloqueo de pantalla do dispositivo</string>
|
<string name="pref_lock_disabled_summary">Para utilizar esta función, axusta o bloqueo de pantalla do dispositivo</string>
|
||||||
<string name="pref_lock_timeout_title">Tempo de espera en inactividade para o bloqueo</string>
|
<string name="pref_lock_timeout_title">Tempo de espera en inactividade para o bloqueo</string>
|
||||||
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
|
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
|
||||||
<string name="pref_lock_timeout_summary">Cando non utilice Briar, bloquear automáticamente tras %s</string>
|
<string name="pref_lock_timeout_summary">Cando non uses Briar, bloquea automáticamente tras %s</string>
|
||||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||||
<string name="pref_lock_timeout_1">1 minuto</string>
|
<string name="pref_lock_timeout_1">1 minuto</string>
|
||||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||||
@@ -475,25 +475,25 @@
|
|||||||
<string name="pref_lock_timeout_60">1 hora</string>
|
<string name="pref_lock_timeout_60">1 hora</string>
|
||||||
<string name="pref_lock_timeout_never">Nunca</string>
|
<string name="pref_lock_timeout_never">Nunca</string>
|
||||||
<string name="pref_lock_timeout_never_summary">Nunca bloquear Briar automáticamente</string>
|
<string name="pref_lock_timeout_never_summary">Nunca bloquear Briar automáticamente</string>
|
||||||
<string name="change_password">Trocar a clave</string>
|
<string name="change_password">Trocar o contrasinal</string>
|
||||||
<string name="current_password">Clave actual</string>
|
<string name="current_password">Contrasinal actual</string>
|
||||||
<string name="choose_new_password">Nova clave</string>
|
<string name="choose_new_password">Novo contrasinal</string>
|
||||||
<string name="confirm_new_password">Confirme a nova clave</string>
|
<string name="confirm_new_password">Confirma o novo contrasinal</string>
|
||||||
<string name="password_changed">Trocouse a clave</string>
|
<string name="password_changed">Cambiaches o contrasinal</string>
|
||||||
<string name="panic_setting">Axustes do botón do pánico</string>
|
<string name="panic_setting">Axustes do botón do pánico</string>
|
||||||
<string name="panic_setting_title">Botón do pánico</string>
|
<string name="panic_setting_title">Botón do pánico</string>
|
||||||
<string name="panic_setting_hint">Axuste como debe reaccionar Briar cando utilice a app botón do pánico</string>
|
<string name="panic_setting_hint">Axuste como debe reaccionar Briar cando utilices a app botón do pánico</string>
|
||||||
<string name="panic_app_setting_title">App Botón do pánico</string>
|
<string name="panic_app_setting_title">App Botón do pánico</string>
|
||||||
<string name="unknown_app">unha aplicación descoñecida</string>
|
<string name="unknown_app">unha aplicación descoñecida</string>
|
||||||
<string name="panic_app_setting_summary">Non se estableceu unha app</string>
|
<string name="panic_app_setting_summary">Non se estableceu unha app</string>
|
||||||
<string name="panic_app_setting_none">Ningún</string>
|
<string name="panic_app_setting_none">Ningún</string>
|
||||||
<string name="dialog_title_connect_panic_app">Confirme a App do pánico</string>
|
<string name="dialog_title_connect_panic_app">Confirma a App do pánico</string>
|
||||||
<string name="dialog_message_connect_panic_app">Está segura de querer permitir a %1$s activar accións destrutivas do botón do pánico?</string>
|
<string name="dialog_message_connect_panic_app">Tes a certeza de querer permitir a %1$s activar accións destrutivas do botón do pánico?</string>
|
||||||
<string name="panic_setting_destructive_action">Accións destructivas</string>
|
<string name="panic_setting_destructive_action">Accións destrutivas</string>
|
||||||
<string name="panic_setting_signout_title">Finalizar sesión</string>
|
<string name="panic_setting_signout_title">Pechar sesión</string>
|
||||||
<string name="panic_setting_signout_summary">Desconecte de Briar si o botón do pánico se preme</string>
|
<string name="panic_setting_signout_summary">Desconecta de Briar se o botón do pánico se preme</string>
|
||||||
<string name="purge_setting_title">Eliminar conta</string>
|
<string name="purge_setting_title">Eliminar conta</string>
|
||||||
<string name="purge_setting_summary">Elimina a súa conta en Briar si se preme o botón do pánico. Coidado: Isto eliminará permanentemente as súas identidades, contactos e mensaxes</string>
|
<string name="purge_setting_summary">Elimina a túa conta en Briar se se preme o botón do pánico. Coidado: Isto eliminará permanentemente as túas identidades, contactos e mensaxes</string>
|
||||||
<!--Settings Notifications-->
|
<!--Settings Notifications-->
|
||||||
<string name="notification_settings_title">Notificacións</string>
|
<string name="notification_settings_title">Notificacións</string>
|
||||||
<string name="notify_sign_in_title">Lembrarme conectar</string>
|
<string name="notify_sign_in_title">Lembrarme conectar</string>
|
||||||
@@ -512,28 +512,28 @@
|
|||||||
<string name="notify_blog_posts_setting_summary_26">Configurar alertas para entradas no blog</string>
|
<string name="notify_blog_posts_setting_summary_26">Configurar alertas para entradas no blog</string>
|
||||||
<string name="notify_vibration_setting">Vibrar</string>
|
<string name="notify_vibration_setting">Vibrar</string>
|
||||||
<string name="notify_sound_setting">Son</string>
|
<string name="notify_sound_setting">Son</string>
|
||||||
<string name="notify_sound_setting_default">Ton por omisión</string>
|
<string name="notify_sound_setting_default">Ton por defecto</string>
|
||||||
<string name="notify_sound_setting_disabled">Ningún</string>
|
<string name="notify_sound_setting_disabled">Ningún</string>
|
||||||
<string name="choose_ringtone_title">Escolla ton</string>
|
<string name="choose_ringtone_title">Escolle ton</string>
|
||||||
<string name="cannot_load_ringtone">Non se cargou o ton</string>
|
<string name="cannot_load_ringtone">Non se cargou o ton</string>
|
||||||
<!--Settings Feedback-->
|
<!--Settings Feedback-->
|
||||||
<string name="feedback_settings_title">Avaliación</string>
|
<string name="feedback_settings_title">Avaliación</string>
|
||||||
<string name="send_feedback">Envíe a avaliación</string>
|
<string name="send_feedback">Envía a avaliación</string>
|
||||||
<!--Link Warning-->
|
<!--Link Warning-->
|
||||||
<string name="link_warning_title">Aviso de ligazón</string>
|
<string name="link_warning_title">Aviso de ligazón</string>
|
||||||
<string name="link_warning_intro">Vai abrir a seguinte ligazón nunha aplicación externa.</string>
|
<string name="link_warning_intro">Vas abrir a seguinte ligazón nunha aplicación externa.</string>
|
||||||
<string name="link_warning_text">Esto pódese utilizar para identificala. Pense ben se confía na persoa que lle envía a ligazón e considere abrila co Navegador Tor.</string>
|
<string name="link_warning_text">Esto pódese utilizar para identificarte. Pensa ben se confías na persoa que che envía a ligazón e considera abrila co Navegador Tor.</string>
|
||||||
<string name="link_warning_open_link">Abrir ligazón</string>
|
<string name="link_warning_open_link">Abrir ligazón</string>
|
||||||
<!--Crash Reporter-->
|
<!--Crash Reporter-->
|
||||||
<string name="crash_report_title">Informe de fallo de Briar</string>
|
<string name="crash_report_title">Informe de fallo de Briar</string>
|
||||||
<string name="briar_crashed">Lamentámolo, Briar fallou.</string>
|
<string name="briar_crashed">Lamentámolo, Briar fallou.</string>
|
||||||
<string name="not_your_fault">Non é culpa súa.</string>
|
<string name="not_your_fault">Non é culpa túa.</string>
|
||||||
<string name="please_send_report">Axúdenos por favor a mellorar Briar enviándonos un informe do fallo.</string>
|
<string name="please_send_report">Axúdanos por favor a mellorar Briar enviándonos un informe do fallo.</string>
|
||||||
<string name="report_is_encrypted">Prometemos que o informe está cifrado e enviado con seguridade.</string>
|
<string name="report_is_encrypted">Prometemos que o informe está cifrado e enviado con seguridade.</string>
|
||||||
<string name="feedback_title">Avaliación</string>
|
<string name="feedback_title">Avaliación</string>
|
||||||
<string name="describe_crash">Describa que aconteceu (optativo)</string>
|
<string name="describe_crash">Describe que aconteceu (optativo)</string>
|
||||||
<string name="enter_feedback">Escriba a súa avaliación</string>
|
<string name="enter_feedback">Escribe a túa avaliación</string>
|
||||||
<string name="optional_contact_email">O seu enderezo e-mail (optativo)</string>
|
<string name="optional_contact_email">O teu enderezo e-mail (optativo)</string>
|
||||||
<string name="include_debug_report_crash">Incluír datos anónimos sobre o fallo</string>
|
<string name="include_debug_report_crash">Incluír datos anónimos sobre o fallo</string>
|
||||||
<string name="include_debug_report_feedback">Incluír datos anónimos sobre este dispositivo</string>
|
<string name="include_debug_report_feedback">Incluír datos anónimos sobre este dispositivo</string>
|
||||||
<string name="dev_report_basic_info">Información básica</string>
|
<string name="dev_report_basic_info">Información básica</string>
|
||||||
@@ -550,13 +550,13 @@
|
|||||||
<string name="close">Pechar</string>
|
<string name="close">Pechar</string>
|
||||||
<string name="dev_report_sending">Enviando comentarios...</string>
|
<string name="dev_report_sending">Enviando comentarios...</string>
|
||||||
<string name="dev_report_sent">Comentarios enviados</string>
|
<string name="dev_report_sent">Comentarios enviados</string>
|
||||||
<string name="dev_report_saved">Informe gardado. Enviarase a seguinte vez que se conecte con Briar.</string>
|
<string name="dev_report_saved">Informe gardado. Enviarase a seguinte vez que te conectes con Briar.</string>
|
||||||
<string name="dev_report_error">Erro: fallou o envío do informe</string>
|
<string name="dev_report_error">Erro: fallou o envío do informe</string>
|
||||||
<!--Sign Out-->
|
<!--Sign Out-->
|
||||||
<string name="progress_title_logout">Desconectando de Briar...</string>
|
<string name="progress_title_logout">Desconectando de Briar...</string>
|
||||||
<!--Screen Filters & Tapjacking-->
|
<!--Screen Filters & Tapjacking-->
|
||||||
<string name="screen_filter_title">Detectouse unha sobreescrita da pantalla</string>
|
<string name="screen_filter_title">Detectouse unha sobreescrita da pantalla</string>
|
||||||
<string name="screen_filter_body">Outra aplicación estase a amosar enriba de Briar. Para protexer a súa seguridade, Briar non responderá a toques cando outra aplicación está debuxando enriba.\n\nAs seguintes aplicacións poderían estar debuxando enriba:\n\n%1$s</string>
|
<string name="screen_filter_body">Outra aplicación estase a amosar enriba de Briar. Para protexer a túa seguridade, Briar non responderá a toques cando outra aplicación está debuxando enriba.\n\nAs seguintes aplicacións poderían estar debuxando enriba:\n\n%1$s</string>
|
||||||
<string name="screen_filter_body_api_30">Outra app ten acceso a ver a pantalla enriba de Briar. Para protexer a túa seguridade, Briar non vai responder aos toques cando outra app ten acceso a pantalla.\n\nRevisa as app de abaixo para atopara a resposable.</string>
|
<string name="screen_filter_body_api_30">Outra app ten acceso a ver a pantalla enriba de Briar. Para protexer a túa seguridade, Briar non vai responder aos toques cando outra app ten acceso a pantalla.\n\nRevisa as app de abaixo para atopara a resposable.</string>
|
||||||
<string name="screen_filter_allow">Permitir a estas aplicación amosarse enriba</string>
|
<string name="screen_filter_allow">Permitir a estas aplicación amosarse enriba</string>
|
||||||
<string name="screen_filter_review_apps">Revisar apps</string>
|
<string name="screen_filter_review_apps">Revisar apps</string>
|
||||||
@@ -564,20 +564,20 @@
|
|||||||
<string name="permission_camera_title">Permiso da cámara</string>
|
<string name="permission_camera_title">Permiso da cámara</string>
|
||||||
<string name="permission_camera_request_body">Para escanear códigos QR, Briar precisa acceso a cámara.</string>
|
<string name="permission_camera_request_body">Para escanear códigos QR, Briar precisa acceso a cámara.</string>
|
||||||
<string name="permission_location_title">Permiso de localización</string>
|
<string name="permission_location_title">Permiso de localización</string>
|
||||||
<string name="permission_location_request_body">Para descubrir dispositivos Bluetooth, Briar precisa permiso para acceder a súa localización.\n\nBriar non garda a súa localización nin a comparte con ninguén.</string>
|
<string name="permission_location_request_body">Para descubrir dispositivos Bluetooth, Briar precisa permiso para acceder a túa localización.\n\nBriar non garda a túa localización nin a comparte con ninguén.</string>
|
||||||
<string name="permission_camera_location_title">Cámara e localización</string>
|
<string name="permission_camera_location_title">Cámara e localización</string>
|
||||||
<string name="permission_camera_location_request_body">Para escanear o código QR, Briar precisa acceso a cámara.\n\nPara descubrir dispositivos Bluetooth, Briar precisa permiso a súa localización.\n\nBriar non garda a súa localización nin a comparte con ninguén.</string>
|
<string name="permission_camera_location_request_body">Para escanear o código QR, Briar precisa acceso a cámara.\n\nPara descubrir dispositivos Bluetooth, Briar precisa permiso a túa localización.\n\nBriar non garda a túa localización nin a comparte con ninguén.</string>
|
||||||
<string name="permission_camera_denied_body">Denegou o permiso de acceso a cámara, pero é necesario para engadir contactos.\n\nPor favor, considere conceder o permiso.</string>
|
<string name="permission_camera_denied_body">Denegaches o permiso de acceso a cámara, pero é necesario para engadir contactos.\n\nPor favor, considera conceder o permiso.</string>
|
||||||
<string name="permission_location_denied_body">Denegache o acceso á localización, mais Briar precisa este permiso para descubrir dispositivos Bluetooth.\n\nConsidera conceder o permiso.</string>
|
<string name="permission_location_denied_body">Denegache o acceso á localización, mais Briar precisa este permiso para descubrir dispositivos Bluetooth.\n\nConsidera conceder o permiso.</string>
|
||||||
<string name="qr_code">Código QR</string>
|
<string name="qr_code">Código QR</string>
|
||||||
<string name="show_qr_code_fullscreen">Amosar o código QR a pantalla completa</string>
|
<string name="show_qr_code_fullscreen">Amosar o código QR a pantalla completa</string>
|
||||||
<!--App Locking-->
|
<!--App Locking-->
|
||||||
<string name="lock_unlock">Desbloquear Briar</string>
|
<string name="lock_unlock">Desbloquear Briar</string>
|
||||||
<string name="lock_unlock_verbose">Introduza o PIN do dispositivo, patrón ou contrasinal para desbloquear Briar</string>
|
<string name="lock_unlock_verbose">Escribe o PIN do dispositivo, patrón ou contrasinal para desbloquear Briar</string>
|
||||||
<string name="lock_unlock_fingerprint_description">Toca o sensor de impresións dixitais co dedo rexistrado para continuar</string>
|
<string name="lock_unlock_fingerprint_description">Toca o sensor de impresións dixitais co dedo rexistrado para continuar</string>
|
||||||
<string name="lock_unlock_password">Utilizar contrasinal</string>
|
<string name="lock_unlock_password">Utilizar contrasinal</string>
|
||||||
<string name="lock_is_locked">Briar está bloqueada</string>
|
<string name="lock_is_locked">Briar está bloqueada</string>
|
||||||
<string name="lock_tap_to_unlock">Toque para desbloquear</string>
|
<string name="lock_tap_to_unlock">Toca para desbloquear</string>
|
||||||
<!--Connections Screen-->
|
<!--Connections Screen-->
|
||||||
<string name="transports_help_text">Briar pode conectar cos teus contactos a través de Internet, Wi-Fi ou Bluetooth.\n\nTodas as conexións a internet pasan a través da rede Tor para máis privacidade.\n\nSe un contacto é accesible de múltiples xeitos, Briar usaráos en paralelo.</string>
|
<string name="transports_help_text">Briar pode conectar cos teus contactos a través de Internet, Wi-Fi ou Bluetooth.\n\nTodas as conexións a internet pasan a través da rede Tor para máis privacidade.\n\nSe un contacto é accesible de múltiples xeitos, Briar usaráos en paralelo.</string>
|
||||||
<!--Screenshots-->
|
<!--Screenshots-->
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<!--Setup-->
|
<!--Setup-->
|
||||||
<string name="setup_title">ברוך הבא אל Briar</string>
|
<string name="setup_title">ברוך בואך אל Briar</string>
|
||||||
<string name="setup_name_explanation">כינויך יוראה ליד תוכן כלשהו שתכתוב. אינך יכול לשנות אותו לאחר יצירת חשבונך.</string>
|
<string name="setup_name_explanation">כינויך יוראה ליד תוכן כלשהו שתכתוב. אינך יכול לשנות אותו לאחר יצירת חשבונך.</string>
|
||||||
<string name="setup_next">הבא</string>
|
<string name="setup_next">הבא</string>
|
||||||
<string name="setup_password_intro">בחר סיסמה</string>
|
<string name="setup_password_intro">נא לבחור סיסמה</string>
|
||||||
<string name="setup_password_explanation">חשבון Briar שלך מאוחסן באופן מוצפן במכשירך, לא בענן. אם תשכח את סיסמתך או תסיר את Briar, אין דרך להשיב את חשבונך.\n\nבחר סיסמה ארוכה שקשה לנחש אותה, כגון ארבע מילים אקראיות, או עשרה תווים אקראיים של אותיות, מספרים וסמלים.</string>
|
<string name="setup_password_explanation">חשבון Briar שלך מאוחסן באופן מוצפן במכשירך, לא בענן. אם תשכח את סיסמתך או תסיר את Briar, אין דרך להשיב את חשבונך.\n\nבחר סיסמה ארוכה שקשה לנחש אותה, כגון ארבע מילים אקראיות, או עשרה תווים אקראיים של אותיות, מספרים וסמלים.</string>
|
||||||
<string name="setup_doze_title">חיבורי רקע</string>
|
<string name="setup_doze_title">חיבורי רקע</string>
|
||||||
<string name="setup_doze_intro">כדי לקבל הודעות, Briar צריך להישאר מחובר ברקע.</string>
|
<string name="setup_doze_intro">כדי לקבל הודעות, Briar צריך להישאר מחובר ברקע.</string>
|
||||||
<string name="setup_doze_explanation">כדי לקבל הודעות, Briar צריך להישאר מחובר ברקע. אנא השבת מיטובי סוללה כך ש־Briar יוכל להישאר מחובר.</string>
|
<string name="setup_doze_explanation">כדי לקבל הודעות, Briar צריך להישאר מחובר ברקע. אנא השבת מיטובי סוללה כך ש־Briar יוכל להישאר מחובר.</string>
|
||||||
<string name="setup_doze_button">התר חיבורים</string>
|
<string name="setup_doze_button">התר חיבורים</string>
|
||||||
<string name="choose_nickname">בחר את הכינוי שלך</string>
|
<string name="choose_nickname">נא לבחור את הכינוי שלך</string>
|
||||||
<string name="choose_password">בחר סיסמה</string>
|
<string name="choose_password">בחר סיסמה</string>
|
||||||
<string name="confirm_password">אשר סיסמה</string>
|
<string name="confirm_password">אשר סיסמה</string>
|
||||||
<string name="name_too_long">השם ארוך מדי</string>
|
<string name="name_too_long">השם ארוך מדי</string>
|
||||||
<string name="password_too_weak">הסיסמה חלשה מדי</string>
|
<string name="password_too_weak">הסיסמה חלשה מדי</string>
|
||||||
<string name="passwords_do_not_match">הסיסמאות לא תואמות</string>
|
<string name="passwords_do_not_match">הסיסמאות לא תואמות</string>
|
||||||
<string name="create_account_button">צור חשבון</string>
|
<string name="create_account_button">יצירת חשבון</string>
|
||||||
<string name="more_info">עוד מידע</string>
|
<string name="more_info">מידע נוסף</string>
|
||||||
<string name="don_t_ask_again">אל תשאל שוב</string>
|
<string name="don_t_ask_again">לא לשאול שוב</string>
|
||||||
<string name="setup_huawei_text">אנא הקש על הכפתור למטה ווודא כי Briar מוגן במסך \"יישומים מוגנים\".</string>
|
<string name="setup_huawei_text">נא להקיש על הכפתור למטה וולוודא כי Briar מוגן במסך \"יישומים מוגנים\".</string>
|
||||||
<string name="setup_huawei_button">הגן על Briar</string>
|
<string name="setup_huawei_button">הגן על Briar</string>
|
||||||
<string name="setup_huawei_help">אם Briar אינו מוסף אל רשימת היישומים המוגנים, הוא לא יוכל לרוץ ברקע.</string>
|
<string name="setup_huawei_help">אם Briar אינו מוסף אל רשימת היישומים המוגנים, הוא לא יוכל לרוץ ברקע.</string>
|
||||||
<string name="warning_dozed">%s לא היה יכול לרוץ ברקע</string>
|
<string name="warning_dozed">%s לא היה יכול לרוץ ברקע</string>
|
||||||
@@ -28,12 +28,12 @@
|
|||||||
<string name="try_again">סיסמה שגויה, נסה שוב</string>
|
<string name="try_again">סיסמה שגויה, נסה שוב</string>
|
||||||
<string name="dialog_title_cannot_check_password">לא יכול לבדוק סיסמה</string>
|
<string name="dialog_title_cannot_check_password">לא יכול לבדוק סיסמה</string>
|
||||||
<string name="dialog_message_cannot_check_password">Briar לא יכול לבדוק את הסיסמה שלך. אנא נסה לאתחל מחדש את המכשיר שלך כדי לפתור בעיה זאת.</string>
|
<string name="dialog_message_cannot_check_password">Briar לא יכול לבדוק את הסיסמה שלך. אנא נסה לאתחל מחדש את המכשיר שלך כדי לפתור בעיה זאת.</string>
|
||||||
<string name="sign_in_button">היכנס</string>
|
<string name="sign_in_button">כניסה</string>
|
||||||
<string name="forgotten_password">שכחתי את הסיסמה שלי</string>
|
<string name="forgotten_password">שכחתי את הסיסמה שלי</string>
|
||||||
<string name="dialog_title_lost_password">סיסמה אבודה</string>
|
<string name="dialog_title_lost_password">סיסמה אבודה</string>
|
||||||
<string name="dialog_message_lost_password">חשבון Briar שלך מאוחסן באופן מוצפן על המכשיר שלך, לא בענן, כך שאנחנו לא יכולים לאפס את הסיסמה שלך. האם תרצה למחוק את החשבון שלך ולהתחיל מחדש?\n\nזהירות: הזהויות, אנשי הקשר וההודעות שלך יאבדו לצמיתות.</string>
|
<string name="dialog_message_lost_password">חשבון Briar שלך מאוחסן באופן מוצפן על המכשיר שלך, לא בענן, כך שאנחנו לא יכולים לאפס את הסיסמה שלך. האם תרצה למחוק את החשבון שלך ולהתחיל מחדש?\n\nזהירות: הזהויות, אנשי הקשר וההודעות שלך יאבדו לצמיתות.</string>
|
||||||
<string name="startup_failed_notification_title">Briar לא היה ניתן להתחיל</string>
|
<string name="startup_failed_notification_title">Briar לא היה ניתן להתחיל</string>
|
||||||
<string name="startup_failed_notification_text">הקש לעוד מידע.</string>
|
<string name="startup_failed_notification_text">יש להקיש למידע נוסף.</string>
|
||||||
<string name="startup_failed_activity_title">כישלון הזנק Briar</string>
|
<string name="startup_failed_activity_title">כישלון הזנק Briar</string>
|
||||||
<string name="startup_failed_db_error">מסיבה כלשהי, מסד נתונים Briar שלך פגום ללא יכולת תיקון. החשבון שלך, הנתונים שלך וכל אנשי הקשר שלך אבדו. למרבה הצער, אתה צריך להתקין מחדש את Briar או להגדיר חשבון חדש ע״י בחירה באפשרות \'שכחתי את הסיסמה שלי\' בתזכיר הסיסמה.</string>
|
<string name="startup_failed_db_error">מסיבה כלשהי, מסד נתונים Briar שלך פגום ללא יכולת תיקון. החשבון שלך, הנתונים שלך וכל אנשי הקשר שלך אבדו. למרבה הצער, אתה צריך להתקין מחדש את Briar או להגדיר חשבון חדש ע״י בחירה באפשרות \'שכחתי את הסיסמה שלי\' בתזכיר הסיסמה.</string>
|
||||||
<string name="startup_failed_data_too_old_error">החשבון שלך נוצר עם גרסה ישנה של יישום זה ואינו יכול להיפתח עם גרסה זו. אתה חייב להתקין מחדש את הגרסה הישנה או להגדיר חשבון חדש ע״י בחירה באפשרות \'שכחתי את הסיסמה שלי\' בתזכיר הסיסמה.</string>
|
<string name="startup_failed_data_too_old_error">החשבון שלך נוצר עם גרסה ישנה של יישום זה ואינו יכול להיפתח עם גרסה זו. אתה חייב להתקין מחדש את הגרסה הישנה או להגדיר חשבון חדש ע״י בחירה באפשרות \'שכחתי את הסיסמה שלי\' בתזכיר הסיסמה.</string>
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="expiry_date_reached">תוכנה זו פגה.\nתודה על הבדיקה!</string>
|
<string name="expiry_date_reached">תוכנה זו פגה.\nתודה על הבדיקה!</string>
|
||||||
<string name="download_briar">כדי להמשיך להשתמש ב־Briar, אנא הורד את השחרור האחרון.</string>
|
<string name="download_briar">כדי להמשיך להשתמש ב־Briar, אנא הורד את השחרור האחרון.</string>
|
||||||
<string name="create_new_account">תיצטרך ליצור חשבון חדש, אבל אתה יכול להשתמש באותו כינוי.</string>
|
<string name="create_new_account">יהיה צריך ליצור חשבון חדש, אבל אפשר להשתמש באותו הכינוי.</string>
|
||||||
<string name="download_briar_button">הורד שחרור אחרון</string>
|
<string name="download_briar_button">הורד שחרור אחרון</string>
|
||||||
<string name="startup_open_database">מפענח מסד נתונים…</string>
|
<string name="startup_open_database">מפענח מסד נתונים…</string>
|
||||||
<string name="startup_migrate_database">משדרג מסד נתונים…</string>
|
<string name="startup_migrate_database">משדרג מסד נתונים…</string>
|
||||||
@@ -62,13 +62,13 @@
|
|||||||
<!--This is part of the main menu. The app will be locked when this is tapped.-->
|
<!--This is part of the main menu. The app will be locked when this is tapped.-->
|
||||||
<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>
|
<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_wifi">לטלפון שלך גישה לאינטרנט באמצעות רשת אלחוטית</string>
|
||||||
<string name="tor_device_status_online_mobile">לטלפון שלך יש חיבור אינטרנט באמצעות נתונים סלולריים</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>
|
||||||
<string name="tor_plugin_status_inactive">Briar אינו יכול להתחבר אל האינטרנט</string>
|
<string name="tor_plugin_status_inactive">Briar אינו יכול להתחבר אל האינטרנט</string>
|
||||||
@@ -77,14 +77,14 @@
|
|||||||
<string name="tor_plugin_status_disabled_battery">Briar מתוצר לא להשתמש באינטרנט בעת הרצה על סוללה</string>
|
<string name="tor_plugin_status_disabled_battery">Briar מתוצר לא להשתמש באינטרנט בעת הרצה על סוללה</string>
|
||||||
<string name="tor_plugin_status_disabled_country_blocked">Briar מתוצר לא להשתמש באינטרנט במדינה זו</string>
|
<string name="tor_plugin_status_disabled_country_blocked">Briar מתוצר לא להשתמש באינטרנט במדינה זו</string>
|
||||||
<!--Transports: Wi-Fi-->
|
<!--Transports: Wi-Fi-->
|
||||||
<string name="transport_lan">Wi-Fi</string>
|
<string name="transport_lan">רשת אלחוטית</string>
|
||||||
<string name="transport_lan_long">אותה רשת Wi-Fi</string>
|
<string name="transport_lan_long">אותה הרשת האלחוטית</string>
|
||||||
<string name="lan_device_status_on">הטלפון שלך מחובר אל Wi-Fi</string>
|
<string name="lan_device_status_on">הטלפון שלך מחובר לרשת אלחוטית</string>
|
||||||
<string name="lan_device_status_off">הטלפון שלך אינו מחובר אל Wi-Fi</string>
|
<string name="lan_device_status_off">הטלפון שלך אינו מחובר לרשת האלחוטית</string>
|
||||||
<string name="lan_plugin_status_enabling">Briar מתחבר אל רשת ה־Wi-Fi</string>
|
<string name="lan_plugin_status_enabling">Briar מתחבר לרשת האלחוטית</string>
|
||||||
<string name="lan_plugin_status_active">Briar מחובר אל רשת ה־Wi-Fi</string>
|
<string name="lan_plugin_status_active">Briar מחובר לרשת האלחוטית</string>
|
||||||
<string name="lan_plugin_status_inactive">Briar אינו יכול להתחבר אל רשת ה־Wi-Fi</string>
|
<string name="lan_plugin_status_inactive">אין באפשרות Briar להתחבר לרשת האלחוטית</string>
|
||||||
<string name="lan_plugin_status_disabled">Briar מתוצר לא להשתמש ברשת ה־Wi-Fi</string>
|
<string name="lan_plugin_status_disabled">Briar מוגדר לא להשתמש ברשת האלחוטית</string>
|
||||||
<!--Transports: Bluetooth-->
|
<!--Transports: Bluetooth-->
|
||||||
<string name="transport_bt">Bluetooth</string>
|
<string name="transport_bt">Bluetooth</string>
|
||||||
<string name="bt_device_status_on">Bluetooth של הטלפון שלך מופעל</string>
|
<string name="bt_device_status_on">Bluetooth של הטלפון שלך מופעל</string>
|
||||||
@@ -128,25 +128,25 @@
|
|||||||
<string name="now">עכשיו</string>
|
<string name="now">עכשיו</string>
|
||||||
<string name="show">הראה</string>
|
<string name="show">הראה</string>
|
||||||
<string name="hide">הסתר</string>
|
<string name="hide">הסתר</string>
|
||||||
<string name="ok">אשר</string>
|
<string name="ok">אישור</string>
|
||||||
<string name="cancel">בטל</string>
|
<string name="cancel">ביטול</string>
|
||||||
<string name="got_it">הבנתי</string>
|
<string name="got_it">הבנתי</string>
|
||||||
<string name="delete">מחק</string>
|
<string name="delete">מחיקה</string>
|
||||||
<string name="accept">קבל</string>
|
<string name="accept">קבל</string>
|
||||||
<string name="decline">סרב</string>
|
<string name="decline">סרב</string>
|
||||||
<string name="online">מקוון</string>
|
<string name="online">מחובר</string>
|
||||||
<string name="offline">לא מקוון</string>
|
<string name="offline">לא מקוון</string>
|
||||||
<string name="send">שלח</string>
|
<string name="send">שליחה</string>
|
||||||
<string name="allow">התר</string>
|
<string name="allow">התר</string>
|
||||||
<string name="open">פתח</string>
|
<string name="open">פתיחה</string>
|
||||||
<string name="change">שנה</string>
|
<string name="change">שינוי</string>
|
||||||
<string name="no_data">אין נתונים</string>
|
<string name="no_data">אין נתונים</string>
|
||||||
<string name="ellipsis">…</string>
|
<string name="ellipsis">…</string>
|
||||||
<string name="text_too_long">הטקסט המוכנס ארוך מדי</string>
|
<string name="text_too_long">הטקסט המוכנס ארוך מדי</string>
|
||||||
<string name="show_onboarding">הראה דו־שיח עזרה</string>
|
<string name="show_onboarding">הצגת דו־שיח עזרה</string>
|
||||||
<string name="fix">תקן</string>
|
<string name="fix">תקן</string>
|
||||||
<string name="help">עזרה</string>
|
<string name="help">עזרה</string>
|
||||||
<string name="sorry">סליחה</string>
|
<string name="sorry">עמך הסליחה</string>
|
||||||
<string name="error_start_activity">בלתי זמין במערכת שלך</string>
|
<string name="error_start_activity">בלתי זמין במערכת שלך</string>
|
||||||
<string name="status_heading">מצב</string>
|
<string name="status_heading">מצב</string>
|
||||||
<!--Contacts and Private Conversations-->
|
<!--Contacts and Private Conversations-->
|
||||||
@@ -162,41 +162,41 @@
|
|||||||
<string name="image_attach_error_invalid_mime_type">תסדיר תמונה בלתי נתמך: %s</string>
|
<string name="image_attach_error_invalid_mime_type">תסדיר תמונה בלתי נתמך: %s</string>
|
||||||
<string name="set_contact_alias">שַׁנֵּה שם איש קשר</string>
|
<string name="set_contact_alias">שַׁנֵּה שם איש קשר</string>
|
||||||
<string name="set_contact_alias_hint">שם איש הקשר</string>
|
<string name="set_contact_alias_hint">שם איש הקשר</string>
|
||||||
<string name="delete_all_messages">מחק את כל ההודעות</string>
|
<string name="delete_all_messages">מחיקת כל ההודעות</string>
|
||||||
<string name="dialog_title_delete_all_messages">אשר מחיקת הודעה</string>
|
<string name="dialog_title_delete_all_messages">אשר מחיקת הודעה</string>
|
||||||
<string name="dialog_message_delete_all_messages">האם אתה בטוח שאתה רוצה למחוק את כל ההודעות?</string>
|
<string name="dialog_message_delete_all_messages">האם אכן ברצונך למחוק את כל ההודעות?</string>
|
||||||
<string name="dialog_title_not_all_messages_deleted">לא היה ניתן למחוק את כל ההודעות</string>
|
<string name="dialog_title_not_all_messages_deleted">לא היה ניתן למחוק את כל ההודעות</string>
|
||||||
<string name="dialog_message_not_deleted_ongoing_both">הודעות שקשורות אל הזמנות והיכרויות מתמשכות אינן יכולות להימחק עד שההיכרויות או ההזמנות יסוימו.</string>
|
<string name="dialog_message_not_deleted_ongoing_both">הודעות שקשורות אל הזמנות והיכרויות מתמשכות אינן יכולות להימחק עד שההיכרויות או ההזמנות יסוימו.</string>
|
||||||
<string name="dialog_message_not_deleted_ongoing_introductions">הודעות שקשורות אל היכרויות מתמשכות אינן יכולות להימחק עד שהההיכרויות יסוימו.</string>
|
<string name="dialog_message_not_deleted_ongoing_introductions">הודעות שקשורות אל היכרויות מתמשכות אינן יכולות להימחק עד שהההיכרויות יסוימו.</string>
|
||||||
<string name="dialog_message_not_deleted_ongoing_invitations">הודעות שקשורות אל הזמנות מתמשכות אינן יכולות להימחק עד שההזמנות יסוימו.</string>
|
<string name="dialog_message_not_deleted_ongoing_invitations">הודעות שקשורות אל הזמנות מתמשכות אינן יכולות להימחק עד שההזמנות יסוימו.</string>
|
||||||
<string name="dialog_message_not_deleted_partly_downloaded">הודעות שירדו חלקית אינן יכולות להימחק עד שהן יסיימו לרדת.</string>
|
<string name="dialog_message_not_deleted_partly_downloaded">הודעות שירדו חלקית אינן יכולות להימחק עד שהן יסיימו לרדת.</string>
|
||||||
<string name="dialog_message_not_deleted_not_all_selected_both">כדי למחוק הזמנה או היכרות, אתה צריך לבחור את הבקשה והתגובה.</string>
|
<string name="dialog_message_not_deleted_not_all_selected_both">כדי למחוק הזמנה או היכרות, צריך לבחור את הבקשה והתגובה.</string>
|
||||||
<string name="dialog_message_not_deleted_not_all_selected_introductions">כדי למחוק את ההיכרות, אתה צריך לבחור את הבקשה והתגובה.</string>
|
<string name="dialog_message_not_deleted_not_all_selected_introductions">כדי למחוק את ההיכרות, צריך לבחור את הבקשה והתגובה.</string>
|
||||||
<string name="dialog_message_not_deleted_not_all_selected_invitations">כדי למחוק את ההזמנה, אתה צריך לבחור את הבקשה והתגובה.</string>
|
<string name="dialog_message_not_deleted_not_all_selected_invitations">כדי למחוק את ההזמנה, צריך לבחור את הבקשה והתגובה.</string>
|
||||||
<string name="delete_contact">מחק איש קשר</string>
|
<string name="delete_contact">מחק איש קשר</string>
|
||||||
<string name="dialog_title_delete_contact">אשר מחיקת איש קשר</string>
|
<string name="dialog_title_delete_contact">אשר מחיקת איש קשר</string>
|
||||||
<string name="dialog_message_delete_contact">האם אתה בטוח שאתה רוצה למחוק איש קשר זה ואת כל ההודעות שהוחלפו עם איש קשר זה?</string>
|
<string name="dialog_message_delete_contact">האם אכן ברצונך למחוק איש קשר זה ואת כל ההודעות שהוחלפו עם איש קשר זה?</string>
|
||||||
<string name="contact_deleted_toast">איש קשר נמחק</string>
|
<string name="contact_deleted_toast">איש קשר נמחק</string>
|
||||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||||
<string name="you">אתה</string>
|
<string name="you">את/ה</string>
|
||||||
<string name="save_image">שמור תמונה</string>
|
<string name="save_image">שמור תמונה</string>
|
||||||
<string name="dialog_title_save_image">לשמור תמונה?</string>
|
<string name="dialog_title_save_image">לשמור את התמונה?</string>
|
||||||
<string name="dialog_message_save_image">שמירת תמונה זו תאפשר ליישומים להשיג גישה אל התמונה.\n\nהאם אתה בטוח שאתה רוצה לשמור?</string>
|
<string name="dialog_message_save_image">שמירת תמונה זו תאפשר ליישומים אחרים לגשת אליה.\n\nהאם אכן ברצונך לשמור?</string>
|
||||||
<string name="save_image_success">תמונה נשמרה</string>
|
<string name="save_image_success">התמונה נשמרה</string>
|
||||||
<string name="save_image_error">לא היה ניתן לשמור תמונה</string>
|
<string name="save_image_error">לא היה ניתן לשמור תמונה</string>
|
||||||
<string name="dialog_title_no_image_support">תמונות בלתי זמינות</string>
|
<string name="dialog_title_no_image_support">תמונות בלתי זמינות</string>
|
||||||
<string name="dialog_message_no_image_support">Briar של איש הקשר שלך אינו תומך בצרופות תמונה. ברגע שהוא ישדרג, תראה צלמית שונה.</string>
|
<string name="dialog_message_no_image_support">Briar של איש הקשר שלך אינו תומך בצרופות תמונה. ברגע שהוא ישדרג, תראה צלמית שונה.</string>
|
||||||
<string name="dialog_title_image_support">אתה יכול כעת לשלוח תמונות אל איש קשר זה</string>
|
<string name="dialog_title_image_support">כעת אפשר לשלוח תמונות אל איש קשר זה</string>
|
||||||
<string name="dialog_message_image_support">הקש על צלמית זו כדי לצרף תמונות.</string>
|
<string name="dialog_message_image_support">הקש על צלמית זו כדי לצרף תמונות.</string>
|
||||||
<string name="messaging_too_many_attachments_toast">רק %d התמונות הראשונות יישלחו</string>
|
<string name="messaging_too_many_attachments_toast">רק %d התמונות הראשונות יישלחו</string>
|
||||||
<!--Adding Contacts-->
|
<!--Adding Contacts-->
|
||||||
<string name="add_contact_title">הוסף איש קשר בקרבה</string>
|
<string name="add_contact_title">הוסף איש קשר בקרבה</string>
|
||||||
<string name="face_to_face">אתה חייב להיפגש עם האדם שאותו אתה רוצה להוסיף כאיש קשר.\n\nזה ימנע מכל אחד להתחזות אליך או לקרוא את ההודעות שלך בעתיד.</string>
|
<string name="face_to_face">אתה חייב להיפגש עם האדם שאותו אתה רוצה להוסיף כאיש קשר.\n\nזה ימנע מכל אחד להתחזות אליך או לקרוא את ההודעות שלך בעתיד.</string>
|
||||||
<string name="continue_button">המשך</string>
|
<string name="continue_button">המשך</string>
|
||||||
<string name="try_again_button">נסה שוב</string>
|
<string name="try_again_button">ניסיון חוזר</string>
|
||||||
<string name="waiting_for_contact_to_scan">ממתין שאיש הקשר יסרוק ויתחבר\u2026</string>
|
<string name="waiting_for_contact_to_scan">ממתין שאיש הקשר יסרוק ויתחבר\u2026</string>
|
||||||
<string name="exchanging_contact_details">מחליף פרטי איש קשר\u2026</string>
|
<string name="exchanging_contact_details">מחליף פרטי איש קשר\u2026</string>
|
||||||
<string name="contact_added_toast">איש קשר התווסף: %s</string>
|
<string name="contact_added_toast">נוסף איש קשר: %s</string>
|
||||||
<string name="contact_already_exists">איש הקשר %s קיים כבר</string>
|
<string name="contact_already_exists">איש הקשר %s קיים כבר</string>
|
||||||
<string name="qr_code_invalid">קוד ה־QR אינו תקף</string>
|
<string name="qr_code_invalid">קוד ה־QR אינו תקף</string>
|
||||||
<string name="qr_code_too_old">קוד ה־QR שסרקת מגיע מגרסה ישנה יותר של %s.\n\nאנא בקש מאיש הקשר שלך לשדרג את הגרסה האחרונה ואז נסה שוב.</string>
|
<string name="qr_code_too_old">קוד ה־QR שסרקת מגיע מגרסה ישנה יותר של %s.\n\nאנא בקש מאיש הקשר שלך לשדרג את הגרסה האחרונה ואז נסה שוב.</string>
|
||||||
@@ -215,19 +215,19 @@
|
|||||||
<string name="paste_button">הדבק</string>
|
<string name="paste_button">הדבק</string>
|
||||||
<string name="add_contact_button">הוסף איש קשר</string>
|
<string name="add_contact_button">הוסף איש קשר</string>
|
||||||
<string name="copy_button">העתק</string>
|
<string name="copy_button">העתק</string>
|
||||||
<string name="share_button">שתף</string>
|
<string name="share_button">שיתוף</string>
|
||||||
<string name="send_link_title">החלף קישורים</string>
|
<string name="send_link_title">החלף קישורים</string>
|
||||||
<string name="add_contact_choose_nickname">בחר כינוי</string>
|
<string name="add_contact_choose_nickname">בחר כינוי</string>
|
||||||
<string name="add_contact_choose_a_nickname">הכנס כינוי</string>
|
<string name="add_contact_choose_a_nickname">הכנס כינוי</string>
|
||||||
<string name="nickname_intro">תן כינוי אל איש הקשר שלך. רק אתה יכול לראות אותו.</string>
|
<string name="nickname_intro">יש לתת את הכינוי לאיש הקשר שלך. רק את/ה יכול/ה לראות אותו.</string>
|
||||||
<string name="your_link">תן את הקישור הזה לאיש הקשר שאתה רוצה להוסיף</string>
|
<string name="your_link">יש לתת את הקישור הזה לאיש הקשר שברצונך להוסיף</string>
|
||||||
<string name="link_clip_label">קישור Briar</string>
|
<string name="link_clip_label">קישור Briar</string>
|
||||||
<string name="link_copied_toast">קישור הועתק</string>
|
<string name="link_copied_toast">קישור הועתק</string>
|
||||||
<string name="adding_contact_error">הייתה שגיאה בהוספת איש הקשר.</string>
|
<string name="adding_contact_error">הייתה שגיאה בהוספת איש הקשר.</string>
|
||||||
<string name="pending_contact_requests_snackbar">יש בקשות ממתינות של אנשי קשר</string>
|
<string name="pending_contact_requests_snackbar">יש בקשות ממתינות של אנשי קשר</string>
|
||||||
<string name="pending_contact_requests">בקשות ממתינות של אנשי קשר</string>
|
<string name="pending_contact_requests">בקשות ממתינות של אנשי קשר</string>
|
||||||
<string name="no_pending_contacts">אין בקשות ממתינות של אנשי קשר</string>
|
<string name="no_pending_contacts">אין בקשות ממתינות של אנשי קשר</string>
|
||||||
<string name="waiting_for_contact_to_come_online">ממתין אל איש הקשר שיהיה מקוון…</string>
|
<string name="waiting_for_contact_to_come_online">ממתין להתחברות איש הקשר…</string>
|
||||||
<string name="connecting">מתחבר…</string>
|
<string name="connecting">מתחבר…</string>
|
||||||
<string name="adding_contact">מוסיף איש קשר…</string>
|
<string name="adding_contact">מוסיף איש קשר…</string>
|
||||||
<string name="adding_contact_failed">הוספת איש קשר נכשלה</string>
|
<string name="adding_contact_failed">הוספת איש קשר נכשלה</string>
|
||||||
@@ -237,7 +237,7 @@
|
|||||||
<string name="nickname_missing">אנא הכנס כינוי</string>
|
<string name="nickname_missing">אנא הכנס כינוי</string>
|
||||||
<string name="invalid_link">קישור בלתי תקף</string>
|
<string name="invalid_link">קישור בלתי תקף</string>
|
||||||
<string name="unsupported_link">קישור זה מגיע מגרסה חדשה יותר של Briar. אנא שדרג אל הגרסה האחרונה ונסה שוב.</string>
|
<string name="unsupported_link">קישור זה מגיע מגרסה חדשה יותר של Briar. אנא שדרג אל הגרסה האחרונה ונסה שוב.</string>
|
||||||
<string name="intent_own_link">פתחת את הקישור של עצמך. השתמש באחד של איש הקשר שאתה רוצה להוסיף!</string>
|
<string name="intent_own_link">פתחת את הקישור של עצמך. יש להשתמש בקישור של איש הקשר שברצונך להוסיף!</string>
|
||||||
<string name="missing_link">אנא הכנס קישור</string>
|
<string name="missing_link">אנא הכנס קישור</string>
|
||||||
<!--This is a numeral indicating the first step in a series of screens-->
|
<!--This is a numeral indicating the first step in a series of screens-->
|
||||||
<string name="step_1">1</string>
|
<string name="step_1">1</string>
|
||||||
@@ -267,10 +267,10 @@
|
|||||||
<string name="pending_contact_updated_toast">איש קשר ממתין עודכן</string>
|
<string name="pending_contact_updated_toast">איש קשר ממתין עודכן</string>
|
||||||
<!--Introductions-->
|
<!--Introductions-->
|
||||||
<string name="introduction_onboarding_title">הכר בין אנשי הקשר שלך</string>
|
<string name="introduction_onboarding_title">הכר בין אנשי הקשר שלך</string>
|
||||||
<string name="introduction_onboarding_text">אתה יכול להכיר בין אנשי הקשר שלך אחד עם השני, כך שהם לא צריכים להיפגש פנים מול פנים כדי להתחבר ב־Briar.</string>
|
<string name="introduction_onboarding_text">אפשר להכיר בין אנשי הקשר שלך אחד עם השני, כך שהם לא צריכים להיפגש פנים מול פנים כדי להתחבר ב־Briar.</string>
|
||||||
<string name="introduction_menu_item">בצע היכרות</string>
|
<string name="introduction_menu_item">בצע היכרות</string>
|
||||||
<string name="introduction_activity_title">בחר איש קשר</string>
|
<string name="introduction_activity_title">בחר איש קשר</string>
|
||||||
<string name="introduction_not_possible">יש לך כבר היכרות אחת בתהליך עם אנשי קשר אלו. אנא התר לה תחילה לסיים. אם אתה או אנשי הקשר שלך לעיתים נדירות מקוונים, זה עשוי לקחת זמן מה.</string>
|
<string name="introduction_not_possible">יש לך כבר היכרות אחת בתהליך עם אנשי קשר אלו. אנא התר לה תחילה לסיים. אם אתה או אנשי הקשר שלך לעיתים נדירות מחוברים, זה עשוי לקחת זמן מה.</string>
|
||||||
<string name="introduction_message_title">הכר בין אנשי קשר</string>
|
<string name="introduction_message_title">הכר בין אנשי קשר</string>
|
||||||
<string name="introduction_message_hint">הוסף הודעה (רשותי)</string>
|
<string name="introduction_message_hint">הוסף הודעה (רשותי)</string>
|
||||||
<string name="introduction_button">בצע היכרות</string>
|
<string name="introduction_button">בצע היכרות</string>
|
||||||
@@ -278,7 +278,7 @@
|
|||||||
<string name="introduction_error">הייתה שגיאה בעת ביצוע ההיכרות.</string>
|
<string name="introduction_error">הייתה שגיאה בעת ביצוע ההיכרות.</string>
|
||||||
<string name="introduction_request_sent">ביקשת להכיר את %1$s בפני %2$s.</string>
|
<string name="introduction_request_sent">ביקשת להכיר את %1$s בפני %2$s.</string>
|
||||||
<string name="introduction_request_received">%1$s ביקש להכיר אותך בפני %2$s. האם אתה רוצה להוסיף את %2$s אל רשימת אנשי הקשר שלך?</string>
|
<string name="introduction_request_received">%1$s ביקש להכיר אותך בפני %2$s. האם אתה רוצה להוסיף את %2$s אל רשימת אנשי הקשר שלך?</string>
|
||||||
<string name="introduction_request_exists_received">%1$s ביקש להכיר אותך בפני %2$s, אבל %2$s כבר ברשימת אנשי הקשר שלך. מאחר ש%1$s כנראה לא יודע זאת, אתה עדיין יכול להגיב:</string>
|
<string name="introduction_request_exists_received">%1$s ביקש להכיר אותך בפני %2$s, אבל %2$s כבר ברשימת אנשי הקשר שלך. מאחר שכנראה %1$s לא יודע זאת, עדיין אפשר להגיב:</string>
|
||||||
<string name="introduction_request_answered_received">%1$s ביקש להכיר אותך בפני %2$s.</string>
|
<string name="introduction_request_answered_received">%1$s ביקש להכיר אותך בפני %2$s.</string>
|
||||||
<string name="introduction_response_accepted_sent">הסכמת אל ההיכרות בפני %1$s.</string>
|
<string name="introduction_response_accepted_sent">הסכמת אל ההיכרות בפני %1$s.</string>
|
||||||
<string name="introduction_response_accepted_sent_info">בטרם %1$s יוכל להתווסף אל אנשי הקשר שלך, הוא צריך לקבל את ההיכרות בנוסף. זה עשוי לקחת זמן מה.</string>
|
<string name="introduction_response_accepted_sent_info">בטרם %1$s יוכל להתווסף אל אנשי הקשר שלך, הוא צריך לקבל את ההיכרות בנוסף. זה עשוי לקחת זמן מה.</string>
|
||||||
@@ -302,7 +302,7 @@
|
|||||||
<string name="groups_create_group_title">צור קבוצה פרטית</string>
|
<string name="groups_create_group_title">צור קבוצה פרטית</string>
|
||||||
<string name="groups_create_group_button">צור קבוצה</string>
|
<string name="groups_create_group_button">צור קבוצה</string>
|
||||||
<string name="groups_create_group_invitation_button">שלח הזמנה</string>
|
<string name="groups_create_group_invitation_button">שלח הזמנה</string>
|
||||||
<string name="groups_create_group_hint">בחר שם לקבוצה הפרטית שלך</string>
|
<string name="groups_create_group_hint">נא לבחור שם לקבוצה הפרטית שלך</string>
|
||||||
<string name="groups_invitation_sent">הזמנה קבוצתית נשלחה</string>
|
<string name="groups_invitation_sent">הזמנה קבוצתית נשלחה</string>
|
||||||
<string name="groups_member_list">רשימת חברי קבוצה</string>
|
<string name="groups_member_list">רשימת חברי קבוצה</string>
|
||||||
<string name="groups_invite_members">הזמן חברי קבוצה</string>
|
<string name="groups_invite_members">הזמן חברי קבוצה</string>
|
||||||
@@ -312,13 +312,13 @@
|
|||||||
<string name="groups_member_joined">%s הצטרף אל הקבוצה</string>
|
<string name="groups_member_joined">%s הצטרף אל הקבוצה</string>
|
||||||
<string name="groups_leave">עזוב קבוצה</string>
|
<string name="groups_leave">עזוב קבוצה</string>
|
||||||
<string name="groups_leave_dialog_title">אשר עזיבת קבוצה</string>
|
<string name="groups_leave_dialog_title">אשר עזיבת קבוצה</string>
|
||||||
<string name="groups_leave_dialog_message">האם אתה בטוח שאתה רוצה לעזוב קבוצה זו?</string>
|
<string name="groups_leave_dialog_message">האם אכן ברצונך לעזוב קבוצה זו?</string>
|
||||||
<string name="groups_dissolve">פרק קבוצה</string>
|
<string name="groups_dissolve">פרק קבוצה</string>
|
||||||
<string name="groups_dissolve_dialog_title">אשר פירוק קבוצה</string>
|
<string name="groups_dissolve_dialog_title">אשר פירוק קבוצה</string>
|
||||||
<string name="groups_dissolve_dialog_message">האם אתה בטוח שאתה רוצה לפרק את הקבוצה הזאתו?\n\nכל חברי הקבוצה האחרים לא יוכלו להמשיך בשיחתם וכנראה לא יקבלו את ההודעות האחרונות.</string>
|
<string name="groups_dissolve_dialog_message">האם אכן ברצונך לפרק את הקבוצה הזאת?\n\nכל חברי הקבוצה האחרים לא יוכלו להמשיך בשיחתם וכנראה לא יקבלו את ההודעות האחרונות.</string>
|
||||||
<string name="groups_dissolve_button">פרק</string>
|
<string name="groups_dissolve_button">פרק</string>
|
||||||
<string name="groups_dissolved_dialog_title">הקבוצה פורקה</string>
|
<string name="groups_dissolved_dialog_title">הקבוצה פורקה</string>
|
||||||
<string name="groups_dissolved_dialog_message">היוצר של הקבוצה הזאת פירק אותה.\n\nאתה לא יכול עוד לכתוב הודעות אל הקבוצה וכנראה שלא תקבל את כל הרשומות שנכתבו.</string>
|
<string name="groups_dissolved_dialog_message">היוצר של הקבוצה הזאת פירק אותה.\n\nאי אפשר לכתוב הודעות לקבוצה יותר וכנראה שלא ייתקבלו כל הרשומות שנכתבו.</string>
|
||||||
<!--Private Group Invitations-->
|
<!--Private Group Invitations-->
|
||||||
<string name="groups_invitations_title">הזמנות לקבוצה</string>
|
<string name="groups_invitations_title">הזמנות לקבוצה</string>
|
||||||
<string name="groups_invitations_invitation_sent">הזמנת את %1$s להצטרף אל הקבוצה \"%2$s\".</string>
|
<string name="groups_invitations_invitation_sent">הזמנת את %1$s להצטרף אל הקבוצה \"%2$s\".</string>
|
||||||
@@ -365,8 +365,8 @@
|
|||||||
<string name="btn_reply">השב</string>
|
<string name="btn_reply">השב</string>
|
||||||
<string name="forum_leave">עזוב פורום</string>
|
<string name="forum_leave">עזוב פורום</string>
|
||||||
<string name="dialog_title_leave_forum">אשר עזיבת פורום</string>
|
<string name="dialog_title_leave_forum">אשר עזיבת פורום</string>
|
||||||
<string name="dialog_message_leave_forum">האם אתה בטוח שאתה רוצה לעזוב פורום זה?\n\nאנשי קשר כלשהם ששיתפת איתם פורום זה עשויים להפסיק לקבל עדכונים.</string>
|
<string name="dialog_message_leave_forum">האם אכן ברצונך לעזוב פורום זה?\n\nכל אנשי הקשר ששיתפת איתם פורום זה עשויים להפסיק לקבל עדכונים.</string>
|
||||||
<string name="dialog_button_leave">עזוב</string>
|
<string name="dialog_button_leave">עזיבה</string>
|
||||||
<string name="forum_left_toast">עזב פורום</string>
|
<string name="forum_left_toast">עזב פורום</string>
|
||||||
<!--Forum Sharing-->
|
<!--Forum Sharing-->
|
||||||
<string name="forum_share_button">שתף פורום</string>
|
<string name="forum_share_button">שתף פורום</string>
|
||||||
@@ -391,7 +391,7 @@
|
|||||||
<string name="forum_invitation_response_declined_received">%s סירב אל ההזמנה לפורום.</string>
|
<string name="forum_invitation_response_declined_received">%s סירב אל ההזמנה לפורום.</string>
|
||||||
<string name="sharing_status">מעמד שיתוף</string>
|
<string name="sharing_status">מעמד שיתוף</string>
|
||||||
<string name="sharing_status_forum">כל חבר פורום יכול לשתף את הפורום עם אנשי הקשר שלו. אתה משתף פורום זה עם אנשי הקשר הבאים. יתכן שיש חברי פורום אחרים שאינך יכול לראות.</string>
|
<string name="sharing_status_forum">כל חבר פורום יכול לשתף את הפורום עם אנשי הקשר שלו. אתה משתף פורום זה עם אנשי הקשר הבאים. יתכן שיש חברי פורום אחרים שאינך יכול לראות.</string>
|
||||||
<string name="shared_with">משותף עם %1$d (%2$d מקוונים)</string>
|
<string name="shared_with">משותף עם %1$d (%2$d מחוברים)</string>
|
||||||
<plurals name="forums_shared">
|
<plurals name="forums_shared">
|
||||||
<item quantity="one">פורום %d משותף ע״י אנשי קשר</item>
|
<item quantity="one">פורום %d משותף ע״י אנשי קשר</item>
|
||||||
<item quantity="two">%d פורומים משותפים ע״י אנשי קשר</item>
|
<item quantity="two">%d פורומים משותפים ע״י אנשי קשר</item>
|
||||||
@@ -404,7 +404,7 @@
|
|||||||
<string name="read_more">קרא עוד</string>
|
<string name="read_more">קרא עוד</string>
|
||||||
<string name="blogs_write_blog_post">כתוב רשומת בלוג</string>
|
<string name="blogs_write_blog_post">כתוב רשומת בלוג</string>
|
||||||
<string name="blogs_write_blog_post_body_hint">הקלד את רשומת הבלוג שלך</string>
|
<string name="blogs_write_blog_post_body_hint">הקלד את רשומת הבלוג שלך</string>
|
||||||
<string name="blogs_publish_blog_post">פרסם</string>
|
<string name="blogs_publish_blog_post">פרסום</string>
|
||||||
<string name="blogs_blog_post_created">רשומת בלוג נוצרה</string>
|
<string name="blogs_blog_post_created">רשומת בלוג נוצרה</string>
|
||||||
<string name="blogs_blog_post_received">רשומת בלוג חדשה התקבלה</string>
|
<string name="blogs_blog_post_received">רשומת בלוג חדשה התקבלה</string>
|
||||||
<string name="blogs_blog_post_scroll_to">גלול אל</string>
|
<string name="blogs_blog_post_scroll_to">גלול אל</string>
|
||||||
@@ -416,7 +416,7 @@
|
|||||||
<string name="blogs_remove_blog_dialog_message">האם אתה בטוח שאתה רוצה להסיר בלוג זה?\n\nרשומות יוסרו ממכשירך אבל לא ממכשירים של אנשים אחרים.\n\nאנשי קשר כלשהם ששיתפת איתם בלוג זה עלולים להפסיק לקבל עדכונים.</string>
|
<string name="blogs_remove_blog_dialog_message">האם אתה בטוח שאתה רוצה להסיר בלוג זה?\n\nרשומות יוסרו ממכשירך אבל לא ממכשירים של אנשים אחרים.\n\nאנשי קשר כלשהם ששיתפת איתם בלוג זה עלולים להפסיק לקבל עדכונים.</string>
|
||||||
<string name="blogs_remove_blog_ok">הסר</string>
|
<string name="blogs_remove_blog_ok">הסר</string>
|
||||||
<string name="blogs_blog_removed">בלוג הוסר</string>
|
<string name="blogs_blog_removed">בלוג הוסר</string>
|
||||||
<string name="blogs_reblog_comment_hint">הוסף תגובה (רשותי)</string>
|
<string name="blogs_reblog_comment_hint">הוספת תגובה (רשות)</string>
|
||||||
<string name="blogs_reblog_button">פרסם מחדש</string>
|
<string name="blogs_reblog_button">פרסם מחדש</string>
|
||||||
<!--Blog Sharing-->
|
<!--Blog Sharing-->
|
||||||
<string name="blogs_sharing_share">שתף בלוג</string>
|
<string name="blogs_sharing_share">שתף בלוג</string>
|
||||||
@@ -443,7 +443,7 @@
|
|||||||
<string name="blogs_rss_feeds_manage_author">מחבר:</string>
|
<string name="blogs_rss_feeds_manage_author">מחבר:</string>
|
||||||
<string name="blogs_rss_feeds_manage_updated">עודכן לאחרונה:</string>
|
<string name="blogs_rss_feeds_manage_updated">עודכן לאחרונה:</string>
|
||||||
<string name="blogs_rss_remove_feed">הסר הזנה</string>
|
<string name="blogs_rss_remove_feed">הסר הזנה</string>
|
||||||
<string name="blogs_rss_remove_feed_dialog_message">האם אתה בטוח שאתה רוצה להסיר הזנה זו?\n\nרשומות יוסרו ממכשירך אבל לא ממכשירים של אנשים אחרים.\n\nאנשי קשר כלשהם ששיתפת איתם הזנה זו עלולים להפסיק לקבל עדכונים. </string>
|
<string name="blogs_rss_remove_feed_dialog_message">האם אכן ברצונך להסיר הזנה זו?\n\nרשומות יוסרו ממכשירך אבל לא ממכשירים של אנשים אחרים.\n\nאנשי קשר כלשהם ששיתפת איתם הזנה זו עלולים להפסיק לקבל עדכונים.</string>
|
||||||
<string name="blogs_rss_remove_feed_ok">הסר</string>
|
<string name="blogs_rss_remove_feed_ok">הסר</string>
|
||||||
<string name="blogs_rss_feeds_manage_delete_error">ההזנה לא יכלה להימחק!</string>
|
<string name="blogs_rss_feeds_manage_delete_error">ההזנה לא יכלה להימחק!</string>
|
||||||
<string name="blogs_rss_feeds_manage_empty_state">אין הזנות RSS להראות\n\nהקש על הצלמית + כדי לייבא הזנה</string>
|
<string name="blogs_rss_feeds_manage_empty_state">אין הזנות RSS להראות\n\nהקש על הצלמית + כדי לייבא הזנה</string>
|
||||||
@@ -451,7 +451,7 @@
|
|||||||
<!--Settings Profile Picture-->
|
<!--Settings Profile Picture-->
|
||||||
<string name="change_profile_picture">הקש כדי לשנות את תמונת הפרופיל שלך</string>
|
<string name="change_profile_picture">הקש כדי לשנות את תמונת הפרופיל שלך</string>
|
||||||
<string name="dialog_confirm_profile_picture_title">שנה תמונת פרופיל</string>
|
<string name="dialog_confirm_profile_picture_title">שנה תמונת פרופיל</string>
|
||||||
<string name="dialog_confirm_profile_picture_remark">רק אנשי הקשר שלך יכולים לראות את תמונת הפרופיל שלך</string>
|
<string name="dialog_confirm_profile_picture_remark">רק אנשי הקשר שלך יכולים לראות את התמונה הזו</string>
|
||||||
<string name="change_profile_picture_failed_message">אנו מצטערים משהו השתבש בעת עדכון תמונת הפרופיל שלך</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>
|
||||||
@@ -466,7 +466,7 @@
|
|||||||
<!--Settings Connections-->
|
<!--Settings Connections-->
|
||||||
<string name="network_settings_title">חיבורים</string>
|
<string name="network_settings_title">חיבורים</string>
|
||||||
<string name="bluetooth_setting">התחבר אל אנשי קשר באמצעות Bluetooth</string>
|
<string name="bluetooth_setting">התחבר אל אנשי קשר באמצעות Bluetooth</string>
|
||||||
<string name="wifi_setting">התחבר אל אנשי קשר באותה רשת Wi-Fi</string>
|
<string name="wifi_setting">התחבר אל אנשי קשר באותה הרשת האלחוטית</string>
|
||||||
<string name="tor_enable_title">התחבר אל אנשי קשר באמצעות האינטרנט</string>
|
<string name="tor_enable_title">התחבר אל אנשי קשר באמצעות האינטרנט</string>
|
||||||
<string name="tor_enable_summary">כל החיבורים עוברים דרך רשת Tor למען פרטיות</string>
|
<string name="tor_enable_summary">כל החיבורים עוברים דרך רשת Tor למען פרטיות</string>
|
||||||
<string name="tor_network_setting">שיטת חיבור עבור רשת Tor</string>
|
<string name="tor_network_setting">שיטת חיבור עבור רשת Tor</string>
|
||||||
@@ -499,7 +499,7 @@
|
|||||||
<string name="pref_lock_timeout_60">שעה 1</string>
|
<string name="pref_lock_timeout_60">שעה 1</string>
|
||||||
<string name="pref_lock_timeout_never">לעולם לא</string>
|
<string name="pref_lock_timeout_never">לעולם לא</string>
|
||||||
<string name="pref_lock_timeout_never_summary">לעולם אל תנעל את Briar באופן אוטומטי</string>
|
<string name="pref_lock_timeout_never_summary">לעולם אל תנעל את Briar באופן אוטומטי</string>
|
||||||
<string name="change_password">שנה סיסמה</string>
|
<string name="change_password">שינוי הסיסמה</string>
|
||||||
<string name="current_password">סיסמה נוכחית</string>
|
<string name="current_password">סיסמה נוכחית</string>
|
||||||
<string name="choose_new_password">סיסמה חדשה</string>
|
<string name="choose_new_password">סיסמה חדשה</string>
|
||||||
<string name="confirm_new_password">אשר סיסמה חדשה</string>
|
<string name="confirm_new_password">אשר סיסמה חדשה</string>
|
||||||
@@ -545,18 +545,18 @@
|
|||||||
<string name="send_feedback">שלח משוב</string>
|
<string name="send_feedback">שלח משוב</string>
|
||||||
<!--Link Warning-->
|
<!--Link Warning-->
|
||||||
<string name="link_warning_title">אזהרת קישור</string>
|
<string name="link_warning_title">אזהרת קישור</string>
|
||||||
<string name="link_warning_intro">אתה עומד לפתוח את הקישור הבא עם יישום חיצוני.</string>
|
<string name="link_warning_intro">הקישור הבא עומד להיפתח עם יישום חיצוני.</string>
|
||||||
<string name="link_warning_text">זה יכול לשמש כדי לזהות אותך. חשוב על האם אתה בוטח באיש ששלח לך קישור זה ושקול לפתוח את הקישור עם דפדפן Tor.</string>
|
<string name="link_warning_text">זה יכול לשמש כדי לזהות אותך. נא לחשוב האם הנך בוטח/ת באיש ששלח לך את הקישור הזה ולשקול לפתוח את הקישור עם דפדפן Tor.</string>
|
||||||
<string name="link_warning_open_link">פתח קישור</string>
|
<string name="link_warning_open_link">פתח קישור</string>
|
||||||
<!--Crash Reporter-->
|
<!--Crash Reporter-->
|
||||||
<string name="crash_report_title">דוח קריסת Briar</string>
|
<string name="crash_report_title">דוח קריסת Briar</string>
|
||||||
<string name="briar_crashed">סליחה, Briar קרס.</string>
|
<string name="briar_crashed">Briar התרסק, עמך הסליחה.</string>
|
||||||
<string name="not_your_fault">זאת לא אשמתך.</string>
|
<string name="not_your_fault">זאת לא אשמתך.</string>
|
||||||
<string name="please_send_report">אנא עזור לנו לבנות Briar טוב יותר ע״י שליחת דוח קריסה אלינו.</string>
|
<string name="please_send_report">אנא עזור לנו לבנות Briar טוב יותר ע״י שליחת דוח קריסה אלינו.</string>
|
||||||
<string name="report_is_encrypted">אנו מבטיחים שהדוח הזה מוצפן ונשלח באופן מאובטח.</string>
|
<string name="report_is_encrypted">אנו מבטיחים שהדוח הזה מוצפן ונשלח באופן מאובטח.</string>
|
||||||
<string name="feedback_title">משוב</string>
|
<string name="feedback_title">משוב</string>
|
||||||
<string name="describe_crash">תאר מה קרה (רשותי)</string>
|
<string name="describe_crash">תאר מה קרה (רשותי)</string>
|
||||||
<string name="enter_feedback">הכנס את משובך</string>
|
<string name="enter_feedback">נא לתת את המשוב שלך</string>
|
||||||
<string name="optional_contact_email">כתובת הדוא״ל שלך (רשותי)</string>
|
<string name="optional_contact_email">כתובת הדוא״ל שלך (רשותי)</string>
|
||||||
<string name="include_debug_report_crash">כלול נתונים אלמוניים לגבי הקריסה</string>
|
<string name="include_debug_report_crash">כלול נתונים אלמוניים לגבי הקריסה</string>
|
||||||
<string name="include_debug_report_feedback">כלול נתונים אלמוניים לגבי מכשיר זה</string>
|
<string name="include_debug_report_feedback">כלול נתונים אלמוניים לגבי מכשיר זה</string>
|
||||||
@@ -571,13 +571,13 @@
|
|||||||
<string name="dev_report_logcat">יומן יישום</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>
|
<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-->
|
||||||
<string name="screen_filter_title">ציפוי מסך התגלה</string>
|
<string name="screen_filter_title">ציפוי מסך התגלה</string>
|
||||||
<string name="screen_filter_body">יישום אחר מציירת מעל Briar. כדי להגן על אבטחתך, Briar לא יגיב לנגיעות כאשר יישום אחר מצייר מעל.\n\nהיישומים הבאים יכולים לצייר מעל:\n\n%1$s</string>
|
<string name="screen_filter_body">יישום אחר מציירת מעל Briar. כדי להגן על אבטחתך, Briar לא יגיב לנגיעות כאשר יישום אחר מצייר מעל.\n\nהיישומים הבאים יכולים לצייר מעל:\n\n%1$s</string>
|
||||||
@@ -601,20 +601,20 @@
|
|||||||
<string name="lock_unlock_fingerprint_description">גע בחיישן טביעת האצבע שלך עם האצבע הרשומה כדי להמשיך</string>
|
<string name="lock_unlock_fingerprint_description">גע בחיישן טביעת האצבע שלך עם האצבע הרשומה כדי להמשיך</string>
|
||||||
<string name="lock_unlock_password">השתמש בסיסמה</string>
|
<string name="lock_unlock_password">השתמש בסיסמה</string>
|
||||||
<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 או Bluetooth.\n\nכל חיבורי האינטרנט עוברים דרך רשת Tor למען פרטיות.\n\nאם איש קשר ניתן להשגה באמצעות שיטות רבות, Briar משתמש בהן במקביל.</string>
|
<string name="transports_help_text">Briar יכול להתחבר אל אנשי הקשר שלך באמצעות האינטרנט, הרשת האלחוטית או Bluetooth.\n\nכל חיבורי האינטרנט עוברים דרך רשת Tor למען פרטיות.\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>
|
||||||
<!--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_bob">יונתן</string>
|
<string name="screenshot_bob">יהונתן</string>
|
||||||
<!--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_carol">דניאל</string>
|
<string name="screenshot_carol">דניאל</string>
|
||||||
<!--This is a message to be used in screenshots. Please use the same translation for Bob!-->
|
<!--This is a message to be used in screenshots. Please use the same translation for Bob!-->
|
||||||
<string name="screenshot_message_1">היי יונתן!</string>
|
<string name="screenshot_message_1">היי יהונתן!</string>
|
||||||
<!--This is a message to be used in screenshots. Please use the same translation for Alice!-->
|
<!--This is a message to be used in screenshots. Please use the same translation for Alice!-->
|
||||||
<string name="screenshot_message_2">היי נועה! תודה שאמרת לי על Briar!</string>
|
<string name="screenshot_message_2">היי נועה! תודה שסיפרת לי על Briar!</string>
|
||||||
<!--This is a message to be used in screenshots.-->
|
<!--This is a message to be used in screenshots.-->
|
||||||
<string name="screenshot_message_3">על לא דבר, אני מקווה שאתה אוהב אותו 😀</string>
|
<string name="screenshot_message_3">על לא דבר, מקווה שאתה אוהב אותו 😀</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -433,6 +433,10 @@ Kapcsolatai, akivel megosztotta ezt a blogot, lehet nem kapnak többé frissít
|
|||||||
<string name="blogs_rss_feeds_manage_empty_state">Nincs megjelenítendő</string>
|
<string name="blogs_rss_feeds_manage_empty_state">Nincs megjelenítendő</string>
|
||||||
<string name="blogs_rss_feeds_manage_error">Hiba történt a feed-jei betöltésével. Kérjük próbálja újra később.</string>
|
<string name="blogs_rss_feeds_manage_error">Hiba történt a feed-jei betöltésével. Kérjük próbálja újra később.</string>
|
||||||
<!--Settings Profile Picture-->
|
<!--Settings Profile Picture-->
|
||||||
|
<string name="change_profile_picture">Érintse meg a profilképe cseréjéhez</string>
|
||||||
|
<string name="dialog_confirm_profile_picture_title">Profil kép cseréje</string>
|
||||||
|
<string name="dialog_confirm_profile_picture_remark">Csak a kapcsolataid láthatják ezt a képet</string>
|
||||||
|
<string name="change_profile_picture_failed_message">Sajnáljuk, de valami hiba történt a profil kép frissítése során</string>
|
||||||
<!--Settings Display-->
|
<!--Settings Display-->
|
||||||
<string name="pref_language_title">Nyelv és régió</string>
|
<string name="pref_language_title">Nyelv és régió</string>
|
||||||
<string name="pref_language_changed">Ez a beállítás a Briar újraindítása után lép életbe. Kérjük lépjen ki és indítsa újra a Briar-t.</string>
|
<string name="pref_language_changed">Ez a beállítás a Briar újraindítása után lép életbe. Kérjük lépjen ki és indítsa újra a Briar-t.</string>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user