Merge pull request #1152 from nextcloud/qaBuild
QA Build & GHActions for Quality Checks
16
.devcontainer/Dockerfile
Normal file
|
@ -0,0 +1,16 @@
|
|||
FROM ubuntu:focal
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ENV ANDROID_HOME=/usr/lib/android-sdk
|
||||
|
||||
RUN apt-get update -y
|
||||
RUN apt-get install -y unzip wget openjdk-8-jdk vim
|
||||
|
||||
RUN wget https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip -O /tmp/commandlinetools.zip
|
||||
RUN cd /tmp && unzip commandlinetools.zip
|
||||
RUN mkdir -p /usr/lib/android-sdk/cmdline-tools/
|
||||
RUN cd /tmp/ && mv cmdline-tools/ latest/ && mv latest/ /usr/lib/android-sdk/cmdline-tools/
|
||||
RUN mkdir /usr/lib/android-sdk/licenses/
|
||||
RUN chmod -R 755 /usr/lib/android-sdk/
|
||||
RUN mkdir -p $HOME/.gradle
|
||||
RUN echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > $HOME/.gradle/gradle.properties
|
5
.devcontainer/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Instructions
|
||||
|
||||
1. Start a DevContainer either on GitHub Codespaces or locally in VSCode
|
||||
2. Accept all licenses by running `yes | /usr/lib/android-sdk/cmdline-tools/latest/bin/sdkmanager --licenses`
|
||||
3. You can now build the app using `./gradlew clean build`
|
3
.devcontainer/devcontainer.env
Normal file
|
@ -0,0 +1,3 @@
|
|||
ANDROID_HOME=/usr/lib/android-sdk
|
||||
JAVA_OPTS="-Xmx8192M"
|
||||
GRADLE_OPTS="-Dorg.gradle.daemon=true"
|
4
.devcontainer/devcontainer.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "NextcloudTalkAndroid",
|
||||
"dockerFile": "Dockerfile",
|
||||
}
|
2
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# You can add one username per supported platform and one custom link
|
||||
custom: https://nextcloud.com/include/
|
23
.github/dependabot.yml
vendored
|
@ -1,11 +1,16 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gradle
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "03:00"
|
||||
timezone: Europe/Paris
|
||||
open-pull-requests-limit: 10
|
||||
labels:
|
||||
- 3. to review
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: gradle
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "03:00"
|
||||
timezone: Europe/Paris
|
||||
open-pull-requests-limit: 10
|
||||
labels:
|
||||
- 3. to review
|
||||
- dependencies
|
||||
|
|
30
.github/workflows/assembleFlavors.yml
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
name: "Assemble"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master, stable-* ]
|
||||
|
||||
jobs:
|
||||
flavor:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
flavor: [ Generic, Gplay ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Build ${{ matrix.flavor }}
|
||||
run: |
|
||||
echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" >> gradle.properties
|
||||
./gradlew assemble${{ matrix.flavor }}
|
||||
- name: Archive apk
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: Nextcloud-APK
|
||||
path: app/build/outputs/apk/**/**/*.apk
|
||||
retention-days: 5
|
13
.github/workflows/autoApproveDependabot.yml
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
name: Auto approve
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [ master, stable-* ]
|
||||
|
||||
jobs:
|
||||
auto-approve:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: hmarr/auto-approve-action@v2.0.0
|
||||
if: github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]'
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
21
.github/workflows/check.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
name: Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master, stable-* ]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
task: [ detekt, ktlint ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Check ${{ matrix.task }}
|
||||
run: ./gradlew ${{ matrix.task }}
|
13
.github/workflows/gradle-wrapper-validation.yml
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
name: "Validate Gradle Wrapper"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master, stable-* ]
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: gradle/wrapper-validation-action@v1
|
29
.github/workflows/qa.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
name: "QA"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master, stable-* ]
|
||||
|
||||
jobs:
|
||||
qa:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Build QA
|
||||
env:
|
||||
KS_PASS: ${{ secrets.KS_PASS }}
|
||||
KEY_PASS: ${{ secrets.KEY_PASS }}
|
||||
LOG_USERNAME: ${{ secrets.LOG_USERNAME }}
|
||||
LOG_PASSWORD: ${{ secrets.LOG_PASSWORD }}
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > $HOME/.gradle/gradle.properties
|
||||
sed -i "/qa/,/\}/ s/versionCode .*/versionCode ${{github.event.number}} /" app/build.gradle
|
||||
sed -i "/qa/,/\}/ s/versionName .*/versionName \"${{github.event.number}}\"/" app/build.gradle
|
||||
./gradlew assembleQaDebug
|
||||
$(find /usr/local/lib/android/sdk/build-tools/*/apksigner | sort | tail -n1) sign --ks-pass pass:$KS_PASS --key-pass pass:$KEY_PASS --ks-key-alias key0 --ks scripts/QA_keystore.jks app/build/outputs/apk/qa/debug/app-qa-*.apk
|
||||
sudo scripts/uploadArtifact.sh $LOG_USERNAME $LOG_PASSWORD ${{github.event.number}} ${{github.event.number}} ${{ secrets.GITHUB_TOKEN }}
|
19
.github/workflows/stale.yml
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
name: 'Close stale issues'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '* */2 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
days-before-stale: 28
|
||||
days-before-close: 14
|
||||
days-before-pr-close: -1
|
||||
only-labels: 'bug,needs info/discussion'
|
||||
stale-issue-message: 'This bug report did not receive an update in the last 4 weeks.
|
||||
Please take a look again and update the issue with new details,
|
||||
otherwise the issue will be automatically closed in 2 weeks. Thank you!'
|
||||
exempt-all-pr-milestones: true
|
219
.idea/codeStyles/Project.xml
Normal file
|
@ -0,0 +1,219 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||
<value />
|
||||
</option>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="com" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="junit" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="net" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="org" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="javax" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
<emptyLine />
|
||||
</value>
|
||||
</option>
|
||||
<option name="RIGHT_MARGIN" value="120" />
|
||||
<option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="com" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="junit" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="net" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="org" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="javax" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
<emptyLine />
|
||||
</value>
|
||||
</option>
|
||||
</JavaCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="PACKAGES_IMPORT_LAYOUT">
|
||||
<value>
|
||||
<package name="" alias="false" withSubpackages="true" />
|
||||
<package name="java" alias="false" withSubpackages="true" />
|
||||
<package name="javax" alias="false" withSubpackages="true" />
|
||||
<package name="kotlin" alias="false" withSubpackages="true" />
|
||||
<package name="" alias="true" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<MarkdownNavigatorCodeStyleSettings>
|
||||
<option name="RIGHT_MARGIN" value="120" />
|
||||
</MarkdownNavigatorCodeStyleSettings>
|
||||
<XML>
|
||||
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
|
||||
</XML>
|
||||
<codeStyleSettings language="JAVA">
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
|
||||
<option name="ALIGN_MULTILINE_METHOD_BRACKETS" value="true" />
|
||||
<option name="WRAP_COMMENTS" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<option name="FIELD_ANNOTATION_WRAP" value="0" />
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Markdown">
|
||||
<option name="RIGHT_MARGIN" value="120" />
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
|
||||
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
7
.idea/inspectionProfiles/ktlint.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="ktlint" />
|
||||
<inspection_tool class="KotlinUnusedImport" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="RedundantSemicolon" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="PROJECT_PROFILE" value="ktlint" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
|
@ -23,6 +23,11 @@ apply plugin: 'kotlin-android'
|
|||
apply plugin: 'findbugs'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'io.gitlab.arturbosch.detekt'
|
||||
|
||||
configurations {
|
||||
ktlint
|
||||
}
|
||||
|
||||
def taskRequest = getGradle().getStartParameter().getTaskRequests().toString()
|
||||
if (taskRequest.contains("Gplay") || taskRequest.contains("findbugs") || taskRequest.contains("lint")) {
|
||||
|
@ -33,7 +38,6 @@ android {
|
|||
compileSdkVersion 29
|
||||
buildToolsVersion '28.0.3'
|
||||
defaultConfig {
|
||||
applicationId "com.nextcloud.talk2"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
@ -49,9 +53,20 @@ android {
|
|||
|
||||
productFlavors {
|
||||
// used for f-droid
|
||||
generic
|
||||
gplay
|
||||
|
||||
generic {
|
||||
applicationId 'com.nextcloud.talk2'
|
||||
dimension "default"
|
||||
}
|
||||
gplay {
|
||||
applicationId 'com.nextcloud.talk2'
|
||||
dimension "default"
|
||||
}
|
||||
qa {
|
||||
applicationId "com.nextcloud.talk2.qa"
|
||||
dimension "default"
|
||||
versionCode 1
|
||||
versionName "1"
|
||||
}
|
||||
}
|
||||
|
||||
// Enabling multidex support.
|
||||
|
@ -86,6 +101,7 @@ android {
|
|||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/NOTICE'
|
||||
exclude 'META-INF/rxjava.properties'
|
||||
|
@ -161,6 +177,7 @@ dependencies {
|
|||
implementation ('com.gitlab.bitfireAT:dav4jvm:f2078bc846', {
|
||||
exclude group: 'org.ogce', module: 'xpp3' // Android comes with its own XmlPullParser
|
||||
})
|
||||
ktlint "com.pinterest:ktlint:0.41.0"
|
||||
implementation 'org.conscrypt:conscrypt-android:2.5.1'
|
||||
|
||||
|
||||
|
@ -272,6 +289,30 @@ dependencies {
|
|||
findbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.4.6'
|
||||
}
|
||||
|
||||
task ktlint(type: JavaExec, group: "verification") {
|
||||
description = "Check Kotlin code style."
|
||||
main = "com.pinterest.ktlint.Main"
|
||||
classpath = configurations.ktlint
|
||||
args "--reporter=plain", "--reporter=plain,output=${buildDir}/ktlint.txt,src/**/*.kt"
|
||||
}
|
||||
|
||||
task ktlintFormat(type: JavaExec, group: "formatting") {
|
||||
description = "Fix Kotlin code style deviations."
|
||||
main = "com.pinterest.ktlint.Main"
|
||||
classpath = configurations.ktlint
|
||||
args "-F", "src/**/*.kt"
|
||||
}
|
||||
|
||||
detekt {
|
||||
reports {
|
||||
xml {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
config = files("../detekt.yml")
|
||||
input = files("src/")
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
|
|
|
@ -104,7 +104,6 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
|||
@Inject
|
||||
var eventBus: EventBus? = null
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
|
@ -148,18 +147,26 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
|||
val pushUtils = PushUtils()
|
||||
val privateKey = pushUtils.readKeyFromFile(false) as PrivateKey
|
||||
try {
|
||||
signatureVerification = pushUtils.verifySignature(base64DecodedSignature,
|
||||
base64DecodedSubject)
|
||||
signatureVerification = pushUtils.verifySignature(
|
||||
base64DecodedSignature,
|
||||
base64DecodedSubject
|
||||
)
|
||||
if (signatureVerification!!.signatureValid) {
|
||||
val cipher = Cipher.getInstance("RSA/None/PKCS1Padding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey)
|
||||
val decryptedSubject = cipher.doFinal(base64DecodedSubject)
|
||||
decryptedPushMessage = LoganSquare.parse(String(decryptedSubject),
|
||||
DecryptedPushMessage::class.java)
|
||||
decryptedPushMessage = LoganSquare.parse(
|
||||
String(decryptedSubject),
|
||||
DecryptedPushMessage::class.java
|
||||
)
|
||||
decryptedPushMessage?.apply {
|
||||
timestamp = System.currentTimeMillis()
|
||||
if (delete) {
|
||||
cancelExistingNotificationWithId(applicationContext, signatureVerification!!.userEntity, notificationId)
|
||||
cancelExistingNotificationWithId(
|
||||
applicationContext,
|
||||
signatureVerification!!.userEntity,
|
||||
notificationId
|
||||
)
|
||||
} else if (deleteAll) {
|
||||
cancelAllNotificationsForAccount(applicationContext, signatureVerification!!.userEntity)
|
||||
} else if (type == "call") {
|
||||
|
@ -171,39 +178,66 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
|||
fullScreenIntent.putExtras(bundle)
|
||||
|
||||
fullScreenIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
val fullScreenPendingIntent = PendingIntent.getActivity(this@MagicFirebaseMessagingService, 0, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val fullScreenPendingIntent = PendingIntent.getActivity(
|
||||
this@MagicFirebaseMessagingService,
|
||||
0,
|
||||
fullScreenIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
val audioAttributesBuilder = AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||
val audioAttributesBuilder =
|
||||
AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||
audioAttributesBuilder.setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST)
|
||||
|
||||
val ringtonePreferencesString: String? = appPreferences!!.callRingtoneUri
|
||||
val soundUri = if (TextUtils.isEmpty(ringtonePreferencesString)) {
|
||||
Uri.parse("android.resource://" + applicationContext.packageName +
|
||||
"/raw/librem_by_feandesign_call")
|
||||
Uri.parse(
|
||||
"android.resource://" + applicationContext.packageName +
|
||||
"/raw/librem_by_feandesign_call"
|
||||
)
|
||||
} else {
|
||||
try {
|
||||
val ringtoneSettings = LoganSquare.parse(ringtonePreferencesString, RingtoneSettings::class.java)
|
||||
val ringtoneSettings =
|
||||
LoganSquare.parse(ringtonePreferencesString, RingtoneSettings::class.java)
|
||||
ringtoneSettings.ringtoneUri
|
||||
} catch (exception: IOException) {
|
||||
Uri.parse("android.resource://" + applicationContext.packageName + "/raw/librem_by_feandesign_call")
|
||||
}
|
||||
}
|
||||
|
||||
val notificationChannelId = NotificationUtils.getNotificationChannelId(applicationContext.resources
|
||||
.getString(R.string.nc_notification_channel_calls), applicationContext.resources
|
||||
.getString(R.string.nc_notification_channel_calls_description), true,
|
||||
NotificationManagerCompat.IMPORTANCE_HIGH, soundUri!!, audioAttributesBuilder.build(), null, false)
|
||||
val notificationChannelId = NotificationUtils.getNotificationChannelId(
|
||||
applicationContext.resources
|
||||
.getString(R.string.nc_notification_channel_calls),
|
||||
applicationContext.resources
|
||||
.getString(R.string.nc_notification_channel_calls_description),
|
||||
true,
|
||||
NotificationManagerCompat.IMPORTANCE_HIGH,
|
||||
soundUri!!,
|
||||
audioAttributesBuilder.build(),
|
||||
null,
|
||||
false
|
||||
)
|
||||
|
||||
createNotificationChannel(applicationContext!!,
|
||||
notificationChannelId, applicationContext.resources
|
||||
.getString(R.string.nc_notification_channel_calls), applicationContext.resources
|
||||
.getString(R.string.nc_notification_channel_calls_description), true,
|
||||
NotificationManagerCompat.IMPORTANCE_HIGH, soundUri, audioAttributesBuilder.build(), null, false)
|
||||
createNotificationChannel(
|
||||
applicationContext!!,
|
||||
notificationChannelId,
|
||||
applicationContext.resources
|
||||
.getString(R.string.nc_notification_channel_calls),
|
||||
applicationContext.resources
|
||||
.getString(R.string.nc_notification_channel_calls_description),
|
||||
true,
|
||||
NotificationManagerCompat.IMPORTANCE_HIGH,
|
||||
soundUri,
|
||||
audioAttributesBuilder.build(),
|
||||
null,
|
||||
false
|
||||
)
|
||||
|
||||
val uri = Uri.parse(signatureVerification!!.userEntity.baseUrl)
|
||||
val baseUrl = uri.host
|
||||
|
||||
val notification = NotificationCompat.Builder(this@MagicFirebaseMessagingService, notificationChannelId)
|
||||
val notification =
|
||||
NotificationCompat.Builder(this@MagicFirebaseMessagingService, notificationChannelId)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_CALL)
|
||||
.setSmallIcon(R.drawable.ic_call_black_24dp)
|
||||
|
@ -213,7 +247,7 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
|||
.setContentTitle(EmojiCompat.get().process(decryptedPushMessage!!.subject))
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(true)
|
||||
//.setTimeoutAfter(45000L)
|
||||
// .setTimeoutAfter(45000L)
|
||||
.setContentIntent(fullScreenPendingIntent)
|
||||
.setFullScreenIntent(fullScreenPendingIntent, true)
|
||||
.setSound(soundUri)
|
||||
|
@ -224,10 +258,12 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
|||
startForeground(decryptedPushMessage!!.timestamp.toInt(), notification)
|
||||
} else {
|
||||
val messageData = Data.Builder()
|
||||
.putString(BundleKeys.KEY_NOTIFICATION_SUBJECT, subject)
|
||||
.putString(BundleKeys.KEY_NOTIFICATION_SIGNATURE, signature)
|
||||
.putString(BundleKeys.KEY_NOTIFICATION_SUBJECT, subject)
|
||||
.putString(BundleKeys.KEY_NOTIFICATION_SIGNATURE, signature)
|
||||
.build()
|
||||
val pushNotificationWork =
|
||||
OneTimeWorkRequest.Builder(NotificationWorker::class.java).setInputData(messageData)
|
||||
.build()
|
||||
val pushNotificationWork = OneTimeWorkRequest.Builder(NotificationWorker::class.java).setInputData(messageData).build()
|
||||
WorkManager.getInstance().enqueue(pushNotificationWork)
|
||||
}
|
||||
}
|
||||
|
@ -244,47 +280,59 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun checkIfCallIsActive(signatureVerification: SignatureVerification, decryptedPushMessage: DecryptedPushMessage) {
|
||||
val ncApi = retrofit!!.newBuilder().client(okHttpClient!!.newBuilder().cookieJar(JavaNetCookieJar(CookieManager())).build()).build().create(NcApi::class.java)
|
||||
private fun checkIfCallIsActive(
|
||||
signatureVerification: SignatureVerification,
|
||||
decryptedPushMessage: DecryptedPushMessage
|
||||
) {
|
||||
val ncApi = retrofit!!.newBuilder()
|
||||
.client(okHttpClient!!.newBuilder().cookieJar(JavaNetCookieJar(CookieManager())).build()).build()
|
||||
.create(NcApi::class.java)
|
||||
var hasParticipantsInCall = false
|
||||
var inCallOnDifferentDevice = false
|
||||
|
||||
ncApi.getPeersForCall(ApiUtils.getCredentials(signatureVerification.userEntity.username, signatureVerification.userEntity.token),
|
||||
ApiUtils.getUrlForCall(signatureVerification.userEntity.baseUrl,
|
||||
decryptedPushMessage.id))
|
||||
.takeWhile {
|
||||
isServiceInForeground
|
||||
ncApi.getPeersForCall(
|
||||
ApiUtils.getCredentials(signatureVerification.userEntity.username, signatureVerification.userEntity.token),
|
||||
ApiUtils.getUrlForCall(
|
||||
signatureVerification.userEntity.baseUrl,
|
||||
decryptedPushMessage.id
|
||||
)
|
||||
)
|
||||
.takeWhile {
|
||||
isServiceInForeground
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(object : Observer<ParticipantsOverall> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(object : Observer<ParticipantsOverall> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
}
|
||||
|
||||
override fun onNext(participantsOverall: ParticipantsOverall) {
|
||||
val participantList: List<Participant> = participantsOverall.ocs.data
|
||||
hasParticipantsInCall = participantList.isNotEmpty()
|
||||
if (!hasParticipantsInCall) {
|
||||
for (participant in participantList) {
|
||||
if (participant.userId == signatureVerification.userEntity.userId) {
|
||||
inCallOnDifferentDevice = true
|
||||
break
|
||||
}
|
||||
override fun onNext(participantsOverall: ParticipantsOverall) {
|
||||
val participantList: List<Participant> = participantsOverall.ocs.data
|
||||
hasParticipantsInCall = participantList.isNotEmpty()
|
||||
if (!hasParticipantsInCall) {
|
||||
for (participant in participantList) {
|
||||
if (participant.userId == signatureVerification.userEntity.userId) {
|
||||
inCallOnDifferentDevice = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasParticipantsInCall || inCallOnDifferentDevice) {
|
||||
stopForeground(true)
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
} else if (isServiceInForeground) {
|
||||
handler.postDelayed({
|
||||
if (!hasParticipantsInCall || inCallOnDifferentDevice) {
|
||||
stopForeground(true)
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
} else if (isServiceInForeground) {
|
||||
handler.postDelayed(
|
||||
{
|
||||
checkIfCallIsActive(signatureVerification, decryptedPushMessage)
|
||||
}, 5000)
|
||||
}
|
||||
},
|
||||
5000
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {}
|
||||
override fun onComplete() {
|
||||
}
|
||||
})
|
||||
override fun onError(e: Throwable) {}
|
||||
override fun onComplete() {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,8 +76,11 @@ open class BaseActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
fun showCertificateDialog(cert: X509Certificate, magicTrustManager: MagicTrustManager,
|
||||
sslErrorHandler: SslErrorHandler?) {
|
||||
fun showCertificateDialog(
|
||||
cert: X509Certificate,
|
||||
magicTrustManager: MagicTrustManager,
|
||||
sslErrorHandler: SslErrorHandler?
|
||||
) {
|
||||
val formatter = DateFormat.getDateInstance(DateFormat.LONG)
|
||||
val validFrom = formatter.format(cert.notBefore)
|
||||
val validUntil = formatter.format(cert.notAfter)
|
||||
|
@ -101,30 +104,30 @@ open class BaseActivity : AppCompatActivity() {
|
|||
issuedFor = cert.subjectDN.name
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatMatches") val dialogText = String.format(resources
|
||||
@SuppressLint("StringFormatMatches") val dialogText = String.format(
|
||||
resources
|
||||
.getString(R.string.nc_certificate_dialog_text),
|
||||
issuedBy, issuedFor, validFrom, validUntil)
|
||||
issuedBy, issuedFor, validFrom, validUntil
|
||||
)
|
||||
|
||||
LovelyStandardDialog(this)
|
||||
.setTopColorRes(R.color.nc_darkRed)
|
||||
.setNegativeButtonColorRes(R.color.nc_darkRed)
|
||||
.setPositiveButtonColorRes(R.color.colorPrimary)
|
||||
.setIcon(R.drawable.ic_security_white_24dp)
|
||||
.setTitle(R.string.nc_certificate_dialog_title)
|
||||
.setMessage(dialogText)
|
||||
.setPositiveButton(R.string.nc_yes) { v ->
|
||||
magicTrustManager.addCertInTrustStore(cert)
|
||||
sslErrorHandler?.proceed()
|
||||
}
|
||||
.setNegativeButton(R.string.nc_no) { view1 ->
|
||||
sslErrorHandler?.cancel()
|
||||
}
|
||||
.show()
|
||||
|
||||
.setTopColorRes(R.color.nc_darkRed)
|
||||
.setNegativeButtonColorRes(R.color.nc_darkRed)
|
||||
.setPositiveButtonColorRes(R.color.colorPrimary)
|
||||
.setIcon(R.drawable.ic_security_white_24dp)
|
||||
.setTitle(R.string.nc_certificate_dialog_title)
|
||||
.setMessage(dialogText)
|
||||
.setPositiveButton(R.string.nc_yes) { v ->
|
||||
magicTrustManager.addCertInTrustStore(cert)
|
||||
sslErrorHandler?.proceed()
|
||||
}
|
||||
.setNegativeButton(R.string.nc_no) { view1 ->
|
||||
sslErrorHandler?.cancel()
|
||||
}
|
||||
.show()
|
||||
} catch (e: CertificateParsingException) {
|
||||
Log.d(TAG, "Failed to parse the certificate")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
|
|
|
@ -38,7 +38,6 @@ import pl.droidsonroids.gif.GifDrawable
|
|||
import pl.droidsonroids.gif.GifImageView
|
||||
import java.io.File
|
||||
|
||||
|
||||
class FullScreenImageActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var path: String
|
||||
|
@ -55,9 +54,11 @@ class FullScreenImageActivity : AppCompatActivity() {
|
|||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == R.id.share) {
|
||||
val shareUri = FileProvider.getUriForFile(this,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
File(path))
|
||||
val shareUri = FileProvider.getUriForFile(
|
||||
this,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
File(path)
|
||||
)
|
||||
|
||||
val shareIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
|
@ -78,19 +79,19 @@ class FullScreenImageActivity : AppCompatActivity() {
|
|||
|
||||
setContentView(R.layout.activity_full_screen_image)
|
||||
setSupportActionBar(findViewById(R.id.imageview_toolbar))
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false);
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false)
|
||||
|
||||
imageWrapperView = findViewById(R.id.image_wrapper_view)
|
||||
photoView = findViewById(R.id.photo_view)
|
||||
gifView = findViewById(R.id.gif_view)
|
||||
|
||||
photoView.setOnPhotoTapListener{ view, x, y ->
|
||||
photoView.setOnPhotoTapListener { view, x, y ->
|
||||
toggleFullscreen()
|
||||
}
|
||||
photoView.setOnOutsidePhotoTapListener{
|
||||
photoView.setOnOutsidePhotoTapListener {
|
||||
toggleFullscreen()
|
||||
}
|
||||
gifView.setOnClickListener{
|
||||
gifView.setOnClickListener {
|
||||
toggleFullscreen()
|
||||
}
|
||||
|
||||
|
@ -115,29 +116,33 @@ class FullScreenImageActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun toggleFullscreen(){
|
||||
showFullscreen = !showFullscreen;
|
||||
if (showFullscreen){
|
||||
private fun toggleFullscreen() {
|
||||
showFullscreen = !showFullscreen
|
||||
if (showFullscreen) {
|
||||
hideSystemUI()
|
||||
supportActionBar?.hide()
|
||||
} else{
|
||||
} else {
|
||||
showSystemUI()
|
||||
supportActionBar?.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideSystemUI() {
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||
window.decorView.systemUiVisibility = (
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
)
|
||||
}
|
||||
|
||||
private fun showSystemUI() {
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
window.decorView.systemUiVisibility = (
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ package com.nextcloud.talk.activities
|
|||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
|
@ -33,7 +32,6 @@ import autodagger.AutoInjector
|
|||
import com.google.android.exoplayer2.MediaItem
|
||||
import com.google.android.exoplayer2.Player
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||
import com.google.android.exoplayer2.ui.PlayerControlView
|
||||
import com.google.android.exoplayer2.ui.StyledPlayerView
|
||||
import com.nextcloud.talk.BuildConfig
|
||||
import com.nextcloud.talk.R
|
||||
|
@ -54,9 +52,11 @@ class FullScreenMediaActivity : AppCompatActivity(), Player.EventListener {
|
|||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == R.id.share) {
|
||||
val shareUri = FileProvider.getUriForFile(this,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
File(path))
|
||||
val shareUri = FileProvider.getUriForFile(
|
||||
this,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
File(path)
|
||||
)
|
||||
|
||||
val shareIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
|
@ -82,11 +82,11 @@ class FullScreenMediaActivity : AppCompatActivity(), Player.EventListener {
|
|||
|
||||
setContentView(R.layout.activity_full_screen_media)
|
||||
setSupportActionBar(findViewById(R.id.mediaview_toolbar))
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false);
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false)
|
||||
|
||||
playerView = findViewById(R.id.player_view)
|
||||
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
|
||||
playerView.showController()
|
||||
if (isAudioOnly) {
|
||||
|
@ -121,7 +121,7 @@ class FullScreenMediaActivity : AppCompatActivity(), Player.EventListener {
|
|||
|
||||
private fun initializePlayer() {
|
||||
player = SimpleExoPlayer.Builder(applicationContext).build()
|
||||
playerView.player = player;
|
||||
playerView.player = player
|
||||
player.playWhenReady = true
|
||||
player.addListener(this)
|
||||
}
|
||||
|
@ -131,17 +131,21 @@ class FullScreenMediaActivity : AppCompatActivity(), Player.EventListener {
|
|||
}
|
||||
|
||||
private fun hideSystemUI() {
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||
window.decorView.systemUiVisibility = (
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
)
|
||||
}
|
||||
|
||||
private fun showSystemUI() {
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
window.decorView.systemUiVisibility = (
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
|
|||
import io.noties.markwon.Markwon
|
||||
import java.io.File
|
||||
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class FullScreenTextViewerActivity : AppCompatActivity() {
|
||||
|
||||
|
@ -48,9 +47,11 @@ class FullScreenTextViewerActivity : AppCompatActivity() {
|
|||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == R.id.share) {
|
||||
val shareUri = FileProvider.getUriForFile(this,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
File(path))
|
||||
val shareUri = FileProvider.getUriForFile(
|
||||
this,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
File(path)
|
||||
)
|
||||
|
||||
val shareIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
|
@ -71,8 +72,7 @@ class FullScreenTextViewerActivity : AppCompatActivity() {
|
|||
|
||||
setContentView(R.layout.activity_full_screen_text)
|
||||
setSupportActionBar(findViewById(R.id.textview_toolbar))
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false);
|
||||
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false)
|
||||
textView = findViewById(R.id.text_view)
|
||||
|
||||
val fileName = intent.getStringExtra("FILE_NAME")
|
||||
|
@ -81,13 +81,12 @@ class FullScreenTextViewerActivity : AppCompatActivity() {
|
|||
var text = readFile(path)
|
||||
|
||||
if (isMarkdown) {
|
||||
val markwon = Markwon.create(applicationContext);
|
||||
markwon.setMarkdown(textView, text);
|
||||
val markwon = Markwon.create(applicationContext)
|
||||
markwon.setMarkdown(textView, text)
|
||||
} else {
|
||||
textView.text = text
|
||||
}
|
||||
}
|
||||
|
||||
private fun readFile(fileName: String) = File(fileName).inputStream().readBytes().toString(Charsets.UTF_8)
|
||||
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ class MagicCallActivity : BaseActivity() {
|
|||
|
||||
@BindView(R.id.controller_container)
|
||||
lateinit var container: ViewGroup
|
||||
|
||||
|
||||
@BindView(R.id.chatControllerView)
|
||||
lateinit var chatContainer: ViewGroup
|
||||
|
||||
|
@ -60,10 +60,12 @@ class MagicCallActivity : BaseActivity() {
|
|||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN or
|
||||
window.addFlags(
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN or
|
||||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
|
||||
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
|
||||
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
||||
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
|
||||
)
|
||||
window.decorView.systemUiVisibility = systemUiVisibility
|
||||
|
||||
setContentView(R.layout.activity_magic_call)
|
||||
|
@ -74,26 +76,32 @@ class MagicCallActivity : BaseActivity() {
|
|||
|
||||
if (!router!!.hasRootController()) {
|
||||
if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) {
|
||||
router!!.setRoot(RouterTransaction.with(CallNotificationController(intent.extras))
|
||||
router!!.setRoot(
|
||||
RouterTransaction.with(CallNotificationController(intent.extras))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
} else {
|
||||
router!!.setRoot(RouterTransaction.with(CallController(intent.extras))
|
||||
router!!.setRoot(
|
||||
RouterTransaction.with(CallController(intent.extras))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val extras = intent.extras ?: Bundle()
|
||||
extras.putBoolean("showToggleChat", true)
|
||||
|
||||
|
||||
chatController = ChatController(extras)
|
||||
chatRouter = Conductor.attachRouter(this, chatContainer, savedInstanceState)
|
||||
chatRouter!!.setRoot(RouterTransaction.with(chatController)
|
||||
chatRouter!!.setRoot(
|
||||
RouterTransaction.with(chatController)
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun showChat() {
|
||||
chatContainer.visibility = View.VISIBLE
|
||||
container.visibility = View.GONE
|
||||
|
|
|
@ -79,23 +79,31 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
|||
|
||||
@BindView(R.id.appBar)
|
||||
lateinit var appBar: AppBarLayout
|
||||
|
||||
@BindView(R.id.toolbar)
|
||||
lateinit var toolbar: MaterialToolbar
|
||||
|
||||
@BindView(R.id.home_toolbar)
|
||||
lateinit var searchCardView: MaterialCardView
|
||||
|
||||
@BindView(R.id.search_text)
|
||||
lateinit var searchInputText: MaterialTextView
|
||||
|
||||
@BindView(R.id.switch_account_button)
|
||||
lateinit var settingsButton: MaterialButton
|
||||
|
||||
@BindView(R.id.controller_container)
|
||||
lateinit var container: ViewGroup
|
||||
|
||||
@Inject
|
||||
lateinit var userUtils: UserUtils
|
||||
|
||||
@Inject
|
||||
lateinit var dataStore: ReactiveEntityStore<Persistable>
|
||||
|
||||
@Inject
|
||||
lateinit var sqlCipherDatabaseSource: SqlCipherDatabaseSource
|
||||
|
||||
@Inject
|
||||
lateinit var ncApi: NcApi
|
||||
|
||||
|
@ -124,39 +132,53 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
|||
|
||||
if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
|
||||
if (!router!!.hasRootController()) {
|
||||
router!!.setRoot(RouterTransaction.with(ConversationsListController())
|
||||
router!!.setRoot(
|
||||
RouterTransaction.with(ConversationsListController())
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
onNewIntent(intent)
|
||||
} else if (!router!!.hasRootController()) {
|
||||
if (hasDb) {
|
||||
if (userUtils.anyUserExists()) {
|
||||
router!!.setRoot(RouterTransaction.with(ConversationsListController())
|
||||
router!!.setRoot(
|
||||
RouterTransaction.with(ConversationsListController())
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
} else {
|
||||
if (!TextUtils.isEmpty(resources.getString(R.string.weblogin_url))) {
|
||||
router!!.pushController(RouterTransaction.with(
|
||||
WebViewLoginController(resources.getString(R.string.weblogin_url), false))
|
||||
router!!.pushController(
|
||||
RouterTransaction.with(
|
||||
WebViewLoginController(resources.getString(R.string.weblogin_url), false)
|
||||
)
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
} else {
|
||||
router!!.setRoot(RouterTransaction.with(ServerSelectionController())
|
||||
router!!.setRoot(
|
||||
RouterTransaction.with(ServerSelectionController())
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!TextUtils.isEmpty(resources.getString(R.string.weblogin_url))) {
|
||||
router!!.pushController(RouterTransaction.with(
|
||||
WebViewLoginController(resources.getString(R.string.weblogin_url), false))
|
||||
router!!.pushController(
|
||||
RouterTransaction.with(
|
||||
WebViewLoginController(resources.getString(R.string.weblogin_url), false)
|
||||
)
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
} else {
|
||||
router!!.setRoot(RouterTransaction.with(ServerSelectionController())
|
||||
router!!.setRoot(
|
||||
RouterTransaction.with(ServerSelectionController())
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +189,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
checkIfWeAreSecure()
|
||||
}
|
||||
|
||||
|
||||
handleActionFromContact(intent)
|
||||
}
|
||||
|
||||
|
@ -182,7 +204,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
|||
// userId @ server
|
||||
userId = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DATA1))
|
||||
}
|
||||
|
||||
|
||||
cursor.close()
|
||||
}
|
||||
|
||||
|
@ -193,65 +215,82 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
|||
if (userUtils.currentUser?.baseUrl?.endsWith(baseUrl) == true) {
|
||||
startConversation(user)
|
||||
} else {
|
||||
Snackbar.make(container, R.string.nc_phone_book_integration_account_not_found, Snackbar
|
||||
.LENGTH_LONG).show()
|
||||
Snackbar.make(
|
||||
container, R.string.nc_phone_book_integration_account_not_found,
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun startConversation(userId: String) {
|
||||
val roomType = "1"
|
||||
val currentUser = userUtils.currentUser ?: return
|
||||
|
||||
val credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token)
|
||||
val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(currentUser.baseUrl, roomType,
|
||||
userId, null)
|
||||
ncApi.createRoom(credentials,
|
||||
retrofitBucket.url, retrofitBucket.queryMap)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<RoomOverall> {
|
||||
override fun onSubscribe(d: Disposable) {}
|
||||
override fun onNext(roomOverall: RoomOverall) {
|
||||
val conversationIntent = Intent(context, MagicCallActivity::class.java)
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(KEY_USER_ENTITY, currentUser)
|
||||
bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs.data.token)
|
||||
bundle.putString(KEY_ROOM_ID, roomOverall.ocs.data.roomId)
|
||||
if (currentUser.hasSpreedFeatureCapability("chat-v2")) {
|
||||
ncApi.getRoom(credentials,
|
||||
ApiUtils.getRoom(currentUser.baseUrl,
|
||||
roomOverall.ocs.data.token))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<RoomOverall> {
|
||||
override fun onSubscribe(d: Disposable) {}
|
||||
override fun onNext(roomOverall: RoomOverall) {
|
||||
bundle.putParcelable(KEY_ACTIVE_CONVERSATION,
|
||||
Parcels.wrap(roomOverall.ocs.data))
|
||||
remapChatController(router!!, currentUser.id,
|
||||
roomOverall.ocs.data.token, bundle, true)
|
||||
}
|
||||
val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
|
||||
currentUser.baseUrl, roomType,
|
||||
userId, null
|
||||
)
|
||||
ncApi.createRoom(
|
||||
credentials,
|
||||
retrofitBucket.url, retrofitBucket.queryMap
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<RoomOverall> {
|
||||
override fun onSubscribe(d: Disposable) {}
|
||||
override fun onNext(roomOverall: RoomOverall) {
|
||||
val conversationIntent = Intent(context, MagicCallActivity::class.java)
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(KEY_USER_ENTITY, currentUser)
|
||||
bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs.data.token)
|
||||
bundle.putString(KEY_ROOM_ID, roomOverall.ocs.data.roomId)
|
||||
if (currentUser.hasSpreedFeatureCapability("chat-v2")) {
|
||||
ncApi.getRoom(
|
||||
credentials,
|
||||
ApiUtils.getRoom(
|
||||
currentUser.baseUrl,
|
||||
roomOverall.ocs.data.token
|
||||
)
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<RoomOverall> {
|
||||
override fun onSubscribe(d: Disposable) {}
|
||||
override fun onNext(roomOverall: RoomOverall) {
|
||||
bundle.putParcelable(
|
||||
KEY_ACTIVE_CONVERSATION,
|
||||
Parcels.wrap(roomOverall.ocs.data)
|
||||
)
|
||||
remapChatController(
|
||||
router!!, currentUser.id,
|
||||
roomOverall.ocs.data.token, bundle, true
|
||||
)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {}
|
||||
override fun onComplete() {}
|
||||
})
|
||||
} else {
|
||||
conversationIntent.putExtras(bundle)
|
||||
startActivity(conversationIntent)
|
||||
Handler().postDelayed({
|
||||
override fun onError(e: Throwable) {}
|
||||
override fun onComplete() {}
|
||||
})
|
||||
} else {
|
||||
conversationIntent.putExtras(bundle)
|
||||
startActivity(conversationIntent)
|
||||
Handler().postDelayed(
|
||||
{
|
||||
if (!isDestroyed) {
|
||||
router!!.popCurrentController()
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
},
|
||||
100
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {}
|
||||
override fun onComplete() {}
|
||||
})
|
||||
override fun onError(e: Throwable) {}
|
||||
override fun onComplete() {}
|
||||
})
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
|
@ -260,16 +299,17 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
|||
if (keyguardManager.isKeyguardSecure && appPreferences.isScreenLocked) {
|
||||
if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)) {
|
||||
if (router != null && router!!.getControllerWithTag(LockedController.TAG) == null) {
|
||||
router!!.pushController(RouterTransaction.with(LockedController())
|
||||
router!!.pushController(
|
||||
RouterTransaction.with(LockedController())
|
||||
.pushChangeHandler(VerticalChangeHandler())
|
||||
.popChangeHandler(VerticalChangeHandler())
|
||||
.tag(LockedController.TAG))
|
||||
.tag(LockedController.TAG)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
|
||||
|
@ -277,12 +317,16 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
|||
|
||||
if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
|
||||
if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) {
|
||||
router!!.pushController(RouterTransaction.with(CallNotificationController(intent.extras))
|
||||
router!!.pushController(
|
||||
RouterTransaction.with(CallNotificationController(intent.extras))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
} else {
|
||||
ConductorRemapping.remapChatController(router!!, intent.getLongExtra(BundleKeys.KEY_INTERNAL_USER_ID, -1),
|
||||
intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN), intent.extras!!, false)
|
||||
ConductorRemapping.remapChatController(
|
||||
router!!, intent.getLongExtra(BundleKeys.KEY_INTERNAL_USER_ID, -1),
|
||||
intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN), intent.extras!!, false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ import autodagger.AutoInjector
|
|||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.facebook.drawee.view.SimpleDraweeView
|
||||
import com.nextcloud.talk.R
|
||||
|
@ -51,7 +50,6 @@ import com.nextcloud.talk.utils.DisplayUtils
|
|||
import com.nextcloud.talk.utils.TextMatchers
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import com.stfalcon.chatkit.messages.MessageHolders
|
||||
import com.stfalcon.chatkit.utils.DateFormatter
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
|
@ -104,8 +102,8 @@ class MagicIncomingTextMessageViewHolder(incomingView: View) : MessageHolders
|
|||
|
||||
init {
|
||||
ButterKnife.bind(
|
||||
this,
|
||||
itemView
|
||||
this,
|
||||
itemView
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -131,13 +129,13 @@ class MagicIncomingTextMessageViewHolder(incomingView: View) : MessageHolders
|
|||
messageUserAvatarView?.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable))
|
||||
} else if (message.actorType == "bots") {
|
||||
val drawable = TextDrawable.builder()
|
||||
.beginConfig()
|
||||
.bold()
|
||||
.endConfig()
|
||||
.buildRound(
|
||||
">",
|
||||
context!!.resources.getColor(R.color.black)
|
||||
)
|
||||
.beginConfig()
|
||||
.bold()
|
||||
.endConfig()
|
||||
.buildRound(
|
||||
">",
|
||||
context!!.resources.getColor(R.color.black)
|
||||
)
|
||||
messageUserAvatarView!!.visibility = View.VISIBLE
|
||||
messageUserAvatarView?.setImageDrawable(drawable)
|
||||
}
|
||||
|
@ -165,9 +163,9 @@ class MagicIncomingTextMessageViewHolder(incomingView: View) : MessageHolders
|
|||
}
|
||||
|
||||
val bubbleDrawable = DisplayUtils.getMessageSelector(
|
||||
bgBubbleColor,
|
||||
resources.getColor(R.color.transparent),
|
||||
bgBubbleColor, bubbleResource
|
||||
bgBubbleColor,
|
||||
resources.getColor(R.color.transparent),
|
||||
bgBubbleColor, bubbleResource
|
||||
)
|
||||
ViewCompat.setBackground(bubble, bubbleDrawable)
|
||||
|
||||
|
@ -187,23 +185,23 @@ class MagicIncomingTextMessageViewHolder(incomingView: View) : MessageHolders
|
|||
if (individualHashMap["type"] == "user" || individualHashMap["type"] == "guest" || individualHashMap["type"] == "call") {
|
||||
if (individualHashMap["id"] == message.activeUser!!.userId) {
|
||||
messageString = DisplayUtils.searchAndReplaceWithMentionSpan(
|
||||
messageText!!.context,
|
||||
messageString,
|
||||
individualHashMap["id"]!!,
|
||||
individualHashMap["name"]!!,
|
||||
individualHashMap["type"]!!,
|
||||
message.activeUser!!,
|
||||
R.xml.chip_you
|
||||
messageText!!.context,
|
||||
messageString,
|
||||
individualHashMap["id"]!!,
|
||||
individualHashMap["name"]!!,
|
||||
individualHashMap["type"]!!,
|
||||
message.activeUser!!,
|
||||
R.xml.chip_you
|
||||
)
|
||||
} else {
|
||||
messageString = DisplayUtils.searchAndReplaceWithMentionSpan(
|
||||
messageText!!.context,
|
||||
messageString,
|
||||
individualHashMap["id"]!!,
|
||||
individualHashMap["name"]!!,
|
||||
individualHashMap["type"]!!,
|
||||
message.activeUser!!,
|
||||
R.xml.chip_others
|
||||
messageText!!.context,
|
||||
messageString,
|
||||
individualHashMap["id"]!!,
|
||||
individualHashMap["name"]!!,
|
||||
individualHashMap["type"]!!,
|
||||
message.activeUser!!,
|
||||
R.xml.chip_others
|
||||
)
|
||||
}
|
||||
} else if (individualHashMap["type"] == "file") {
|
||||
|
@ -231,18 +229,21 @@ class MagicIncomingTextMessageViewHolder(incomingView: View) : MessageHolders
|
|||
parentChatMessage.imageUrl?.let {
|
||||
quotedMessagePreview?.visibility = View.VISIBLE
|
||||
quotedMessagePreview?.load(it) {
|
||||
addHeader("Authorization", ApiUtils.getCredentials(message.activeUser.username, message.activeUser.token))
|
||||
addHeader(
|
||||
"Authorization",
|
||||
ApiUtils.getCredentials(message.activeUser.username, message.activeUser.token)
|
||||
)
|
||||
}
|
||||
} ?: run {
|
||||
quotedMessagePreview?.visibility = View.GONE
|
||||
}
|
||||
quotedUserName?.text = parentChatMessage.actorDisplayName
|
||||
?: context!!.getText(R.string.nc_nick_guest)
|
||||
?: context!!.getText(R.string.nc_nick_guest)
|
||||
quotedMessage?.text = parentChatMessage.text
|
||||
|
||||
quotedUserName?.setTextColor(context!!.resources.getColor(R.color.textColorMaxContrast))
|
||||
|
||||
if(parentChatMessage.actorId?.equals(message.activeUser.userId) == true) {
|
||||
if (parentChatMessage.actorId?.equals(message.activeUser.userId) == true) {
|
||||
quoteColoredView?.setBackgroundResource(R.color.colorPrimary)
|
||||
} else {
|
||||
quoteColoredView?.setBackgroundResource(R.color.textColorMaxContrast)
|
||||
|
|
|
@ -36,7 +36,6 @@ import autodagger.AutoInjector
|
|||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
|
@ -48,8 +47,7 @@ import com.nextcloud.talk.utils.DisplayUtils.getMessageSelector
|
|||
import com.nextcloud.talk.utils.DisplayUtils.searchAndReplaceWithMentionSpan
|
||||
import com.nextcloud.talk.utils.TextMatchers
|
||||
import com.stfalcon.chatkit.messages.MessageHolders.OutcomingTextMessageViewHolder
|
||||
import com.stfalcon.chatkit.utils.DateFormatter
|
||||
import java.util.*
|
||||
import java.util.HashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
|
@ -57,6 +55,7 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
|||
@JvmField
|
||||
@BindView(R.id.messageText)
|
||||
var messageText: EmojiTextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.messageTime)
|
||||
var messageTimeView: TextView? = null
|
||||
|
@ -104,20 +103,26 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
|||
for (key in messageParameters.keys) {
|
||||
val individualHashMap: HashMap<String, String>? = message.messageParameters[key]
|
||||
if (individualHashMap != null) {
|
||||
if (individualHashMap["type"] == "user" || (individualHashMap["type"]
|
||||
== "guest") || individualHashMap["type"] == "call") {
|
||||
messageString = searchAndReplaceWithMentionSpan(messageText!!.context,
|
||||
messageString,
|
||||
individualHashMap["id"]!!,
|
||||
individualHashMap["name"]!!,
|
||||
individualHashMap["type"]!!,
|
||||
message.activeUser,
|
||||
R.xml.chip_others)
|
||||
if (individualHashMap["type"] == "user" || (
|
||||
individualHashMap["type"] == "guest"
|
||||
) || individualHashMap["type"] == "call"
|
||||
) {
|
||||
messageString = searchAndReplaceWithMentionSpan(
|
||||
messageText!!.context,
|
||||
messageString,
|
||||
individualHashMap["id"]!!,
|
||||
individualHashMap["name"]!!,
|
||||
individualHashMap["type"]!!,
|
||||
message.activeUser,
|
||||
R.xml.chip_others
|
||||
)
|
||||
} else if (individualHashMap["type"] == "file") {
|
||||
realView.setOnClickListener(View.OnClickListener { v: View? ->
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap["link"]))
|
||||
context!!.startActivity(browserIntent)
|
||||
})
|
||||
realView.setOnClickListener(
|
||||
View.OnClickListener { v: View? ->
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap["link"]))
|
||||
context!!.startActivity(browserIntent)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,17 +140,19 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
|||
}
|
||||
if (message.isGrouped) {
|
||||
val bubbleDrawable = getMessageSelector(
|
||||
bgBubbleColor,
|
||||
resources.getColor(R.color.transparent),
|
||||
bgBubbleColor,
|
||||
R.drawable.shape_grouped_outcoming_message)
|
||||
bgBubbleColor,
|
||||
resources.getColor(R.color.transparent),
|
||||
bgBubbleColor,
|
||||
R.drawable.shape_grouped_outcoming_message
|
||||
)
|
||||
ViewCompat.setBackground(bubble, bubbleDrawable)
|
||||
} else {
|
||||
val bubbleDrawable = getMessageSelector(
|
||||
bgBubbleColor,
|
||||
resources.getColor(R.color.transparent),
|
||||
bgBubbleColor,
|
||||
R.drawable.shape_outcoming_message)
|
||||
bgBubbleColor,
|
||||
resources.getColor(R.color.transparent),
|
||||
bgBubbleColor,
|
||||
R.drawable.shape_outcoming_message
|
||||
)
|
||||
ViewCompat.setBackground(bubble, bubbleDrawable)
|
||||
}
|
||||
messageText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
|
@ -160,13 +167,16 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
|||
parentChatMessage.imageUrl?.let {
|
||||
quotedMessagePreview?.visibility = View.VISIBLE
|
||||
quotedMessagePreview?.load(it) {
|
||||
addHeader("Authorization", ApiUtils.getCredentials(message.activeUser.username, message.activeUser.token))
|
||||
addHeader(
|
||||
"Authorization",
|
||||
ApiUtils.getCredentials(message.activeUser.username, message.activeUser.token)
|
||||
)
|
||||
}
|
||||
} ?: run {
|
||||
quotedMessagePreview?.visibility = View.GONE
|
||||
}
|
||||
quotedUserName?.text = parentChatMessage.actorDisplayName
|
||||
?: context!!.getText(R.string.nc_nick_guest)
|
||||
?: context!!.getText(R.string.nc_nick_guest)
|
||||
quotedMessage?.text = parentChatMessage.text
|
||||
quotedMessage?.setTextColor(context!!.resources.getColor(R.color.nc_outcoming_text_default))
|
||||
quotedUserName?.setTextColor(context!!.resources.getColor(R.color.nc_grey))
|
||||
|
|
|
@ -90,6 +90,7 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
|
|||
|
||||
@Inject
|
||||
lateinit var appPreferences: AppPreferences
|
||||
|
||||
@Inject
|
||||
lateinit var okHttpClient: OkHttpClient
|
||||
//endregion
|
||||
|
@ -105,8 +106,10 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
|
|||
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true)
|
||||
}
|
||||
|
||||
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(this)
|
||||
.createInitializationOptions())
|
||||
PeerConnectionFactory.initialize(
|
||||
PeerConnectionFactory.InitializationOptions.builder(this)
|
||||
.createInitializationOptions()
|
||||
)
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
|
@ -120,8 +123,8 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
|
|||
|
||||
val securityKeyManager = SecurityKeyManager.getInstance()
|
||||
val securityKeyConfig = SecurityKeyManagerConfig.Builder()
|
||||
.setEnableDebugLogging(BuildConfig.DEBUG)
|
||||
.build()
|
||||
.setEnableDebugLogging(BuildConfig.DEBUG)
|
||||
.build()
|
||||
securityKeyManager.init(this, securityKeyConfig)
|
||||
|
||||
initializeWebRtc()
|
||||
|
@ -136,13 +139,15 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
|
|||
super.onCreate()
|
||||
|
||||
val imagePipelineConfig = ImagePipelineConfig.newBuilder(this)
|
||||
.setNetworkFetcher(OkHttpNetworkFetcherWithCache(okHttpClient))
|
||||
.setMainDiskCacheConfig(DiskCacheConfig.newBuilder(this)
|
||||
.setMaxCacheSize(0)
|
||||
.setMaxCacheSizeOnLowDiskSpace(0)
|
||||
.setMaxCacheSizeOnVeryLowDiskSpace(0)
|
||||
.build())
|
||||
.build()
|
||||
.setNetworkFetcher(OkHttpNetworkFetcherWithCache(okHttpClient))
|
||||
.setMainDiskCacheConfig(
|
||||
DiskCacheConfig.newBuilder(this)
|
||||
.setMaxCacheSize(0)
|
||||
.setMaxCacheSizeOnLowDiskSpace(0)
|
||||
.setMaxCacheSizeOnVeryLowDiskSpace(0)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
|
||||
Fresco.initialize(this, imagePipelineConfig)
|
||||
Security.insertProviderAt(Conscrypt.newProvider(), 1)
|
||||
|
@ -152,8 +157,10 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
|
|||
|
||||
val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java).build()
|
||||
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
|
||||
val periodicCapabilitiesUpdateWork = PeriodicWorkRequest.Builder(CapabilitiesWorker::class.java,
|
||||
12, TimeUnit.HOURS).build()
|
||||
val periodicCapabilitiesUpdateWork = PeriodicWorkRequest.Builder(
|
||||
CapabilitiesWorker::class.java,
|
||||
12, TimeUnit.HOURS
|
||||
).build()
|
||||
val capabilitiesUpdateWork = OneTimeWorkRequest.Builder(CapabilitiesWorker::class.java).build()
|
||||
val signalingSettingsWork = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java).build()
|
||||
|
||||
|
@ -161,7 +168,11 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
|
|||
WorkManager.getInstance().enqueue(accountRemovalWork)
|
||||
WorkManager.getInstance().enqueue(capabilitiesUpdateWork)
|
||||
WorkManager.getInstance().enqueue(signalingSettingsWork)
|
||||
WorkManager.getInstance().enqueueUniquePeriodicWork("DailyCapabilitiesUpdateWork", ExistingPeriodicWorkPolicy.REPLACE, periodicCapabilitiesUpdateWork)
|
||||
WorkManager.getInstance().enqueueUniquePeriodicWork(
|
||||
"DailyCapabilitiesUpdateWork",
|
||||
ExistingPeriodicWorkPolicy.REPLACE,
|
||||
periodicCapabilitiesUpdateWork
|
||||
)
|
||||
|
||||
val config = BundledEmojiCompatConfig(this)
|
||||
config.setReplaceAll(true)
|
||||
|
@ -176,17 +187,16 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
|
|||
}
|
||||
//endregion
|
||||
|
||||
|
||||
//region Protected methods
|
||||
protected fun buildComponent() {
|
||||
componentApplication = DaggerNextcloudTalkApplicationComponent.builder()
|
||||
.busModule(BusModule())
|
||||
.contextModule(ContextModule(applicationContext))
|
||||
.databaseModule(DatabaseModule())
|
||||
.restModule(RestModule(applicationContext))
|
||||
.userModule(UserModule())
|
||||
.arbitraryStorageModule(ArbitraryStorageModule())
|
||||
.build()
|
||||
.busModule(BusModule())
|
||||
.contextModule(ContextModule(applicationContext))
|
||||
.databaseModule(DatabaseModule())
|
||||
.restModule(RestModule(applicationContext))
|
||||
.userModule(UserModule())
|
||||
.arbitraryStorageModule(ArbitraryStorageModule())
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
|
@ -196,19 +206,20 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
|
|||
|
||||
private fun buildDefaultImageLoader(): ImageLoader {
|
||||
return ImageLoader.Builder(applicationContext)
|
||||
.availableMemoryPercentage(0.5) // Use 50% of the application's available memory.
|
||||
.crossfade(true) // Show a short crossfade when loading images from network or disk into an ImageView.
|
||||
.componentRegistry {
|
||||
if (SDK_INT >= P) {
|
||||
add(ImageDecoderDecoder(applicationContext))
|
||||
} else {
|
||||
add(GifDecoder())
|
||||
}
|
||||
add(SvgDecoder(applicationContext))
|
||||
.availableMemoryPercentage(0.5) // Use 50% of the application's available memory.
|
||||
.crossfade(true) // Show a short crossfade when loading images from network or disk into an ImageView.
|
||||
.componentRegistry {
|
||||
if (SDK_INT >= P) {
|
||||
add(ImageDecoderDecoder(applicationContext))
|
||||
} else {
|
||||
add(GifDecoder())
|
||||
}
|
||||
.okHttpClient(okHttpClient)
|
||||
.build()
|
||||
add(SvgDecoder(applicationContext))
|
||||
}
|
||||
.okHttpClient(okHttpClient)
|
||||
.build()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = NextcloudTalkApplication::class.java.simpleName
|
||||
//region Singleton
|
||||
|
|
|
@ -44,10 +44,6 @@ import android.widget.ImageView;
|
|||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
|
||||
import com.bluelinelabs.logansquare.LoganSquare;
|
||||
|
@ -71,7 +67,6 @@ import com.nextcloud.talk.models.RingtoneSettings;
|
|||
import com.nextcloud.talk.models.database.UserEntity;
|
||||
import com.nextcloud.talk.models.json.conversations.Conversation;
|
||||
import com.nextcloud.talk.models.json.conversations.RoomOverall;
|
||||
import com.nextcloud.talk.models.json.conversations.RoomsOverall;
|
||||
import com.nextcloud.talk.models.json.participants.Participant;
|
||||
import com.nextcloud.talk.models.json.participants.ParticipantsOverall;
|
||||
import com.nextcloud.talk.utils.ApiUtils;
|
||||
|
@ -94,6 +89,9 @@ import java.util.List;
|
|||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import autodagger.AutoInjector;
|
||||
import butterknife.BindView;
|
||||
import butterknife.OnClick;
|
||||
|
@ -206,13 +204,13 @@ public class CallNotificationController extends BaseController {
|
|||
originalBundle.putString(BundleKeys.INSTANCE.getKEY_CONVERSATION_NAME(), currentConversation.getDisplayName());
|
||||
|
||||
getRouter().replaceTopController(RouterTransaction.with(new CallController(originalBundle))
|
||||
.popChangeHandler(new HorizontalChangeHandler())
|
||||
.pushChangeHandler(new HorizontalChangeHandler()));
|
||||
.popChangeHandler(new HorizontalChangeHandler())
|
||||
.pushChangeHandler(new HorizontalChangeHandler()));
|
||||
}
|
||||
|
||||
private void checkIfAnyParticipantsRemainInRoom() {
|
||||
ncApi.getPeersForCall(credentials, ApiUtils.getUrlForCall(userBeingCalled.getBaseUrl(),
|
||||
currentConversation.getToken()))
|
||||
currentConversation.getToken()))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.takeWhile(observable -> !leavingScreen)
|
||||
.subscribe(new Observer<ParticipantsOverall>() {
|
||||
|
@ -261,7 +259,7 @@ public class CallNotificationController extends BaseController {
|
|||
|
||||
private void handleFromNotification() {
|
||||
boolean isConversationApiV3 = userBeingCalled.hasSpreedFeatureCapability("conversation-v3");
|
||||
if(isConversationApiV3) {
|
||||
if (isConversationApiV3) {
|
||||
ncApi.getRoom(credentials, ApiUtils.getRoomV3(userBeingCalled.getBaseUrl(), roomId))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.retry(3)
|
||||
|
@ -279,12 +277,12 @@ public class CallNotificationController extends BaseController {
|
|||
|
||||
boolean hasCallFlags = userBeingCalled.hasSpreedFeatureCapability("conversation-call-flags");
|
||||
if (hasCallFlags) {
|
||||
if (isInCallWithVideo(currentConversation.callFlag)){
|
||||
if (isInCallWithVideo(currentConversation.callFlag)) {
|
||||
incomingCallVoiceOrVideoTextView.setText(String.format(getResources().getString(R.string.nc_call_video),
|
||||
getResources().getString(R.string.nc_app_name)));
|
||||
getResources().getString(R.string.nc_app_name)));
|
||||
} else {
|
||||
incomingCallVoiceOrVideoTextView.setText(String.format(getResources().getString(R.string.nc_call_voice),
|
||||
getResources().getString(R.string.nc_app_name)));
|
||||
getResources().getString(R.string.nc_app_name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -412,7 +410,7 @@ public class CallNotificationController extends BaseController {
|
|||
|
||||
ImageRequest imageRequest =
|
||||
DisplayUtils.getImageRequestForUrl(ApiUtils.getUrlForAvatarWithName(userBeingCalled.getBaseUrl(),
|
||||
currentConversation.getName(), R.dimen.avatar_size_very_big), null);
|
||||
currentConversation.getName(), R.dimen.avatar_size_very_big), null);
|
||||
|
||||
ImagePipeline imagePipeline = Fresco.getImagePipeline();
|
||||
DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(imageRequest, null);
|
||||
|
@ -422,11 +420,11 @@ public class CallNotificationController extends BaseController {
|
|||
protected void onNewResultImpl(@Nullable Bitmap bitmap) {
|
||||
if (avatarImageView != null) {
|
||||
avatarImageView.getHierarchy().setImage(new BitmapDrawable(bitmap), 100,
|
||||
true);
|
||||
true);
|
||||
|
||||
if (getResources() != null) {
|
||||
incomingTextRelativeLayout.setBackground(getResources().getDrawable(R.drawable
|
||||
.incoming_gradient));
|
||||
.incoming_gradient));
|
||||
}
|
||||
|
||||
if ((AvatarStatusCodeHolder.getInstance().getStatusCode() == 200 || AvatarStatusCodeHolder.getInstance().getStatusCode() == 0) &&
|
||||
|
@ -512,7 +510,7 @@ public class CallNotificationController extends BaseController {
|
|||
if (TextUtils.isEmpty(callRingtonePreferenceString)) {
|
||||
// play default sound
|
||||
ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() +
|
||||
"/raw/librem_by_feandesign_call");
|
||||
"/raw/librem_by_feandesign_call");
|
||||
} else {
|
||||
try {
|
||||
RingtoneSettings ringtoneSettings = LoganSquare.parse(callRingtonePreferenceString, RingtoneSettings.class);
|
||||
|
@ -520,7 +518,7 @@ public class CallNotificationController extends BaseController {
|
|||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to parse ringtone settings");
|
||||
ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() +
|
||||
"/raw/librem_by_feandesign_call");
|
||||
"/raw/librem_by_feandesign_call");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -531,7 +529,7 @@ public class CallNotificationController extends BaseController {
|
|||
|
||||
mediaPlayer.setLooping(true);
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder().setContentType(AudioAttributes
|
||||
.CONTENT_TYPE_SONIFICATION).setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build();
|
||||
.CONTENT_TYPE_SONIFICATION).setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build();
|
||||
mediaPlayer.setAudioAttributes(audioAttributes);
|
||||
|
||||
mediaPlayer.setOnPreparedListener(mp -> mediaPlayer.start());
|
||||
|
|
|
@ -85,50 +85,69 @@ import io.reactivex.schedulers.Schedulers
|
|||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.util.*
|
||||
import java.util.Calendar
|
||||
import java.util.Collections
|
||||
import java.util.Comparator
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleAdapter.OnItemClickListener {
|
||||
|
||||
@BindView(R.id.notification_settings)
|
||||
lateinit var notificationsPreferenceScreen: MaterialPreferenceScreen
|
||||
|
||||
@BindView(R.id.progressBar)
|
||||
lateinit var progressBar: ProgressBar
|
||||
|
||||
@BindView(R.id.conversation_info_message_notifications)
|
||||
lateinit var messageNotificationLevel: MaterialChoicePreference
|
||||
|
||||
@BindView(R.id.webinar_settings)
|
||||
lateinit var conversationInfoWebinar: MaterialPreferenceScreen
|
||||
|
||||
@BindView(R.id.conversation_info_lobby)
|
||||
lateinit var conversationInfoLobby: MaterialSwitchPreference
|
||||
|
||||
@BindView(R.id.conversation_info_name)
|
||||
lateinit var nameCategoryView: MaterialPreferenceCategory
|
||||
|
||||
@BindView(R.id.start_time_preferences)
|
||||
lateinit var startTimeView: MaterialStandardPreference
|
||||
|
||||
@BindView(R.id.avatar_image)
|
||||
lateinit var conversationAvatarImageView: SimpleDraweeView
|
||||
|
||||
@BindView(R.id.display_name_text)
|
||||
lateinit var conversationDisplayName: EmojiTextView
|
||||
|
||||
@BindView(R.id.participants_list_category)
|
||||
lateinit var participantsListCategory: MaterialPreferenceCategory
|
||||
|
||||
@BindView(R.id.addParticipantsAction)
|
||||
lateinit var addParticipantsAction: MaterialStandardPreference;
|
||||
lateinit var addParticipantsAction: MaterialStandardPreference
|
||||
|
||||
@BindView(R.id.recycler_view)
|
||||
lateinit var recyclerView: RecyclerView
|
||||
|
||||
@BindView(R.id.deleteConversationAction)
|
||||
lateinit var deleteConversationAction: MaterialStandardPreference
|
||||
|
||||
@BindView(R.id.leaveConversationAction)
|
||||
lateinit var leaveConversationAction: MaterialStandardPreference
|
||||
|
||||
@BindView(R.id.ownOptions)
|
||||
lateinit var ownOptionsCategory: MaterialPreferenceCategory
|
||||
|
||||
@BindView(R.id.muteCalls)
|
||||
lateinit var muteCalls: MaterialSwitchPreference
|
||||
|
||||
@set:Inject
|
||||
lateinit var ncApi: NcApi
|
||||
|
||||
@set:Inject
|
||||
lateinit var context: Context
|
||||
|
||||
@set:Inject
|
||||
lateinit var eventBus: EventBus
|
||||
|
||||
|
@ -164,7 +183,7 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
NextcloudTalkApplication.sharedApplication?.componentApplication?.inject(this)
|
||||
conversationUser = args.getParcelable(BundleKeys.KEY_USER_ENTITY)
|
||||
conversationToken = args.getString(BundleKeys.KEY_ROOM_TOKEN)
|
||||
hasAvatarSpacing = args.getBoolean(BundleKeys.KEY_ROOM_ONE_TO_ONE, false);
|
||||
hasAvatarSpacing = args.getBoolean(BundleKeys.KEY_ROOM_ONE_TO_ONE, false)
|
||||
credentials = ApiUtils.getCredentials(conversationUser!!.username, conversationUser.token)
|
||||
}
|
||||
|
||||
|
@ -207,14 +226,19 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
}
|
||||
|
||||
private fun setupWebinaryView() {
|
||||
if (conversationUser!!.hasSpreedFeatureCapability("webinary-lobby") && (conversation!!.type
|
||||
== Conversation.ConversationType.ROOM_GROUP_CALL || conversation!!.type ==
|
||||
Conversation.ConversationType.ROOM_PUBLIC_CALL) && conversation!!.canModerate(conversationUser)) {
|
||||
if (conversationUser!!.hasSpreedFeatureCapability("webinary-lobby") &&
|
||||
(
|
||||
conversation!!.type == Conversation.ConversationType.ROOM_GROUP_CALL ||
|
||||
conversation!!.type == Conversation.ConversationType.ROOM_PUBLIC_CALL
|
||||
) &&
|
||||
conversation!!.canModerate(conversationUser)
|
||||
) {
|
||||
conversationInfoWebinar.visibility = View.VISIBLE
|
||||
|
||||
val isLobbyOpenToModeratorsOnly = conversation!!.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY
|
||||
val isLobbyOpenToModeratorsOnly =
|
||||
conversation!!.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY
|
||||
(conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat)
|
||||
.isChecked = isLobbyOpenToModeratorsOnly
|
||||
.isChecked = isLobbyOpenToModeratorsOnly
|
||||
|
||||
reconfigureLobbyTimerView()
|
||||
|
||||
|
@ -225,12 +249,17 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
currentTimeCalendar.timeInMillis = conversation!!.lobbyTimer * 1000
|
||||
}
|
||||
|
||||
dateTimePicker(minDateTime = Calendar.getInstance(), requireFutureDateTime =
|
||||
true, currentDateTime = currentTimeCalendar, dateTimeCallback = { _,
|
||||
dateTime ->
|
||||
reconfigureLobbyTimerView(dateTime)
|
||||
submitLobbyChanges()
|
||||
})
|
||||
dateTimePicker(
|
||||
minDateTime = Calendar.getInstance(),
|
||||
requireFutureDateTime =
|
||||
true,
|
||||
currentDateTime = currentTimeCalendar,
|
||||
dateTimeCallback = { _,
|
||||
dateTime ->
|
||||
reconfigureLobbyTimerView(dateTime)
|
||||
submitLobbyChanges()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,7 +282,7 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
}
|
||||
|
||||
conversation!!.lobbyState = if (isChecked) Conversation.LobbyState
|
||||
.LOBBY_STATE_MODERATORS_ONLY else Conversation.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS
|
||||
.LOBBY_STATE_MODERATORS_ONLY else Conversation.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS
|
||||
|
||||
if (conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != java.lang.Long.MIN_VALUE && conversation!!.lobbyTimer != 0L) {
|
||||
startTimeView.setSummary(DateUtils.getLocalDateStringFromTimestampForLobby(conversation!!.lobbyTimer))
|
||||
|
@ -269,27 +298,34 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
}
|
||||
|
||||
fun submitLobbyChanges() {
|
||||
val state = if ((conversationInfoLobby.findViewById<View>(R.id
|
||||
.mp_checkable) as SwitchCompat).isChecked) 1 else 0
|
||||
ncApi.setLobbyForConversation(ApiUtils.getCredentials(conversationUser!!.username,
|
||||
conversationUser.token), ApiUtils.getUrlForLobbyForConversation
|
||||
(conversationUser.baseUrl, conversation!!.token), state, conversation!!.lobbyTimer)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<GenericOverall> {
|
||||
override fun onComplete() {
|
||||
}
|
||||
val state = if (
|
||||
(
|
||||
conversationInfoLobby.findViewById<View>(
|
||||
R.id.mp_checkable
|
||||
) as SwitchCompat
|
||||
).isChecked
|
||||
) 1 else 0
|
||||
ncApi.setLobbyForConversation(
|
||||
ApiUtils.getCredentials(conversationUser!!.username, conversationUser.token),
|
||||
ApiUtils.getUrlForLobbyForConversation(conversationUser.baseUrl, conversation!!.token),
|
||||
state,
|
||||
conversation!!.lobbyTimer
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<GenericOverall> {
|
||||
override fun onComplete() {
|
||||
}
|
||||
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
}
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
}
|
||||
|
||||
override fun onNext(t: GenericOverall) {
|
||||
}
|
||||
override fun onNext(t: GenericOverall) {
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
}
|
||||
|
||||
})
|
||||
override fun onError(e: Throwable) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun showLovelyDialog(dialogId: Int, savedInstanceState: Bundle) {
|
||||
|
@ -313,17 +349,21 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
private fun showDeleteConversationDialog(savedInstanceState: Bundle?) {
|
||||
if (activity != null) {
|
||||
LovelyStandardDialog(activity, LovelyStandardDialog.ButtonLayout.HORIZONTAL)
|
||||
.setTopColorRes(R.color.nc_darkRed)
|
||||
.setIcon(DisplayUtils.getTintedDrawable(context!!.resources,
|
||||
R.drawable.ic_delete_black_24dp, R.color.bg_default))
|
||||
.setPositiveButtonColor(context!!.resources.getColor(R.color.nc_darkRed))
|
||||
.setTitle(R.string.nc_delete_call)
|
||||
.setMessage(conversation!!.deleteWarningMessage)
|
||||
.setPositiveButton(R.string.nc_delete) { deleteConversation() }
|
||||
.setNegativeButton(R.string.nc_cancel, null)
|
||||
.setInstanceStateHandler(ID_DELETE_CONVERSATION_DIALOG, saveStateHandler!!)
|
||||
.setSavedInstanceState(savedInstanceState)
|
||||
.show()
|
||||
.setTopColorRes(R.color.nc_darkRed)
|
||||
.setIcon(
|
||||
DisplayUtils.getTintedDrawable(
|
||||
context!!.resources,
|
||||
R.drawable.ic_delete_black_24dp, R.color.bg_default
|
||||
)
|
||||
)
|
||||
.setPositiveButtonColor(context!!.resources.getColor(R.color.nc_darkRed))
|
||||
.setTitle(R.string.nc_delete_call)
|
||||
.setMessage(conversation!!.deleteWarningMessage)
|
||||
.setPositiveButton(R.string.nc_delete) { deleteConversation() }
|
||||
.setNegativeButton(R.string.nc_cancel, null)
|
||||
.setInstanceStateHandler(ID_DELETE_CONVERSATION_DIALOG, saveStateHandler!!)
|
||||
.setSavedInstanceState(savedInstanceState)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,8 +375,8 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
override fun onRestoreViewState(view: View, savedViewState: Bundle) {
|
||||
super.onRestoreViewState(view, savedViewState)
|
||||
if (LovelySaveStateHandler.wasDialogOnScreen(savedViewState)) {
|
||||
//Dialog won't be restarted automatically, so we need to call this method.
|
||||
//Each dialog knows how to restore its state
|
||||
// Dialog won't be restarted automatically, so we need to call this method.
|
||||
// Each dialog knows how to restore its state
|
||||
showLovelyDialog(LovelySaveStateHandler.getSavedDialogId(savedViewState), savedViewState)
|
||||
}
|
||||
}
|
||||
|
@ -397,27 +437,28 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
}
|
||||
|
||||
private fun getListOfParticipants() {
|
||||
ncApi.getPeersForCall(credentials, ApiUtils.getUrlForParticipants(conversationUser!!.baseUrl, conversationToken))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<ParticipantsOverall> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
participantsDisposable = d
|
||||
}
|
||||
ncApi.getPeersForCall(
|
||||
credentials,
|
||||
ApiUtils.getUrlForParticipants(conversationUser!!.baseUrl, conversationToken)
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<ParticipantsOverall> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
participantsDisposable = d
|
||||
}
|
||||
|
||||
override fun onNext(participantsOverall: ParticipantsOverall) {
|
||||
handleParticipants(participantsOverall.ocs.data)
|
||||
}
|
||||
override fun onNext(participantsOverall: ParticipantsOverall) {
|
||||
handleParticipants(participantsOverall.ocs.data)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
participantsDisposable!!.dispose()
|
||||
}
|
||||
})
|
||||
override fun onError(e: Throwable) {
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
participantsDisposable!!.dispose()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@OnClick(R.id.addParticipantsAction)
|
||||
|
@ -430,21 +471,33 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
existingParticipantsId.add(userItem.model.userId)
|
||||
}
|
||||
|
||||
bundle.putBoolean(BundleKeys.KEY_ADD_PARTICIPANTS, true);
|
||||
bundle.putBoolean(BundleKeys.KEY_ADD_PARTICIPANTS, true)
|
||||
bundle.putStringArrayList(BundleKeys.KEY_EXISTING_PARTICIPANTS, existingParticipantsId)
|
||||
bundle.putString(BundleKeys.KEY_TOKEN, conversation!!.token)
|
||||
|
||||
getRouter().pushController((RouterTransaction.with(ContactsController(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())))
|
||||
getRouter().pushController(
|
||||
(
|
||||
RouterTransaction.with(
|
||||
ContactsController(bundle)
|
||||
)
|
||||
.pushChangeHandler(
|
||||
HorizontalChangeHandler()
|
||||
)
|
||||
.popChangeHandler(
|
||||
HorizontalChangeHandler()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@OnClick(R.id.leaveConversationAction)
|
||||
internal fun leaveConversation() {
|
||||
workerData?.let {
|
||||
WorkManager.getInstance().enqueue(OneTimeWorkRequest.Builder
|
||||
(LeaveConversationWorker::class
|
||||
.java).setInputData(it).build()
|
||||
WorkManager.getInstance().enqueue(
|
||||
OneTimeWorkRequest.Builder(
|
||||
LeaveConversationWorker::class
|
||||
.java
|
||||
).setInputData(it).build()
|
||||
)
|
||||
popTwoLastControllers()
|
||||
}
|
||||
|
@ -452,8 +505,11 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
|
||||
private fun deleteConversation() {
|
||||
workerData?.let {
|
||||
WorkManager.getInstance().enqueue(OneTimeWorkRequest.Builder
|
||||
(DeleteConversationWorker::class.java).setInputData(it).build())
|
||||
WorkManager.getInstance().enqueue(
|
||||
OneTimeWorkRequest.Builder(
|
||||
DeleteConversationWorker::class.java
|
||||
).setInputData(it).build()
|
||||
)
|
||||
popTwoLastControllers()
|
||||
}
|
||||
}
|
||||
|
@ -471,69 +527,67 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
|
||||
private fun fetchRoomInfo() {
|
||||
ncApi.getRoom(credentials, ApiUtils.getRoom(conversationUser!!.baseUrl, conversationToken))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<RoomOverall> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
roomDisposable = d
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<RoomOverall> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
roomDisposable = d
|
||||
}
|
||||
|
||||
override fun onNext(roomOverall: RoomOverall) {
|
||||
conversation = roomOverall.ocs.data
|
||||
|
||||
val conversationCopy = conversation
|
||||
|
||||
if (conversationCopy!!.canModerate(conversationUser)) {
|
||||
addParticipantsAction.visibility = View.VISIBLE
|
||||
} else {
|
||||
addParticipantsAction.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onNext(roomOverall: RoomOverall) {
|
||||
conversation = roomOverall.ocs.data
|
||||
if (isAttached && (!isBeingDestroyed || !isDestroyed)) {
|
||||
ownOptionsCategory.visibility = View.VISIBLE
|
||||
|
||||
val conversationCopy = conversation
|
||||
setupWebinaryView()
|
||||
|
||||
if (conversationCopy!!.canModerate(conversationUser)) {
|
||||
addParticipantsAction.visibility = View.VISIBLE
|
||||
if (!conversation!!.canLeave(conversationUser)) {
|
||||
leaveConversationAction.visibility = View.GONE
|
||||
} else {
|
||||
addParticipantsAction.visibility = View.GONE
|
||||
leaveConversationAction.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
if (isAttached && (!isBeingDestroyed || !isDestroyed)) {
|
||||
ownOptionsCategory.visibility = View.VISIBLE
|
||||
|
||||
setupWebinaryView()
|
||||
|
||||
if (!conversation!!.canLeave(conversationUser)) {
|
||||
leaveConversationAction.visibility = View.GONE
|
||||
} else {
|
||||
leaveConversationAction.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
if (!conversation!!.canModerate(conversationUser)) {
|
||||
deleteConversationAction.visibility = View.GONE
|
||||
} else {
|
||||
deleteConversationAction.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
if (Conversation.ConversationType.ROOM_SYSTEM == conversation!!.type) {
|
||||
muteCalls.visibility = View.GONE
|
||||
}
|
||||
|
||||
getListOfParticipants()
|
||||
|
||||
progressBar.visibility = View.GONE
|
||||
|
||||
nameCategoryView.visibility = View.VISIBLE
|
||||
|
||||
conversationDisplayName.text = conversation!!.displayName
|
||||
|
||||
|
||||
loadConversationAvatar()
|
||||
adjustNotificationLevelUI()
|
||||
|
||||
notificationsPreferenceScreen.visibility = View.VISIBLE
|
||||
if (!conversation!!.canModerate(conversationUser)) {
|
||||
deleteConversationAction.visibility = View.GONE
|
||||
} else {
|
||||
deleteConversationAction.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
if (Conversation.ConversationType.ROOM_SYSTEM == conversation!!.type) {
|
||||
muteCalls.visibility = View.GONE
|
||||
}
|
||||
|
||||
}
|
||||
getListOfParticipants()
|
||||
|
||||
override fun onComplete() {
|
||||
roomDisposable!!.dispose()
|
||||
progressBar.visibility = View.GONE
|
||||
|
||||
nameCategoryView.visibility = View.VISIBLE
|
||||
|
||||
conversationDisplayName.text = conversation!!.displayName
|
||||
|
||||
loadConversationAvatar()
|
||||
adjustNotificationLevelUI()
|
||||
|
||||
notificationsPreferenceScreen.visibility = View.VISIBLE
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
roomDisposable!!.dispose()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun adjustNotificationLevelUI() {
|
||||
|
@ -543,12 +597,13 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
messageNotificationLevel.alpha = 1.0f
|
||||
|
||||
if (conversation!!.notificationLevel != Conversation.NotificationLevel.DEFAULT) {
|
||||
val stringValue: String = when (EnumNotificationLevelConverter().convertToInt(conversation!!.notificationLevel)) {
|
||||
1 -> "always"
|
||||
2 -> "mention"
|
||||
3 -> "never"
|
||||
else -> "mention"
|
||||
}
|
||||
val stringValue: String =
|
||||
when (EnumNotificationLevelConverter().convertToInt(conversation!!.notificationLevel)) {
|
||||
1 -> "always"
|
||||
2 -> "mention"
|
||||
3 -> "never"
|
||||
else -> "mention"
|
||||
}
|
||||
|
||||
messageNotificationLevel.value = stringValue
|
||||
} else {
|
||||
|
@ -577,22 +632,38 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
|
||||
private fun loadConversationAvatar() {
|
||||
when (conversation!!.type) {
|
||||
Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty
|
||||
(conversation!!.name)) {
|
||||
Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (
|
||||
!TextUtils.isEmpty(conversation!!.name)
|
||||
) {
|
||||
val draweeController = Fresco.newDraweeControllerBuilder()
|
||||
.setOldController(conversationAvatarImageView.controller)
|
||||
.setAutoPlayAnimations(true)
|
||||
.setImageRequest(DisplayUtils.getImageRequestForUrl(ApiUtils.getUrlForAvatarWithName(conversationUser!!.baseUrl,
|
||||
conversation!!.name, R.dimen.avatar_size_big), conversationUser))
|
||||
.build()
|
||||
.setOldController(conversationAvatarImageView.controller)
|
||||
.setAutoPlayAnimations(true)
|
||||
.setImageRequest(
|
||||
DisplayUtils.getImageRequestForUrl(
|
||||
ApiUtils.getUrlForAvatarWithName(
|
||||
conversationUser!!.baseUrl,
|
||||
conversation!!.name, R.dimen.avatar_size_big
|
||||
),
|
||||
conversationUser
|
||||
)
|
||||
)
|
||||
.build()
|
||||
conversationAvatarImageView.controller = draweeController
|
||||
}
|
||||
Conversation.ConversationType.ROOM_GROUP_CALL -> conversationAvatarImageView.hierarchy.setPlaceholderImage(DisplayUtils
|
||||
.getRoundedBitmapDrawableFromVectorDrawableResource(resources,
|
||||
R.drawable.ic_people_group_white_24px))
|
||||
Conversation.ConversationType.ROOM_PUBLIC_CALL -> conversationAvatarImageView.hierarchy.setPlaceholderImage(DisplayUtils
|
||||
.getRoundedBitmapDrawableFromVectorDrawableResource(resources,
|
||||
R.drawable.ic_link_white_24px))
|
||||
Conversation.ConversationType.ROOM_GROUP_CALL -> conversationAvatarImageView.hierarchy.setPlaceholderImage(
|
||||
DisplayUtils
|
||||
.getRoundedBitmapDrawableFromVectorDrawableResource(
|
||||
resources,
|
||||
R.drawable.ic_people_group_white_24px
|
||||
)
|
||||
)
|
||||
Conversation.ConversationType.ROOM_PUBLIC_CALL -> conversationAvatarImageView.hierarchy.setPlaceholderImage(
|
||||
DisplayUtils
|
||||
.getRoundedBitmapDrawableFromVectorDrawableResource(
|
||||
resources,
|
||||
R.drawable.ic_link_white_24px
|
||||
)
|
||||
)
|
||||
Conversation.ConversationType.ROOM_SYSTEM -> {
|
||||
val layers = arrayOfNulls<Drawable>(2)
|
||||
layers[0] = context.getDrawable(R.drawable.ic_launcher_background)
|
||||
|
@ -610,13 +681,14 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
val userItem = adapter?.getItem(position) as UserItem
|
||||
val participant = userItem.model
|
||||
|
||||
|
||||
if (participant.userId != conversationUser!!.userId) {
|
||||
var items = mutableListOf(
|
||||
BasicListItemWithImage(R.drawable.ic_pencil_grey600_24dp, context.getString(R.string.nc_promote)),
|
||||
BasicListItemWithImage(R.drawable.ic_pencil_grey600_24dp, context.getString(R.string.nc_demote)),
|
||||
BasicListItemWithImage(R.drawable.ic_delete_grey600_24dp,
|
||||
context.getString(R.string.nc_remove_participant))
|
||||
BasicListItemWithImage(R.drawable.ic_pencil_grey600_24dp, context.getString(R.string.nc_promote)),
|
||||
BasicListItemWithImage(R.drawable.ic_pencil_grey600_24dp, context.getString(R.string.nc_demote)),
|
||||
BasicListItemWithImage(
|
||||
R.drawable.ic_delete_grey600_24dp,
|
||||
context.getString(R.string.nc_remove_participant)
|
||||
)
|
||||
)
|
||||
|
||||
if (!conversation!!.canModerate(conversationUser)) {
|
||||
|
@ -629,7 +701,6 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
if (items.isNotEmpty()) {
|
||||
MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show {
|
||||
cornerRadius(res = R.dimen.corner_radius)
|
||||
|
@ -639,38 +710,62 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
|
||||
if (index == 0) {
|
||||
if (participant.type == Participant.ParticipantType.MODERATOR) {
|
||||
ncApi.demoteModeratorToUser(credentials, ApiUtils.getUrlForModerators(conversationUser.baseUrl, conversation!!.token), participant.userId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
getListOfParticipants()
|
||||
}
|
||||
ncApi.demoteModeratorToUser(
|
||||
credentials,
|
||||
ApiUtils.getUrlForModerators(conversationUser.baseUrl, conversation!!.token),
|
||||
participant.userId
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
getListOfParticipants()
|
||||
}
|
||||
} else if (participant.type == Participant.ParticipantType.USER) {
|
||||
ncApi.promoteUserToModerator(credentials, ApiUtils.getUrlForModerators(conversationUser.baseUrl, conversation!!.token), participant.userId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
getListOfParticipants()
|
||||
}
|
||||
ncApi.promoteUserToModerator(
|
||||
credentials,
|
||||
ApiUtils.getUrlForModerators(conversationUser.baseUrl, conversation!!.token),
|
||||
participant.userId
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
getListOfParticipants()
|
||||
}
|
||||
}
|
||||
} else if (index == 1) {
|
||||
if (participant.type == Participant.ParticipantType.GUEST ||
|
||||
participant.type == Participant.ParticipantType.USER_FOLLOWING_LINK) {
|
||||
ncApi.removeParticipantFromConversation(credentials, ApiUtils.getUrlForRemovingParticipantFromConversation(conversationUser.baseUrl, conversation!!.token, true), participant.sessionId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
getListOfParticipants()
|
||||
}
|
||||
|
||||
participant.type == Participant.ParticipantType.USER_FOLLOWING_LINK
|
||||
) {
|
||||
ncApi.removeParticipantFromConversation(
|
||||
credentials,
|
||||
ApiUtils.getUrlForRemovingParticipantFromConversation(
|
||||
conversationUser.baseUrl,
|
||||
conversation!!.token,
|
||||
true
|
||||
),
|
||||
participant.sessionId
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
getListOfParticipants()
|
||||
}
|
||||
} else {
|
||||
ncApi.removeParticipantFromConversation(credentials, ApiUtils.getUrlForRemovingParticipantFromConversation(conversationUser.baseUrl, conversation!!.token, false), participant.userId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
getListOfParticipants()
|
||||
// get participants again
|
||||
}
|
||||
ncApi.removeParticipantFromConversation(
|
||||
credentials,
|
||||
ApiUtils.getUrlForRemovingParticipantFromConversation(
|
||||
conversationUser.baseUrl,
|
||||
conversation!!.token,
|
||||
false
|
||||
),
|
||||
participant.userId
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
getListOfParticipants()
|
||||
// get participants again
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -678,7 +773,7 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -709,7 +804,7 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
|
|||
}
|
||||
|
||||
return left.model.displayName.toLowerCase(Locale.ROOT).compareTo(
|
||||
right.model.displayName.toLowerCase(Locale.ROOT)
|
||||
right.model.displayName.toLowerCase(Locale.ROOT)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,5 +53,4 @@ abstract class ButterKnifeController : Controller {
|
|||
unbinder!!.unbind()
|
||||
unbinder = null
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,10 +29,11 @@ interface ListItemWithImage {
|
|||
}
|
||||
|
||||
data class BasicListItemWithImage(
|
||||
@DrawableRes val iconRes: Int,
|
||||
override val title: String) : ListItemWithImage {
|
||||
@DrawableRes val iconRes: Int,
|
||||
override val title: String
|
||||
) : ListItemWithImage {
|
||||
|
||||
override fun populateIcon(imageView: ImageView) {
|
||||
imageView.setImageResource(iconRes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ package com.nextcloud.talk.controllers.bottomsheet.items
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.WhichButton
|
||||
|
@ -34,14 +33,14 @@ import com.afollestad.materialdialogs.internal.rtl.RtlTextView
|
|||
import com.afollestad.materialdialogs.list.getItemSelector
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.inflate
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.maybeSetTextColor
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import com.nextcloud.talk.R
|
||||
|
||||
private const val KEY_ACTIVATED_INDEX = "activated_index"
|
||||
|
||||
internal class ListItemViewHolder(
|
||||
itemView: View,
|
||||
private val adapter: ListIconDialogAdapter<*>) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
|
||||
itemView: View,
|
||||
private val adapter: ListIconDialogAdapter<*>
|
||||
) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
|
||||
init {
|
||||
itemView.setOnClickListener(this)
|
||||
}
|
||||
|
@ -53,11 +52,12 @@ internal class ListItemViewHolder(
|
|||
}
|
||||
|
||||
internal class ListIconDialogAdapter<IT : ListItemWithImage>(
|
||||
private var dialog: MaterialDialog,
|
||||
private var items: List<IT>,
|
||||
disabledItems: IntArray?,
|
||||
private var waitForPositiveButton: Boolean,
|
||||
private var selection: ListItemListener<IT>) : RecyclerView.Adapter<ListItemViewHolder>(), DialogAdapter<IT, ListItemListener<IT>> {
|
||||
private var dialog: MaterialDialog,
|
||||
private var items: List<IT>,
|
||||
disabledItems: IntArray?,
|
||||
private var waitForPositiveButton: Boolean,
|
||||
private var selection: ListItemListener<IT>
|
||||
) : RecyclerView.Adapter<ListItemViewHolder>(), DialogAdapter<IT, ListItemListener<IT>> {
|
||||
|
||||
private var disabledIndices: IntArray = disabledItems ?: IntArray(0)
|
||||
|
||||
|
@ -81,12 +81,13 @@ internal class ListIconDialogAdapter<IT : ListItemWithImage>(
|
|||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int): ListItemViewHolder {
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): ListItemViewHolder {
|
||||
val listItemView: View = parent.inflate(dialog.windowContext, R.layout.menu_item_sheet)
|
||||
val viewHolder = ListItemViewHolder(
|
||||
itemView = listItemView,
|
||||
adapter = this
|
||||
itemView = listItemView,
|
||||
adapter = this
|
||||
)
|
||||
viewHolder.titleView.maybeSetTextColor(dialog.windowContext, R.attr.md_color_content)
|
||||
return viewHolder
|
||||
|
@ -95,8 +96,9 @@ internal class ListIconDialogAdapter<IT : ListItemWithImage>(
|
|||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: ListItemViewHolder,
|
||||
position: Int) {
|
||||
holder: ListItemViewHolder,
|
||||
position: Int
|
||||
) {
|
||||
holder.itemView.isEnabled = !disabledIndices.contains(position)
|
||||
val currentItem = items[position]
|
||||
|
||||
|
@ -121,8 +123,9 @@ internal class ListIconDialogAdapter<IT : ListItemWithImage>(
|
|||
}
|
||||
|
||||
override fun replaceItems(
|
||||
items: List<IT>,
|
||||
listener: ListItemListener<IT>) {
|
||||
items: List<IT>,
|
||||
listener: ListItemListener<IT>
|
||||
) {
|
||||
this.items = items
|
||||
if (listener != null) {
|
||||
this.selection = listener
|
||||
|
|
|
@ -28,37 +28,40 @@ import com.afollestad.materialdialogs.list.customListAdapter
|
|||
import com.afollestad.materialdialogs.list.getListAdapter
|
||||
|
||||
typealias ListItemListener<IT> =
|
||||
((dialog: MaterialDialog, index: Int, item: IT) -> Unit)?
|
||||
((dialog: MaterialDialog, index: Int, item: IT) -> Unit)?
|
||||
|
||||
@CheckResult fun <IT : ListItemWithImage> MaterialDialog.listItemsWithImage(
|
||||
items: List<IT>,
|
||||
disabledIndices: IntArray? = null,
|
||||
waitForPositiveButton: Boolean = true,
|
||||
selection: ListItemListener<IT> = null): MaterialDialog {
|
||||
@CheckResult
|
||||
fun <IT : ListItemWithImage> MaterialDialog.listItemsWithImage(
|
||||
items: List<IT>,
|
||||
disabledIndices: IntArray? = null,
|
||||
waitForPositiveButton: Boolean = true,
|
||||
selection: ListItemListener<IT> = null
|
||||
): MaterialDialog {
|
||||
|
||||
if (getListAdapter() != null) {
|
||||
return updateListItemsWithImage(
|
||||
items = items,
|
||||
disabledIndices = disabledIndices
|
||||
items = items,
|
||||
disabledIndices = disabledIndices
|
||||
)
|
||||
}
|
||||
|
||||
val layoutManager = LinearLayoutManager(windowContext)
|
||||
return customListAdapter(
|
||||
adapter = ListIconDialogAdapter(
|
||||
dialog = this,
|
||||
items = items,
|
||||
disabledItems = disabledIndices,
|
||||
waitForPositiveButton = waitForPositiveButton,
|
||||
selection = selection
|
||||
),
|
||||
layoutManager = layoutManager
|
||||
adapter = ListIconDialogAdapter(
|
||||
dialog = this,
|
||||
items = items,
|
||||
disabledItems = disabledIndices,
|
||||
waitForPositiveButton = waitForPositiveButton,
|
||||
selection = selection
|
||||
),
|
||||
layoutManager = layoutManager
|
||||
)
|
||||
}
|
||||
|
||||
fun MaterialDialog.updateListItemsWithImage(
|
||||
items: List<ListItemWithImage>,
|
||||
disabledIndices: IntArray? = null): MaterialDialog {
|
||||
items: List<ListItemWithImage>,
|
||||
disabledIndices: IntArray? = null
|
||||
): MaterialDialog {
|
||||
val adapter = getListAdapter()
|
||||
check(adapter != null) {
|
||||
"updateGridItems(...) can't be used before you've created a bottom sheet grid dialog."
|
||||
|
|
|
@ -20,6 +20,4 @@
|
|||
|
||||
package com.nextcloud.talk.events
|
||||
|
||||
class CallNotificationClick {
|
||||
|
||||
}
|
||||
class CallNotificationClick
|
||||
|
|
|
@ -33,7 +33,11 @@ import android.provider.ContactsContract
|
|||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import androidx.work.*
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import autodagger.AutoInjector
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.google.gson.Gson
|
||||
|
@ -54,10 +58,9 @@ import okhttp3.MediaType
|
|||
import okhttp3.RequestBody
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class ContactAddressBookWorker(val context: Context, workerParameters: WorkerParameters) :
|
||||
Worker(context, workerParameters) {
|
||||
Worker(context, workerParameters) {
|
||||
|
||||
@Inject
|
||||
lateinit var ncApi: NcApi
|
||||
|
@ -85,7 +88,7 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
}
|
||||
|
||||
val deleteAll = inputData.getBoolean(DELETE_ALL, false)
|
||||
if(deleteAll){
|
||||
if (deleteAll) {
|
||||
deleteAllLinkedAccounts()
|
||||
return Result.success()
|
||||
}
|
||||
|
@ -99,7 +102,7 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
}
|
||||
}
|
||||
|
||||
if(AccountManager.get(context).getAccountsByType(accountType).isEmpty()){
|
||||
if (AccountManager.get(context).getAccountsByType(accountType).isEmpty()) {
|
||||
AccountManager.get(context).addAccountExplicitly(Account(accountName, accountType), "", null)
|
||||
} else {
|
||||
Log.d(TAG, "Account already exists")
|
||||
|
@ -107,7 +110,7 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
|
||||
val deviceContactsWithNumbers = collectContactsWithPhoneNumbersFromDevice()
|
||||
|
||||
if(deviceContactsWithNumbers.isNotEmpty()){
|
||||
if (deviceContactsWithNumbers.isNotEmpty()) {
|
||||
val currentLocale = ConfigurationCompat.getLocales(context.resources.configuration)[0].country
|
||||
|
||||
val map = mutableMapOf<String, Any>()
|
||||
|
@ -117,28 +120,29 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
val json = Gson().toJson(map)
|
||||
|
||||
ncApi.searchContactsByPhoneNumber(
|
||||
ApiUtils.getCredentials(currentUser.username, currentUser.token),
|
||||
ApiUtils.getUrlForSearchByNumber(currentUser.baseUrl),
|
||||
RequestBody.create(MediaType.parse("application/json"), json))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<ContactsByNumberOverall> {
|
||||
override fun onComplete() {
|
||||
}
|
||||
ApiUtils.getCredentials(currentUser.username, currentUser.token),
|
||||
ApiUtils.getUrlForSearchByNumber(currentUser.baseUrl),
|
||||
RequestBody.create(MediaType.parse("application/json"), json)
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<ContactsByNumberOverall> {
|
||||
override fun onComplete() {
|
||||
}
|
||||
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
}
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
}
|
||||
|
||||
override fun onNext(foundContacts: ContactsByNumberOverall) {
|
||||
val contactsWithAssociatedPhoneNumbers = foundContacts.ocs.map
|
||||
deleteLinkedAccounts(contactsWithAssociatedPhoneNumbers)
|
||||
createLinkedAccounts(contactsWithAssociatedPhoneNumbers)
|
||||
}
|
||||
override fun onNext(foundContacts: ContactsByNumberOverall) {
|
||||
val contactsWithAssociatedPhoneNumbers = foundContacts.ocs.map
|
||||
deleteLinkedAccounts(contactsWithAssociatedPhoneNumbers)
|
||||
createLinkedAccounts(contactsWithAssociatedPhoneNumbers)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e(javaClass.simpleName, "Failed to searchContactsByPhoneNumber", e)
|
||||
}
|
||||
})
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e(javaClass.simpleName, "Failed to searchContactsByPhoneNumber", e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// store timestamp
|
||||
|
@ -151,11 +155,11 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
val deviceContactsWithNumbers: MutableMap<String, List<String>> = mutableMapOf()
|
||||
|
||||
val contactCursor = context.contentResolver.query(
|
||||
ContactsContract.Contacts.CONTENT_URI,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
ContactsContract.Contacts.CONTENT_URI,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
||||
if (contactCursor != null) {
|
||||
|
@ -163,7 +167,8 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
contactCursor.moveToFirst()
|
||||
for (i in 0 until contactCursor.count) {
|
||||
val id = contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts._ID))
|
||||
val lookup = contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY))
|
||||
val lookup =
|
||||
contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY))
|
||||
deviceContactsWithNumbers[lookup] = getPhoneNumbersFromDeviceContact(id)
|
||||
contactCursor.moveToNext()
|
||||
}
|
||||
|
@ -178,39 +183,50 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
Log.d(TAG, "deleteLinkedAccount")
|
||||
fun deleteLinkedAccount(id: String) {
|
||||
val rawContactUri = ContactsContract.RawContacts.CONTENT_URI
|
||||
.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
|
||||
.build()
|
||||
val count = context.contentResolver.delete(rawContactUri, ContactsContract.RawContacts.CONTACT_ID + " " +
|
||||
"LIKE \"" + id + "\"", null)
|
||||
Log.d(TAG, "deleted $count linked accounts for id $id")
|
||||
}
|
||||
|
||||
val rawContactUri = ContactsContract.Data.CONTENT_URI
|
||||
.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
|
||||
.appendQueryParameter(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat")
|
||||
.build()
|
||||
val count = context.contentResolver.delete(
|
||||
rawContactUri,
|
||||
ContactsContract.RawContacts.CONTACT_ID + " " + "LIKE \"" + id + "\"",
|
||||
null
|
||||
)
|
||||
Log.d(TAG, "deleted $count linked accounts for id $id")
|
||||
}
|
||||
|
||||
val rawContactUri = ContactsContract.Data.CONTENT_URI
|
||||
.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
|
||||
.appendQueryParameter(
|
||||
ContactsContract.Data.MIMETYPE,
|
||||
"vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat"
|
||||
)
|
||||
.build()
|
||||
|
||||
val rawContactsCursor = context.contentResolver.query(
|
||||
rawContactUri,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
rawContactUri,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
||||
if (rawContactsCursor != null) {
|
||||
if (rawContactsCursor.count > 0) {
|
||||
while (rawContactsCursor.moveToNext()) {
|
||||
val lookupKey = rawContactsCursor.getString(rawContactsCursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY))
|
||||
val contactId = rawContactsCursor.getString(rawContactsCursor.getColumnIndex(ContactsContract.Data.CONTACT_ID))
|
||||
val lookupKey =
|
||||
rawContactsCursor.getString(rawContactsCursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY))
|
||||
val contactId =
|
||||
rawContactsCursor.getString(rawContactsCursor.getColumnIndex(ContactsContract.Data.CONTACT_ID))
|
||||
|
||||
if (contactsWithAssociatedPhoneNumbers == null || !contactsWithAssociatedPhoneNumbers.containsKey(lookupKey)) {
|
||||
if (contactsWithAssociatedPhoneNumbers == null || !contactsWithAssociatedPhoneNumbers.containsKey(
|
||||
lookupKey
|
||||
)
|
||||
) {
|
||||
deleteLinkedAccount(contactId)
|
||||
}
|
||||
}
|
||||
|
@ -222,25 +238,29 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
}
|
||||
|
||||
private fun createLinkedAccounts(contactsWithAssociatedPhoneNumbers: Map<String, String>?) {
|
||||
fun hasLinkedAccount(id: String) : Boolean {
|
||||
fun hasLinkedAccount(id: String): Boolean {
|
||||
var hasLinkedAccount = false
|
||||
val where = ContactsContract.Data.MIMETYPE + " = ? AND " + ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " = ?"
|
||||
val where =
|
||||
ContactsContract.Data.MIMETYPE + " = ? AND " + ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " = ?"
|
||||
val params = arrayOf(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, id)
|
||||
|
||||
val rawContactUri = ContactsContract.Data.CONTENT_URI
|
||||
.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
|
||||
.appendQueryParameter(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat")
|
||||
.build()
|
||||
.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
|
||||
.appendQueryParameter(
|
||||
ContactsContract.Data.MIMETYPE,
|
||||
"vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat"
|
||||
)
|
||||
.build()
|
||||
|
||||
val rawContactsCursor = context.contentResolver.query(
|
||||
rawContactUri,
|
||||
null,
|
||||
where,
|
||||
params,
|
||||
null
|
||||
rawContactUri,
|
||||
null,
|
||||
where,
|
||||
params,
|
||||
null
|
||||
)
|
||||
|
||||
if (rawContactsCursor != null) {
|
||||
|
@ -259,18 +279,19 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
val lookupUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey)
|
||||
val lookupContactUri = ContactsContract.Contacts.lookupContact(context.contentResolver, lookupUri)
|
||||
val contactCursor = context.contentResolver.query(
|
||||
lookupContactUri,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null)
|
||||
lookupContactUri,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
||||
if (contactCursor != null) {
|
||||
if (contactCursor.count > 0) {
|
||||
contactCursor.moveToFirst()
|
||||
|
||||
val id = contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts._ID))
|
||||
if(hasLinkedAccount(id)){
|
||||
if (hasLinkedAccount(id)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -285,34 +306,60 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
val rawContactsUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon().build()
|
||||
val dataUri = ContactsContract.Data.CONTENT_URI.buildUpon().build()
|
||||
|
||||
ops.add(ContentProviderOperation
|
||||
ops.add(
|
||||
ContentProviderOperation
|
||||
.newInsert(rawContactsUri)
|
||||
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
|
||||
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
|
||||
.withValue(ContactsContract.RawContacts.AGGREGATION_MODE,
|
||||
ContactsContract.RawContacts.AGGREGATION_MODE_DEFAULT)
|
||||
.withValue(
|
||||
ContactsContract.RawContacts.AGGREGATION_MODE,
|
||||
ContactsContract.RawContacts.AGGREGATION_MODE_DEFAULT
|
||||
)
|
||||
.withValue(ContactsContract.RawContacts.SYNC2, cloudId)
|
||||
.build())
|
||||
ops.add(ContentProviderOperation
|
||||
.build()
|
||||
)
|
||||
ops.add(
|
||||
ContentProviderOperation
|
||||
.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
|
||||
.withValue(
|
||||
ContactsContract.Data.MIMETYPE,
|
||||
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
|
||||
)
|
||||
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, numbers[0])
|
||||
.build())
|
||||
ops.add(ContentProviderOperation
|
||||
.build()
|
||||
)
|
||||
ops.add(
|
||||
ContentProviderOperation
|
||||
.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
|
||||
.withValue(
|
||||
ContactsContract.Data.MIMETYPE,
|
||||
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE
|
||||
)
|
||||
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
|
||||
.build())
|
||||
ops.add(ContentProviderOperation
|
||||
.build()
|
||||
)
|
||||
ops.add(
|
||||
ContentProviderOperation
|
||||
.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat")
|
||||
.withValue(
|
||||
ContactsContract.Data.MIMETYPE,
|
||||
"vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat"
|
||||
)
|
||||
.withValue(ContactsContract.Data.DATA1, cloudId)
|
||||
.withValue(ContactsContract.Data.DATA2, String.format(context.resources.getString(R
|
||||
.string.nc_phone_book_integration_chat_via), accountName))
|
||||
.build())
|
||||
.withValue(
|
||||
ContactsContract.Data.DATA2,
|
||||
String.format(
|
||||
context.resources.getString(
|
||||
R.string.nc_phone_book_integration_chat_via
|
||||
),
|
||||
accountName
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
|
||||
try {
|
||||
context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
|
||||
|
@ -322,8 +369,11 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
Log.e(javaClass.simpleName, "", e)
|
||||
}
|
||||
|
||||
Log.d(TAG, "added new entry for contact $displayName (cloudId: $cloudId | lookupKey: $lookupKey" +
|
||||
" | id: $id)")
|
||||
Log.d(
|
||||
TAG,
|
||||
"added new entry for contact $displayName (cloudId: $cloudId | lookupKey: $lookupKey" +
|
||||
" | id: $id)"
|
||||
)
|
||||
}
|
||||
contactCursor.close()
|
||||
}
|
||||
|
@ -341,18 +391,21 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
}
|
||||
|
||||
private fun getDisplayNameFromDeviceContact(id: String?): String? {
|
||||
var displayName:String? = null
|
||||
val whereName = ContactsContract.Data.MIMETYPE + " = ? AND " + ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " = ?"
|
||||
var displayName: String? = null
|
||||
val whereName =
|
||||
ContactsContract.Data.MIMETYPE + " = ? AND " + ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " = ?"
|
||||
val whereNameParams = arrayOf(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, id)
|
||||
val nameCursor = context.contentResolver.query(
|
||||
ContactsContract.Data.CONTENT_URI,
|
||||
null,
|
||||
whereName,
|
||||
whereNameParams,
|
||||
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)
|
||||
ContactsContract.Data.CONTENT_URI,
|
||||
null,
|
||||
whereName,
|
||||
whereNameParams,
|
||||
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME
|
||||
)
|
||||
if (nameCursor != null) {
|
||||
while (nameCursor.moveToNext()) {
|
||||
displayName = nameCursor.getString(nameCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME))
|
||||
displayName =
|
||||
nameCursor.getString(nameCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME))
|
||||
}
|
||||
nameCursor.close()
|
||||
}
|
||||
|
@ -362,11 +415,12 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
private fun getPhoneNumbersFromDeviceContact(id: String?): MutableList<String> {
|
||||
val numbers = mutableListOf<String>()
|
||||
val phonesNumbersCursor = context.contentResolver.query(
|
||||
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
|
||||
null,
|
||||
ContactsContract.Data.CONTACT_ID + " = " + id,
|
||||
null,
|
||||
null)
|
||||
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
|
||||
null,
|
||||
ContactsContract.Data.CONTACT_ID + " = " + id,
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
||||
if (phonesNumbersCursor != null) {
|
||||
while (phonesNumbersCursor.moveToNext()) {
|
||||
|
@ -374,7 +428,7 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
}
|
||||
phonesNumbersCursor.close()
|
||||
}
|
||||
if(numbers.size > 0){
|
||||
if (numbers.size > 0) {
|
||||
Log.d(TAG, "Found ${numbers.size} phone numbers for contact with id $id")
|
||||
}
|
||||
return numbers
|
||||
|
@ -382,11 +436,11 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
|
||||
fun deleteAllLinkedAccounts() {
|
||||
val rawContactUri = ContactsContract.RawContacts.CONTENT_URI
|
||||
.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
|
||||
.build()
|
||||
.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
|
||||
.build()
|
||||
context.contentResolver.delete(rawContactUri, null, null)
|
||||
Log.d(TAG, "deleted all linked accounts")
|
||||
}
|
||||
|
@ -398,42 +452,63 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
|||
const val DELETE_ALL = "DELETE_ALL"
|
||||
|
||||
fun run(context: Context) {
|
||||
if (ContextCompat.checkSelfPermission(context,
|
||||
Manifest.permission.WRITE_CONTACTS) == PackageManager.PERMISSION_GRANTED &&
|
||||
ContextCompat.checkSelfPermission(context,
|
||||
Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.WRITE_CONTACTS
|
||||
) == PackageManager.PERMISSION_GRANTED &&
|
||||
ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.READ_CONTACTS
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
WorkManager
|
||||
.getInstance()
|
||||
.enqueue(OneTimeWorkRequest.Builder(ContactAddressBookWorker::class.java)
|
||||
.setInputData(Data.Builder().putBoolean(KEY_FORCE, false).build())
|
||||
.build())
|
||||
.getInstance()
|
||||
.enqueue(
|
||||
OneTimeWorkRequest.Builder(ContactAddressBookWorker::class.java)
|
||||
.setInputData(Data.Builder().putBoolean(KEY_FORCE, false).build())
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkPermission(controller: Controller, context: Context): Boolean {
|
||||
if (ContextCompat.checkSelfPermission(context,
|
||||
Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED ||
|
||||
ContextCompat.checkSelfPermission(context,
|
||||
Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
|
||||
controller.requestPermissions(arrayOf(Manifest.permission.WRITE_CONTACTS,
|
||||
Manifest.permission.READ_CONTACTS), REQUEST_PERMISSION)
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.WRITE_CONTACTS
|
||||
) != PackageManager.PERMISSION_GRANTED ||
|
||||
ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.READ_CONTACTS
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
controller.requestPermissions(
|
||||
arrayOf(
|
||||
Manifest.permission.WRITE_CONTACTS,
|
||||
Manifest.permission.READ_CONTACTS
|
||||
),
|
||||
REQUEST_PERMISSION
|
||||
)
|
||||
return false
|
||||
} else {
|
||||
WorkManager
|
||||
.getInstance()
|
||||
.enqueue(OneTimeWorkRequest.Builder(ContactAddressBookWorker::class.java)
|
||||
.setInputData(Data.Builder().putBoolean(KEY_FORCE, true).build())
|
||||
.build())
|
||||
.getInstance()
|
||||
.enqueue(
|
||||
OneTimeWorkRequest.Builder(ContactAddressBookWorker::class.java)
|
||||
.setInputData(Data.Builder().putBoolean(KEY_FORCE, true).build())
|
||||
.build()
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteAll() {
|
||||
WorkManager
|
||||
.getInstance()
|
||||
.enqueue(OneTimeWorkRequest.Builder(ContactAddressBookWorker::class.java)
|
||||
.setInputData(Data.Builder().putBoolean(DELETE_ALL, true).build())
|
||||
.build())
|
||||
WorkManager
|
||||
.getInstance()
|
||||
.enqueue(
|
||||
OneTimeWorkRequest.Builder(ContactAddressBookWorker::class.java)
|
||||
.setInputData(Data.Builder().putBoolean(DELETE_ALL, true).build())
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,13 +33,16 @@ import com.nextcloud.talk.utils.ApiUtils
|
|||
import com.nextcloud.talk.utils.database.user.UserUtils
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import okhttp3.ResponseBody
|
||||
import java.io.*
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class DownloadFileToCacheWorker(val context: Context, workerParameters: WorkerParameters) :
|
||||
Worker(context, workerParameters) {
|
||||
Worker(context, workerParameters) {
|
||||
|
||||
private var totalFileSize: Int = -1
|
||||
|
||||
|
@ -86,8 +89,9 @@ class DownloadFileToCacheWorker(val context: Context, workerParameters: WorkerPa
|
|||
|
||||
private fun downloadFile(currentUser: UserEntity, url: String, fileName: String): Result {
|
||||
val downloadCall = ncApi.downloadFile(
|
||||
ApiUtils.getCredentials(currentUser.username, currentUser.token),
|
||||
url)
|
||||
ApiUtils.getCredentials(currentUser.username, currentUser.token),
|
||||
url
|
||||
)
|
||||
|
||||
return executeDownload(downloadCall.execute().body(), fileName)
|
||||
}
|
||||
|
@ -152,6 +156,5 @@ class DownloadFileToCacheWorker(val context: Context, workerParameters: WorkerPa
|
|||
const val KEY_FILE_SIZE = "KEY_FILE_SIZE"
|
||||
const val PROGRESS = "PROGRESS"
|
||||
const val SUCCESS = "SUCCESS"
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,11 @@ package com.nextcloud.talk.jobs
|
|||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.work.*
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import autodagger.AutoInjector
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
|
@ -46,13 +50,12 @@ import retrofit2.Response
|
|||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import java.util.ArrayList
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerParameters) :
|
||||
Worker(context, workerParameters) {
|
||||
Worker(context, workerParameters) {
|
||||
|
||||
@Inject
|
||||
lateinit var ncApi: NcApi
|
||||
|
@ -107,31 +110,37 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
|
|||
return requestBody
|
||||
}
|
||||
|
||||
private fun uploadFile(currentUser: UserEntity, ncTargetpath: String?, filename: String, roomToken: String?,
|
||||
requestBody: RequestBody?, sourcefileUri: Uri) {
|
||||
private fun uploadFile(
|
||||
currentUser: UserEntity,
|
||||
ncTargetpath: String?,
|
||||
filename: String,
|
||||
roomToken: String?,
|
||||
requestBody: RequestBody?,
|
||||
sourcefileUri: Uri
|
||||
) {
|
||||
ncApi.uploadFile(
|
||||
ApiUtils.getCredentials(currentUser.username, currentUser.token),
|
||||
ApiUtils.getUrlForFileUpload(currentUser.baseUrl, currentUser.userId, ncTargetpath, filename),
|
||||
requestBody
|
||||
ApiUtils.getCredentials(currentUser.username, currentUser.token),
|
||||
ApiUtils.getUrlForFileUpload(currentUser.baseUrl, currentUser.userId, ncTargetpath, filename),
|
||||
requestBody
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<Response<GenericOverall>> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<Response<GenericOverall>> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
}
|
||||
|
||||
override fun onNext(t: Response<GenericOverall>) {
|
||||
}
|
||||
override fun onNext(t: Response<GenericOverall>) {
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e(TAG, "failed to upload file $filename")
|
||||
}
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e(TAG, "failed to upload file $filename")
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
shareFile(roomToken, currentUser, ncTargetpath, filename)
|
||||
copyFileToCache(sourcefileUri, filename)
|
||||
}
|
||||
})
|
||||
override fun onComplete() {
|
||||
shareFile(roomToken, currentUser, ncTargetpath, filename)
|
||||
copyFileToCache(sourcefileUri, filename)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun copyFileToCache(sourceFileUri: Uri, filename: String) {
|
||||
|
@ -151,13 +160,13 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
|
|||
paths.add("$ncTargetpath/$filename")
|
||||
|
||||
val data = Data.Builder()
|
||||
.putLong(KEY_INTERNAL_USER_ID, currentUser.id)
|
||||
.putString(KEY_ROOM_TOKEN, roomToken)
|
||||
.putStringArray(KEY_FILE_PATHS, paths.toTypedArray())
|
||||
.build()
|
||||
.putLong(KEY_INTERNAL_USER_ID, currentUser.id)
|
||||
.putString(KEY_ROOM_TOKEN, roomToken)
|
||||
.putStringArray(KEY_FILE_PATHS, paths.toTypedArray())
|
||||
.build()
|
||||
val shareWorker = OneTimeWorkRequest.Builder(ShareOperationWorker::class.java)
|
||||
.setInputData(data)
|
||||
.build()
|
||||
.setInputData(data)
|
||||
.build()
|
||||
WorkManager.getInstance().enqueue(shareWorker)
|
||||
}
|
||||
|
||||
|
@ -167,4 +176,4 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
|
|||
const val NC_TARGETPATH = "NC_TARGETPATH"
|
||||
const val ROOM_TOKEN = "ROOM_TOKEN"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ package com.nextcloud.talk.models.json.chat
|
|||
class ChatUtils {
|
||||
companion object {
|
||||
fun getParsedMessage(message: String?, messageParameters: HashMap<String?, HashMap<String?, String?>>?):
|
||||
String? {
|
||||
String? {
|
||||
var resultMessage = message
|
||||
if (messageParameters != null && messageParameters.size > 0) {
|
||||
for (key in messageParameters.keys) {
|
||||
|
@ -46,4 +46,3 @@ class ChatUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,11 +22,28 @@ package com.nextcloud.talk.models.json.converters
|
|||
|
||||
import com.bluelinelabs.logansquare.typeconverters.StringBasedTypeConverter
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.*
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CALL_ENDED
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CALL_JOINED
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CALL_LEFT
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CALL_STARTED
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CONVERSATION_CREATED
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CONVERSATION_RENAMED
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.DUMMY
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.FILE_SHARED
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.GUESTS_ALLOWED
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.GUESTS_DISALLOWED
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.LOBBY_NONE
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.LOBBY_NON_MODERATORS
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.LOBBY_OPEN_TO_EVERYONE
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.MODERATOR_DEMOTED
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.MODERATOR_PROMOTED
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.PARENT_MESSAGE_DELETED
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.PASSWORD_REMOVED
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.PASSWORD_SET
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.USER_ADDED
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.USER_REMOVED
|
||||
|
||||
/*
|
||||
|
||||
conversation_created - {actor} created the conversation
|
||||
conversation_renamed - {actor} renamed the conversation from "foo" to "bar"
|
||||
call_joined - {actor} joined the call
|
||||
|
@ -40,7 +57,6 @@ import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.*
|
|||
user_removed - {actor} removed {user} from the conversation
|
||||
moderator_promoted - {actor} promoted {user} to moderator
|
||||
moderator_demoted - {actor} demoted {user} from moderator
|
||||
|
||||
*/
|
||||
class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.SystemMessageType>() {
|
||||
override fun getFromString(string: String): ChatMessage.SystemMessageType {
|
||||
|
|
|
@ -20,12 +20,10 @@
|
|||
|
||||
package com.nextcloud.talk.receivers
|
||||
|
||||
import android.app.NotificationChannelGroup
|
||||
import android.app.NotificationManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
|
@ -34,7 +32,6 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
|
|||
import com.nextcloud.talk.utils.NotificationUtils
|
||||
import com.nextcloud.talk.utils.database.user.UserUtils
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
|
@ -50,16 +47,20 @@ class PackageReplacedReceiver : BroadcastReceiver() {
|
|||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
if (intent != null && intent.action != null &&
|
||||
intent.action == "android.intent.action.MY_PACKAGE_REPLACED") {
|
||||
intent.action == "android.intent.action.MY_PACKAGE_REPLACED"
|
||||
) {
|
||||
try {
|
||||
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
if (packageInfo.versionCode > 43 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val notificationManager = context.getSystemService(Context
|
||||
.NOTIFICATION_SERVICE) as NotificationManager
|
||||
val notificationManager = context.getSystemService(
|
||||
Context
|
||||
.NOTIFICATION_SERVICE
|
||||
) as NotificationManager
|
||||
|
||||
if (!appPreferences.isNotificationChannelUpgradedToV2) {
|
||||
for (notificationChannelGroup in notificationManager
|
||||
.notificationChannelGroups) {
|
||||
for (
|
||||
notificationChannelGroup in notificationManager.notificationChannelGroups
|
||||
) {
|
||||
notificationManager.deleteNotificationChannelGroup(notificationChannelGroup.id)
|
||||
}
|
||||
|
||||
|
@ -80,7 +81,6 @@ class PackageReplacedReceiver : BroadcastReceiver() {
|
|||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Log.e(TAG, "Failed to fetch package info")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@ import com.nextcloud.talk.R
|
|||
import com.nextcloud.talk.components.filebrowser.controllers.BrowserController
|
||||
import com.nextcloud.talk.controllers.ChatController
|
||||
|
||||
|
||||
class AttachmentDialog(val activity: Activity, var chatController: ChatController) : BottomSheetDialog(activity) {
|
||||
|
||||
@BindView(R.id.txt_attach_file_from_local)
|
||||
|
@ -54,7 +53,7 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
|
|||
|
||||
var serverName = chatController.conversationUser?.serverName
|
||||
attachFromCloud?.text = chatController.resources?.let {
|
||||
if(serverName.isNullOrEmpty()){
|
||||
if (serverName.isNullOrEmpty()) {
|
||||
serverName = it.getString(R.string.nc_server_product_name)
|
||||
}
|
||||
String.format(it.getString(R.string.nc_upload_from_cloud), serverName)
|
||||
|
|
|
@ -30,12 +30,13 @@ import com.nextcloud.talk.R
|
|||
import com.nextcloud.talk.controllers.ProfileController
|
||||
import com.nextcloud.talk.models.json.userprofile.Scope
|
||||
|
||||
|
||||
class ScopeDialog(con: Context,
|
||||
private val userInfoAdapter: ProfileController.UserInfoAdapter,
|
||||
private val field: ProfileController.Field,
|
||||
private val position: Int) :
|
||||
BottomSheetDialog(con) {
|
||||
class ScopeDialog(
|
||||
con: Context,
|
||||
private val userInfoAdapter: ProfileController.UserInfoAdapter,
|
||||
private val field: ProfileController.Field,
|
||||
private val position: Int
|
||||
) :
|
||||
BottomSheetDialog(con) {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val view = layoutInflater.inflate(R.layout.dialog_scope, null)
|
||||
|
|
|
@ -32,7 +32,8 @@ import com.nextcloud.talk.R
|
|||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.models.ImportAccount
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
import java.util.*
|
||||
import java.util.ArrayList
|
||||
import java.util.Arrays
|
||||
|
||||
object AccountUtils {
|
||||
|
||||
|
@ -60,14 +61,15 @@ object AccountUtils {
|
|||
break
|
||||
}
|
||||
} else {
|
||||
if (internalUserEntity.username == importAccount.username && (internalUserEntity
|
||||
.baseUrl == "http://" + importAccount.baseUrl ||
|
||||
internalUserEntity.baseUrl == "https://" + importAccount
|
||||
.baseUrl)) {
|
||||
if (internalUserEntity.username == importAccount.username &&
|
||||
(
|
||||
internalUserEntity.baseUrl == "http://" + importAccount.baseUrl ||
|
||||
internalUserEntity.baseUrl == "https://" + importAccount.baseUrl
|
||||
)
|
||||
) {
|
||||
accountFound = true
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
accountFound = true
|
||||
|
@ -88,8 +90,12 @@ object AccountUtils {
|
|||
val packageManager = context.packageManager
|
||||
var appName = ""
|
||||
try {
|
||||
appName = packageManager.getApplicationLabel(packageManager.getApplicationInfo(packageName,
|
||||
PackageManager.GET_META_DATA)) as String
|
||||
appName = packageManager.getApplicationLabel(
|
||||
packageManager.getApplicationInfo(
|
||||
packageName,
|
||||
PackageManager.GET_META_DATA
|
||||
)
|
||||
) as String
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Log.e(TAG, "Failed to get app name based on package")
|
||||
}
|
||||
|
@ -103,7 +109,10 @@ object AccountUtils {
|
|||
val packageInfo = pm.getPackageInfo(context.getString(R.string.nc_import_accounts_from), 0)
|
||||
if (packageInfo.versionCode >= 30060151) {
|
||||
val ownSignatures = pm.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES).signatures
|
||||
val filesAppSignatures = pm.getPackageInfo(context.getString(R.string.nc_import_accounts_from), PackageManager.GET_SIGNATURES).signatures
|
||||
val filesAppSignatures = pm.getPackageInfo(
|
||||
context.getString(R.string.nc_import_accounts_from),
|
||||
PackageManager.GET_SIGNATURES
|
||||
).signatures
|
||||
|
||||
if (Arrays.equals(ownSignatures, filesAppSignatures)) {
|
||||
val accMgr = AccountManager.get(context)
|
||||
|
@ -118,7 +127,7 @@ object AccountUtils {
|
|||
}
|
||||
}
|
||||
} catch (appNotFoundException: PackageManager.NameNotFoundException) {
|
||||
|
||||
// ignore
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -146,4 +155,3 @@ object AccountUtils {
|
|||
return ImportAccount(username, password, urlString)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,13 @@ import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
|||
import com.nextcloud.talk.controllers.ChatController
|
||||
|
||||
object ConductorRemapping {
|
||||
fun remapChatController(router: Router, internalUserId: Long, roomTokenOrId: String, bundle: Bundle, replaceTop: Boolean) {
|
||||
fun remapChatController(
|
||||
router: Router,
|
||||
internalUserId: Long,
|
||||
roomTokenOrId: String,
|
||||
bundle: Bundle,
|
||||
replaceTop: Boolean
|
||||
) {
|
||||
val tag = "$internalUserId@$roomTokenOrId"
|
||||
if (router.getControllerWithTag(tag) != null) {
|
||||
val backstack = router.backstack
|
||||
|
@ -44,13 +50,17 @@ object ConductorRemapping {
|
|||
router.setBackstack(backstack, HorizontalChangeHandler())
|
||||
} else {
|
||||
if (!replaceTop) {
|
||||
router.pushController(RouterTransaction.with(ChatController(bundle))
|
||||
router.pushController(
|
||||
RouterTransaction.with(ChatController(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()).tag(tag))
|
||||
.popChangeHandler(HorizontalChangeHandler()).tag(tag)
|
||||
)
|
||||
} else {
|
||||
router.replaceTopController(RouterTransaction.with(ChatController(bundle))
|
||||
router.replaceTopController(
|
||||
RouterTransaction.with(ChatController(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()).tag(tag))
|
||||
.popChangeHandler(HorizontalChangeHandler()).tag(tag)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,9 @@
|
|||
package com.nextcloud.talk.utils
|
||||
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
object DateUtils {
|
||||
fun getLocalDateTimeStringFromTimestamp(timestamp: Long): String {
|
||||
|
@ -30,14 +31,16 @@ object DateUtils {
|
|||
val tz = cal.timeZone
|
||||
|
||||
/* date formatter in local timezone */
|
||||
val format = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.SHORT, Locale
|
||||
.getDefault())
|
||||
val format = DateFormat.getDateTimeInstance(
|
||||
DateFormat.DEFAULT, DateFormat.SHORT,
|
||||
Locale.getDefault()
|
||||
)
|
||||
format.timeZone = tz
|
||||
|
||||
return format.format(Date(timestamp))
|
||||
}
|
||||
|
||||
fun getLocalDateStringFromTimestampForLobby(timestamp: Long): String {
|
||||
return getLocalDateTimeStringFromTimestamp(timestamp * 1000);
|
||||
return getLocalDateTimeStringFromTimestamp(timestamp * 1000)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,12 +21,10 @@
|
|||
package com.nextcloud.talk.utils
|
||||
|
||||
import com.nextcloud.talk.R
|
||||
|
||||
import java.util.HashMap
|
||||
|
||||
object DrawableUtils {
|
||||
|
||||
|
||||
fun getDrawableResourceIdForMimeType(mimetype: String): Int {
|
||||
var localMimetype = mimetype
|
||||
val drawableMap = HashMap<String, Int>()
|
||||
|
@ -55,15 +53,20 @@ object DrawableUtils {
|
|||
drawableMap["application/vnd.google-earth.kmz"] = R.drawable.ic_mimetype_location
|
||||
drawableMap["application/vnd.ms-excel"] = R.drawable.ic_mimetype_x_office_spreadsheet
|
||||
drawableMap["application/vnd.ms-excel.addin.macroEnabled.12"] = R.drawable.ic_mimetype_x_office_spreadsheet
|
||||
drawableMap["application/vnd.ms-excel.sheet.binary.macroEnabled.12"] = R.drawable.ic_mimetype_x_office_spreadsheet
|
||||
drawableMap["application/vnd.ms-excel.sheet.binary.macroEnabled.12"] =
|
||||
R.drawable.ic_mimetype_x_office_spreadsheet
|
||||
drawableMap["application/vnd.ms-excel.sheet.macroEnabled.12"] = R.drawable.ic_mimetype_x_office_spreadsheet
|
||||
drawableMap["application/vnd.ms-excel.template.macroEnabled.12"] = R.drawable.ic_mimetype_x_office_spreadsheet
|
||||
drawableMap["application/vnd.ms-fontobject"] = R.drawable.ic_mimetype_image
|
||||
drawableMap["application/vnd.ms-powerpoint"] = R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.ms-powerpoint.addin.macroEnabled.12"] = R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.ms-powerpoint.presentation.macroEnabled.12"] = R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.ms-powerpoint.slideshow.macroEnabled.12"] = R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.ms-powerpoint.template.macroEnabled.12"] = R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.ms-powerpoint.addin.macroEnabled.12"] =
|
||||
R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.ms-powerpoint.presentation.macroEnabled.12"] =
|
||||
R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.ms-powerpoint.slideshow.macroEnabled.12"] =
|
||||
R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.ms-powerpoint.template.macroEnabled.12"] =
|
||||
R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.ms-visio.drawing.macroEnabled.12"] = R.drawable.ic_mimetype_x_office_document
|
||||
drawableMap["application/vnd.ms-visio.drawing"] = R.drawable.ic_mimetype_x_office_document
|
||||
drawableMap["application/vnd.ms-visio.stencil.macroEnabled.12"] = R.drawable.ic_mimetype_x_office_document
|
||||
|
@ -72,20 +75,29 @@ object DrawableUtils {
|
|||
drawableMap["application/vnd.ms-visio.template"] = R.drawable.ic_mimetype_x_office_document
|
||||
drawableMap["application/vnd.ms-word.template.macroEnabled.12"] = R.drawable.ic_mimetype_x_office_document
|
||||
drawableMap["application/vnd.oasis.opendocument.presentation"] = R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.oasis.opendocument.presentation-template"] = R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.oasis.opendocument.presentation-template"] =
|
||||
R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.oasis.opendocument.spreadsheet"] = R.drawable.ic_mimetype_x_office_spreadsheet
|
||||
drawableMap["application/vnd.oasis.opendocument.spreadsheet-template"] = R.drawable.ic_mimetype_x_office_spreadsheet
|
||||
drawableMap["application/vnd.oasis.opendocument.spreadsheet-template"] =
|
||||
R.drawable.ic_mimetype_x_office_spreadsheet
|
||||
drawableMap["application/vnd.oasis.opendocument.text"] = R.drawable.ic_mimetype_x_office_document
|
||||
drawableMap["application/vnd.oasis.opendocument.text-master"] = R.drawable.ic_mimetype_x_office_document
|
||||
drawableMap["application/vnd.oasis.opendocument.text-template"] = R.drawable.ic_mimetype_x_office_document
|
||||
drawableMap["application/vnd.oasis.opendocument.text-web"] = R.drawable.ic_mimetype_x_office_document
|
||||
drawableMap["application/vnd.openxmlformats-officedocument.presentationml.presentation"] = R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.openxmlformats-officedocument.presentationml.slideshow"] = R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.openxmlformats-officedocument.presentationml.template"] = R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"] = R.drawable.ic_mimetype_x_office_spreadsheet
|
||||
drawableMap["application/vnd.openxmlformats-officedocument.spreadsheetml.template"] = R.drawable.ic_mimetype_x_office_spreadsheet
|
||||
drawableMap["application/vnd.openxmlformats-officedocument.wordprocessingml.document"] = R.drawable.ic_mimetype_x_office_document
|
||||
drawableMap["application/vnd.openxmlformats-officedocument.wordprocessingml.template"] = R.drawable.ic_mimetype_x_office_document
|
||||
drawableMap["application/vnd.openxmlformats-officedocument.presentationml.presentation"] =
|
||||
R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.openxmlformats-officedocument.presentationml.slideshow"] =
|
||||
R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.openxmlformats-officedocument.presentationml.template"] =
|
||||
R.drawable.ic_mimetype_x_office_presentation
|
||||
drawableMap["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"] =
|
||||
R.drawable.ic_mimetype_x_office_spreadsheet
|
||||
drawableMap["application/vnd.openxmlformats-officedocument.spreadsheetml.template"] =
|
||||
R.drawable.ic_mimetype_x_office_spreadsheet
|
||||
drawableMap["application/vnd.openxmlformats-officedocument.wordprocessingml.document"] =
|
||||
R.drawable.ic_mimetype_x_office_document
|
||||
drawableMap["application/vnd.openxmlformats-officedocument.wordprocessingml.template"] =
|
||||
R.drawable.ic_mimetype_x_office_document
|
||||
drawableMap["application/vnd.visio"] = R.drawable.ic_mimetype_x_office_document
|
||||
drawableMap["application/vnd.wordperfect"] = R.drawable.ic_mimetype_x_office_document
|
||||
drawableMap["application/x-7z-compressed"] = R.drawable.ic_mimetype_package_x_generic
|
||||
|
|
|
@ -29,7 +29,7 @@ import com.nextcloud.talk.BuildConfig
|
|||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
|
||||
object LoggingUtils {
|
||||
fun writeLogEntryToFile(context: Context, logEntry: String) {
|
||||
|
@ -38,8 +38,10 @@ object LoggingUtils {
|
|||
val logEntryWithDateTime = dateFormat.format(date) + ": " + logEntry + "\n"
|
||||
|
||||
try {
|
||||
val outputStream = context.openFileOutput("nc_log.txt",
|
||||
Context.MODE_PRIVATE or Context.MODE_APPEND)
|
||||
val outputStream = context.openFileOutput(
|
||||
"nc_log.txt",
|
||||
Context.MODE_PRIVATE or Context.MODE_APPEND
|
||||
)
|
||||
outputStream.write(logEntryWithDateTime.toByteArray())
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
|
@ -48,13 +50,12 @@ object LoggingUtils {
|
|||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun sendMailWithAttachment(context: Context) {
|
||||
val logFile = context.getFileStreamPath("nc_log.txt")
|
||||
val emailIntent = Intent(Intent.ACTION_SEND)
|
||||
val mailto = "mario@nextcloud.com"
|
||||
val mailto = "android@nextcloud.com"
|
||||
emailIntent.putExtra(Intent.EXTRA_EMAIL, arrayOf(mailto))
|
||||
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Talk logs")
|
||||
emailIntent.type = "text/plain"
|
||||
|
|
|
@ -33,7 +33,7 @@ import android.service.notification.StatusBarNotification
|
|||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import java.util.*
|
||||
import java.util.Objects
|
||||
|
||||
object NotificationUtils {
|
||||
val NOTIFICATION_CHANNEL_CALLS = "NOTIFICATION_CHANNEL_CALLS"
|
||||
|
@ -47,25 +47,50 @@ object NotificationUtils {
|
|||
return longArrayOf(0L, 400L, 800L, 600L, 800L, 800L, 800L, 1000L)
|
||||
}
|
||||
|
||||
fun getNotificationChannelId(channelName: String,
|
||||
channelDescription: String, enableLights: Boolean,
|
||||
importance: Int, sound: Uri, audioAttributes: AudioAttributes, vibrationPattern: LongArray?, bypassDnd: Boolean): String {
|
||||
return Objects.hash(channelName, channelDescription, enableLights, importance, sound, audioAttributes, vibrationPattern, bypassDnd).toString()
|
||||
fun getNotificationChannelId(
|
||||
channelName: String,
|
||||
channelDescription: String,
|
||||
enableLights: Boolean,
|
||||
importance: Int,
|
||||
sound: Uri,
|
||||
audioAttributes: AudioAttributes,
|
||||
vibrationPattern: LongArray?,
|
||||
bypassDnd: Boolean
|
||||
): String {
|
||||
return Objects.hash(
|
||||
channelName,
|
||||
channelDescription,
|
||||
enableLights,
|
||||
importance,
|
||||
sound,
|
||||
audioAttributes,
|
||||
vibrationPattern,
|
||||
bypassDnd
|
||||
).toString()
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
fun createNotificationChannel(context: Context,
|
||||
channelId: String, channelName: String,
|
||||
channelDescription: String, enableLights: Boolean,
|
||||
importance: Int, sound: Uri, audioAttributes: AudioAttributes,
|
||||
vibrationPattern: LongArray?, bypassDnd: Boolean = false) {
|
||||
fun createNotificationChannel(
|
||||
context: Context,
|
||||
channelId: String,
|
||||
channelName: String,
|
||||
channelDescription: String,
|
||||
enableLights: Boolean,
|
||||
importance: Int,
|
||||
sound: Uri,
|
||||
audioAttributes: AudioAttributes,
|
||||
vibrationPattern: LongArray?,
|
||||
bypassDnd: Boolean = false
|
||||
) {
|
||||
|
||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && notificationManager.getNotificationChannel(channelId) == null) {
|
||||
|
||||
val channel = NotificationChannel(channelId, channelName,
|
||||
importance)
|
||||
val channel = NotificationChannel(
|
||||
channelId, channelName,
|
||||
importance
|
||||
)
|
||||
|
||||
channel.description = channelDescription
|
||||
channel.enableLights(enableLights)
|
||||
|
@ -84,8 +109,11 @@ object NotificationUtils {
|
|||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
fun createNotificationChannelGroup(context: Context,
|
||||
groupId: String, groupName: CharSequence) {
|
||||
fun createNotificationChannelGroup(
|
||||
context: Context,
|
||||
groupId: String,
|
||||
groupName: CharSequence
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
|
@ -113,12 +141,12 @@ object NotificationUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun cancelExistingNotificationWithId(context: Context?, conversationUser: UserEntity, notificationId: Long) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && conversationUser.id != -1L &&
|
||||
context != null) {
|
||||
context != null
|
||||
) {
|
||||
|
||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
|
@ -128,7 +156,10 @@ object NotificationUtils {
|
|||
notification = statusBarNotification.notification
|
||||
|
||||
if (notification != null && !notification.extras.isEmpty) {
|
||||
if (conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && notificationId == notification.extras.getLong(BundleKeys.KEY_NOTIFICATION_ID)) {
|
||||
if (conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && notificationId == notification.extras.getLong(
|
||||
BundleKeys.KEY_NOTIFICATION_ID
|
||||
)
|
||||
) {
|
||||
notificationManager.cancel(statusBarNotification.id)
|
||||
}
|
||||
}
|
||||
|
@ -136,11 +167,14 @@ object NotificationUtils {
|
|||
}
|
||||
}
|
||||
|
||||
fun findNotificationForRoom(context: Context?,
|
||||
conversationUser: UserEntity,
|
||||
roomTokenOrId: String): StatusBarNotification? {
|
||||
fun findNotificationForRoom(
|
||||
context: Context?,
|
||||
conversationUser: UserEntity,
|
||||
roomTokenOrId: String
|
||||
): StatusBarNotification? {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && conversationUser.id != -1L &&
|
||||
context != null) {
|
||||
context != null
|
||||
) {
|
||||
|
||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
|
@ -150,7 +184,10 @@ object NotificationUtils {
|
|||
notification = statusBarNotification.notification
|
||||
|
||||
if (notification != null && !notification.extras.isEmpty) {
|
||||
if (conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && roomTokenOrId == statusBarNotification.notification.extras.getString(BundleKeys.KEY_ROOM_TOKEN)) {
|
||||
if (conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && roomTokenOrId == statusBarNotification.notification.extras.getString(
|
||||
BundleKeys.KEY_ROOM_TOKEN
|
||||
)
|
||||
) {
|
||||
return statusBarNotification
|
||||
}
|
||||
}
|
||||
|
@ -160,10 +197,14 @@ object NotificationUtils {
|
|||
return null
|
||||
}
|
||||
|
||||
fun cancelExistingNotificationsForRoom(context: Context?, conversationUser: UserEntity,
|
||||
roomTokenOrId: String) {
|
||||
fun cancelExistingNotificationsForRoom(
|
||||
context: Context?,
|
||||
conversationUser: UserEntity,
|
||||
roomTokenOrId: String
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && conversationUser.id != -1L &&
|
||||
context != null) {
|
||||
context != null
|
||||
) {
|
||||
|
||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
|
@ -173,7 +214,11 @@ object NotificationUtils {
|
|||
notification = statusBarNotification.notification
|
||||
|
||||
if (notification != null && !notification.extras.isEmpty) {
|
||||
if (conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && roomTokenOrId == statusBarNotification.notification.extras.getString(BundleKeys.KEY_ROOM_TOKEN)) {
|
||||
if (conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) &&
|
||||
roomTokenOrId == statusBarNotification.notification.extras.getString(
|
||||
BundleKeys.KEY_ROOM_TOKEN
|
||||
)
|
||||
) {
|
||||
notificationManager.cancel(statusBarNotification.id)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import android.database.Cursor
|
|||
import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Log
|
||||
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
|
||||
|
||||
object UriUtils {
|
||||
|
||||
|
@ -51,5 +50,4 @@ object UriUtils {
|
|||
}
|
||||
return filename
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
package com.nextcloud.talk.utils.bundle
|
||||
|
||||
object BundleKeys {
|
||||
val KEY_SELECTED_USERS = "KEY_SELECTED_USERS";
|
||||
val KEY_SELECTED_GROUPS = "KEY_SELECTED_GROUPS";
|
||||
val KEY_SELECTED_USERS = "KEY_SELECTED_USERS"
|
||||
val KEY_SELECTED_GROUPS = "KEY_SELECTED_GROUPS"
|
||||
val KEY_USERNAME = "KEY_USERNAME"
|
||||
val KEY_TOKEN = "KEY_TOKEN"
|
||||
val KEY_BASE_URL = "KEY_BASE_URL"
|
||||
|
|
|
@ -13,11 +13,17 @@ import java.io.IOException
|
|||
import java.net.InetAddress
|
||||
import java.net.Socket
|
||||
import java.security.GeneralSecurityException
|
||||
import java.util.*
|
||||
import javax.net.ssl.*
|
||||
import java.util.LinkedList
|
||||
import javax.net.ssl.KeyManager
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLSocket
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
class SSLSocketFactoryCompat(keyManager: KeyManager?,
|
||||
trustManager: X509TrustManager) : SSLSocketFactory() {
|
||||
class SSLSocketFactoryCompat(
|
||||
keyManager: KeyManager?,
|
||||
trustManager: X509TrustManager
|
||||
) : SSLSocketFactory() {
|
||||
|
||||
private var delegate: SSLSocketFactory
|
||||
|
||||
|
@ -50,24 +56,24 @@ class SSLSocketFactoryCompat(keyManager: KeyManager?,
|
|||
|
||||
/* set up reasonable cipher suites */
|
||||
val knownCiphers = arrayOf<String>(
|
||||
// TLS 1.2
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384",
|
||||
"TLS_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
// maximum interoperability
|
||||
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
"SSL_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA",
|
||||
// additionally
|
||||
"TLS_RSA_WITH_AES_256_CBC_SHA",
|
||||
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
|
||||
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
|
||||
// TLS 1.2
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384",
|
||||
"TLS_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
// maximum interoperability
|
||||
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
"SSL_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA",
|
||||
// additionally
|
||||
"TLS_RSA_WITH_AES_256_CBC_SHA",
|
||||
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
|
||||
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
|
||||
)
|
||||
val availableCiphers = socket.supportedCipherSuites
|
||||
|
||||
|
@ -89,31 +95,31 @@ class SSLSocketFactoryCompat(keyManager: KeyManager?,
|
|||
} catch (e: IOException) {
|
||||
// Exception is to be ignored
|
||||
} finally {
|
||||
socket?.close() // doesn't implement Closeable on all supported Android versions
|
||||
socket?.close() // doesn't implement Closeable on all supported Android versions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
try {
|
||||
val sslContext = SSLContext.getInstance("TLS")
|
||||
sslContext.init(
|
||||
if (keyManager != null) arrayOf(keyManager) else null,
|
||||
arrayOf(trustManager),
|
||||
null)
|
||||
if (keyManager != null) arrayOf(keyManager) else null,
|
||||
arrayOf(trustManager),
|
||||
null
|
||||
)
|
||||
delegate = sslContext.socketFactory
|
||||
} catch (e: GeneralSecurityException) {
|
||||
throw IllegalStateException() // system has no TLS
|
||||
throw IllegalStateException() // system has no TLS
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDefaultCipherSuites(): Array<String>? = cipherSuites
|
||||
?: delegate.defaultCipherSuites
|
||||
?: delegate.defaultCipherSuites
|
||||
|
||||
override fun getSupportedCipherSuites(): Array<String>? = cipherSuites
|
||||
?: delegate.supportedCipherSuites
|
||||
?: delegate.supportedCipherSuites
|
||||
|
||||
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket {
|
||||
val ssl = delegate.createSocket(s, host, port, autoClose)
|
||||
|
@ -150,10 +156,8 @@ class SSLSocketFactoryCompat(keyManager: KeyManager?,
|
|||
return ssl
|
||||
}
|
||||
|
||||
|
||||
private fun upgradeTLS(ssl: SSLSocket) {
|
||||
protocols?.let { ssl.enabledProtocols = it }
|
||||
cipherSuites?.let { ssl.enabledCipherSuites = it }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
BIN
app/src/qa/ic_launcher-web.png
Normal file
After Width: | Height: | Size: 30 KiB |
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.utils;
|
||||
|
||||
|
||||
import com.nextcloud.talk.interfaces.ClosedInterface;
|
||||
|
||||
public class ClosedInterfaceImpl implements ClosedInterface {
|
||||
@Override
|
||||
public void providerInstallerInstallIfNeededAsync() {
|
||||
// does absolutely nothing :)
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGooglePlayServicesAvailable() {
|
||||
return false;
|
||||
}
|
||||
}
|
26
app/src/qa/res/drawable-v24/ic_launcher_background.xml
Normal file
|
@ -0,0 +1,26 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group android:scaleX="0.08035714"
|
||||
android:scaleY="0.08035714">
|
||||
<path
|
||||
android:pathData="M0,0h1344v1344h-1344z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillType="evenOdd">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="1344.0002"
|
||||
android:startX="163.34073"
|
||||
android:endY="1.2959057E-4"
|
||||
android:endX="1343.9999"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF0082C9"/>
|
||||
<item android:offset="1" android:color="#FF1CAFFF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</group>
|
||||
</vector>
|
37
app/src/qa/res/drawable/ic_launcher_background.xml
Normal file
|
@ -0,0 +1,37 @@
|
|||
<!--
|
||||
~ /*
|
||||
~ * Nextcloud Talk application
|
||||
~ *
|
||||
~ * @author Mario Danic
|
||||
~ * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
|
||||
~ *
|
||||
~ * This program is free software: you can redistribute it and/or modify
|
||||
~ * it under the terms of the GNU General Public License as published by
|
||||
~ * the Free Software Foundation, either version 3 of the License, or
|
||||
~ * at your option) any later version.
|
||||
~ *
|
||||
~ * This program is distributed in the hope that it will be useful,
|
||||
~ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ * GNU General Public License for more details.
|
||||
~ *
|
||||
~ * You should have received a copy of the GNU General Public License
|
||||
~ * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
~ */
|
||||
-->
|
||||
|
||||
<vector android:autoMirrored="true" android:height="108dp"
|
||||
android:viewportHeight="1344" android:viewportWidth="1344"
|
||||
android:width="108dp" xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillType="evenOdd"
|
||||
android:pathData="M0,0h1344v1344h-1344z" android:strokeLineJoin="round">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient android:endX="1343.9999"
|
||||
android:endY="1.2959057E-4" android:startX="163.34073"
|
||||
android:startY="1344.0002" android:type="linear">
|
||||
<item android:color="#FF0082C9" android:offset="0"/>
|
||||
<item android:color="#FF1CAFFF" android:offset="1"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
15
app/src/qa/res/drawable/ic_launcher_foreground.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group android:scaleX="0.06428572"
|
||||
android:scaleY="0.06428572"
|
||||
android:translateX="10.8"
|
||||
android:translateY="10.8">
|
||||
<path
|
||||
android:pathData="M671.96,347C493.7,347 347.02,493.69 347.02,671.95L347.02,671.96C347.02,850.22 493.7,996.91 671.96,996.91C731.43,996.79 789.75,980.34 840.52,949.37C880.46,965.24 969.91,1012.33 991.21,991.99C1013.45,970.74 965.09,870.74 953.49,833.58C981.82,784.43 996.79,728.7 996.9,671.96C996.9,493.7 850.22,347.02 671.96,347.01L671.96,347ZM672,470.54C782.53,470.54 873.48,561.5 873.48,672.03C873.48,782.57 782.52,873.51 672,873.51C561.47,873.51 470.52,782.57 470.51,672.03C470.51,561.5 561.46,470.54 672,470.54L672,470.54ZM670.27,655.83C670.27,674.12 666.59,689.32 659.24,701.43C651.88,713.54 641.5,721.74 628.09,726.04L662.07,761.39L637.95,761.39L610.12,729.17L604.75,729.36C583.72,729.36 567.49,722.93 556.06,710.08C544.64,697.22 538.93,679.07 538.93,655.63C538.93,632.39 544.66,614.37 556.11,601.58C567.57,588.79 583.85,582.39 604.94,582.39C625.45,582.39 641.47,588.9 652.99,601.92C664.51,614.94 670.27,632.91 670.27,655.83ZM787.85,727.41L770.08,682L712.85,682L695.27,727.41L678.48,727.41L734.92,584.05L748.89,584.05L805.04,727.41L787.85,727.41ZM556.5,655.83C556.5,675.16 560.62,689.83 568.86,699.82C577.09,709.82 589.06,714.81 604.75,714.81C620.57,714.81 632.51,709.83 640.59,699.87C648.66,689.91 652.7,675.23 652.7,655.83C652.7,636.62 648.67,622.05 640.64,612.13C632.59,602.2 620.7,597.23 604.94,597.23C589.12,597.23 577.09,602.23 568.86,612.22C560.62,622.22 556.5,636.75 556.5,655.83ZM764.9,667.06L748.3,622.82C746.15,617.22 743.94,610.35 741.66,602.21C740.23,608.46 738.18,615.33 735.51,622.82L718.71,667.06L764.9,667.06Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="nonZero"/>
|
||||
</group>
|
||||
</vector>
|
5
app/src/qa/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
BIN
app/src/qa/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
app/src/qa/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
app/src/qa/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
app/src/qa/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
app/src/qa/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 16 KiB |
25
app/src/qa/res/values/setup.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Andy Scherzinger
|
||||
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<string name="nc_app_name">Nextcloud Talk QA</string>
|
||||
<string name="nc_server_product_name">Nextcloud</string>
|
||||
</resources>
|
|
@ -34,6 +34,7 @@ buildscript {
|
|||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
|
||||
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.13.1"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
472
detekt.yml
Normal file
|
@ -0,0 +1,472 @@
|
|||
build:
|
||||
maxIssues: 346
|
||||
weights:
|
||||
# complexity: 2
|
||||
# LongParameterList: 1
|
||||
# style: 1
|
||||
# comments: 1
|
||||
|
||||
processors:
|
||||
active: true
|
||||
exclude:
|
||||
# - 'FunctionCountProcessor'
|
||||
# - 'PropertyCountProcessor'
|
||||
# - 'ClassCountProcessor'
|
||||
# - 'PackageCountProcessor'
|
||||
# - 'KtFileCountProcessor'
|
||||
|
||||
console-reports:
|
||||
active: true
|
||||
exclude:
|
||||
# - 'ProjectStatisticsReport'
|
||||
# - 'ComplexityReport'
|
||||
# - 'NotificationReport'
|
||||
# - 'FindingsReport'
|
||||
# - 'BuildFailureReport'
|
||||
|
||||
comments:
|
||||
active: true
|
||||
CommentOverPrivateFunction:
|
||||
active: false
|
||||
CommentOverPrivateProperty:
|
||||
active: false
|
||||
EndOfSentenceFormat:
|
||||
active: false
|
||||
endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!]$)
|
||||
UndocumentedPublicClass:
|
||||
active: false
|
||||
searchInNestedClass: true
|
||||
searchInInnerClass: true
|
||||
searchInInnerObject: true
|
||||
searchInInnerInterface: true
|
||||
UndocumentedPublicFunction:
|
||||
active: false
|
||||
|
||||
complexity:
|
||||
active: true
|
||||
ComplexCondition:
|
||||
active: true
|
||||
threshold: 4
|
||||
ComplexInterface:
|
||||
active: false
|
||||
threshold: 10
|
||||
includeStaticDeclarations: false
|
||||
ComplexMethod:
|
||||
active: true
|
||||
threshold: 10
|
||||
ignoreSingleWhenExpression: false
|
||||
ignoreSimpleWhenEntries: false
|
||||
excludes: ['**/androidTest/**']
|
||||
LabeledExpression:
|
||||
active: false
|
||||
ignoredLabels: ""
|
||||
LargeClass:
|
||||
active: true
|
||||
threshold: 600
|
||||
LongMethod:
|
||||
active: true
|
||||
threshold: 60
|
||||
excludes: ['**/androidTest/**']
|
||||
LongParameterList:
|
||||
active: true
|
||||
threshold: 6
|
||||
ignoreDefaultParameters: false
|
||||
MethodOverloading:
|
||||
active: false
|
||||
threshold: 6
|
||||
NestedBlockDepth:
|
||||
active: true
|
||||
threshold: 4
|
||||
StringLiteralDuplication:
|
||||
active: false
|
||||
threshold: 3
|
||||
ignoreAnnotation: true
|
||||
excludeStringsWithLessThan5Characters: true
|
||||
ignoreStringsRegex: '$^'
|
||||
TooManyFunctions:
|
||||
active: true
|
||||
thresholdInFiles: 15
|
||||
thresholdInClasses: 15
|
||||
thresholdInInterfaces: 15
|
||||
thresholdInObjects: 15
|
||||
thresholdInEnums: 11
|
||||
ignoreDeprecated: true
|
||||
ignorePrivate: false
|
||||
ignoreOverridden: true
|
||||
|
||||
empty-blocks:
|
||||
active: true
|
||||
EmptyCatchBlock:
|
||||
active: true
|
||||
allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
|
||||
EmptyClassBlock:
|
||||
active: true
|
||||
EmptyDefaultConstructor:
|
||||
active: true
|
||||
EmptyDoWhileBlock:
|
||||
active: true
|
||||
EmptyElseBlock:
|
||||
active: true
|
||||
EmptyFinallyBlock:
|
||||
active: true
|
||||
EmptyForBlock:
|
||||
active: true
|
||||
EmptyFunctionBlock:
|
||||
active: true
|
||||
ignoreOverridden: false
|
||||
EmptyIfBlock:
|
||||
active: true
|
||||
EmptyInitBlock:
|
||||
active: true
|
||||
EmptyKtFile:
|
||||
active: true
|
||||
EmptySecondaryConstructor:
|
||||
active: true
|
||||
EmptyWhenBlock:
|
||||
active: true
|
||||
EmptyWhileBlock:
|
||||
active: true
|
||||
|
||||
exceptions:
|
||||
active: true
|
||||
ExceptionRaisedInUnexpectedLocation:
|
||||
active: false
|
||||
methodNames: 'toString,hashCode,equals,finalize'
|
||||
InstanceOfCheckForException:
|
||||
active: false
|
||||
NotImplementedDeclaration:
|
||||
active: false
|
||||
PrintStackTrace:
|
||||
active: false
|
||||
RethrowCaughtException:
|
||||
active: false
|
||||
ReturnFromFinally:
|
||||
active: false
|
||||
SwallowedException:
|
||||
active: false
|
||||
ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException'
|
||||
ThrowingExceptionFromFinally:
|
||||
active: false
|
||||
ThrowingExceptionInMain:
|
||||
active: false
|
||||
ThrowingExceptionsWithoutMessageOrCause:
|
||||
active: false
|
||||
exceptions: 'IllegalArgumentException,IllegalStateException,IOException'
|
||||
ThrowingNewInstanceOfSameException:
|
||||
active: false
|
||||
TooGenericExceptionCaught:
|
||||
active: true
|
||||
exceptionNames:
|
||||
- ArrayIndexOutOfBoundsException
|
||||
- Error
|
||||
- Exception
|
||||
- IllegalMonitorStateException
|
||||
- NullPointerException
|
||||
- IndexOutOfBoundsException
|
||||
- RuntimeException
|
||||
- Throwable
|
||||
allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
|
||||
TooGenericExceptionThrown:
|
||||
active: true
|
||||
exceptionNames:
|
||||
- Error
|
||||
- Exception
|
||||
- Throwable
|
||||
- RuntimeException
|
||||
|
||||
formatting:
|
||||
active: true
|
||||
android: false
|
||||
ChainWrapping:
|
||||
active: true
|
||||
CommentSpacing:
|
||||
active: true
|
||||
Filename:
|
||||
active: true
|
||||
FinalNewline:
|
||||
active: true
|
||||
ImportOrdering:
|
||||
active: false
|
||||
Indentation:
|
||||
active: true
|
||||
indentSize: 4
|
||||
continuationIndentSize: 4
|
||||
MaximumLineLength:
|
||||
active: true
|
||||
maxLineLength: 120
|
||||
ModifierOrdering:
|
||||
active: true
|
||||
NoBlankLineBeforeRbrace:
|
||||
active: true
|
||||
NoConsecutiveBlankLines:
|
||||
active: true
|
||||
NoEmptyClassBody:
|
||||
active: true
|
||||
NoLineBreakAfterElse:
|
||||
active: true
|
||||
NoLineBreakBeforeAssignment:
|
||||
active: true
|
||||
NoMultipleSpaces:
|
||||
active: true
|
||||
NoSemicolons:
|
||||
active: true
|
||||
NoTrailingSpaces:
|
||||
active: true
|
||||
NoUnitReturn:
|
||||
active: true
|
||||
NoUnusedImports:
|
||||
active: true
|
||||
NoWildcardImports:
|
||||
active: true
|
||||
PackageName:
|
||||
active: true
|
||||
ParameterListWrapping:
|
||||
active: true
|
||||
indentSize: 4
|
||||
SpacingAroundColon:
|
||||
active: true
|
||||
SpacingAroundComma:
|
||||
active: true
|
||||
SpacingAroundCurly:
|
||||
active: true
|
||||
SpacingAroundKeyword:
|
||||
active: true
|
||||
SpacingAroundOperators:
|
||||
active: true
|
||||
SpacingAroundParens:
|
||||
active: true
|
||||
SpacingAroundRangeOperator:
|
||||
active: true
|
||||
StringTemplate:
|
||||
active: true
|
||||
|
||||
naming:
|
||||
active: true
|
||||
ClassNaming:
|
||||
active: true
|
||||
classPattern: '[A-Z$][a-zA-Z0-9$]*'
|
||||
ConstructorParameterNaming:
|
||||
active: true
|
||||
parameterPattern: '[a-z][A-Za-z0-9]*'
|
||||
privateParameterPattern: '[a-z][A-Za-z0-9]*'
|
||||
excludeClassPattern: '$^'
|
||||
EnumNaming:
|
||||
active: true
|
||||
enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*'
|
||||
ForbiddenClassName:
|
||||
active: false
|
||||
forbiddenName: ''
|
||||
FunctionMaxLength:
|
||||
active: false
|
||||
maximumFunctionNameLength: 30
|
||||
FunctionMinLength:
|
||||
active: false
|
||||
minimumFunctionNameLength: 3
|
||||
FunctionNaming:
|
||||
active: true
|
||||
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
|
||||
excludeClassPattern: '$^'
|
||||
ignoreOverridden: true
|
||||
excludes: "**/*Test.kt"
|
||||
FunctionParameterNaming:
|
||||
active: true
|
||||
parameterPattern: '[a-z][A-Za-z0-9]*'
|
||||
excludeClassPattern: '$^'
|
||||
ignoreOverridden: true
|
||||
MatchingDeclarationName:
|
||||
active: true
|
||||
MemberNameEqualsClassName:
|
||||
active: false
|
||||
ignoreOverridden: true
|
||||
ObjectPropertyNaming:
|
||||
active: true
|
||||
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
|
||||
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
|
||||
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
|
||||
PackageNaming:
|
||||
active: true
|
||||
packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$'
|
||||
TopLevelPropertyNaming:
|
||||
active: true
|
||||
constantPattern: '[A-Z][_A-Z0-9]*'
|
||||
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
|
||||
privatePropertyPattern: '(_)?[A-Za-z][A-Za-z0-9]*'
|
||||
VariableMaxLength:
|
||||
active: false
|
||||
maximumVariableNameLength: 64
|
||||
VariableMinLength:
|
||||
active: false
|
||||
minimumVariableNameLength: 1
|
||||
VariableNaming:
|
||||
active: true
|
||||
variablePattern: '[a-z][A-Za-z0-9]*'
|
||||
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
|
||||
excludeClassPattern: '$^'
|
||||
ignoreOverridden: true
|
||||
|
||||
performance:
|
||||
active: true
|
||||
ArrayPrimitive:
|
||||
active: false
|
||||
ForEachOnRange:
|
||||
active: true
|
||||
SpreadOperator:
|
||||
active: true
|
||||
UnnecessaryTemporaryInstantiation:
|
||||
active: true
|
||||
|
||||
potential-bugs:
|
||||
active: true
|
||||
DuplicateCaseInWhenExpression:
|
||||
active: true
|
||||
EqualsAlwaysReturnsTrueOrFalse:
|
||||
active: false
|
||||
EqualsWithHashCodeExist:
|
||||
active: true
|
||||
ExplicitGarbageCollectionCall:
|
||||
active: true
|
||||
InvalidRange:
|
||||
active: false
|
||||
IteratorHasNextCallsNextMethod:
|
||||
active: false
|
||||
IteratorNotThrowingNoSuchElementException:
|
||||
active: false
|
||||
LateinitUsage:
|
||||
active: false
|
||||
excludeAnnotatedProperties: ""
|
||||
ignoreOnClassesPattern: ""
|
||||
UnconditionalJumpStatementInLoop:
|
||||
active: false
|
||||
UnreachableCode:
|
||||
active: true
|
||||
UnsafeCallOnNullableType:
|
||||
active: false
|
||||
UnsafeCast:
|
||||
active: false
|
||||
UselessPostfixExpression:
|
||||
active: false
|
||||
WrongEqualsTypeParameter:
|
||||
active: false
|
||||
|
||||
style:
|
||||
active: true
|
||||
CollapsibleIfStatements:
|
||||
active: false
|
||||
DataClassContainsFunctions:
|
||||
active: false
|
||||
conversionFunctionPrefix: 'to'
|
||||
EqualsNullCall:
|
||||
active: false
|
||||
EqualsOnSignatureLine:
|
||||
active: false
|
||||
ExplicitItLambdaParameter:
|
||||
active: false
|
||||
ExpressionBodySyntax:
|
||||
active: false
|
||||
includeLineWrapping: false
|
||||
ForbiddenComment:
|
||||
active: true
|
||||
values: 'TODO:,FIXME:,STOPSHIP:'
|
||||
ForbiddenImport:
|
||||
active: false
|
||||
imports: ''
|
||||
ForbiddenVoid:
|
||||
active: false
|
||||
FunctionOnlyReturningConstant:
|
||||
active: false
|
||||
ignoreOverridableFunction: true
|
||||
excludedFunctions: 'describeContents'
|
||||
LoopWithTooManyJumpStatements:
|
||||
active: false
|
||||
maxJumpCount: 1
|
||||
MagicNumber:
|
||||
active: true
|
||||
ignoreNumbers: '-1,0,1,2'
|
||||
ignoreHashCodeFunction: true
|
||||
ignorePropertyDeclaration: false
|
||||
ignoreConstantDeclaration: true
|
||||
ignoreCompanionObjectPropertyDeclaration: true
|
||||
ignoreAnnotation: false
|
||||
ignoreNamedArgument: true
|
||||
ignoreEnums: false
|
||||
excludes: "**/*Test.kt"
|
||||
MandatoryBracesIfStatements:
|
||||
active: false
|
||||
MaxLineLength:
|
||||
active: true
|
||||
maxLineLength: 120
|
||||
excludePackageStatements: true
|
||||
excludeImportStatements: true
|
||||
excludeCommentStatements: false
|
||||
MayBeConst:
|
||||
active: false
|
||||
ModifierOrder:
|
||||
active: true
|
||||
NestedClassesVisibility:
|
||||
active: false
|
||||
NewLineAtEndOfFile:
|
||||
active: true
|
||||
NoTabs:
|
||||
active: false
|
||||
OptionalAbstractKeyword:
|
||||
active: true
|
||||
OptionalUnit:
|
||||
active: false
|
||||
OptionalWhenBraces:
|
||||
active: false
|
||||
PreferToOverPairSyntax:
|
||||
active: false
|
||||
ProtectedMemberInFinalClass:
|
||||
active: false
|
||||
RedundantVisibilityModifierRule:
|
||||
active: false
|
||||
ReturnCount:
|
||||
active: true
|
||||
max: 2
|
||||
excludedFunctions: "equals"
|
||||
excludeLabeled: false
|
||||
excludeReturnFromLambda: true
|
||||
SafeCast:
|
||||
active: true
|
||||
SerialVersionUIDInSerializableClass:
|
||||
active: false
|
||||
SpacingBetweenPackageAndImports:
|
||||
active: false
|
||||
ThrowsCount:
|
||||
active: true
|
||||
max: 2
|
||||
TrailingWhitespace:
|
||||
active: false
|
||||
UnderscoresInNumericLiterals:
|
||||
active: false
|
||||
acceptableDecimalLength: 5
|
||||
UnnecessaryAbstractClass:
|
||||
active: false
|
||||
excludeAnnotatedClasses: "dagger.Module"
|
||||
UnnecessaryApply:
|
||||
active: false
|
||||
UnnecessaryInheritance:
|
||||
active: false
|
||||
UnnecessaryLet:
|
||||
active: false
|
||||
UnnecessaryParentheses:
|
||||
active: false
|
||||
UntilInsteadOfRangeTo:
|
||||
active: false
|
||||
UnusedImports:
|
||||
active: false
|
||||
UnusedPrivateClass:
|
||||
active: false
|
||||
UnusedPrivateMember:
|
||||
active: false
|
||||
allowedNames: "(_|ignored|expected|serialVersionUID)"
|
||||
UseDataClass:
|
||||
active: false
|
||||
excludeAnnotatedClasses: ""
|
||||
UtilityClassWithPublicConstructor:
|
||||
active: false
|
||||
VarCouldBeVal:
|
||||
active: false
|
||||
WildcardImport:
|
||||
active: true
|
||||
excludeImports: 'java.util.*,kotlinx.android.synthetic.*'
|
|
@ -1,309 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 1344 1344" version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"
|
||||
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
|
||||
<rect x="-30.496" y="-19.169" width="1488.2" height="1362.73" style="fill:rgb(0,130,201);"/>
|
||||
<g id="g13338" transform="matrix(5.13054,0,0,5.13054,-5362.99,82.5409)">
|
||||
<path id="path256" d="M1298,54.1C1299.6,54.1 1300.9,52.8 1300.9,51.2C1300.9,49.6 1299.6,48.3 1298,48.3C1296.4,48.3 1295.1,49.6 1295.1,51.2C1295.1,52.8 1296.4,54.1 1298,54.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path258" d="M1307.5,54.1C1309.1,54.1 1310.4,52.8 1310.4,51.2C1310.4,49.6 1309.1,48.3 1307.5,48.3C1305.9,48.3 1304.6,49.6 1304.6,51.2C1304.6,52.8 1305.9,54.1 1307.5,54.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path310" d="M1298,63.5C1299.6,63.5 1300.9,62.2 1300.9,60.6C1300.9,59 1299.6,57.7 1298,57.7C1296.4,57.7 1295.1,59 1295.1,60.6C1295.1,62.2 1296.4,63.5 1298,63.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path312" d="M1307.5,63.5C1309.1,63.5 1310.4,62.2 1310.4,60.6C1310.4,59 1309.1,57.7 1307.5,57.7C1305.9,57.7 1304.6,59 1304.6,60.6C1304.6,62.2 1305.9,63.5 1307.5,63.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path366" d="M1260.3,73C1262,73 1263.3,71.7 1263.3,70C1263.3,68.4 1262,67 1260.3,67C1258.7,67 1257.3,68.3 1257.3,70C1257.3,71.7 1258.7,73 1260.3,73Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path368" d="M1279.2,72.9C1280.8,72.9 1282.1,71.6 1282.1,70C1282.1,68.4 1280.8,67.1 1279.2,67.1C1277.6,67.1 1276.3,68.4 1276.3,70C1276.3,71.6 1277.6,72.9 1279.2,72.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path370" d="M1288.6,73.5C1290.5,73.5 1292,72 1292,70.1C1292,68.2 1290.5,66.7 1288.6,66.7C1286.7,66.7 1285.2,68.2 1285.2,70.1C1285.2,71.9 1286.7,73.5 1288.6,73.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path372" d="M1298,73C1299.6,73 1301,71.7 1301,70.1C1301,68.5 1299.7,67.2 1298,67.2C1296.4,67.2 1295.1,68.5 1295.1,70.1C1295.1,71.7 1296.4,73 1298,73Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path374" d="M1307.5,73C1309.2,73 1310.5,71.7 1310.5,70C1310.5,68.4 1309.2,67 1307.5,67C1305.8,67 1304.5,68.3 1304.5,70C1304.5,71.7 1305.8,73 1307.5,73Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path438" d="M1250.8,82.4C1252.4,82.4 1253.7,81.1 1253.7,79.5C1253.7,77.9 1252.4,76.6 1250.8,76.6C1249.2,76.6 1247.9,77.9 1247.9,79.5C1248,81.1 1249.3,82.4 1250.8,82.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path440" d="M1260.3,82.4C1262,82.4 1263.3,81.1 1263.3,79.4C1263.3,77.8 1262,76.4 1260.3,76.4C1258.7,76.4 1257.3,77.7 1257.3,79.4C1257.3,81.1 1258.7,82.4 1260.3,82.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path442" d="M1279.2,82.4C1280.8,82.4 1282.1,81.1 1282.1,79.5C1282.1,77.9 1280.8,76.6 1279.2,76.6C1277.6,76.6 1276.3,77.9 1276.3,79.5C1276.3,81.1 1277.6,82.4 1279.2,82.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path444" d="M1288.6,82.4C1290.2,82.4 1291.5,81.1 1291.5,79.5C1291.5,77.9 1290.2,76.6 1288.6,76.6C1287,76.6 1285.7,77.9 1285.7,79.5C1285.7,81.1 1287,82.4 1288.6,82.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path446" d="M1307.5,82.4C1309.2,82.4 1310.5,81.1 1310.5,79.4C1310.5,77.8 1309.2,76.4 1307.5,76.4C1305.8,76.4 1304.5,77.7 1304.5,79.4C1304.5,81.1 1305.8,82.4 1307.5,82.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path504" d="M1279.2,91.8C1280.8,91.8 1282.1,90.5 1282.1,88.9C1282.1,87.3 1280.8,86 1279.2,86C1277.6,86 1276.3,87.3 1276.3,88.9C1276.3,90.5 1277.6,91.8 1279.2,91.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path564" d="M1260.3,101.2C1261.9,101.2 1263.2,99.9 1263.2,98.3C1263.2,96.7 1261.9,95.4 1260.3,95.4C1258.7,95.4 1257.4,96.7 1257.4,98.3C1257.4,99.9 1258.7,101.2 1260.3,101.2Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path566" d="M1269.7,101.3C1271.3,101.3 1272.6,100 1272.6,98.4C1272.6,96.8 1271.3,95.5 1269.7,95.5C1268.1,95.5 1266.7,96.8 1266.7,98.4C1266.8,100 1268.1,101.3 1269.7,101.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path568" d="M1279.2,101.4C1280.9,101.4 1282.3,100 1282.3,98.3C1282.3,96.6 1280.9,95.2 1279.2,95.2C1277.5,95.2 1276.1,96.6 1276.1,98.3C1276.1,100.1 1277.4,101.4 1279.2,101.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path570" d="M1288.6,101.7C1290.5,101.7 1292,100.2 1292,98.3C1292,96.5 1290.5,94.9 1288.6,94.9C1286.8,94.9 1285.3,96.4 1285.3,98.3C1285.2,100.2 1286.7,101.7 1288.6,101.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path572" d="M1298,102.1C1300.1,102.1 1301.7,100.4 1301.7,98.4C1301.7,96.3 1300,94.7 1298,94.7C1296,94.7 1294.3,96.4 1294.3,98.4C1294.3,100.4 1296,102.1 1298,102.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path574" d="M1307.5,101.2C1309.1,101.2 1310.4,99.9 1310.4,98.3C1310.4,96.7 1309.1,95.4 1307.5,95.4C1305.9,95.4 1304.6,96.7 1304.6,98.3C1304.6,99.9 1305.9,101.2 1307.5,101.2Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path642" d="M1250.8,110.7C1252.4,110.7 1253.7,109.4 1253.7,107.8C1253.7,106.2 1252.4,104.9 1250.8,104.9C1249.2,104.9 1247.9,106.2 1247.9,107.8C1248,109.4 1249.3,110.7 1250.8,110.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path644" d="M1260.3,110.8C1262,110.8 1263.3,109.5 1263.3,107.8C1263.3,106.2 1262,104.8 1260.3,104.8C1258.7,104.8 1257.3,106.1 1257.3,107.8C1257.3,109.4 1258.7,110.8 1260.3,110.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path646" d="M1269.7,110.8C1271.3,110.8 1272.7,109.5 1272.7,107.8C1272.7,106.2 1271.4,104.8 1269.7,104.8C1268,104.8 1266.7,106.1 1266.7,107.8C1266.8,109.4 1268.1,110.8 1269.7,110.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path648" d="M1279.2,111.5C1281.2,111.5 1282.9,109.8 1282.9,107.8C1282.9,105.8 1281.2,104.1 1279.2,104.1C1277.2,104.1 1275.5,105.8 1275.5,107.8C1275.4,109.8 1277.1,111.5 1279.2,111.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path650" d="M1288.6,110.7C1290.2,110.7 1291.5,109.4 1291.5,107.8C1291.5,106.2 1290.2,104.9 1288.6,104.9C1287,104.9 1285.7,106.2 1285.7,107.8C1285.7,109.4 1287,110.7 1288.6,110.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path652" d="M1298,111.2C1299.9,111.2 1301.4,109.7 1301.4,107.8C1301.4,105.9 1299.9,104.4 1298,104.4C1296.1,104.4 1294.6,105.9 1294.6,107.8C1294.6,109.7 1296.1,111.2 1298,111.2Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path654" d="M1307.5,111.8C1309.7,111.8 1311.5,110 1311.5,107.8C1311.5,105.6 1309.7,103.8 1307.5,103.8C1305.3,103.8 1303.5,105.6 1303.5,107.8C1303.5,110 1305.2,111.8 1307.5,111.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path722" d="M1250.8,120.8C1252.8,120.8 1254.4,119.2 1254.4,117.2C1254.4,115.2 1252.8,113.6 1250.8,113.6C1248.8,113.6 1247.2,115.2 1247.2,117.2C1247.3,119.2 1248.9,120.8 1250.8,120.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path724" d="M1260.3,120.2C1262,120.2 1263.3,118.9 1263.3,117.2C1263.3,115.6 1262,114.2 1260.3,114.2C1258.7,114.2 1257.3,115.5 1257.3,117.2C1257.3,118.9 1258.7,120.2 1260.3,120.2Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path726" d="M1269.7,120.2C1271.3,120.2 1272.7,118.9 1272.7,117.2C1272.7,115.6 1271.4,114.2 1269.7,114.2C1268,114.2 1266.7,115.5 1266.7,117.2C1266.8,118.9 1268.1,120.2 1269.7,120.2Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path728" d="M1279.2,120.9C1281.2,120.9 1282.9,119.2 1282.9,117.2C1282.9,115.2 1281.2,113.5 1279.2,113.5C1277.2,113.5 1275.5,115.2 1275.5,117.2C1275.4,119.3 1277.1,120.9 1279.2,120.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path730" d="M1288.6,120.1C1290.2,120.1 1291.5,118.8 1291.5,117.2C1291.5,115.6 1290.2,114.3 1288.6,114.3C1287,114.3 1285.7,115.6 1285.7,117.2C1285.7,118.8 1287,120.1 1288.6,120.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path732" d="M1298,120.6C1299.9,120.6 1301.4,119.1 1301.4,117.2C1301.4,115.3 1299.9,113.8 1298,113.8C1296.1,113.8 1294.6,115.3 1294.6,117.2C1294.6,119.1 1296.1,120.6 1298,120.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path734" d="M1307.5,120.1C1309.1,120.1 1310.4,118.8 1310.4,117.2C1310.4,115.6 1309.1,114.3 1307.5,114.3C1305.9,114.3 1304.6,115.6 1304.6,117.2C1304.6,118.8 1305.9,120.1 1307.5,120.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path798" d="M1250.8,129.5C1252.4,129.5 1253.7,128.2 1253.7,126.6C1253.7,125 1252.4,123.7 1250.8,123.7C1249.2,123.7 1247.9,125 1247.9,126.6C1248,128.3 1249.3,129.5 1250.8,129.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path800" d="M1260.3,129.5C1261.9,129.5 1263.2,128.2 1263.2,126.6C1263.2,125 1261.9,123.7 1260.3,123.7C1258.7,123.7 1257.4,125 1257.4,126.6C1257.4,128.3 1258.7,129.5 1260.3,129.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path802" d="M1269.7,129.5C1271.3,129.5 1272.6,128.2 1272.6,126.6C1272.6,125 1271.3,123.7 1269.7,123.7C1268.1,123.7 1266.8,125 1266.8,126.6C1266.8,128.3 1268.1,129.5 1269.7,129.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path804" d="M1279.2,129.6C1280.8,129.6 1282.2,128.3 1282.2,126.6C1282.2,124.9 1280.9,123.6 1279.2,123.6C1277.5,123.6 1276.2,124.9 1276.2,126.6C1276.2,128.3 1277.5,129.6 1279.2,129.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path806" d="M1288.6,130C1290.5,130 1292,128.5 1292,126.7C1292,124.8 1290.5,123.3 1288.6,123.3C1286.8,123.3 1285.3,124.8 1285.3,126.7C1285.2,128.5 1286.7,130 1288.6,130Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path808" d="M1298,129.5C1299.6,129.5 1300.9,128.2 1300.9,126.6C1300.9,125 1299.6,123.7 1298,123.7C1296.4,123.7 1295.1,125 1295.1,126.6C1295.1,128.3 1296.4,129.5 1298,129.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path810" d="M1307.5,129.5C1309.1,129.5 1310.4,128.2 1310.4,126.6C1310.4,125 1309.1,123.7 1307.5,123.7C1305.9,123.7 1304.6,125 1304.6,126.6C1304.6,128.3 1305.9,129.5 1307.5,129.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path874" d="M1203.7,139C1205.3,139 1206.6,137.7 1206.6,136.1C1206.6,134.5 1205.3,133.2 1203.7,133.2C1202.1,133.2 1200.8,134.5 1200.8,136.1C1200.8,137.7 1202.1,139 1203.7,139Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path876" d="M1213.1,139.8C1215.2,139.8 1216.8,138.1 1216.8,136.1C1216.8,134 1215.1,132.4 1213.1,132.4C1211.1,132.4 1209.4,134.1 1209.4,136.1C1209.4,138.1 1211.1,139.8 1213.1,139.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path878" d="M1232,139C1233.6,139 1234.9,137.7 1234.9,136.1C1234.9,134.5 1233.6,133.2 1232,133.2C1230.4,133.2 1229.1,134.5 1229.1,136.1C1229.1,137.7 1230.4,139 1232,139Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path880" d="M1241.4,139.7C1243.4,139.7 1245,138.1 1245,136.1C1245,134.1 1243.4,132.5 1241.4,132.5C1239.4,132.5 1237.8,134.1 1237.8,136.1C1237.8,138.1 1239.4,139.7 1241.4,139.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path882" d="M1260.3,139C1261.9,139 1263.2,137.7 1263.2,136.1C1263.2,134.5 1261.9,133.2 1260.3,133.2C1258.7,133.2 1257.4,134.5 1257.4,136.1C1257.4,137.7 1258.7,139 1260.3,139Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path884" d="M1269.7,139C1271.3,139 1272.6,137.7 1272.6,136.1C1272.6,134.5 1271.3,133.2 1269.7,133.2C1268.1,133.2 1266.8,134.5 1266.8,136.1C1266.8,137.7 1268.1,139 1269.7,139Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path886" d="M1288.6,139C1290.2,139 1291.5,137.7 1291.5,136.1C1291.5,134.5 1290.2,133.2 1288.6,133.2C1287,133.2 1285.7,134.5 1285.7,136.1C1285.7,137.7 1287,139 1288.6,139Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path888" d="M1298,139C1299.6,139 1300.9,137.7 1300.9,136.1C1300.9,134.5 1299.6,133.2 1298,133.2C1296.4,133.2 1295.1,134.5 1295.1,136.1C1295.1,137.7 1296.4,139 1298,139Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path890" d="M1307.5,140C1309.7,140 1311.4,138.3 1311.4,136.1C1311.4,134 1309.7,132.2 1307.5,132.2C1305.3,132.2 1303.6,133.9 1303.6,136.1C1303.6,138.2 1305.3,140 1307.5,140Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path954" d="M1203.7,148.4C1205.3,148.4 1206.6,147.1 1206.6,145.5C1206.6,143.9 1205.3,142.6 1203.7,142.6C1202.1,142.6 1200.8,143.9 1200.8,145.5C1200.8,147.1 1202.1,148.4 1203.7,148.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path956" d="M1213.1,149.3C1215.2,149.3 1216.8,147.6 1216.8,145.6C1216.8,143.5 1215.1,141.9 1213.1,141.9C1211.1,141.9 1209.4,143.6 1209.4,145.6C1209.4,147.6 1211.1,149.3 1213.1,149.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path958" d="M1241.4,148.4C1243,148.4 1244.3,147.1 1244.3,145.5C1244.3,143.9 1243,142.6 1241.4,142.6C1239.8,142.6 1238.5,143.9 1238.5,145.5C1238.5,147.1 1239.8,148.4 1241.4,148.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path960" d="M1260.3,148.4C1261.9,148.4 1263.2,147.1 1263.2,145.5C1263.2,143.9 1261.9,142.6 1260.3,142.6C1258.7,142.6 1257.4,143.9 1257.4,145.5C1257.4,147.1 1258.7,148.4 1260.3,148.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path962" d="M1269.7,148.9C1271.6,148.9 1273.1,147.4 1273.1,145.5C1273.1,143.6 1271.6,142.1 1269.7,142.1C1267.8,142.1 1266.3,143.6 1266.3,145.5C1266.3,147.4 1267.8,148.9 1269.7,148.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path964" d="M1279.2,148.4C1280.8,148.4 1282.1,147.1 1282.1,145.5C1282.1,143.9 1280.8,142.6 1279.2,142.6C1277.6,142.6 1276.3,143.9 1276.3,145.5C1276.3,147.1 1277.6,148.4 1279.2,148.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path966" d="M1288.6,148.4C1290.2,148.4 1291.5,147.1 1291.5,145.5C1291.5,143.9 1290.2,142.6 1288.6,142.6C1287,142.6 1285.7,143.9 1285.7,145.5C1285.7,147.1 1287,148.4 1288.6,148.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path968" d="M1298,148.5C1299.6,148.5 1301,147.2 1301,145.6C1301,144 1299.7,142.7 1298,142.7C1296.4,142.7 1295.1,144 1295.1,145.6C1295.1,147.1 1296.4,148.5 1298,148.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path970" d="M1307.5,148.4C1309.1,148.4 1310.4,147.1 1310.4,145.5C1310.4,143.9 1309.1,142.6 1307.5,142.6C1305.9,142.6 1304.6,143.9 1304.6,145.5C1304.6,147.1 1305.9,148.4 1307.5,148.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1034" d="M1203.7,157.8C1205.3,157.8 1206.6,156.5 1206.6,154.9C1206.6,153.3 1205.3,152 1203.7,152C1202.1,152 1200.8,153.3 1200.8,154.9C1200.8,156.5 1202.1,157.8 1203.7,157.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1036" d="M1222.5,157.8C1224.1,157.8 1225.4,156.5 1225.4,154.9C1225.4,153.3 1224.1,152 1222.5,152C1220.9,152 1219.6,153.3 1219.6,154.9C1219.7,156.5 1221,157.8 1222.5,157.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1038" d="M1232,158.5C1233.9,158.5 1235.5,156.9 1235.5,155C1235.5,153.1 1233.9,151.5 1232,151.5C1230,151.5 1228.5,153.1 1228.5,155C1228.5,156.9 1230,158.5 1232,158.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1040" d="M1241.4,157.8C1243,157.8 1244.3,156.5 1244.3,154.9C1244.3,153.3 1243,152 1241.4,152C1239.8,152 1238.5,153.3 1238.5,154.9C1238.6,156.5 1239.8,157.8 1241.4,157.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1042" d="M1250.8,157.8C1252.4,157.8 1253.7,156.5 1253.7,154.9C1253.7,153.3 1252.4,152 1250.8,152C1249.2,152 1247.9,153.3 1247.9,154.9C1248,156.5 1249.3,157.8 1250.8,157.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1044" d="M1260.3,158.3C1262.1,158.3 1263.6,156.8 1263.6,155C1263.6,153.2 1262.1,151.7 1260.3,151.7C1258.5,151.7 1257,153.2 1257,155C1257,156.8 1258.4,158.3 1260.3,158.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1046" d="M1269.7,158.3C1271.5,158.3 1273,156.8 1273,155C1273,153.2 1271.5,151.7 1269.7,151.7C1267.9,151.7 1266.4,153.2 1266.4,155C1266.4,156.8 1267.9,158.3 1269.7,158.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1048" d="M1279.2,158.3C1281,158.3 1282.5,156.8 1282.5,154.9C1282.5,153.1 1281,151.6 1279.2,151.6C1277.3,151.6 1275.8,153.1 1275.8,154.9C1275.8,156.8 1277.3,158.3 1279.2,158.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1050" d="M1288.6,158.3C1290.5,158.3 1292,156.8 1292,154.9C1292,153.1 1290.5,151.6 1288.6,151.6C1286.8,151.6 1285.3,153.1 1285.3,154.9C1285.2,156.8 1286.7,158.3 1288.6,158.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1052" d="M1298,158.4C1299.9,158.4 1301.4,156.9 1301.4,155C1301.4,153.1 1299.9,151.6 1298,151.6C1296.1,151.6 1294.6,153.1 1294.6,155C1294.6,156.8 1296.1,158.4 1298,158.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1054" d="M1307.5,157.8C1309.1,157.8 1310.4,156.5 1310.4,154.9C1310.4,153.3 1309.1,152 1307.5,152C1305.9,152 1304.6,153.3 1304.6,154.9C1304.6,156.5 1305.9,157.8 1307.5,157.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1118" d="M1147.1,167.3C1148.7,167.3 1150,166 1150,164.4C1150,162.8 1148.7,161.5 1147.1,161.5C1145.5,161.5 1144.2,162.8 1144.2,164.4C1144.2,166 1145.5,167.3 1147.1,167.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1120" d="M1156.5,167.3C1158.1,167.3 1159.4,166 1159.4,164.4C1159.4,162.8 1158.1,161.5 1156.5,161.5C1154.9,161.5 1153.6,162.8 1153.6,164.4C1153.6,166 1154.9,167.3 1156.5,167.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1122" d="M1166,167.3C1167.6,167.3 1168.9,166 1168.9,164.4C1168.9,162.8 1167.6,161.5 1166,161.5C1164.4,161.5 1163.1,162.8 1163.1,164.4C1163.1,166 1164.4,167.3 1166,167.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1124" d="M1175.4,167.6C1177.2,167.6 1178.6,166.2 1178.6,164.4C1178.6,162.6 1177.1,161.2 1175.4,161.2C1173.6,161.2 1172.2,162.6 1172.2,164.4C1172.2,166.2 1173.6,167.6 1175.4,167.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1126" d="M1213.1,167.3C1214.7,167.3 1216,166 1216,164.4C1216,162.8 1214.7,161.5 1213.1,161.5C1211.5,161.5 1210.2,162.8 1210.2,164.4C1210.2,166 1211.5,167.3 1213.1,167.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1128" d="M1222.5,168.4C1224.7,168.4 1226.5,166.6 1226.5,164.4C1226.5,162.2 1224.7,160.4 1222.5,160.4C1220.3,160.4 1218.5,162.2 1218.5,164.4C1218.5,166.6 1220.4,168.4 1222.5,168.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1130" d="M1232,167.3C1233.6,167.3 1234.9,166 1234.9,164.4C1234.9,162.8 1233.6,161.5 1232,161.5C1230.4,161.5 1229.1,162.8 1229.1,164.4C1229.1,166 1230.4,167.3 1232,167.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1132" d="M1241.4,168.4C1243.6,168.4 1245.4,166.6 1245.4,164.4C1245.4,162.2 1243.6,160.4 1241.4,160.4C1239.2,160.4 1237.4,162.2 1237.4,164.4C1237.4,166.6 1239.2,168.4 1241.4,168.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1134" d="M1250.8,167.7C1252.6,167.7 1254.1,166.2 1254.1,164.4C1254.1,162.6 1252.6,161.1 1250.8,161.1C1249,161.1 1247.5,162.6 1247.5,164.4C1247.6,166.2 1249,167.7 1250.8,167.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1136" d="M1260.3,167.7C1262.1,167.7 1263.6,166.2 1263.6,164.4C1263.6,162.6 1262.1,161.1 1260.3,161.1C1258.5,161.1 1257,162.6 1257,164.4C1257,166.2 1258.4,167.7 1260.3,167.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1138" d="M1269.7,167.7C1271.5,167.7 1273,166.2 1273,164.4C1273,162.6 1271.5,161.1 1269.7,161.1C1267.9,161.1 1266.4,162.6 1266.4,164.4C1266.4,166.2 1267.9,167.7 1269.7,167.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1140" d="M1279.2,167.7C1281,167.7 1282.5,166.2 1282.5,164.4C1282.5,162.5 1281,161 1279.2,161C1277.3,161 1275.8,162.5 1275.8,164.4C1275.8,166.2 1277.3,167.7 1279.2,167.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1142" d="M1288.6,167.3C1290.2,167.3 1291.5,166 1291.5,164.4C1291.5,162.8 1290.2,161.5 1288.6,161.5C1287,161.5 1285.7,162.8 1285.7,164.4C1285.7,166 1287,167.3 1288.6,167.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1144" d="M1298,167.9C1300,167.9 1301.5,166.3 1301.5,164.4C1301.5,162.4 1299.9,160.9 1298,160.9C1296.1,160.9 1294.5,162.5 1294.5,164.4C1294.5,166.3 1296.1,167.9 1298,167.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1146" d="M1307.5,167.8C1309.4,167.8 1310.9,166.3 1310.9,164.4C1310.9,162.5 1309.4,161 1307.5,161C1305.6,161 1304.1,162.5 1304.1,164.4C1304.1,166.3 1305.6,167.8 1307.5,167.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1210" d="M1156.5,176.7C1158.1,176.7 1159.4,175.4 1159.4,173.8C1159.4,172.2 1158.1,170.9 1156.5,170.9C1154.9,170.9 1153.6,172.2 1153.6,173.8C1153.6,175.4 1154.9,176.7 1156.5,176.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1212" d="M1166,176.7C1167.6,176.7 1168.9,175.4 1168.9,173.8C1168.9,172.2 1167.6,170.9 1166,170.9C1164.4,170.9 1163.1,172.2 1163.1,173.8C1163.1,175.4 1164.4,176.7 1166,176.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1214" d="M1175.4,176.7C1177,176.7 1178.3,175.4 1178.3,173.8C1178.3,172.2 1177,170.9 1175.4,170.9C1173.8,170.9 1172.5,172.2 1172.5,173.8C1172.5,175.4 1173.8,176.7 1175.4,176.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1216" d="M1184.8,176.7C1186.4,176.7 1187.7,175.4 1187.7,173.8C1187.7,172.2 1186.4,170.9 1184.8,170.9C1183.2,170.9 1181.9,172.2 1181.9,173.8C1181.9,175.4 1183.2,176.7 1184.8,176.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1218" d="M1213.1,176.7C1214.7,176.7 1216,175.4 1216,173.8C1216,172.2 1214.7,170.9 1213.1,170.9C1211.5,170.9 1210.2,172.2 1210.2,173.8C1210.2,175.4 1211.5,176.7 1213.1,176.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1220" d="M1222.5,176.7C1224.1,176.7 1225.4,175.4 1225.4,173.8C1225.4,172.2 1224.1,170.9 1222.5,170.9C1220.9,170.9 1219.6,172.2 1219.6,173.8C1219.7,175.4 1221,176.7 1222.5,176.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1222" d="M1232,177.5C1234,177.5 1235.7,175.8 1235.7,173.8C1235.7,171.7 1234,170.1 1232,170.1C1229.9,170.1 1228.3,171.8 1228.3,173.8C1228.3,175.9 1229.9,177.5 1232,177.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1224" d="M1241.4,177.8C1243.6,177.8 1245.4,176 1245.4,173.8C1245.4,171.6 1243.6,169.8 1241.4,169.8C1239.2,169.8 1237.4,171.6 1237.4,173.8C1237.4,176 1239.2,177.8 1241.4,177.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1226" d="M1250.8,177.8C1253,177.8 1254.8,176 1254.8,173.8C1254.8,171.6 1253,169.8 1250.8,169.8C1248.6,169.8 1246.8,171.6 1246.8,173.8C1246.8,176 1248.6,177.8 1250.8,177.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1228" d="M1260.3,177.2C1262.1,177.2 1263.6,175.7 1263.6,173.9C1263.6,172.1 1262.1,170.6 1260.3,170.6C1258.5,170.6 1257,172.1 1257,173.9C1257,175.7 1258.4,177.2 1260.3,177.2Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1230" d="M1269.7,176.7C1271.3,176.7 1272.6,175.4 1272.6,173.8C1272.6,172.2 1271.3,170.9 1269.7,170.9C1268.1,170.9 1266.8,172.2 1266.8,173.8C1266.8,175.4 1268.1,176.7 1269.7,176.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1232" d="M1279.2,176.7C1280.8,176.7 1282.1,175.4 1282.1,173.8C1282.1,172.2 1280.8,170.9 1279.2,170.9C1277.6,170.9 1276.3,172.2 1276.3,173.8C1276.3,175.4 1277.6,176.7 1279.2,176.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1234" d="M1288.6,176.7C1290.2,176.7 1291.5,175.4 1291.5,173.8C1291.5,172.2 1290.2,170.9 1288.6,170.9C1287,170.9 1285.7,172.2 1285.7,173.8C1285.7,175.4 1287,176.7 1288.6,176.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1236" d="M1298,177.4C1300,177.4 1301.5,175.8 1301.5,173.8C1301.5,171.9 1299.9,170.3 1298,170.3C1296.1,170.3 1294.5,171.9 1294.5,173.8C1294.5,175.8 1296.1,177.4 1298,177.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1238" d="M1307.5,177.2C1309.4,177.2 1310.9,175.7 1310.9,173.8C1310.9,171.9 1309.4,170.4 1307.5,170.4C1305.6,170.4 1304.1,171.9 1304.1,173.8C1304,175.7 1305.6,177.2 1307.5,177.2Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1302" d="M1156.5,186.1C1158.1,186.1 1159.4,184.8 1159.4,183.2C1159.4,181.6 1158.1,180.3 1156.5,180.3C1154.9,180.3 1153.6,181.6 1153.6,183.2C1153.6,184.9 1154.9,186.1 1156.5,186.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1304" d="M1166,186.4C1167.7,186.4 1169.2,185 1169.2,183.2C1169.2,181.4 1167.7,180 1166,180C1164.2,180 1162.8,181.4 1162.8,183.2C1162.8,185 1164.2,186.4 1166,186.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1306" d="M1175.4,186.1C1177,186.1 1178.3,184.8 1178.3,183.2C1178.3,181.6 1177,180.3 1175.4,180.3C1173.8,180.3 1172.5,181.6 1172.5,183.2C1172.5,184.9 1173.8,186.1 1175.4,186.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1308" d="M1184.8,186.1C1186.4,186.1 1187.7,184.8 1187.7,183.2C1187.7,181.6 1186.4,180.3 1184.8,180.3C1183.2,180.3 1181.9,181.6 1181.9,183.2C1181.9,184.9 1183.2,186.1 1184.8,186.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1310" d="M1194.3,187C1196.3,187 1198,185.3 1198,183.3C1198,181.2 1196.3,179.6 1194.3,179.6C1192.2,179.6 1190.6,181.3 1190.6,183.3C1190.5,185.3 1192.2,187 1194.3,187Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1312" d="M1203.7,186.2C1205.3,186.2 1206.7,184.9 1206.7,183.2C1206.7,181.6 1205.4,180.2 1203.7,180.2C1202.1,180.2 1200.7,181.5 1200.7,183.2C1200.7,184.9 1202.1,186.2 1203.7,186.2Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1314" d="M1213.1,186.1C1214.7,186.1 1216,184.8 1216,183.2C1216,181.6 1214.7,180.3 1213.1,180.3C1211.5,180.3 1210.2,181.6 1210.2,183.2C1210.2,184.9 1211.5,186.1 1213.1,186.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1316" d="M1222.5,186.1C1224.1,186.1 1225.4,184.8 1225.4,183.2C1225.4,181.6 1224.1,180.3 1222.5,180.3C1220.9,180.3 1219.6,181.6 1219.6,183.2C1219.7,184.9 1221,186.1 1222.5,186.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1318" d="M1232,187C1234,187 1235.7,185.3 1235.7,183.3C1235.7,181.2 1234,179.6 1232,179.6C1229.9,179.6 1228.3,181.3 1228.3,183.3C1228.3,185.3 1229.9,187 1232,187Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1320" d="M1241.4,186.6C1243.2,186.6 1244.7,185.1 1244.7,183.2C1244.7,181.3 1243.2,179.8 1241.4,179.8C1239.5,179.8 1238,181.3 1238,183.2C1238.1,185.1 1239.6,186.6 1241.4,186.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1322" d="M1250.8,186.5C1252.6,186.5 1254.1,185 1254.1,183.2C1254.1,181.4 1252.6,179.9 1250.8,179.9C1249,179.9 1247.5,181.4 1247.5,183.2C1247.6,185.1 1249,186.5 1250.8,186.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1324" d="M1260.3,186.6C1262.1,186.6 1263.6,185.1 1263.6,183.3C1263.6,181.5 1262.1,180 1260.3,180C1258.5,180 1257,181.5 1257,183.3C1257,185.1 1258.4,186.6 1260.3,186.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1326" d="M1269.7,186.6C1271.5,186.6 1273,185.1 1273,183.3C1273,181.5 1271.5,180 1269.7,180C1267.9,180 1266.4,181.5 1266.4,183.3C1266.4,185.1 1267.9,186.6 1269.7,186.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1328" d="M1279.2,186.1C1280.8,186.1 1282.1,184.8 1282.1,183.2C1282.1,181.6 1280.8,180.3 1279.2,180.3C1277.6,180.3 1276.3,181.6 1276.3,183.2C1276.3,184.9 1277.6,186.1 1279.2,186.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1330" d="M1288.6,186.6C1290.5,186.6 1292,185.1 1292,183.2C1292,181.3 1290.5,179.8 1288.6,179.8C1286.8,179.8 1285.3,181.3 1285.3,183.2C1285.2,185.1 1286.7,186.6 1288.6,186.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1332" d="M1298,186.7C1299.9,186.7 1301.4,185.2 1301.4,183.3C1301.4,181.4 1299.9,179.9 1298,179.9C1296.1,179.9 1294.6,181.4 1294.6,183.3C1294.6,185.1 1296.1,186.7 1298,186.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1334" d="M1307.5,186.7C1309.4,186.7 1310.9,185.2 1310.9,183.3C1310.9,181.4 1309.4,179.9 1307.5,179.9C1305.6,179.9 1304.1,181.4 1304.1,183.3C1304,185.1 1305.6,186.7 1307.5,186.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1398" d="M1166,195.6C1167.6,195.6 1168.9,194.3 1168.9,192.7C1168.9,191.1 1167.6,189.8 1166,189.8C1164.4,189.8 1163.1,191.1 1163.1,192.7C1163.1,194.3 1164.4,195.6 1166,195.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1400" d="M1175.4,195.6C1177,195.6 1178.3,194.3 1178.3,192.7C1178.3,191.1 1177,189.8 1175.4,189.8C1173.8,189.8 1172.5,191.1 1172.5,192.7C1172.5,194.3 1173.8,195.6 1175.4,195.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1402" d="M1194.3,195.6C1195.9,195.6 1197.2,194.3 1197.2,192.7C1197.2,191.1 1195.9,189.8 1194.3,189.8C1192.7,189.8 1191.4,191.1 1191.4,192.7C1191.4,194.3 1192.6,195.6 1194.3,195.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1404" d="M1203.7,195.6C1205.3,195.6 1206.6,194.3 1206.6,192.7C1206.6,191.1 1205.3,189.8 1203.7,189.8C1202.1,189.8 1200.8,191.1 1200.8,192.7C1200.8,194.3 1202.1,195.6 1203.7,195.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1406" d="M1213.1,195.6C1214.7,195.6 1216,194.3 1216,192.7C1216,191.1 1214.7,189.8 1213.1,189.8C1211.5,189.8 1210.2,191.1 1210.2,192.7C1210.2,194.3 1211.5,195.6 1213.1,195.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1408" d="M1222.5,195.6C1224.1,195.6 1225.4,194.3 1225.4,192.7C1225.4,191.1 1224.1,189.8 1222.5,189.8C1220.9,189.8 1219.6,191.1 1219.6,192.7C1219.7,194.3 1221,195.6 1222.5,195.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1410" d="M1232,195.6C1233.6,195.6 1234.9,194.3 1234.9,192.7C1234.9,191.1 1233.6,189.8 1232,189.8C1230.4,189.8 1229.1,191.1 1229.1,192.7C1229.1,194.3 1230.4,195.6 1232,195.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1412" d="M1241.4,196C1243.2,196 1244.7,194.5 1244.7,192.6C1244.7,190.8 1243.2,189.3 1241.4,189.3C1239.5,189.3 1238,190.8 1238,192.6C1238.1,194.5 1239.6,196 1241.4,196Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1414" d="M1250.8,196C1252.6,196 1254.1,194.5 1254.1,192.7C1254.1,190.9 1252.6,189.4 1250.8,189.4C1249,189.4 1247.5,190.9 1247.5,192.7C1247.6,194.5 1249,196 1250.8,196Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1416" d="M1260.3,196C1262.1,196 1263.6,194.5 1263.6,192.7C1263.6,190.9 1262.1,189.4 1260.3,189.4C1258.5,189.4 1257,190.9 1257,192.7C1257,194.5 1258.4,196 1260.3,196Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1418" d="M1269.7,196C1271.5,196 1273,194.5 1273,192.7C1273,190.9 1271.5,189.4 1269.7,189.4C1267.9,189.4 1266.4,190.9 1266.4,192.7C1266.4,194.5 1267.9,196 1269.7,196Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1420" d="M1279.2,196C1281,196 1282.5,194.5 1282.5,192.6C1282.5,190.8 1281,189.3 1279.2,189.3C1277.3,189.3 1275.8,190.8 1275.8,192.6C1275.8,194.5 1277.3,196 1279.2,196Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1422" d="M1288.6,196C1290.5,196 1292,194.5 1292,192.6C1292,190.8 1290.5,189.3 1288.6,189.3C1286.8,189.3 1285.3,190.8 1285.3,192.6C1285.2,194.5 1286.7,196 1288.6,196Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1424" d="M1298,196.1C1299.9,196.1 1301.4,194.6 1301.4,192.7C1301.4,190.8 1299.9,189.3 1298,189.3C1296.1,189.3 1294.6,190.8 1294.6,192.7C1294.6,194.6 1296.1,196.1 1298,196.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1426" d="M1307.5,196.1C1309.4,196.1 1310.9,194.6 1310.9,192.7C1310.9,190.8 1309.4,189.3 1307.5,189.3C1305.6,189.3 1304.1,190.8 1304.1,192.7C1304.1,194.6 1305.6,196.1 1307.5,196.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1490" d="M1118.8,205C1120.4,205 1121.7,203.7 1121.7,202.1C1121.7,200.5 1120.4,199.2 1118.8,199.2C1117.2,199.2 1115.9,200.5 1115.9,202.1C1115.9,203.7 1117.2,205 1118.8,205Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1492" d="M1128.2,205C1129.8,205 1131.1,203.7 1131.1,202.1C1131.1,200.5 1129.8,199.2 1128.2,199.2C1126.6,199.2 1125.3,200.5 1125.3,202.1C1125.3,203.7 1126.6,205 1128.2,205Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1494" d="M1137.6,205C1139.2,205 1140.5,203.7 1140.5,202.1C1140.5,200.5 1139.2,199.2 1137.6,199.2C1136,199.2 1134.7,200.5 1134.7,202.1C1134.8,203.7 1136.1,205 1137.6,205Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1496" d="M1166,205C1167.6,205 1168.9,203.7 1168.9,202.1C1168.9,200.5 1167.6,199.2 1166,199.2C1164.4,199.2 1163.1,200.5 1163.1,202.1C1163.1,203.7 1164.4,205 1166,205Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1498" d="M1175.4,205.1C1177,205.1 1178.4,203.8 1178.4,202.2C1178.4,200.6 1177.1,199.3 1175.4,199.3C1173.8,199.3 1172.5,200.6 1172.5,202.2C1172.5,203.7 1173.8,205.1 1175.4,205.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1500" d="M1184.8,205.1C1186.5,205.1 1187.8,203.7 1187.8,202.1C1187.8,200.4 1186.4,199.1 1184.8,199.1C1183.1,199.1 1181.8,200.4 1181.8,202.1C1181.8,203.8 1183.2,205.1 1184.8,205.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1502" d="M1194.3,205.4C1196.1,205.4 1197.5,203.9 1197.5,202.2C1197.5,200.4 1196,199 1194.3,199C1192.6,199 1191.1,200.4 1191.1,202.2C1191,203.9 1192.5,205.4 1194.3,205.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1504" d="M1203.7,205C1205.3,205 1206.6,203.7 1206.6,202.1C1206.6,200.5 1205.3,199.2 1203.7,199.2C1202.1,199.2 1200.8,200.5 1200.8,202.1C1200.8,203.7 1202.1,205 1203.7,205Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1506" d="M1213.1,205C1214.7,205 1216,203.7 1216,202.1C1216,200.5 1214.7,199.2 1213.1,199.2C1211.5,199.2 1210.2,200.5 1210.2,202.1C1210.2,203.7 1211.5,205 1213.1,205Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1508" d="M1222.5,206.1C1224.7,206.1 1226.4,204.3 1226.4,202.2C1226.4,200 1224.7,198.3 1222.5,198.3C1220.4,198.3 1218.6,200.1 1218.6,202.2C1218.6,204.3 1220.4,206.1 1222.5,206.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1510" d="M1232,205.4C1233.8,205.4 1235.2,203.9 1235.2,202.2C1235.2,200.4 1233.7,199 1232,199C1230.3,199 1228.8,200.4 1228.8,202.2C1228.8,203.9 1230.2,205.4 1232,205.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1512" d="M1241.4,205.4C1243.2,205.4 1244.6,203.9 1244.6,202.2C1244.6,200.4 1243.1,199 1241.4,199C1239.6,199 1238.2,200.4 1238.2,202.2C1238.2,203.9 1239.6,205.4 1241.4,205.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1514" d="M1250.8,205.4C1252.6,205.4 1254.1,203.9 1254.1,202.1C1254.1,200.3 1252.6,198.8 1250.8,198.8C1249,198.8 1247.5,200.3 1247.5,202.1C1247.6,203.9 1249,205.4 1250.8,205.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1516" d="M1260.3,205.4C1262.1,205.4 1263.6,203.9 1263.6,202.1C1263.6,200.3 1262.1,198.8 1260.3,198.8C1258.5,198.8 1257,200.3 1257,202.1C1257,204 1258.4,205.4 1260.3,205.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1518" d="M1269.7,205.4C1271.5,205.4 1273,203.9 1273,202.1C1273,200.3 1271.5,198.8 1269.7,198.8C1267.9,198.8 1266.4,200.3 1266.4,202.1C1266.4,204 1267.9,205.4 1269.7,205.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1520" d="M1279.2,205.5C1281,205.5 1282.5,204 1282.5,202.1C1282.5,200.3 1281,198.7 1279.2,198.7C1277.3,198.7 1275.8,200.2 1275.8,202.1C1275.8,204 1277.3,205.5 1279.2,205.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1522" d="M1288.6,205.5C1290.5,205.5 1292,204 1292,202.1C1292,200.3 1290.5,198.7 1288.6,198.7C1286.8,198.7 1285.3,200.2 1285.3,202.1C1285.2,204 1286.7,205.5 1288.6,205.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1524" d="M1298,205.5C1299.9,205.5 1301.4,204 1301.4,202.1C1301.4,200.2 1299.9,198.7 1298,198.7C1296.1,198.7 1294.6,200.2 1294.6,202.1C1294.6,204 1296.1,205.5 1298,205.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1526" d="M1307.5,205.5C1309.4,205.5 1310.9,204 1310.9,202.1C1310.9,200.2 1309.4,198.7 1307.5,198.7C1305.6,198.7 1304.1,200.2 1304.1,202.1C1304,204 1305.6,205.5 1307.5,205.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1590" d="M1081,214.5C1082.6,214.5 1083.9,213.2 1083.9,211.6C1083.9,210 1082.6,208.7 1081,208.7C1079.4,208.7 1078.1,210 1078.1,211.6C1078.2,213.1 1079.5,214.5 1081,214.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1592" d="M1090.5,215.6C1092.7,215.6 1094.5,213.8 1094.5,211.6C1094.5,209.4 1092.7,207.6 1090.5,207.6C1088.3,207.6 1086.5,209.4 1086.5,211.6C1086.5,213.8 1088.3,215.6 1090.5,215.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1594" d="M1156.5,214.5C1158.1,214.5 1159.4,213.2 1159.4,211.6C1159.4,210 1158.1,208.7 1156.5,208.7C1154.9,208.7 1153.6,210 1153.6,211.6C1153.6,213.1 1154.9,214.5 1156.5,214.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1596" d="M1166,214.5C1167.6,214.5 1168.9,213.2 1168.9,211.6C1168.9,210 1167.6,208.7 1166,208.7C1164.4,208.7 1163.1,210 1163.1,211.6C1163.1,213.1 1164.4,214.5 1166,214.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1598" d="M1184.8,214.5C1186.4,214.5 1187.7,213.2 1187.7,211.6C1187.7,210 1186.4,208.7 1184.8,208.7C1183.2,208.7 1181.9,210 1181.9,211.6C1181.9,213.1 1183.2,214.5 1184.8,214.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1600" d="M1194.3,214.5C1195.9,214.5 1197.2,213.2 1197.2,211.6C1197.2,210 1195.9,208.7 1194.3,208.7C1192.7,208.7 1191.4,210 1191.4,211.6C1191.4,213.1 1192.6,214.5 1194.3,214.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1602" d="M1203.7,214.6C1205.4,214.6 1206.8,213.2 1206.8,211.5C1206.8,209.8 1205.4,208.4 1203.7,208.4C1202,208.4 1200.6,209.8 1200.6,211.5C1200.6,213.3 1202,214.6 1203.7,214.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1604" d="M1213.1,214.8C1214.9,214.8 1216.3,213.4 1216.3,211.6C1216.3,209.8 1214.9,208.4 1213.1,208.4C1211.3,208.4 1209.9,209.8 1209.9,211.6C1209.9,213.3 1211.3,214.8 1213.1,214.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1606" d="M1222.5,214.8C1224.3,214.8 1225.7,213.4 1225.7,211.6C1225.7,209.8 1224.2,208.4 1222.5,208.4C1220.7,208.4 1219.3,209.8 1219.3,211.6C1219.3,213.3 1220.8,214.8 1222.5,214.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1608" d="M1232,214.5C1233.6,214.5 1234.9,213.2 1234.9,211.6C1234.9,210 1233.6,208.7 1232,208.7C1230.4,208.7 1229.1,210 1229.1,211.6C1229.1,213.1 1230.4,214.5 1232,214.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1610" d="M1241.4,214.8C1243.2,214.8 1244.6,213.4 1244.6,211.6C1244.6,209.8 1243.1,208.4 1241.4,208.4C1239.6,208.4 1238.2,209.8 1238.2,211.6C1238.2,213.3 1239.6,214.8 1241.4,214.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1612" d="M1250.8,214.8C1252.6,214.8 1254.1,213.3 1254.1,211.5C1254.1,209.7 1252.6,208.2 1250.8,208.2C1249,208.2 1247.5,209.7 1247.5,211.5C1247.6,213.4 1249,214.8 1250.8,214.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1614" d="M1260.3,214.9C1262.1,214.9 1263.6,213.4 1263.6,211.6C1263.6,209.8 1262.1,208.3 1260.3,208.3C1258.5,208.3 1257,209.8 1257,211.6C1257,213.4 1258.4,214.9 1260.3,214.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1616" d="M1269.7,214.9C1271.5,214.9 1273,213.4 1273,211.6C1273,209.8 1271.5,208.3 1269.7,208.3C1267.9,208.3 1266.4,209.8 1266.4,211.6C1266.4,213.4 1267.9,214.9 1269.7,214.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1618" d="M1279.2,214.9C1281,214.9 1282.5,213.4 1282.5,211.5C1282.5,209.7 1281,208.2 1279.2,208.2C1277.3,208.2 1275.8,209.7 1275.8,211.5C1275.8,213.4 1277.3,214.9 1279.2,214.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1620" d="M1288.6,214.9C1290.5,214.9 1292,213.4 1292,211.5C1292,209.7 1290.5,208.2 1288.6,208.2C1286.8,208.2 1285.3,209.7 1285.3,211.5C1285.2,213.4 1286.7,214.9 1288.6,214.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1622" d="M1298,215C1299.9,215 1301.4,213.5 1301.4,211.6C1301.4,209.7 1299.9,208.2 1298,208.2C1296.1,208.2 1294.6,209.7 1294.6,211.6C1294.6,213.4 1296.1,215 1298,215Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1624" d="M1307.5,215C1309.4,215 1310.9,213.5 1310.9,211.6C1310.9,209.7 1309.4,208.2 1307.5,208.2C1305.6,208.2 1304.1,209.7 1304.1,211.6C1304,213.4 1305.6,215 1307.5,215Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1688" d="M1194.3,223.9C1195.9,223.9 1197.2,222.6 1197.2,221C1197.2,219.4 1195.9,218.1 1194.3,218.1C1192.7,218.1 1191.4,219.4 1191.4,221C1191.4,222.6 1192.6,223.9 1194.3,223.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1690" d="M1213.1,223.9C1214.7,223.9 1216,222.6 1216,221C1216,219.4 1214.7,218.1 1213.1,218.1C1211.5,218.1 1210.2,219.4 1210.2,221C1210.2,222.6 1211.5,223.9 1213.1,223.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1692" d="M1222.5,223.9C1224.1,223.9 1225.4,222.6 1225.4,221C1225.4,219.4 1224.1,218.1 1222.5,218.1C1220.9,218.1 1219.6,219.4 1219.6,221C1219.6,222.6 1221,223.9 1222.5,223.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1694" d="M1232,223.9C1233.6,223.9 1234.9,222.6 1234.9,221C1234.9,219.4 1233.6,218.1 1232,218.1C1230.4,218.1 1229.1,219.4 1229.1,221C1229.1,222.6 1230.4,223.9 1232,223.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1696" d="M1241.4,223.9C1243,223.9 1244.3,222.6 1244.3,221C1244.3,219.4 1243,218.1 1241.4,218.1C1239.8,218.1 1238.5,219.4 1238.5,221C1238.5,222.6 1239.8,223.9 1241.4,223.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1698" d="M1250.8,224.8C1252.9,224.8 1254.6,223.1 1254.6,221C1254.6,218.9 1252.9,217.2 1250.8,217.2C1248.7,217.2 1247,218.9 1247,221C1247.1,223.1 1248.8,224.8 1250.8,224.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1700" d="M1260.3,223.9C1261.9,223.9 1263.2,222.6 1263.2,221C1263.2,219.4 1261.9,218.1 1260.3,218.1C1258.7,218.1 1257.4,219.4 1257.4,221C1257.4,222.6 1258.7,223.9 1260.3,223.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1702" d="M1269.7,223.9C1271.3,223.9 1272.6,222.6 1272.6,221C1272.6,219.4 1271.3,218.1 1269.7,218.1C1268.1,218.1 1266.8,219.4 1266.8,221C1266.8,222.6 1268.1,223.9 1269.7,223.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1704" d="M1279.2,223.9C1280.8,223.9 1282.1,222.6 1282.1,221C1282.1,219.4 1280.8,218.1 1279.2,218.1C1277.6,218.1 1276.3,219.4 1276.3,221C1276.3,222.6 1277.6,223.9 1279.2,223.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1706" d="M1288.6,224.4C1290.5,224.4 1292,222.9 1292,221C1292,219.2 1290.5,217.6 1288.6,217.6C1286.8,217.6 1285.3,219.1 1285.3,221C1285.2,222.8 1286.7,224.4 1288.6,224.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1708" d="M1298,224.4C1299.9,224.4 1301.4,222.9 1301.4,221C1301.4,219.1 1299.9,217.6 1298,217.6C1296.1,217.6 1294.6,219.1 1294.6,221C1294.6,222.9 1296.1,224.4 1298,224.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1710" d="M1307.5,224.4C1309.4,224.4 1310.9,222.9 1310.9,221C1310.9,219.1 1309.4,217.6 1307.5,217.6C1305.6,217.6 1304.1,219.1 1304.1,221C1304.1,222.9 1305.6,224.4 1307.5,224.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1774" d="M1175.4,233.3C1177,233.3 1178.3,232 1178.3,230.4C1178.3,228.8 1177,227.5 1175.4,227.5C1173.8,227.5 1172.5,228.8 1172.5,230.4C1172.5,232 1173.8,233.3 1175.4,233.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1776" d="M1184.8,233.9C1186.7,233.9 1188.3,232.3 1188.3,230.4C1188.3,228.5 1186.8,226.9 1184.8,226.9C1182.9,226.9 1181.3,228.5 1181.3,230.4C1181.4,232.3 1182.9,233.9 1184.8,233.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1778" d="M1194.3,233.3C1195.9,233.3 1197.2,232 1197.2,230.4C1197.2,228.8 1195.9,227.5 1194.3,227.5C1192.7,227.5 1191.4,228.8 1191.4,230.4C1191.4,232 1192.6,233.3 1194.3,233.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1780" d="M1203.7,233.3C1205.3,233.3 1206.6,232 1206.6,230.4C1206.6,228.8 1205.3,227.5 1203.7,227.5C1202.1,227.5 1200.8,228.8 1200.8,230.4C1200.8,232 1202.1,233.3 1203.7,233.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1782" d="M1213.1,233.3C1214.7,233.3 1216,232 1216,230.4C1216,228.8 1214.7,227.5 1213.1,227.5C1211.5,227.5 1210.2,228.8 1210.2,230.4C1210.2,232 1211.5,233.3 1213.1,233.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1784" d="M1222.5,233.3C1224.1,233.3 1225.4,232 1225.4,230.4C1225.4,228.8 1224.1,227.5 1222.5,227.5C1220.9,227.5 1219.6,228.8 1219.6,230.4C1219.7,232 1221,233.3 1222.5,233.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1786" d="M1232,233.5C1233.7,233.5 1235.1,232.1 1235.1,230.4C1235.1,228.7 1233.7,227.3 1232,227.3C1230.3,227.3 1228.9,228.7 1228.9,230.4C1228.9,232.1 1230.3,233.5 1232,233.5Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1788" d="M1241.4,233.3C1243,233.3 1244.3,232 1244.3,230.4C1244.3,228.8 1243,227.5 1241.4,227.5C1239.8,227.5 1238.5,228.8 1238.5,230.4C1238.6,232 1239.8,233.3 1241.4,233.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1790" d="M1250.8,233.3C1252.4,233.3 1253.7,232 1253.7,230.4C1253.7,228.8 1252.4,227.5 1250.8,227.5C1249.2,227.5 1247.9,228.8 1247.9,230.4C1248,232 1249.3,233.3 1250.8,233.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1792" d="M1260.3,233.3C1261.9,233.3 1263.2,232 1263.2,230.4C1263.2,228.8 1261.9,227.5 1260.3,227.5C1258.7,227.5 1257.4,228.8 1257.4,230.4C1257.4,232 1258.7,233.3 1260.3,233.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1794" d="M1269.7,233.3C1271.3,233.3 1272.6,232 1272.6,230.4C1272.6,228.8 1271.3,227.5 1269.7,227.5C1268.1,227.5 1266.8,228.8 1266.8,230.4C1266.8,232 1268.1,233.3 1269.7,233.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1796" d="M1279.2,234.4C1281.4,234.4 1283.2,232.6 1283.2,230.4C1283.2,228.2 1281.4,226.4 1279.2,226.4C1277,226.4 1275.2,228.2 1275.2,230.4C1275.1,232.6 1276.9,234.4 1279.2,234.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1798" d="M1288.6,233.8C1290.5,233.8 1292,232.3 1292,230.4C1292,228.6 1290.5,227.1 1288.6,227.1C1286.8,227.1 1285.3,228.6 1285.3,230.4C1285.2,232.3 1286.7,233.8 1288.6,233.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1800" d="M1298,233.8C1299.9,233.8 1301.4,232.3 1301.4,230.4C1301.4,228.5 1299.9,227 1298,227C1296.1,227 1294.6,228.5 1294.6,230.4C1294.6,232.3 1296.1,233.8 1298,233.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1802" d="M1307.5,233.8C1309.4,233.8 1310.9,232.3 1310.9,230.4C1310.9,228.5 1309.4,227 1307.5,227C1305.6,227 1304.1,228.5 1304.1,230.4C1304,232.3 1305.6,233.8 1307.5,233.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1866" d="M1175.4,242.7C1177,242.7 1178.3,241.4 1178.3,239.8C1178.3,238.2 1177,236.9 1175.4,236.9C1173.8,236.9 1172.5,238.2 1172.5,239.8C1172.5,241.5 1173.8,242.7 1175.4,242.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1868" d="M1184.8,242.8C1186.4,242.8 1187.8,241.5 1187.8,239.9C1187.8,238.3 1186.5,237 1184.8,237C1183.2,237 1181.9,238.3 1181.9,239.9C1181.9,241.5 1183.2,242.8 1184.8,242.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1870" d="M1194.3,242.7C1195.9,242.7 1197.2,241.4 1197.2,239.8C1197.2,238.2 1195.9,236.9 1194.3,236.9C1192.7,236.9 1191.4,238.2 1191.4,239.8C1191.4,241.5 1192.6,242.7 1194.3,242.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1872" d="M1203.7,242.7C1205.3,242.7 1206.6,241.4 1206.6,239.8C1206.6,238.2 1205.3,236.9 1203.7,236.9C1202.1,236.9 1200.8,238.2 1200.8,239.8C1200.8,241.5 1202.1,242.7 1203.7,242.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1874" d="M1213.1,242.7C1214.7,242.7 1216,241.4 1216,239.8C1216,238.2 1214.7,236.9 1213.1,236.9C1211.5,236.9 1210.2,238.2 1210.2,239.8C1210.2,241.5 1211.5,242.7 1213.1,242.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1876" d="M1222.5,242.7C1224.1,242.7 1225.4,241.4 1225.4,239.8C1225.4,238.2 1224.1,236.9 1222.5,236.9C1220.9,236.9 1219.6,238.2 1219.6,239.8C1219.7,241.5 1221,242.7 1222.5,242.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1878" d="M1232,242.7C1233.6,242.7 1234.9,241.4 1234.9,239.8C1234.9,238.2 1233.6,236.9 1232,236.9C1230.4,236.9 1229.1,238.2 1229.1,239.8C1229.1,241.5 1230.4,242.7 1232,242.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1880" d="M1241.4,243.1C1243.2,243.1 1244.6,241.7 1244.6,239.9C1244.6,238.1 1243.1,236.7 1241.4,236.7C1239.6,236.7 1238.2,238.1 1238.2,239.9C1238.2,241.7 1239.6,243.1 1241.4,243.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1882" d="M1250.8,243.1C1252.6,243.1 1254.1,241.6 1254.1,239.8C1254.1,238 1252.6,236.5 1250.8,236.5C1249,236.5 1247.5,238 1247.5,239.8C1247.5,241.6 1249,243.1 1250.8,243.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1884" d="M1260.3,242.7C1261.9,242.7 1263.2,241.4 1263.2,239.8C1263.2,238.2 1261.9,236.9 1260.3,236.9C1258.7,236.9 1257.4,238.2 1257.4,239.8C1257.4,241.5 1258.7,242.7 1260.3,242.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1886" d="M1269.7,242.8C1271.3,242.8 1272.7,241.5 1272.7,239.8C1272.7,238.2 1271.4,236.8 1269.7,236.8C1268,236.8 1266.7,238.1 1266.7,239.8C1266.8,241.5 1268.1,242.8 1269.7,242.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1888" d="M1279.2,243.6C1281.2,243.6 1282.9,241.9 1282.9,239.9C1282.9,237.8 1281.2,236.2 1279.2,236.2C1277.1,236.2 1275.5,237.9 1275.5,239.9C1275.4,241.9 1277.1,243.6 1279.2,243.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1890" d="M1288.6,243.2C1290.5,243.2 1292,241.7 1292,239.8C1292,238 1290.5,236.4 1288.6,236.4C1286.8,236.4 1285.3,237.9 1285.3,239.8C1285.2,241.7 1286.7,243.2 1288.6,243.2Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1892" d="M1298,243.3C1299.9,243.3 1301.4,241.8 1301.4,239.9C1301.4,238 1299.9,236.5 1298,236.5C1296.1,236.5 1294.6,238 1294.6,239.9C1294.6,241.8 1296.1,243.3 1298,243.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1894" d="M1307.5,243.3C1309.4,243.3 1310.9,241.8 1310.9,239.9C1310.9,238 1309.4,236.5 1307.5,236.5C1305.6,236.5 1304.1,238 1304.1,239.9C1304.1,241.8 1305.6,243.3 1307.5,243.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1972" d="M1203.7,253.3C1205.9,253.3 1207.7,251.5 1207.7,249.3C1207.7,247.1 1205.9,245.3 1203.7,245.3C1201.5,245.3 1199.7,247.1 1199.7,249.3C1199.7,251.5 1201.5,253.3 1203.7,253.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1976" d="M1222.5,253.3C1224.7,253.3 1226.5,251.5 1226.5,249.3C1226.5,247.1 1224.7,245.3 1222.5,245.3C1220.3,245.3 1218.5,247.1 1218.5,249.3C1218.6,251.5 1220.4,253.3 1222.5,253.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1982" d="M1250.8,252.6C1252.6,252.6 1254.1,251.1 1254.1,249.3C1254.1,247.5 1252.6,246 1250.8,246C1249,246 1247.5,247.5 1247.5,249.3C1247.6,251.1 1249,252.6 1250.8,252.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1984" d="M1260.3,252.6C1262.1,252.6 1263.6,251.1 1263.6,249.3C1263.6,247.5 1262.1,246 1260.3,246C1258.5,246 1257,247.5 1257,249.3C1257,251.1 1258.4,252.6 1260.3,252.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1988" d="M1279.2,253C1281.2,253 1282.9,251.3 1282.9,249.3C1282.9,247.3 1281.2,245.6 1279.2,245.6C1277.1,245.6 1275.5,247.3 1275.5,249.3C1275.4,251.3 1277.1,253 1279.2,253Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1990" d="M1288.6,252.6C1290.5,252.6 1292,251.1 1292,249.2C1292,247.4 1290.5,245.9 1288.6,245.9C1286.8,245.9 1285.3,247.4 1285.3,249.2C1285.2,251.1 1286.7,252.6 1288.6,252.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1992" d="M1298,252.7C1299.9,252.7 1301.4,251.2 1301.4,249.3C1301.4,247.4 1299.9,245.9 1298,245.9C1296.1,245.9 1294.6,247.4 1294.6,249.3C1294.6,251.2 1296.1,252.7 1298,252.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path1994" d="M1307.5,252.7C1309.4,252.7 1310.9,251.2 1310.9,249.3C1310.9,247.4 1309.4,245.9 1307.5,245.9C1305.6,245.9 1304.1,247.4 1304.1,249.3C1304,251.2 1305.6,252.7 1307.5,252.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g id="g26674" transform="matrix(5.83508,0,0,5.79928,-2923.37,-686.141)">
|
||||
<path id="path15294" d="M586,223.7C584.4,223.7 583.1,225 583.1,226.6C583.1,228.2 584.4,229.5 586,229.5C587.6,229.5 588.9,228.2 588.9,226.6C588.8,225 587.6,223.7 586,223.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15296" d="M576.5,223.1C574.6,223.1 573,224.7 573,226.6C573,228.5 574.6,230.1 576.5,230.1C578.4,230.1 580,228.5 580,226.6C580,224.7 578.5,223.1 576.5,223.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15506" d="M586,204.9C584.4,204.9 583.1,206.2 583.1,207.8C583.1,209.4 584.4,210.7 586,210.7C587.6,210.7 588.9,209.4 588.9,207.8C588.8,206.2 587.6,204.9 586,204.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15508" d="M576.5,204.9C574.9,204.9 573.6,206.2 573.6,207.8C573.6,209.4 574.9,210.7 576.5,210.7C578.1,210.7 579.4,209.4 579.4,207.8C579.4,206.2 578.1,204.9 576.5,204.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15510" d="M519.9,204.9C518.3,204.9 517,206.2 517,207.8C517,209.4 518.3,210.7 519.9,210.7C521.5,210.7 522.8,209.4 522.8,207.8C522.8,206.2 521.5,204.9 519.9,204.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15512" d="M510.5,204.9C508.9,204.9 507.6,206.2 507.6,207.8C507.6,209.4 508.9,210.7 510.5,210.7C512.1,210.7 513.4,209.4 513.4,207.8C513.4,206.2 512.1,204.9 510.5,204.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15634" d="M567.1,195.4C565.5,195.4 564.2,196.7 564.2,198.3C564.2,199.9 565.5,201.2 567.1,201.2C568.7,201.2 570,199.9 570,198.3C570,196.7 568.7,195.4 567.1,195.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15636" d="M538.8,195.4C537.2,195.4 535.9,196.7 535.9,198.3C535.9,199.9 537.2,201.2 538.8,201.2C540.4,201.2 541.7,199.9 541.7,198.3C541.7,196.7 540.4,195.4 538.8,195.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15638" d="M519.9,195.4C518.3,195.4 517,196.7 517,198.3C517,199.9 518.3,201.2 519.9,201.2C521.5,201.2 522.8,199.9 522.8,198.3C522.8,196.7 521.5,195.4 519.9,195.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<circle id="circle15640" cx="510.5" cy="198.3" r="3" style="fill:rgb(76,167,217);"/>
|
||||
<path id="path15766" d="M595.4,186C593.8,186 592.5,187.3 592.5,188.9C592.5,190.5 593.8,191.8 595.4,191.8C597,191.8 598.3,190.5 598.3,188.9C598.3,187.3 597,186 595.4,186Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15768" d="M567.1,186C565.5,186 564.2,187.3 564.2,188.9C564.2,190.5 565.5,191.8 567.1,191.8C568.7,191.8 570,190.5 570,188.9C570,187.3 568.7,186 567.1,186Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15770" d="M548.2,186C546.6,186 545.3,187.3 545.3,188.9C545.3,190.5 546.6,191.8 548.2,191.8C549.8,191.8 551.1,190.5 551.1,188.9C551.1,187.3 549.8,186 548.2,186Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15772" d="M538.8,186C537.2,186 535.9,187.3 535.9,188.9C535.9,190.5 537.2,191.8 538.8,191.8C540.4,191.8 541.7,190.5 541.7,188.9C541.7,187.3 540.4,186 538.8,186Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15774" d="M529.4,185C527.3,185 525.5,186.7 525.5,188.9C525.5,191 527.2,192.8 529.4,192.8C531.5,192.8 533.3,191.1 533.3,188.9C533.2,186.7 531.5,185 529.4,185Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15776" d="M519.9,185.7C518.1,185.7 516.7,187.1 516.7,188.9C516.7,190.7 518.1,192.1 519.9,192.1C521.7,192.1 523.1,190.7 523.1,188.9C523.1,187.1 521.7,185.7 519.9,185.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15778" d="M510.5,184.9C508.3,184.9 506.5,186.7 506.5,188.9C506.5,191.1 508.3,192.9 510.5,192.9C512.7,192.9 514.5,191.1 514.5,188.9C514.5,186.7 512.7,184.9 510.5,184.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15780" d="M501.1,186C499.5,186 498.2,187.3 498.2,188.9C498.2,190.5 499.5,191.8 501.1,191.8C502.7,191.8 504,190.5 504,188.9C503.9,187.3 502.6,186 501.1,186Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15904" d="M595.4,176.6C593.8,176.6 592.5,177.9 592.5,179.5C592.5,181.1 593.8,182.4 595.4,182.4C597,182.4 598.3,181.1 598.3,179.5C598.3,177.9 597,176.6 595.4,176.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15906" d="M576.5,176.6C574.9,176.6 573.6,177.9 573.6,179.5C573.6,181.1 574.9,182.4 576.5,182.4C578.1,182.4 579.4,181.1 579.4,179.5C579.4,177.9 578.1,176.6 576.5,176.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15908" d="M567.1,176.6C565.5,176.6 564.2,177.9 564.2,179.5C564.2,181.1 565.5,182.4 567.1,182.4C568.7,182.4 570,181.1 570,179.5C570,177.9 568.7,176.6 567.1,176.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15910" d="M557.7,176.4C556,176.4 554.6,177.8 554.6,179.5C554.6,181.2 556,182.6 557.7,182.6C559.4,182.6 560.8,181.2 560.8,179.5C560.7,177.7 559.4,176.4 557.7,176.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15912" d="M548.2,175.4C546,175.4 544.2,177.2 544.2,179.4C544.2,181.6 546,183.4 548.2,183.4C550.4,183.4 552.2,181.6 552.2,179.4C552.2,177.2 550.4,175.4 548.2,175.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15914" d="M538.8,176.6C537.2,176.6 535.9,177.9 535.9,179.5C535.9,181.1 537.2,182.4 538.8,182.4C540.4,182.4 541.7,181.1 541.7,179.5C541.7,177.9 540.4,176.6 538.8,176.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15916" d="M529.4,176.6C527.8,176.6 526.5,177.9 526.5,179.5C526.5,181.1 527.8,182.4 529.4,182.4C531,182.4 532.3,181.1 532.3,179.5C532.2,177.9 531,176.6 529.4,176.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15918" d="M519.9,176.4C518.2,176.4 516.9,177.7 516.9,179.4C516.9,181.1 518.3,182.4 519.9,182.4C521.6,182.4 522.9,181.1 522.9,179.4C522.9,177.8 521.6,176.4 519.9,176.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15920" d="M510.5,176.2C508.7,176.2 507.3,177.6 507.3,179.4C507.3,181.2 508.7,182.6 510.5,182.6C512.3,182.6 513.7,181.2 513.7,179.4C513.7,177.7 512.3,176.2 510.5,176.2Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path15922" d="M501.1,176.6C499.5,176.6 498.2,177.9 498.2,179.5C498.2,181.1 499.5,182.4 501.1,182.4C502.7,182.4 504,181.1 504,179.5C503.9,177.9 502.6,176.6 501.1,176.6Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16048" d="M652,167.1C650.4,167.1 649.1,168.4 649.1,170C649.1,171.6 650.4,172.9 652,172.9C653.6,172.9 654.9,171.6 654.9,170C654.9,168.4 653.6,167.1 652,167.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16050" d="M623.7,167.1C622.1,167.1 620.8,168.4 620.8,170C620.8,171.6 622.1,172.9 623.7,172.9C625.3,172.9 626.6,171.6 626.6,170C626.6,168.4 625.3,167.1 623.7,167.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16052" d="M614.3,167.1C612.7,167.1 611.4,168.4 611.4,170C611.4,171.6 612.7,172.9 614.3,172.9C615.9,172.9 617.2,171.6 617.2,170C617.2,168.4 615.8,167.1 614.3,167.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16054" d="M604.8,167.1C603.2,167.1 601.9,168.4 601.9,170C601.9,171.6 603.2,172.9 604.8,172.9C606.4,172.9 607.7,171.6 607.7,170C607.7,168.4 606.4,167.1 604.8,167.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16056" d="M595.4,167.1C593.8,167.1 592.5,168.4 592.5,170C592.5,171.6 593.8,172.9 595.4,172.9C597,172.9 598.3,171.6 598.3,170C598.3,168.4 597,167.1 595.4,167.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16058" d="M586,166.3C584,166.3 582.3,168 582.3,170C582.3,172.1 584,173.7 586,173.7C588,173.7 589.7,172 589.7,170C589.6,168 588,166.3 586,166.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16060" d="M576.5,167.1C574.9,167.1 573.6,168.4 573.6,170C573.6,171.6 574.9,172.9 576.5,172.9C578.1,172.9 579.4,171.6 579.4,170C579.4,168.4 578.1,167.1 576.5,167.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16062" d="M567.1,167C565.4,167 564,168.4 564,170.1C564,171.8 565.4,173.2 567.1,173.2C568.8,173.2 570.2,171.8 570.2,170.1C570.2,168.3 568.8,167 567.1,167Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16064" d="M557.7,166.7C555.9,166.7 554.4,168.2 554.4,170C554.4,171.9 555.9,173.4 557.7,173.4C559.6,173.4 561.1,171.9 561.1,170C561,168.2 559.5,166.7 557.7,166.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16066" d="M548.2,166.2C546.1,166.2 544.4,167.9 544.4,170C544.4,172.1 546.1,173.8 548.2,173.8C550.3,173.8 552,172.1 552,170C552,167.9 550.3,166.2 548.2,166.2Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16068" d="M538.8,166C536.6,166 534.8,167.8 534.8,170C534.8,172.2 536.6,174 538.8,174C541,174 542.8,172.2 542.8,170C542.8,167.8 541,166 538.8,166Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16070" d="M529.4,167.1C527.8,167.1 526.5,168.4 526.5,170C526.5,171.6 527.8,172.9 529.4,172.9C531,172.9 532.3,171.6 532.3,170C532.2,168.4 531,167.1 529.4,167.1Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16072" d="M519.9,167C518.2,167 516.9,168.4 516.9,170C516.9,171.7 518.3,173 519.9,173C521.6,173 522.9,171.7 522.9,170C522.9,168.4 521.6,167 519.9,167Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16074" d="M510.5,166.8C508.7,166.8 507.3,168.2 507.3,170C507.3,171.8 508.7,173.2 510.5,173.2C512.3,173.2 513.7,171.8 513.7,170C513.7,168.3 512.3,166.8 510.5,166.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16076" d="M501.1,166.7C499.3,166.7 497.8,168.2 497.8,170C497.8,171.9 499.3,173.4 501.1,173.4C503,173.4 504.5,171.9 504.5,170C504.4,168.2 502.9,166.7 501.1,166.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16158" d="M614.3,157.7C612.7,157.7 611.4,159 611.4,160.6C611.4,162.2 612.7,163.5 614.3,163.5C615.9,163.5 617.2,162.2 617.2,160.6C617.2,159 615.8,157.7 614.3,157.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16160" d="M604.8,157.7C603.2,157.7 601.9,159 601.9,160.6C601.9,162.2 603.2,163.5 604.8,163.5C606.4,163.5 607.7,162.2 607.7,160.6C607.8,159 606.4,157.7 604.8,157.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16162" d="M595.4,157.7C593.8,157.7 592.5,159 592.5,160.6C592.5,162.2 593.8,163.5 595.4,163.5C597,163.5 598.3,162.2 598.3,160.6C598.3,159 597,157.7 595.4,157.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16244" d="M548.2,148.3C546.6,148.3 545.3,149.6 545.3,151.2C545.3,152.8 546.6,154.1 548.2,154.1C549.8,154.1 551.1,152.8 551.1,151.2C551.1,149.6 549.8,148.3 548.2,148.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16246" d="M538.8,148.3C537.2,148.3 535.9,149.6 535.9,151.2C535.9,152.8 537.2,154.1 538.8,154.1C540.4,154.1 541.7,152.8 541.7,151.2C541.7,149.6 540.4,148.3 538.8,148.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16248" d="M529.4,147.7C527.5,147.7 525.9,149.3 525.9,151.2C525.9,153.1 527.4,154.7 529.4,154.7C531.3,154.7 532.9,153.1 532.9,151.2C532.8,149.2 531.3,147.7 529.4,147.7Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16370" d="M576.5,138.8C574.9,138.8 573.6,140.1 573.6,141.7C573.6,143.3 574.9,144.6 576.5,144.6C578.1,144.6 579.4,143.3 579.4,141.7C579.4,140.1 578.1,138.8 576.5,138.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16372" d="M557.7,138.8C556.1,138.8 554.8,140.1 554.8,141.7C554.8,143.3 556.1,144.6 557.7,144.6C559.3,144.6 560.6,143.3 560.6,141.7C560.5,140.1 559.2,138.8 557.7,138.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16374" d="M548.2,138.8C546.6,138.8 545.3,140.1 545.3,141.7C545.3,143.3 546.6,144.6 548.2,144.6C549.8,144.6 551.1,143.3 551.1,141.7C551.1,140.1 549.8,138.8 548.2,138.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16376" d="M538.8,138.8C537.2,138.8 535.8,140.1 535.8,141.8C535.8,143.4 537.1,144.8 538.8,144.8C540.4,144.8 541.8,143.5 541.8,141.8C541.8,140.1 540.4,138.8 538.8,138.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16378" d="M529.4,138.3C527.5,138.3 525.9,139.8 525.9,141.8C525.9,143.7 527.4,145.3 529.4,145.3C531.3,145.3 532.9,143.7 532.9,141.8C532.8,139.8 531.3,138.3 529.4,138.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16380" d="M519.9,138.8C518.3,138.8 517,140.1 517,141.7C517,143.3 518.3,144.6 519.9,144.6C521.5,144.6 522.8,143.3 522.8,141.7C522.8,140.1 521.5,138.8 519.9,138.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<circle id="circle16382" cx="510.5" cy="141.7" r="3.1" style="fill:rgb(76,167,217);"/>
|
||||
<path id="path16384" d="M501.1,138.8C499.5,138.8 498.2,140.1 498.2,141.7C498.2,143.3 499.5,144.6 501.1,144.6C502.7,144.6 504,143.3 504,141.7C503.9,140.1 502.6,138.8 501.1,138.8Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16514" d="M689.7,129.4C688.1,129.4 686.8,130.7 686.8,132.3C686.8,133.9 688.1,135.2 689.7,135.2C691.3,135.2 692.6,133.9 692.6,132.3C692.6,130.7 691.3,129.4 689.7,129.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16516" d="M680.3,129C678.5,129 677,130.5 677,132.3C677,134.1 678.5,135.6 680.3,135.6C682.1,135.6 683.6,134.1 683.6,132.3C683.6,130.5 682.1,129 680.3,129Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16518" d="M652,129.4C650.4,129.4 649.1,130.7 649.1,132.3C649.1,133.9 650.4,135.2 652,135.2C653.6,135.2 654.9,133.9 654.9,132.3C654.9,130.7 653.6,129.4 652,129.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16520" d="M642.6,128.9C640.8,128.9 639.2,130.4 639.2,132.2C639.2,134.1 640.7,135.6 642.6,135.6C644.4,135.6 646,134.1 646,132.2C645.9,130.4 644.4,128.9 642.6,128.9Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16522" d="M623.7,129.4C622.1,129.4 620.8,130.7 620.8,132.3C620.8,133.9 622.1,135.2 623.7,135.2C625.3,135.2 626.6,133.9 626.6,132.3C626.6,130.7 625.3,129.4 623.7,129.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16524" d="M614.3,129.4C612.7,129.4 611.4,130.7 611.4,132.3C611.4,133.9 612.7,135.2 614.3,135.2C615.9,135.2 617.2,133.9 617.2,132.3C617.2,130.7 615.8,129.4 614.3,129.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16526" d="M604.8,129.4C603.2,129.4 601.9,130.7 601.9,132.3C601.9,133.9 603.2,135.2 604.8,135.2C606.4,135.2 607.7,133.9 607.7,132.3C607.7,130.7 606.4,129.4 604.8,129.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16528" d="M595.4,129.4C593.8,129.4 592.5,130.7 592.5,132.3C592.5,133.9 593.8,135.2 595.4,135.2C597,135.2 598.3,133.9 598.3,132.3C598.3,130.7 597,129.4 595.4,129.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16530" d="M586,129.4C584.4,129.4 583.1,130.7 583.1,132.3C583.1,133.9 584.4,135.2 586,135.2C587.6,135.2 588.9,133.9 588.9,132.3C588.9,130.7 587.6,129.4 586,129.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16532" d="M576.5,129.4C574.9,129.4 573.6,130.7 573.6,132.3C573.6,133.9 574.9,135.2 576.5,135.2C578.1,135.2 579.4,133.9 579.4,132.3C579.4,130.7 578.1,129.4 576.5,129.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16534" d="M567.1,129.3C565.5,129.3 564.2,130.6 564.2,132.2C564.2,133.8 565.5,135.1 567.1,135.1C568.7,135.1 570,133.8 570,132.2C570,130.7 568.7,129.3 567.1,129.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16536" d="M557.7,129.4C556.1,129.4 554.8,130.7 554.8,132.3C554.8,133.9 556.1,135.2 557.7,135.2C559.3,135.2 560.6,133.9 560.6,132.3C560.6,130.7 559.2,129.4 557.7,129.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16538" d="M548.2,129.4C546.6,129.4 545.3,130.7 545.3,132.3C545.3,133.9 546.6,135.2 548.2,135.2C549.8,135.2 551.1,133.9 551.1,132.3C551.1,130.7 549.8,129.4 548.2,129.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16540" d="M538.8,129.3C537.2,129.3 535.8,130.6 535.8,132.3C535.8,134 537.1,135.3 538.8,135.3C540.4,135.3 541.8,134 541.8,132.3C541.8,130.6 540.4,129.3 538.8,129.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16542" d="M529.4,129.3C527.8,129.3 526.4,130.6 526.4,132.3C526.4,134 527.7,135.3 529.4,135.3C531,135.3 532.4,134 532.4,132.3C532.4,130.6 531,129.3 529.4,129.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16544" d="M519.9,129.3C518.3,129.3 516.9,130.6 516.9,132.3C516.9,134 518.2,135.3 519.9,135.3C521.5,135.3 522.9,134 522.9,132.3C522.9,130.6 521.6,129.3 519.9,129.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16546" d="M510.5,129.3C508.9,129.3 507.5,130.6 507.5,132.3C507.5,134 508.8,135.3 510.5,135.3C512.1,135.3 513.5,134 513.5,132.3C513.5,130.6 512.1,129.3 510.5,129.3Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16548" d="M501.1,129.4C499.5,129.4 498.2,130.7 498.2,132.3C498.2,133.9 499.5,135.2 501.1,135.2C502.7,135.2 504,133.9 504,132.3C504,130.7 502.6,129.4 501.1,129.4Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16670" d="M708.6,120C707,120 705.7,121.3 705.7,122.9C705.7,124.5 707,125.8 708.6,125.8C710.2,125.8 711.5,124.5 711.5,122.9C711.5,121.3 710.2,120 708.6,120Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16672" d="M699.2,120C697.6,120 696.3,121.3 696.3,122.9C696.3,124.5 697.6,125.8 699.2,125.8C700.8,125.8 702.1,124.5 702.1,122.9C702,121.3 700.8,120 699.2,120Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16674" d="M595.4,120C593.8,120 592.5,121.3 592.5,122.9C592.5,124.5 593.8,125.8 595.4,125.8C597,125.8 598.3,124.5 598.3,122.9C598.3,121.3 597,120 595.4,120Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
<path id="path16676" d="M586,120C584.4,120 583.1,121.3 583.1,122.9C583.1,124.5 584.4,125.8 586,125.8C587.6,125.8 588.9,124.5 588.9,122.9C588.8,121.3 587.6,120 586,120Z" style="fill:rgb(76,167,217);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4142" version="1.1"
|
||||
viewBox="0 0 1344 1344" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="a" x1="18.23" x2="150" y1="150" y2="-7.6294e-6" gradientTransform="matrix(8.96 0 0 8.96 -7.8457e-5 .00019795)" gradientUnits="userSpaceOnUse"><stop stop-color="#0082c9" offset="0"/><stop
|
||||
stop-color="#1cafff" offset="1" /></linearGradient></defs>
|
||||
<rect y=".00012207" width="1344" height="1344" fill="url(#a)" fill-rule="evenodd" /></svg>
|
||||
|
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 565 B |
5
drawable_resources/icon-foreground_qa.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 1344 1344" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path id="path2" d="M671.959,347C493.701,347.003 347.017,493.69 347.017,671.951L347.017,671.96C347.017,850.222 493.701,996.909 671.959,996.912C731.433,996.791 789.748,980.344 840.521,949.372C880.458,965.242 969.914,1012.33 991.205,991.988C1013.45,970.741 965.085,870.743 953.49,833.584C981.82,784.426 996.787,728.695 996.898,671.958C996.895,493.7 850.215,347.016 671.96,347.011L671.959,347ZM671.997,470.543C782.529,470.543 873.483,561.5 873.483,672.035C873.476,782.565 782.524,873.512 671.996,873.512C561.469,873.512 470.517,782.565 470.509,672.035C470.509,561.5 561.464,470.543 671.996,470.543L671.997,470.543ZM670.273,655.827C670.273,674.121 666.595,689.323 659.238,701.433C651.882,713.542 641.497,721.745 628.086,726.042L662.07,761.394L637.949,761.394L610.117,729.167L604.746,729.362C583.717,729.362 567.49,722.933 556.064,710.075C544.639,697.217 538.926,679.069 538.926,655.632C538.926,632.39 544.655,614.372 556.113,601.579C567.572,588.786 583.848,582.39 604.941,582.39C625.449,582.39 641.465,588.9 652.988,601.921C664.512,614.942 670.273,632.91 670.273,655.827ZM787.852,727.409L770.078,681.999L712.852,681.999L695.273,727.409L678.477,727.409L734.922,584.05L748.887,584.05L805.039,727.409L787.852,727.409ZM556.504,655.827C556.504,675.163 560.622,689.828 568.857,699.821C577.093,709.815 589.056,714.811 604.746,714.811C620.566,714.811 632.513,709.831 640.586,699.87C648.659,689.909 652.695,675.228 652.695,655.827C652.695,636.621 648.675,622.054 640.635,612.126C632.594,602.198 620.697,597.233 604.941,597.233C589.121,597.233 577.093,602.23 568.857,612.224C560.622,622.217 556.504,636.752 556.504,655.827ZM764.902,667.058L748.301,622.819C746.152,617.22 743.939,610.352 741.66,602.214C740.228,608.464 738.177,615.332 735.508,622.819L718.711,667.058L764.902,667.058Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
BIN
scripts/QA_keystore.jks
Normal file
35
scripts/uploadArtifact.sh
Executable file
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
#1: LOG_USERNAME
|
||||
#2: LOG_PASSWORD
|
||||
#3: DRONE_BUILD_NUMBER
|
||||
#4: DRONE_PULL_REQUEST
|
||||
#5: GITHUB_TOKEN
|
||||
|
||||
DAV_URL=https://nextcloud.kaminsky.me/remote.php/webdav/android-artifacts/
|
||||
PUBLIC_URL=https://www.kaminsky.me/nc-dev/android-artifacts
|
||||
USER=$1
|
||||
PASS=$2
|
||||
BUILD=$3
|
||||
PR=$4
|
||||
GITHUB_TOKEN=$5
|
||||
|
||||
if ! test -e app/build/outputs/apk/qa/debug/app-qa-*.apk ; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Uploaded artifact to $DAV_URL/$BUILD-talk.apk"
|
||||
|
||||
# delete all old comments, starting with "APK file:"
|
||||
oldComments=$(curl 2>/dev/null --header "authorization: Bearer $GITHUB_TOKEN" -X GET https://api.github.com/repos/nextcloud/talk-android/issues/$PR/comments | jq '.[] | (.id |tostring) + "|" + (.user.login | test("github-actions") | tostring) + "|" + (.body | test("APK file:.*") | tostring)' | grep "true|true" | tr -d "\"" | cut -f1 -d"|")
|
||||
|
||||
echo $oldComments | while read comment ; do
|
||||
curl 2>/dev/null --header "authorization: Bearer $GITHUB_TOKEN" -X DELETE https://api.github.com/repos/nextcloud/talk-android/issues/comments/$comment
|
||||
done
|
||||
|
||||
apt-get -y install qrencode
|
||||
|
||||
qrencode -o $PR.png "$PUBLIC_URL/$BUILD-talk.apk"
|
||||
|
||||
curl -u $USER:$PASS -X PUT $DAV_URL/$BUILD-talk.apk --upload-file app/build/outputs/apk/qa/debug/app-qa-*.apk
|
||||
curl -u $USER:$PASS -X PUT $DAV_URL/$BUILD-talk.png --upload-file $PR.png
|
||||
curl --header "authorization: Bearer $GITHUB_TOKEN" -X POST https://api.github.com/repos/nextcloud/talk-android/issues/$PR/comments -d "{ \"body\" : \"APK file: $PUBLIC_URL/$BUILD-talk.apk <br/><br/> ![qrcode]($PUBLIC_URL/$BUILD-talk.png) <br/><br/>To test this change/fix you can simply download above APK file and install and test it in parallel to your existing Nextcloud Talk app. \" }"
|