Compare commits

..

No commits in common. "develop" and "2.3.8" have entirely different histories.

1237 changed files with 46104 additions and 74498 deletions

11
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,11 @@
### Reporting Bugs and getting help
The issue tracker on Github is for bug reports only. It is not a general support forum.
**A bug is everything you can reproduce. ie *if I do this that happens but something else should happen instead*.**
Please search the issue tracker (including closed issues). Your bug might be a server bug that has already been addressed.
Please fill in the template when creating new issues and provide information about your device and your server. The [adb logcat](https://wiki.cyanogenmod.org/w/Doc:_debugging_with_logcat) can be omitted if you can reproduce the bug under every condition. (ie *click button X and the application crashes*) But the adb logcat is required if the bug happens only under certain conditions or involves network problem (Server not found, messages not arriving)
**If you are seeking help or are unable to reproduce the exact problem please join our MUC at conversations@conference.siacs.eu**

35
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,35 @@
#### General information
* **Version:** eg 1.21.0
* **Device:** eg Google Nexus 5
* **Android Version:** eg Android 6.0 Stock or Android 5.1 Cyanogenmod
* **Server name:** eg conversations.im, jabber.at or self hosted
* **Server software:** ejabberd 16.04 or prosody 0.10 (if known)
* **Installed server modules:** eg Stream Managment, CSI, MAM
* **Conversations source:** eg PlayStore, PlayStore Beta Channel, F-Droid, self build (latest HEAD)
#### Steps to reproduce
1. …
2. …
#### Expected result
What is the expected output?
#### Actual result
What do you see instead?
#### Debug output
Please post the output of adb logcat. The log should begin with the start of Conversations and include all the
steps it takes to reproduce the problem.
````
adb logcat -s conversations
````

View File

@ -1,38 +0,0 @@
name: Android CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
- name: Download WebRTC
run: mkdir libs && wget -O libs/libwebrtc-m92.aar https://gultsch.de/files/libwebrtc-m92.aar
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build Quicksy (Compat)
run: ./gradlew assembleQuicksyFreeCompatDebug
- name: Build Quicksy (System)
run: ./gradlew assembleQuicksyFreeSystemDebug
- name: Build Conversations (Compat)
run: ./gradlew assembleConversationsFreeCompatDebug
- name: Build Conversations (System)
run: ./gradlew assembleConversationsFreeSystemDebug
- uses: actions/upload-artifact@v2
with:
name: Conversations all-flavors (debug)
path: ./build/outputs/apk/**/debug/Conversations-*.apk

3
.gitignore vendored
View File

@ -9,13 +9,12 @@ src/quicksyPlaystore/res/values/push.xml
# https://github.com/github/gitignore/blob/master/Gradle.gitignore
.gradle/
build/
gradle.properties
captures/
signing.properties
# Ignore Gradle GUI config
gradle-app.setting
libs/*.aar
# https://github.com/github/gitignore/blob/master/Android.gitignore
# Built application files
*.apk

View File

@ -1,54 +0,0 @@
image: registry.gitlab.com/fdroid/ci-images-client:latest
cache:
paths:
- .gradle/wrapper
- .gradle/caches
stages:
- build
before_script:
- export GRADLE_USER_HOME=$PWD/.gradle
- export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' build.gradle`
- echo y | sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" > /dev/null
build:
stage: build
except:
- develop
script:
- ./gradlew assembleConversationsFreeCompatDebug
artifacts:
paths:
- build/outputs/
publish:
stage: build
only:
- develop
script:
- find -type d -name "siacs" -print0 | xargs --null -I{} bash -c 'x="{}"; mv "$x" "${x/siacs/sum7}" '
- find -type f -name "*.java" -exec sed -i "/eu.siacs.conversations.axolotl/! s/eu.siacs./eu.sum7./" "{}" \;
- find -type f -name "*.xml" -exec sed -i "/eu.siacs.conversations.axolotl/! s/eu.siacs./eu.sum7./" "{}" \;
# workaround for bug in fdroid nightly
- sed -i "s/-debug.apk/-unsigned.apk/" /usr/lib/python3/dist-packages/fdroidserver/nightly.py
- sed -i "s/servergitmirror = 'git@' +/servergitmirror = 'gitlab@' +/" /usr/lib/python3/dist-packages/fdroidserver/nightly.py
- sed -i "s/git@/gitlab@/" /usr/lib/python3/dist-packages/fdroidserver/index.py
# generate new version
- export versionCode="$CI_JOB_ID"
- export versionName="$(git describe --tag --abbrev=0)-${CI_JOB_ID}_${CI_COMMIT_REF_NAME}"
- echo "set VersionCode '${versionCode}' and VersonName '${versionName}'"
- sed -i "s/^\(\s*versionCode\s*\).*$/\1$versionCode/" build.gradle
- sed -i "0,/versionName/s/^\(\s*versionName\).*/\1 \"$versionName\"/" build.gradle
- cat -n build.gradle
# build free version
- ./gradlew assembleConversationsFreeCompatRelease
- ls build/outputs/apk/conversationsFreeCompat/*
# publish on nightly fdroid repo
- fdroid nightly -v
after_script:
# this file changes every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/

16
.travis.yml Normal file
View File

@ -0,0 +1,16 @@
language: android
jdk:
- oraclejdk8
android:
components:
- platform-tools
- tools
- build-tools-27.0.3
- extra-google-google_play_services
licenses:
- '.+'
script:
- ./gradlew assembleConversationsFreeSystemRelease
before_install:
- yes | sdkmanager "platforms;android-27"

View File

@ -2,20 +2,7 @@
host = https://www.transifex.com
lang_map = af_ZA: af-rZA, am_ET: am-rET, ar_AE: ar-rAE, ar_BH: ar-rBH, ar_DZ: ar-rDZ, ar_EG: ar-rEG, ar_IQ: ar-rIQ, ar_JO: ar-rJO, ar_KW: ar-rKW, ar_LB: ar-rLB, ar_LY: ar-rLY, ar_MA: ar-rMA, ar_OM: ar-rOM, ar_QA: ar-rQA, ar_SA: ar-rSA, ar_SY: ar-rSY, ar_TN: ar-rTN, ar_YE: ar-rYE, arn_CL: arn-rCL, as_IN: as-rIN, az_AZ: az-rAZ, ba_RU: ba-rRU, be_BY: be-rBY, bg_BG: bg-rBG, bn_BD: bn-rBD, bn_IN: bn-rIN, bo_CN: bo-rCN, br_FR: br-rFR, bs_BA: bs-rBA, ca_ES: ca-rES, co_FR: co-rFR, cs_CZ: cs-rCZ, cy_GB: cy-rGB, da_DK: da-rDK, de_AT: de-rAT, de_CH: de-rCH, de_DE: de-rDE, de_LI: de-rLI, de_LU: de-rLU, dsb_DE: dsb-rDE, dv_MV: dv-rMV, el_GR: el-rGR, en_AU: en-rAU, en_BZ: en-rBZ, en_CA: en-rCA, en_GB: en-rGB, en_IE: en-rIE, en_IN: en-rIN, en_JM: en-rJM, en_MY: en-rMY, en_NZ: en-rNZ, en_PH: en-rPH, en_SG: en-rSG, en_TT: en-rTT, en_US: en-rUS, en_ZA: en-rZA, en_ZW: en-rZW, es_AR: es-rAR, es_BO: es-rBO, es_CL: es-rCL, es_CO: es-rCO, es_CR: es-rCR, es_DO: es-rDO, es_EC: es-rEC, es_ES: es-rES, es_GT: es-rGT, es_HN: es-rHN, es_MX: es-rMX, es_NI: es-rNI, es_PA: es-rPA, es_PE: es-rPE, es_PR: es-rPR, es_PY: es-rPY, es_SV: es-rSV, es_US: es-rUS, es_UY: es-rUY, es_VE: es-rVE, et_EE: et-rEE, eu_ES: eu-rES, fa_IR: fa-rIR, fi_FI: fi-rFI, fil_PH: fil-rPH, fo_FO: fo-rFO, fr_BE: fr-rBE, fr_CA: fr-rCA, fr_CH: fr-rCH, fr_FR: fr-rFR, fr_LU: fr-rLU, fr_MC: fr-rMC, fy_NL: fy-rNL, ga_IE: ga-rIE, gd_GB: gd-rGB, gl_ES: gl-rES, gsw_FR: gsw-rFR, gu_IN: gu-rIN, ha_NG: ha-rNG, hi_IN: hi-rIN, hr_BA: hr-rBA, hr_HR: hr-rHR, hsb_DE: hsb-rDE, hu_HU: hu-rHU, hy_AM: hy-rAM, id_ID: id-rID, ig_NG: ig-rNG, ii_CN: ii-rCN, is_IS: is-rIS, it_CH: it-rCH, it_IT: it-rIT, iu_CA: iu-rCA, ja_JP: ja-rJP, ka_GE: ka-rGE, kk_KZ: kk-rKZ, kl_GL: kl-rGL, km_KH: km-rKH, kn_IN: kn-rIN, ko_KR: ko-rKR, kok_IN: kok-rIN, ky_KG: ky-rKG, lb_LU: lb-rLU, lo_LA: lo-rLA, lt_LT: lt-rLT, lv_LV: lv-rLV, mi_NZ: mi-rNZ, mk_MK: mk-rMK, ml_IN: ml-rIN, mn_CN: mn-rCN, mn_MN: mn-rMN, moh_CA: moh-rCA, mr_IN: mr-rIN, ms_BN: ms-rBN, ms_MY: ms-rMY, mt_MT: mt-rMT, nb_NO: nb-rNO, ne_NP: ne-rNP, nl_BE: nl-rBE, nl_NL: nl-rNL, nn_NO: nn-rNO, nso_ZA: nso-rZA, oc_FR: oc-rFR, or_IN: or-rIN, pa_IN: pa-rIN, pl_PL: pl-rPL, prs_AF: prs-rAF, ps_AF: ps-rAF, pt_BR: pt-rBR, pt_PT: pt-rPT, qut_GT: qut-rGT, quz_BO: quz-rBO, quz_EC: quz-rEC, quz_PE: quz-rPE, rm_CH: rm-rCH, ro_RO: ro-rRO, ru_RU: ru-rRU, rw_RW: rw-rRW, sa_IN: sa-rIN, sah_RU: sah-rRU, se_FI: se-rFI, se_NO: se-rNO, se_SE: se-rSE, si_LK: si-rLK, sk_SK: sk-rSK, sl_SI: sl-rSI, sma_NO: sma-rNO, sma_SE: sma-rSE, smj_NO: smj-rNO, smj_SE: smj-rSE, smn_FI: smn-rFI, sms_FI: sms-rFI, sq_AL: sq-rAL, sr_BA: sr-rBA, sr_CS: sr-rCS, sr_ME: sr-rME, sr_RS: sr-rRS, sv_FI: sv-rFI, sv_SE: sv-rSE, sw_KE: sw-rKE, syr_SY: syr-rSY, ta_IN: ta-rIN, te_IN: te-rIN, tg_TJ: tg-rTJ, th_TH: th-rTH, tk_TM: tk-rTM, tn_ZA: tn-rZA, tr_TR: tr-rTR, tt_RU: tt-rRU, tzm_DZ: tzm-rDZ, ug_CN: ug-rCN, uk_UA: uk-rUA, ur_PK: ur-rPK, uz_UZ: uz-rUZ, vi_VN: vi-rVN, wo_SN: wo-rSN, xh_ZA: xh-rZA, yo_NG: yo-rNG, zh_CN: zh-rCN, zh_HK: zh-rHK, zh_MO: zh-rMO, zh_SG: zh-rSG, zh_TW: zh-rTW, zu_ZA: zu-rZA, no_NO: no-rNO, he_IL: iw-rIL, he: iw
[conversations.main-strings]
[conversations.strings]
file_filter = src/main/res/values-<lang>/strings.xml
source_file = src/main/res/values/strings.xml
source_lang = en
[conversations.quicksy-strings]
file_filter = src/quicksy/res/values-<lang>/strings.xml
source_file = src/quicksy/res/values/strings.xml
source_lang = en
type = ANDROID
[conversations.conversations-strings]
file_filter = src/conversations/res/values-<lang>/strings.xml
source_file = src/conversations/res/values/strings.xml
source_lang = en
type = ANDROID

View File

@ -1,273 +1,5 @@
# Changelog
### Version 2.10.2
* Fix crash when rendering some quotes
* Fix crash in welcome screen
### Version 2.10.1
* Fix issue with some videos not being compressed
* Fix rare crash when opening notification
### Version 2.10.0
* Show black bars when remote video does not match aspect ratio of screen
* Improve search performance
* Add setting to prevent screenshots
### Version 2.9.13
* minor A/V improvements
### Version 2.9.12
* Always verify domain name. No user overwrite
* Support roster pre authentication
### Version 2.9.11
* Fixed 'No Connectivity' issues on Android 7.1
### Version 2.9.10
* fix HTTP up/download for users that dont trust system CAs
### Version 2.9.9
* Various bug fixes around Tor support
* Improve call compatibility with Dino
### Version 2.9.8
* Verify A/V calls with preexisting OMEMO sessions
* Improve compatibility with non libwebrtc WebRTC implementations
### Version 2.9.7
* Ability to select incoming call ringtone
* Fix OpenPGP key id discovery for OpenKeychain 5.6+
* Properly verify punycode TLS certificates
* Improve stability of RTP session establishment (calling)
### Version 2.9.6
* Show call button for offline contacts if they previously announced support
* Back button no longer ends call when call is connected
* bug fixes
### Version 2.9.5
* Quicksy: Automatically receive verification SMS
### Version 2.9.4
* minor stability improvements for A/V calls
* Conversations releases from here on forward require Android 5
### Version 2.9.3
* Fixed connectivity issues when different accounts used different SCRAM mechanisms
* Add support for SCRAM-SHA-512
* Allow P2P (Jingle) file transfer with self contact
### Version 2.9.2
* Offer Easy Invite generation on supporting servers
* Display GIFs send from Movim
* store avatars in cache
### Version 2.9.1
* fixed search on Android <= 5
* optimize memory consumption
### Version 2.9.0
* Search individual conversations
* Notify user if message delivery fails
* Remember display names (nicks) from Quicksy users across restarts
* Add button to start Orbot (Tor) from notification if necessary
### Version 2.8.10
* Handle GPX files
* Improve performance for backup restore
* bug fixes
### Version 2.8.9
* add 'Return to chat' to audio call screen
* Improve keyboard shortcuts
* bug fixes
### Version 2.8.8
* Fixed notifications not showing up under certain conditions
* Fixed compatibility issues and crashes related to A/V calls
### Version 2.8.7
* Show help button if A/V call fails
* Fixed some annoying crashes
* Fixed Jingle connections (file transfer + calls) with bare JIDs
### Version 2.8.6
* Offer to record voice message when callee is busy
### Version 2.8.5
* Reduce echo during calls on some devices
* Fix login when passwords contains special characters
* Play dial and busy tones on speaker during video calls
### Version 2.8.4
* Rework Login with certificate UI
* Add ability to pin chats on top (add to favorites)
### Version 2.8.3
* Move call icon to the left in order to keep other toolbar icons in a consistent place
* Show call duration during audio calls
* Tie breaking for A/V calls (the same two people calling each other at the same time)
### Version 2.8.2
* Add button to switch camera during video call
* Fixed voice calls on tablets
### Version 2.8.1
* Audible feedback (dialing, call started, call ended) for voice calls.
* Fixed issue with retrying failed video call
### Version 2.8.0
* Audio/Video calls (Requires server support in form of STUN and TURN servers discoverable via XEP-0215)
### Version 2.7.1
* Fix avatar selection on some Android 10 devices
* Fix file transfer for larger files
### Version 2.7.0
* Provide PDF preview on Android 5+
* Use 12 byte IVs for OMEMO
### Version 2.6.4
* Support automatic theme switching on Android 10
### Version 2.6.3
* Support for ?register and ?register;preauth XMPP uri parameters
### Version 2.6.2
* let users set their own nick name
* resume download of OMEMO encrypted files
* Channels now use '#' as symbol in avatar
* Quicksy uses 'always' as OMEMO encryption default (hides lock icon)
### Version 2.6.1
* fixes for Jingle IBB file transfer
* fixes for repeated corrections filling up the database
* switched to Last Message Correction v1.1
### Version 2.6.0
* Introduce expert setting to perform channel discovery on local server instead of [search.jabber.network](https://search.jabber.network)
* Enable delivery check marks by default and remove setting
* Enable Send button indicates status by default and remove setting
* Move Backup and Foreground Service settings to main screen
### Version 2.5.12
* Jingle file transfer fixes
* Fixed OMEMO self healing (after backup restore) on servers w/o MAM
### Version 2.5.11
* Fixed crash on Android <5.0
### Version 2.5.10
* Fixed crash on Xiaomi devices running Android 8.0 + 8.1
### Version 2.5.9
* fixed minor security issues
* Share XMPP uri from channel search by long pressing a result
### Version 2.5.8
* fixed connection issues over Tor
* P2P file transfer (Jingle) now offers direct candidates
* Support XEP-0396: Jingle Encrypted Transports - OMEMO
### Version 2.5.7
* fixed crash when scanning QR codes on Android 6 and lower
* when sharing a message from and to Conversations insert it as quote
### Version 2.5.6
* fixes for Jingle file transfer
* fixed some rare crashes
### Version 2.5.5
* allow backups to be restored from anywhere
* bug fixes
### Version 2.5.4
* stability improvements for group chats and channels
### Version 2.5.3
* bug fixes for peer to peer file transfer (Jingle)
* fixed server info for unlimited/unknown max file size
### Version 2.5.2
* bug fixes
### Version 2.5.1
* minor bug fixes
* Set own OMEMO devices to inactive after not seeing them for 14 days. (was 7 days)
### Version 2.5.0
* Added channel search via search.jabbercat.org
* Reworked onboarding screens
* Warn when trying to enter domain address or channel address in Add Contact dialog
### Version 2.4.3
* Fixed display of private messages sent from another client
* Fixed backup creation on long time installations
### Version 2.4.2
* Fix image preview on older Android version
### Version 2.4.1
* Fixed crash in message view
### Version 2.4.0
* New Backup / Restore feature
* Clearly distinguish between (private) group chats and (public) channels
* Redesigned participants view for group chats and channels
* Redesigned create new contact/group chat/channel flow in Start Conversation screen
### Version 2.3.12
* Fixed rare crash on start up
* Fixed avatar not being refreshed in group chats
### Version 2.3.11
* Support for Android 9 'message style' notifications
* OMEMO stability improvements
* Added ability to destroy group chats
* Do not show deleted files in media browser
* Added 'Keep Original' as video quality choice
### Version 2.3.10
* lower minimum required Android version to 4.1
* Synchronize group chat join/leaves across multiple clients
* Fixed sending PGP encrypted messages from quick reply
### Version 2.3.9
* OMEMO stability improvements
* Context menu when long pressing avatar in 1:1 chat
### Version 2.3.8
* make PEP avatars public to play nice with Prosody 0.11
* Fixed re-sending failed files in group chats
@ -299,7 +31,7 @@
### Version 2.3.1
* Stronger compression for video files
* Use SNI on STARTTLS to fix gtalk
* Fix Quiet Hours on Android 8+
* Fix Quite Hours on Android 8+
* Use Consistent Color Generation (XEP-0392)
### Version 2.3.0
@ -499,9 +231,10 @@
* Icons for attach menu
### Version 1.16.2
* change mam catchup strategy. support mam:1
* change mam catchup strategie. support mam:1
* bug fixes
### Version 1.16.1
* UI performance fixes
* bug fixes
@ -551,7 +284,7 @@
* bug fixes
### Version 1.14.6
* make error notification dismissible
* make error notification dismissable
* bug fixes
@ -575,7 +308,7 @@
* bug fixes
### Version 1.14.0
* Improvements for N
* Improvments for N
* Quick Reply to Notifications on N
* Don't download avatars and files when data saver is on
* bug fixes
@ -748,12 +481,12 @@
### Version 1.7.2
* decode PGP messages in background
### Version 1.7.1
####Versrion 1.7.1
* performance improvements when opening a conversation
### Version 1.7.0
* CAPTCHA support
* SASL EXTERNAL (client certificates)
* SASL EXTERNAL (client certifiates)
* fetching MUC history via MAM
* redownload deleted files from HTTP hosts
* Expert setting to automatically set presence
@ -861,7 +594,7 @@
* accept more ciphers
### Version 1.0
* MUC controls (Affiliation changes)
* MUC controls (Affiliaton changes)
* Added download button to notification
* Added check box to hide offline contacts
* Use Material theme and icons on Android L
@ -967,7 +700,7 @@
* XEP-0333. Mark whether the other party has read your messages
* Delayed messages are now tagged properly
* Share images from the Gallery
* Infinite history scrolling
* Infinit history scrolling
* Mark the last used presence in presence selection dialog
### Version 0.3

214
README.md
View File

@ -1,36 +1,33 @@
<h1 align="center">Conv6ations for Sum7 is a fork of <a href="https://f-droid.org/packages/eu.siacs.conversations/">Conversations</a></h1>
<h1 align="center">Conversations</h1>
<p align="center">A Jabber/XMPP chat client which is fair to IPv6</p>
<p align="center">Conversations: the very last word in instant messaging</p>
<p align="center">
<a href="https://conversations.im/j/support@conference.chat.sum7.eu">
<img src="https://inverse.chat/badge.svg?room=support@conference.chat.sum7.eu"
alt="chat on our conference room">
</a>
<a href="https://dev.sum7.eu/sum7/Conversations/pipelines">
<img src="https://dev.sum7.eu/sum7/Conversations/badges/develop/pipeline.svg"
alt="build status">
</a>
</a>
<p align="center">
<img src="metadata/en-US/phoneScreenshots/1.jpg" width="19%" alt="screenshot 1"/>
<img src="metadata/en-US/phoneScreenshots/2.jpg" width="19%" alt="screenshot 2"/>
<img src="metadata/en-US/phoneScreenshots/3.jpg" width="19%" alt="screenshot 3"/>
<img src="metadata/en-US/phoneScreenshots/4.jpg" width="19%" alt="screenshot 4"/>
<img src="metadata/en-US/phoneScreenshots/5.jpg" width="19%" alt="screenshot 5"/>
<a href="https://conversations.im/j/conversations@conference.siacs.eu">
<img src="https://inverse.chat/badge.svg?room=conversations@conference.siacs.eu"
alt="chat on our conference room">
</a>
<a href="https://travis-ci.org/siacs/Conversations">
<img src="https://travis-ci.org/siacs/Conversations.svg?branch=master"
alt="build status">
</a>
<a href="https://bountysource.com/teams/siacs">
<img src="https://api.bountysource.com/badge/tracker?tracker_id=519483" alt="Bountysource">
</a>
</p>
[<img src="https://f-droid.org/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/en/packages/eu.sum7.conversations)
### Nightly:
[![https://dev.sum7.eu/sum7/Conversations-nightly/raw/master/fdroid/repo](https://dev.sum7.eu/sum7/Conversations-nightly/raw/master/icon.png)](https://dev.sum7.eu/sum7/Conversations-nightly/raw/master/fdroid/repo)
## Changes to origin:
* replace the hardcoded IPv4 preference to easy Happy Eyeball, for faster connection and fair to both IP version.
* rebrands it as chat.sum7.eu (to run both version together)
<p align="center">
<a href="https://play.google.com/store/apps/details?id=eu.siacs.conversations&amp;referrer=utm_source%3Dgithub">
<img src="https://conversations.im/images/en-play-badge.png"
alt="Google Play">
</a>
<a href="http://www.amazon.com/dp/B00WD35AAC/">
<img src="https://images-na.ssl-images-amazon.com/images/G/01/AmazonMobileApps/amazon-apps-store-us-black.png"
alt="Amazon App Store">
</a>
</p>
![screenshots](https://raw.githubusercontent.com/siacs/Conversations/master/screenshots.png)
## Design principles
@ -43,13 +40,12 @@
* End-to-end encryption with [OMEMO](http://conversations.im/omemo/) or [OpenPGP](http://openpgp.org/about/)
* Send and receive images as well as other kind of files
* [Encrypted audio and video calls (DTLS-SRTP)](https://help.conversations.im)
* Share your location
* Send voice messages
* Share your location via an external [plug-in](https://play.google.com/store/apps/details?id=eu.siacs.conversations.sharelocation&referrer=utm_source%3Dgithub)
* Send voice messages via an external [plug-in](https://play.google.com/store/apps/details?id=eu.siacs.conversations.voicerecorder&referrer=utm_source%3Dgithub)
* Indication when your contact has read your message
* Intuitive UI that follows Android Design guidelines
* Pictures / Avatars for your Contacts
* Synchronizes with desktop client
* Syncs with desktop client
* Conferences (with support for bookmarks)
* Address book integration
* Multiple accounts / unified inbox
@ -96,17 +92,45 @@ Conversations is entirely open source and licensed under GPLv3. So if you are a
software developer you can check out the sources from GitHub and use Gradle to
build your apk file.
The more convenient way — which not only gives you automatic updates but also
supports the further development of Conversations — is to buy the App in the
Google [Play Store](https://play.google.com/store/apps/details?id=eu.siacs.conversations&referrer=utm_source%3Dgithub).
Buying the App from the Play Store will also give you access to our [beta test](#beta).
#### I don't have a Google Account but I would still like to make a contribution
I accept donations over PayPal, bank transfer and various crypto currencies. For donations via PayPal you
can use the email address `donate@siacs.eu` or the button below.
[![Donate with PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CW3SYT3KG5PDL)
**Disclaimer:** I'm not a huge fan of PayPal and their business policies. For
larger contributions please get in touch with me beforehand and we can talk
about bank transfer (SEPA).
##### Crypto currencies
Bitcoin: `1AeqNAcg85APAZj9BZfAjdFCC5zesqXp2B`
Bitcoin Cash: `16ABkXzYAwWz8Y5DcWFfbBRqL63g3hzEaU`
Ether: `0x5c4e5239cd9c6f4a909e4e8361526e2e3c8ba9fa`
#### How do I create an account?
XMPP, like email, is a federated protocol, which means that there is not one company you can create an *official XMPP account* with. Instead there are hundreds, or even thousands, of providers out there. One of those providers is our very own [chat.sum7.eu](https://chat.sum7.eu). If you dont like to use *chat.sum7.eu* use a web search engine of your choice to find another provider. Or maybe your university has one. Or you can run your own. Or ask a friend to run one. Once you've found one, you can use Conversations to create an account. Just select *register new account* on server within the create account dialog.
XMPP, like email, is a federated protocol, which means that there is not one company you can create an *official XMPP account* with. Instead there are hundreds, or even thousands, of providers out there. One of those providers is our very own [conversations.im](https://account.conversations.im). If you dont like to use *conversations.im* use a web search engine of your choice to find another provider. Or maybe your university has one. Or you can run your own. Or ask a friend to run one. Once you've found one, you can use Conversations to create an account. Just select *register new account* on server within the create account dialog.
##### Domain hosting
Using your own domain not only gives you a more recognizable Jabber ID, it also gives you the flexibility to migrate your account between different XMPP providers. This is a good compromise between the responsibilities of having to operate your own server and the downsides of being dependent on a single provider.
Learn more about [conversations.im Jabber/XMPP domain hosting](https://account.conversations.im/domain/).
##### Running your own
If you already have a server somewhere and are willing and able to put the necessary work in you can run your own XMPP server.
If you already have a server somewhere and are willing and able to put the necessary work in, one alternative-in the spirit of federation-is to run your own. We recommend either [Prosody](https://prosody.im/) or [ejabberd](https://www.ejabberd.im/). Both of which have their own strengths. Ejabberd is slightly more mature nowadays but Prosody is arguably easier to set up.
As of 2019 we recommend you use [ejabberd](https://ejabberd.im). The default configuration file already enables everything you need to pass the [Conversations Compliance Suite](https://compliance.conversations.im). Make sure your Linux distribution ships a fairly recent version.
For Prosody you need a couple of so called [community modules](https://modules.prosody.im/) most of which are maintained by the same people that develop Prosody.
With a little bit of effort [Prosody](https://prosody.im) can be configured to support all necessary extensions as well. However you will have to rely on so called [Community Modules](https://modules.prosody.im/) of varying quality. Prosody can be interesting to people who like to modify their server and create / prototype own modules.
Performance wise - for small deployments - both ejabberd and Prosody should be fine.
If you pick ejabberd make sure you use the latest version. Linux Distributions might bundle some very old versions of it.
#### Where can I set up a custom hostname / port
Conversations will automatically look up the SRV records for your domain name
@ -133,56 +157,42 @@ SCRAM-SHA1, PLAIN, EXTERNAL (client certs) and DIGEST-MD5.
Some Bind failures are transient and resolve themselves after a reconnect.
When trying to connect to OpenFire the bind failure can be a permanent problem when the domain part of the Jabber ID entered in Conversations doesnt match the domain the OpenFire server feels responsible for. For example OpenFire is configured to use the domain `a.tld` but the Jabber ID entered is `user@b.tld` where `b.tld` also points to the same host. During bind OpenFire tries to reassign the Jabber to `user@a.tld`. Conversations doesnt like that.
This can be fixed by creating a new account in Conversations that uses the Jabber ID `user@a.tld`.
This can be fixed by creating a new account in Conversations that uses the Jabber ID `user@a.tld`.
Note: This is kind of a weird quirk in OpenFire. Most other servers would just throw a 'Server not responsible for domain' error instead of attempting to reassign the Jabber ID.
Maybe you attempted to use the Jabber ID `test@b.tld` because `a.tld` doesnt point to the correct host. In that case you might have to enable the extended connection settings in the expert settings of Conversations and set a host name.
#### I get 'Stream opening error'. What does that mean?
In most cases this error is caused by ejabberd advertising support for TLSv1.3 but not properly supporting it. This can happen if the OpenSSL version on the server already supports TLSv1.3 but the fast\_tls wrapper library used by ejabberd not (properly) support it. Upgrading fast\_tls and ejabberd or - theoretically - downgrading OpenSSL should fix the issue. A work around is to explicitly disable TLSv1.3 support in the ejabberd configuration. More information can be found on [this issue on the ejabberd issue tracker](https://github.com/processone/ejabberd/issues/2614).
#### Im getting this annoying permanent notification
Starting with Conversations 2.3.6 Conversations releases distributed over the Google Play Store will display a permanent notification if you are running it on Android 8 and above. This is a rule that it is essentially enforced by the Google Play Store. (You wont have the problem of a *forced* foreground notification if you are getting your app from F-Droid.)
Starting with Conversations 2.3.6 Conversations releases distributed over the Google Play Store will display a permanent notification if you are running it on Android 8 and above. This is a rule that it is essentially enforced by the Google Play Store (you wont have the problem if you are getting your app from F-Droid).
However you can disable the notification via settings of the operating system. (Not settings in Conversations.)
However you can disable the notification via settings of the operating system. (Not settings in Conversations.). For that you can long press the permanent notification and disable that particular type of notification by moving the slider to the left. This will make the notification disappear but create another notification (this time created by the operating system itself.) that will complain about Conversations (and other apps) using battery. Starting with Android 8.1 you can disable that notification again with the same method described above.
**The battery consumption and the entire behavior of Conversations will remain the same (as good or as bad as it was before). Why is Google doing this to you? We have no idea.**
The battery consumption and the entire behaviour of Conversations will remain the same (as good or as bad as it was before). Why is Google doing this to you? We have no idea.
##### Android &lt;= 7.1 or Conversations from F-Droid (all Android versions)
The foreground notification is still controlled over the expert settings within Conversations as it always has been. Whether or not you need to enable it depends on how aggressive the non-standard 'power saving' features are that your phone vendor has built into the operating system.
##### Android 8.x
Long press the permanent notification and disable that particular type of notification by moving the slider to the left. This will make the notification disappear but create another notification (this time created by the operating system itself.) that will complain about Conversations (and other apps) using battery. Starting with Android 8.1 you can disable that notification again with the same method described above.
##### Android 9.0+
Long press the permanent notification and press the info `(i)` button to get into the App info screen. In that screen touch the 'Notification' entry. In the next screen remove the checkbox for the 'Foreground service' entry.
P.S.: For Android version up to and including 7.1 the foreground notification is still controlled over the expert settings within Conversations as it always has been.
#### How do XEP-0357: Push Notifications work?
You need to be running the Play Store version of Conversations and your server needs to support push notifications.¹ Because *Googles Firebase Cloud Messaging (FCM)* are tied with an API key to a specific app your server can not initiate the push message directly. Instead your server will send the push notification to the [Conversations App server](https://github.com/iNPUTmice/p2) (operated by us) which then acts as a proxy and initiates the push message for you. The push message sent from our App server through FCM doesnt contain any personal information. It is just an empty message which will wake up your device and tell Conversations to reconnect to your server. The information sent from your server to our App server depends on the configuration of your server but can be limited to your account name. (In any case the Conversations App server won't redirect any information through FCM even if your server sends this information.)
You need to be running the Play Store version of Conversations and your server needs to support push notifications.¹ Because *Google Cloud Notifications (GCM)* are tied with an API key to a specific app your server can not initiate the push message directly. Instead your server will send the push notification to the Conversations App server (operated by us) which then acts as a proxy and initiates the push message for you. The push message sent from our App server through GCM doesnt contain any personal information. It is just an empty message which will wake up your device and tell Conversations to reconnect to your server. The information sent from your server to our App server depends on the configuration of your server but can be limited to your account name. (In any case the Conversations App server won't redirect any information through GCM even if your server sends this information.)
In summary Google will never get hold of any personal information besides that *something* happened. (Which doesnt even have to be a message but can be some automated event as well.) We - as the operator of the App server - will just get hold of your account name (without being able to tie this to your specific device).
If you dont want this simply pick a server which does not offer Push Notifications or build Conversations yourself without support for push notifications. (This is available via a gradle build flavor.) Non-play store source of Conversations like the Amazon App store will also offer a version without push notifications. Conversations will just work as before and maintain its own TCP connection in the background.
You can find a detailed description of how your server, the app server and FCM are interacting with each other in the [README](https://github.com/iNPUTmice/p2/blob/master/README.md) of the Conversations App Server.
¹ If you use the Play Store version you do **not** need to run your own app server. Your server only needs to support the server side of [XEP-0357: Push Notifications](http://xmpp.org/extensions/xep-0357.html) and [XEP-0198: Stream Management](https://xmpp.org/extensions/xep-0198.html). The prosody server modules are called *mod_cloud_notify* and *mod_smacks*. The ejabberd server modules are called *mod_push* and *mod_stream_mgmt*.
#### But why do I need a permanent notification if I use Google Push?
FCM (Google Push) allows an app to wake up from *Doze* which is (as the name suggests) a hibernation feature of the Android operating system that cuts the network connection and also reduces the number of times the app is allowed to wake up (to ping the server for example). The app can ask to be excluded from doze. Non push variants of the app (from F-Droid or if the server doesnt support it) will do this on first start up. So if you get exemption from *Doze*, or if you get regular push events sent to you, Doze should not pose a threat to Conversatons working properly. But even with *Doze* the app is still open in the background (kept in memory); it is just limited in the actions it can do. Conversations needs to stay in memory to hold certain session state (online status of contacts, join status of group chats, …). However with Android 8 Google changed all of this again and now an App that wants to stay in memory needs to have a foreground service which is visible to the user via the annoying notification. But why does Conversations need to hold that state? XMPP is a statefull protocol that has a lot of per-session information; packets need to be counted, presence information needs to be held, some features like Message Carbons get activated once per session, MAM catch-up happens once, service discovery happens only once; the list goes on. When Conversations was created in early 2014 none of this was a problem because apps were just allowed to stay in memory. Basically every XMPP client out there holds that information in memory because it would be a lot more complicated trying to persist it to disk. An entire rewrite of Conversations in the year 2019 would attempt to do that and would probably succeed however it would require exactly that; a complete rewrite which is not feasible right now. Thats by the way also the reason why it is difficult to write an XMPP client on iOS. Or more broadly put this is also the reason why other protocols are designed as or migrated to stateless protocols (often based on HTTP); take for example the migration of IMAP to [JMAP](https://jmap.io/).
¹ Your server only needs to support the server side of [XEP-0357: Push Notifications](http://xmpp.org/extensions/xep-0357.html). If you use the Play Store version you do **not** need to run your own app server. The server modules are called *mod_cloud_notify* on Prosody and *mod_push* on ejabberd.
#### Conversations doesnt work for me. Where can I get help?
You can join our conference room on [`support@conference.chat.sum7.eu`](https://conversations.im/j/support@conference.chat.sum7.eu).
You can join our conference room on `conversations@conference.siacs.eu`.
A lot of people in there are able to answer basic questions about the usage of
Conversations or can provide you with tips on running your own XMPP server. If
you found a bug or your app crashes please read the Developer / Report Bugs
section of this document.
#### I need professional support with Conversations or setting up my server
I'm available for hire. Contact me at `inputmice@siacs.eu`.
#### How does the address book integration work?
The address book integration was designed to protect your privacy. Conversations
@ -258,23 +268,12 @@ Conversations is trying to get rid of old behaviours and set an example for
other clients.
#### Translations
Translations are managed on [Transifex](https://www.transifex.com/projects/p/conversations/).
If you want to become a translator Please register on transifex, apply to join
the translation team and then step by our group chat on
[conversations@conference.siacs.eu](https://conversations.im/j/conversations@conference.siacs.eu)
and introduce yourself to `iNPUTmice` so he can approve your join request.
Translations are managed on [Transifex](https://www.transifex.com/projects/p/conversations/)
#### How do I backup / move Conversations to a new device?
On the one hand Conversations supports Message Archive Management to keep a server side history of your messages so when migrating to a new device that device can display your entire history. However that does not work if you enable OMEMO due to its forward secrecy. (Read [The State of Mobile XMPP in 2016](https://gultsch.de/xmpp_2016.html) especially the section on encryption.)
As of version 2.4.0 an integrated Backup & Restore function will help with this, go to Settings and youll find a setting called Create backup. A notification will pop-up during the creation process that will announce you when it's ready. After the files, one for each account, are created, you can move the **Conversations** folder *(if you want your old media files too)* or only the **Conversations/Backup** folder *(for OMEMO keys and history only)* to your new device (or to a storage place) where a freshly installed Conversations can restore each account. Don't forget to enable the accounts after a successfull restore.
This backup method will include your OMEMO keys. Due to forward secrecy you will not be able to recover messages sent and received between creating the backup and restoring it. If you have a server side archive (MAM) those messages will be retrieved but displayed as *unable to decrypt*. For technical reasons you might also lose the first message you either sent or receive after the restore; for each conversation you have. This message will then also show up as *unable to decrypt*, but this will automatically recover itself as long as both participants are on Conversations 2.3.11+. Note that this doesnt happen if you just transfer to a new phone and no messages have been exchanged between backup and restore.
In the vast, vast majority of cases you wont have to manually delete OMEMO keys or do anything like that. Conversations only introduced the official backup feature in 2.4.0 after making sure the *OMEMO self healing* mechanism introduced in 2.3.11 works fine.
**WARNING**: Be sure to know your accounts passwords or find ways to reset them **before** doing the backup as the files are encrypted using those passwords and the Restore process will ask for them.
**WARNING**: Do not use the restore backup feature in an attempt to clone (run simultaneously) an installation. Restoring a backup is only meant for migrations or in case youve lost the original device.
If you migrate to a new device and would still like to keep your history please use a third party backup tool like [oandbackup](https://github.com/jensstein/oandbackup) (needs root access on the device) or ```adb backup``` (no root access needed) from your computer. It is important that you deactivate your account before backup and activate it only after a successful restore, otherwise OMEMO might not work afterwards. Also, remember that you can **only** transfer the backup to either the same version of Android or to a newer one (eg. 5.1.1 -> 5.1.1 or 5.1.1 -> 6.0.1).
#### Conversations is missing a certain feature
@ -292,6 +291,10 @@ will it accelerate the development.
Just write it yourself and send me a pull request. If I like it I will happily
merge it if I don't at least you and like minded people get to enjoy it.
#### I need a feature and I need it now!
I am available for hire. Contact me via XMPP: `inputmice@siacs.eu`
### Security
#### Why are there two end-to-end encryption methods and which one should I choose?
@ -315,26 +318,26 @@ To use OpenPGP you have to install the open source app
manage accounts and choose renew PGP announcement from the contextual menu.
#### OMEMO is grayed out. What do I do?
OMEMO is only available in 1:1 chats and private (members-only, non-anonymous) group chats. Encrypting public group chats makes little to no sense since anyone (including a hypothetical attacker) can join and a user couldnt possibily verify all participants anyway. Furthermore for a lot of public group chat it is desirable to give new comers access to the full history.
OMEMO has two requirements: Your server and the server of your contact need to support PEP. Both of you can verify that individually by opening your account details and selecting ```Server info``` from the menu. The appearing table should list PEP as available. The second requirement is mutual presence subscription. You can verify that by opening the contact details and see if both check boxes *Send presence updates* and *Receive presence updates* are checked.
#### OMEMO doesnt work. I get a 'Something went wrong' message in the 'Trust OMEMO Fingerprints' screen.
OMEMO has two requirements: Your server and the server of your contact need to support PEP. Both of you can verify that individually by opening your account details and selecting ```Server info``` from the menu. The appearing table should list PEP as available. The second requirement is that the initial sender needs to have access to the published key material. This can either be achieved by having mutual presence subscription (you can verify that by opening the contact details and see if both check boxes *Send presence updates* and *Receive presence updates* are checked) or by using a server that makes the public key material accessible to anyone. In the [Compliance Tester](https://compliance.conversations.im) this is indicated by the 'OMEMO' feature. Since it is very common that the first messages are exchanged *before* adding each other to the contact list it is desirable to use servers that have 'OMEMO support'.
#### How does the encryption for conferences work?
#### How does the encryption for group chats work?
For conferences only OMEMO and OpenPGP are supported as encryption method..
##### OMEMO
OMEMO encryption works only in private (members only) conferences that are non-anonymous. Non-anonymous (being able to discover the real JID of other participants) is a technical requirement to discover the key material. Members only is a sort of arbitrary requirement imposed by Conversations. (see 'OMEMO is grayed out')
OMEMO encryption works only in private (members only) conferences that are non-anonymous.
The server of all participants need to pass the OMEMO [Compliance Test](https://conversations.im/compliance/).
In other words they either need to run ejabberd 18.01+ or Prosody 0.11+.
(Alternatively it would also work if all participants had each other in their contact list; But that rarely is the case in larger group chats.)
In other words they either need to run version 18.01+ of ejabberd or have the `omemo_all_access` module installed on Prosody.
The owner of a conference can make a public conference private by going into the conference
details and hit the settings button (the one with the gears) and select both *private* and
*members only*.
If OMEMO is grayed out long pressing the lock icon will reveal some quick hints on why OMEMO
is disabled.
##### OpenPGP
Every participant has to announce their OpenPGP key (see answer above).
@ -353,37 +356,52 @@ this.)
Read more about the concept on https://gultsch.de/trust.html
#### What happened to OTR support?
OTR was removed because it was highly unreliable. It didnt work with multiple devices and was never really specified to work with XMPP. The codebase was a mess (There was an HTML parser in there for crying out loud to deal with the garbage some OTR clients would send.) Verification was implemented in a non-blocking way. It would tell you if the current session was using an unknown fingerprint but it didnt actively stopped you from sending messages until you have confirmed the new fingerprint. (Like Conversations would do now with BTBV after verification or when BTBV is turned off.) Considering the previous points there was little to no desire from my point to fix this potential security issue or clean up the code base. Another reason for the removal was that people would use it *accidentally* even to communicate between two Conversations clients because they read somewhere that OTR is good.
### What clients do I use on other platforms
There are XMPP Clients available for all major platforms.
#### Windows / Linux
For your desktop computer we recommend that you use [Gajim](https://gajim.org). You need to install the plugins `OMEMO`, `HTTP Upload` and `URL image preview` to get the best compatibility with Conversations. Plugins can be installed from within the app.
#### iOS
Unfortunately we dont have a recommendation for iPhones right now. There are three clients available [Siskin](https://siskin.im/), [ChatSecure](https://chatsecure.org/) and [Monal](https://monal.im/). Each with their own pros and cons.
Unfortunately we dont have a recommendation for iPhones right now. There are two clients available [ChatSecure](https://chatsecure.org/) and [Monal](https://monal.im/). Both with their own pros and cons.
### Development
#### How do I build Conversations
<a name="beta"></a>
#### Beta testing
If you bought the App on [Google Play](https://play.google.com/store/apps/details?id=eu.siacs.conversations)
you can get access to the the latest beta version by signing up using [this link](https://play.google.com/apps/testing/eu.siacs.conversations).
**Note:** Starting with version 2.8.0 you will need to compile libwebrtc.
[Instructions](https://webrtc.github.io/webrtc-org/native-code/android/) can be found on the WebRTC
website. Place the resulting libwebrtc.aar in the `libs/` directory. The PlayStore release currently
uses the stable M90 release and renamed the file name to `libwebrtc-m90.aar` put potentially you can
reference any file name by modifying `build.gradle`.
#### How do I build Conversations
Make sure to have ANDROID_HOME point to your Android SDK. Use the Android SDK Manager to install missing dependencies.
git clone https://github.com/inputmice/Conversations.git
git clone https://github.com/siacs/Conversations.git
cd Conversations
./gradlew assembleConversationsFreeSystemDebug
There are two build flavors available. *free* and *playstore*. Unless you know what you are doing you only need *free*.
[![Build Status](https://dev.sum7.eu/sum7/Conversations/badges/develop/build.svg)](https://dev.sum7.eu/sum7/Conversations/pipelines)
[![Build Status](https://travis-ci.org/siacs/Conversations.svg?branch=development)](https://travis-ci.org/siacs/Conversations)
#### How do I update/add external libraries?
If the library you want to update is in Maven Central or JCenter (or has its own
Maven repo), add it or update its version in `build.gradle`. If the library is
in the `libs/` directory, you can update it using a subtree merge by doing the
following (using `minidns` as an example):
git remote add minidns https://github.com/rtreffer/minidns.git
git fetch minidns
git merge -s subtree minidns master
To add a new dependency to the `libs/` directory (replacing "name", "branch" and
"url" as necessary):
git remote add name url
git merge -s ours --no-commit name/branch
git read-tree --prefix=libs/name -u name/branch
git commit -m "Subtree merged in name"
#### How do I debug Conversations
@ -400,7 +418,7 @@ Debian/Ubuntu for example it is called `android-tools-adb`.
Furthermore you might have to enable 'USB debugging' in the Developer options of your
phone. After that you can just execute the following on your computer:
adb -d logcat -v time -s conver6ations
adb -d logcat -v time -s conversations
If need be there are also some Apps on the PlayStore that can be used to show the logcat
directly on your rooted phone. (Search for logcat). However in regards to further processing
@ -415,4 +433,4 @@ Play Store version or the current HEAD. If you are having problems connecting to
your XMPP server your file transfer doesnt work as expected please always
include a logcat debug output with your issue (see above).
[issues]: https://dev.sum7.eu/sum7/Conversations/issues
[issues]: https://github.com/siacs/Conversations/issues

View File

@ -155,9 +155,9 @@
inkscape:connector-curvature="0"
id="path3805"
d="m 8,8 c 2,2 4,6 4,10 L 16,8 Z"
style="display:none;fill:#282828;fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter3811)" />
style="display:none;fill:#424242;fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter3811)" />
<rect
style="fill:#282828;fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter3811)"
style="fill:#424242;fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter3811)"
id="rect2987"
width="20"
height="20"

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" fill="black" width="24px" height="24px"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M9,12c0,1.66,1.34,3,3,3s3-1.34,3-3s-1.34-3-3-3S9,10.34,9,12z"/><path d="M8,10V8H5.09C6.47,5.61,9.05,4,12,4c3.72,0,6.85,2.56,7.74,6h2.06c-0.93-4.56-4.96-8-9.8-8C8.73,2,5.82,3.58,4,6.01V4H2v6 H8z"/><path d="M16,14v2h2.91c-1.38,2.39-3.96,4-6.91,4c-3.72,0-6.85-2.56-7.74-6H2.2c0.93,4.56,4.96,8,9.8,8c3.27,0,6.18-1.58,8-4.01V20 h2v-6H16z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 547 B

View File

@ -147,15 +147,15 @@
inkscape:export-xdpi="100"
transform="matrix(0.3835576,0,0,0.3835576,-246.60108,-156.11013)"
id="g5317"
style="display:inline;fill:#EF6C00;fill-opacity:1">
style="display:inline;fill:#00a000;fill-opacity:1">
<path
style="display:inline;fill:#EF6C00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="display:inline;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 1120.8042,772.36056 -118.0025,103.66316 118.5792,46.01918 c 8.4859,3.29325 19.6524,7.94481 27.2622,0.71376 7.3868,-7.01907 5.6502,-14.13839 3.0935,-24.54095 z"
id="path5319"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccsssc" />
<circle
style="display:inline;opacity:1;fill:#EF6C00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="circle5321"
cx="883.16943"
cy="677.19611"
@ -171,7 +171,7 @@
inkscape:connector-curvature="0"
id="path6884"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#EF6C00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
@ -180,7 +180,7 @@
inkscape:connector-curvature="0"
id="path6888"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#EF6C00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
@ -189,7 +189,7 @@
inkscape:connector-curvature="0"
id="path6892"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#EF6C00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
@ -198,7 +198,7 @@
inkscape:connector-curvature="0"
id="path6896"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#EF6C00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
@ -207,7 +207,7 @@
inkscape:connector-curvature="0"
id="path6900"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#EF6C00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
@ -216,7 +216,7 @@
inkscape:connector-curvature="0"
id="path6904"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#EF6C00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
@ -225,7 +225,7 @@
inkscape:connector-curvature="0"
id="path6908"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#EF6C00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
@ -234,7 +234,7 @@
inkscape:connector-curvature="0"
id="path6912"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#EF6C00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<filter
inkscape:collect="always"
@ -256,7 +256,7 @@
inkscape:connector-curvature="0"
id="path5747"
d="M 99.908581,-2.3831968e-4 A 95.889392,95.889392 0 0 0 4.0199102,95.888436 95.889392,95.889392 0 0 0 99.908581,191.77906 95.889392,95.889392 0 0 0 142.61366,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 L 183.8285,142.24002 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 195.79921,95.888466 95.889392,95.889392 0 0 0 99.908581,-2.0831968e-4 Z"
style="display:inline;opacity:1;fill:#EF6C00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
</defs>
<metadata
@ -290,7 +290,7 @@
inkscape:groupmode="layer"
transform="translate(-4,2.6816348)">
<path
style="display:inline;opacity:1;fill:#EF6C00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="M 104.88867,-1.9377566 A 95.889392,95.889392 0 0 0 8.9999996,93.950918 95.889392,95.889392 0 0 0 104.88867,189.84154 95.889392,95.889392 0 0 0 147.59375,179.76341 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 L 188.80859,140.3025 a 95.889392,95.889392 0 0 1 -0.002,0.002 l 0,-0.008 0.002,0.006 A 95.889392,95.889392 0 0 0 200.7793,93.950948 95.889392,95.889392 0 0 0 104.88867,-1.9377266 Z"
id="circle6661"
inkscape:connector-curvature="0" />
@ -313,7 +313,7 @@
style="display:inline"
transform="translate(-4,2.6816164)">
<path
style="opacity:1;fill:#FF9800;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6910)"
d="m 145.16406,11.183594 -5.13232,9.649402 c -0.77924,1.465076 -0.65974,2.41396 0.66876,3.18097 9.66686,5.488467 18.12303,12.874168 24.86104,21.711122 1.05534,1.616079 2.08054,1.713076 3.67763,0.571565 L 178.04883,40 C 169.45271,27.990203 158.19857,18.128379 145.16406,11.183594 Z"
id="path7364"
@ -321,7 +321,7 @@
sodipodi:nodetypes="csccscc"
transform="translate(4.9999996,-1.9374999)" />
<path
style="opacity:1;fill:#FF9800;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6906)"
d="m 193.80469,75.615234 -9.62713,2.062751 c -2.66266,0.570512 -3.40763,1.172953 -2.90593,3.917433 0.85823,4.714633 1.30424,9.497137 1.33189,14.293254 -0.028,5.578758 -0.62194,11.137108 -1.77093,16.589918 -0.86591,3.23162 0.13682,3.77092 3.16149,4.58138 l 8.98639,2.30136 c 1.98177,-7.66828 3.00584,-15.55255 3.04883,-23.472658 -0.0187,-6.817681 -0.76446,-13.613926 -2.22461,-20.273438 z"
id="path7366"
@ -329,7 +329,7 @@
sodipodi:nodetypes="csccccccc"
transform="translate(4.9999996,-1.9374999)" />
<path
style="opacity:1;fill:#FF9800;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6902)"
d="m 14.264281,102.76512 -10.2076406,0.87943 c 1.2093798,14.83154 5.8540346,29.17808 13.5664056,41.90429 l 8.544301,-5.23239 c 2.394983,-1.46665 1.895406,-3.37834 0.986202,-5.04513 -5.118253,-9.40257 -8.359018,-19.71635 -9.536202,-30.36553 0,-2.09418 -1.881577,-2.26744 -3.353066,-2.14067 z"
id="path7372"
@ -337,7 +337,7 @@
sodipodi:nodetypes="sccsccs"
transform="translate(4.9999996,-1.9374999)" />
<path
style="opacity:1;fill:#FF9800;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6898)"
d="m 51.504371,166.60235 -5.82273,8.50898 c 12.710503,8.71282 27.333669,14.23394 42.630859,16.0957 l 1.220329,-9.90843 c 0.355066,-2.88295 -1.085712,-3.52946 -3.332252,-3.90256 -10.402329,-1.73697 -20.373956,-5.45322 -29.373754,-10.94516 -1.647505,-1.06744 -3.639993,-2.30718 -5.322452,0.15147 z"
id="path7370"
@ -345,7 +345,7 @@
sodipodi:nodetypes="sccsccs"
transform="translate(4.9999996,-1.9374999)" />
<path
style="opacity:1;fill:#FF9800;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6894)"
d="M 32.208984,27.683594 C 21.779177,38.079001 13.883707,50.736882 9.1347656,64.675781 L 19.33617,68.090365 c 1.658147,0.55501 2.832564,-0.120955 3.374272,-1.591979 3.777598,-10.021698 9.470788,-19.210103 16.759132,-27.052307 1.561136,-1.561136 1.567283,-2.960058 0.447507,-4.076606 z"
id="path7374"
@ -353,7 +353,7 @@
sodipodi:nodetypes="ccsccsc"
transform="translate(4.9999996,-1.9374999)" />
<path
style="opacity:1;fill:#FF9800;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6890)"
d="M 99.888672,-0.25 C 87.701045,-0.2239408 75.630114,2.1252837 64.322266,6.671875 l 3.530435,8.74898 c 1.063314,2.635062 1.616754,3.526314 4.973913,2.352259 8.692057,-3.031338 17.839027,-4.588849 27.062058,-4.599286 5.555828,0 6.486278,0.350026 6.780788,-3.4460223 l 0.74851,-9.64772758 C 104.9135,-0.12857239 102.40179,-0.23868346 99.888672,-0.25 Z"
id="path7376"
@ -361,7 +361,7 @@
sodipodi:nodetypes="ccsccscc"
transform="translate(4.9999996,-1.9374999)" />
<path
style="display:inline;fill:#FF9800;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="display:inline;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6886)"
d="m 138.72416,168.48439 c -4.17634,2.25458 -8.55959,4.09055 -13.0504,5.63418 -1.00363,0.34498 -1.20742,1.18222 -0.8682,2.27372 l 3.44056,11.0706 c 4.92985,-1.53124 9.72799,-3.45808 14.34766,-5.76172 l 0.12695,0.0137 14.0293,5.44532 4.12174,-10.20577 c 0.7548,-1.86894 -0.0184,-2.7016 -1.59462,-3.31324 l -14.72114,-5.71251 c -1.86679,-0.7244 -3.68834,-0.60144 -5.83185,0.55572 z"
id="path5005"
@ -369,7 +369,7 @@
sodipodi:nodetypes="cssccccsssc"
transform="translate(4.9999996,-1.9374999)" />
<path
style="display:inline;fill:#FF9800;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="display:inline;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6882)"
d="m 186.53125,152.80469 -10.6386,2.70888 c -0.78879,0.20085 -1.67397,1.02386 -1.35494,2.33801 l 9.75918,40.15428 c 8.56713,5.97538 15.30408,3.06731 11.01563,-9.47266 z"
id="path5071"

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
width="95"
height="95"
id="Yes_check"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_received_indicator.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="1156"
id="namedview8"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="5.04"
inkscape:cx="-4.3215257"
inkscape:cy="37.489149"
inkscape:window-x="2880"
inkscape:window-y="20"
inkscape:window-maximized="0"
inkscape:current-layer="Yes_check"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<defs
id="defs1373">
<linearGradient
id="linearGradient2250">
<stop
style="stop-color:#008700;stop-opacity:1"
offset="0"
id="stop2252" />
<stop
style="stop-color:#006f00;stop-opacity:1"
offset="1"
id="stop2254" />
</linearGradient>
</defs>
<path
d="m 2.3894499,61.412131 c 0,0 16.7473651,20.271938 22.3528491,26.154483 3.648598,3.026816 12.878061,3.83429 14.880462,0 1.64903,-2.636163 2.380404,-5.8348 2.991819,-7.931771 C 49.920898,54.575958 72.297563,22.337321 92.321082,10.50894 96.814837,5.2377522 86.327596,3.5063483 77.217442,6.9958109 63.487006,12.254946 34.107717,59.529917 29.270873,69.192545 22.40265,70.841418 12.518762,52.447046 12.518762,52.447046 7.3805037,52.552428 1.8841059,52.071763 2.3894499,61.412131 z"
style="fill:#259b24;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.29981154;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="check"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccscsccc" />
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1 +0,0 @@
new_logo.png

Before

Width:  |  Height:  |  Size: 12 B

After

Width:  |  Height:  |  Size: 43 KiB

BIN
art/logo.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 B

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -94,7 +94,7 @@
<path
d="M24 4c-7.73 0-14 6.27-14 14 0 10.5 14 26 14 26s14-15.5 14-26c0-7.73-6.27-14-14-14zm0 19c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"
id="path4"
style="fill:#EF6C00;fill-opacity:1;stroke:none;stroke-opacity:0.53333336;stroke-width:1.70000002;stroke-miterlimit:4;stroke-dasharray:none" />
style="fill:#00a000;fill-opacity:1;stroke:none;stroke-opacity:0.53333336;stroke-width:1.70000002;stroke-miterlimit:4;stroke-dasharray:none" />
<path
style="display:inline;opacity:0.19211821;fill:url(#radialGradient3883);fill-opacity:1;stroke:none"
d="m 53.884912,1.7373006 c -18.322492,0 -33.173092,14.5823714 -33.173092,32.5686504 0,3.794038 0.661899,7.436601 1.877335,10.821463 1.505391,0.209531 3.044508,0.317391 4.607513,0.317391 5.584539,0 9.890238,-1.147853 14.805425,-2.934259 l 15.611481,6.295152 a 2.0568126,2.0577227 0 0 0 2.766588,-2.403594 l -4.227888,-17.09591 c 2.717518,-4.771967 3.645449,-10.205846 3.645449,-15.810885 0,-4.0761111 -0.781533,-7.9714274 -2.20495,-11.5551094 -1.217366,-0.132888 -2.454715,-0.202899 -3.707861,-0.202899 z"

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -140,7 +140,7 @@
transform="translate(0,-2)">
<g
id="g3759"
style="fill:#FF9800;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)">
style="fill:#4d8e50;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)">
<path
style="display:none"
d="m 8,6 c 2,2 4,6 4,10 L 16,6 z"

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -140,7 +140,7 @@
transform="translate(0,-2)">
<g
id="g3759"
style="fill:#BF360C;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)">
style="fill:#295e2d;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)">
<path
style="display:none"
d="m 8,6 c 2,2 4,6 4,10 L 16,6 z"

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -140,9 +140,9 @@
transform="translate(0,-2)">
<g
id="g3759"
style="fill:#282828;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)">
style="fill:#424242;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)">
<path
style="display:none;fill:#282828;fill-opacity:1"
style="display:none;fill:#424242;fill-opacity:1"
d="m 8,6 c 2,2 4,6 4,10 L 16,6 z"
id="path3805"
inkscape:connector-curvature="0"
@ -153,7 +153,7 @@
id="path2989"
d="M 4,4 16,16 16,4 z"
sodipodi:nodetypes="cccc"
style="fill:#282828;fill-opacity:1" />
style="fill:#424242;fill-opacity:1" />
<rect
ry="2"
y="4"
@ -161,7 +161,7 @@
height="20"
width="20"
id="rect2987"
style="fill:#282828;fill-opacity:1" />
style="fill:#424242;fill-opacity:1" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -140,9 +140,9 @@
transform="translate(0,-2)">
<g
id="g3759"
style="fill:#282828;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)">
style="fill:#424242;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)">
<path
style="display:none;fill:#282828;fill-opacity:1"
style="display:none;fill:#424242;fill-opacity:1"
d="M 28,18 C 26,16 24,12 24,8 l -4,10 z"
id="path3809"
inkscape:connector-curvature="0"
@ -153,7 +153,7 @@
id="path2989"
d="m 20,12 0,12 12,0 z"
sodipodi:nodetypes="cccc"
style="fill:#282828;fill-opacity:1" />
style="fill:#424242;fill-opacity:1" />
<rect
ry="2"
y="4"
@ -161,7 +161,7 @@
height="20"
width="20"
id="rect2987"
style="fill:#282828;fill-opacity:1" />
style="fill:#424242;fill-opacity:1" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg4"
sodipodi:docname="open_pdf_black.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="1560"
id="namedview6"
showgrid="false"
inkscape:zoom="9.8333333"
inkscape:cx="-6.1016949"
inkscape:cy="12"
inkscape:window-x="4800"
inkscape:window-y="18"
inkscape:window-maximized="0"
inkscape:current-layer="svg4" />
<path
d="M12,10.5H13V13.5H12V10.5M7,11.5H8V10.5H7V11.5M20,6V18A2,2 0 0,1 18,20H6A2,2 0 0,1 4,18V6A2,2 0 0,1 6,4H18A2,2 0 0,1 20,6M9.5,10.5A1.5,1.5 0 0,0 8,9H5.5V15H7V13H8A1.5,1.5 0 0,0 9.5,11.5V10.5M14.5,10.5A1.5,1.5 0 0,0 13,9H10.5V15H13A1.5,1.5 0 0,0 14.5,13.5V10.5M18.5,9H15.5V15H17V13H18.5V11.5H17V10.5H18.5V9Z"
id="path2"
style="fill:#000000;fill-opacity:0.5411765" />
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg4"
sodipodi:docname="open_pdf_white.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="3840"
inkscape:window-height="1600"
id="namedview6"
showgrid="false"
inkscape:zoom="9.8333333"
inkscape:cx="-23.084746"
inkscape:cy="11.084746"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg4" />
<path
d="M12,10.5H13V13.5H12V10.5M7,11.5H8V10.5H7V11.5M20,6V18A2,2 0 0,1 18,20H6A2,2 0 0,1 4,18V6A2,2 0 0,1 6,4H18A2,2 0 0,1 20,6M9.5,10.5A1.5,1.5 0 0,0 8,9H5.5V15H7V13H8A1.5,1.5 0 0,0 9.5,11.5V10.5M14.5,10.5A1.5,1.5 0 0,0 13,9H10.5V15H13A1.5,1.5 0 0,0 14.5,13.5V10.5M18.5,9H15.5V15H17V13H18.5V11.5H17V10.5H18.5V9Z"
id="path2"
style="fill:#ffffff;fill-opacity:0.69803923" />
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -9,42 +9,18 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="20.68mm"
height="20.68mm"
viewBox="0 0 20.680001 20.680001"
width="56.24334mm"
height="56.24334mm"
viewBox="0 0 56.24334 56.243339"
version="1.1"
id="svg14750"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="quicksy.svg"
inkscape:export-filename="/home/smagnan/Work/osp/osp.cloud/osp.work.quicksy/package-quicksy/quicksy.png"
inkscape:export-xdpi="175"
inkscape:export-ydpi="175">
inkscape:export-filename="/home/daniel/quicksy.png"
inkscape:export-xdpi="231.22382"
inkscape:export-ydpi="231.22382">
<defs
id="defs14744">
<inkscape:path-effect
effect="spiro"
id="path-effect963"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect937"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect893"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect998"
effect="spiro" />
<inkscape:path-effect
is_visible="true"
id="path-effect890"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect852"
is_visible="true" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6910-2">
@ -117,78 +93,6 @@
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6898-8-8">
<path
inkscape:connector-curvature="0"
id="path6900-9-1"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6886-7-2">
<path
inkscape:connector-curvature="0"
id="path6888-5-8"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6882-4-9">
<path
inkscape:connector-curvature="0"
id="path6884-7-3"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6906-8-6">
<path
inkscape:connector-curvature="0"
id="path6908-0-8"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6910-2-0">
<path
inkscape:connector-curvature="0"
id="path6912-1-2"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6890-4-1">
<path
inkscape:connector-curvature="0"
id="path6892-8-0"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6894-1-5">
<path
inkscape:connector-curvature="0"
id="path6896-2-1"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6902-3-1">
<path
inkscape:connector-curvature="0"
id="path6904-6-0"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
@ -197,31 +101,22 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="7.9195959"
inkscape:cx="24.499812"
inkscape:cy="6.9964144"
inkscape:zoom="2.8"
inkscape:cx="-104.30661"
inkscape:cy="87.246329"
inkscape:document-units="mm"
inkscape:current-layer="g14606"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="3836"
inkscape:window-height="1561"
inkscape:window-width="1916"
inkscape:window-height="504"
inkscape:window-x="1920"
inkscape:window-y="18"
inkscape:window-y="1072"
inkscape:window-maximized="0"
showguides="true"
inkscape:guide-bbox="true"
borderlayer="false"
inkscape:pagecheckerboard="true">
<sodipodi:guide
position="51.630255,101.26165"
orientation="0,1"
id="guide949"
inkscape:locked="false" />
</sodipodi:namedview>
units="mm" />
<metadata
id="metadata14747">
<rdf:RDF>
@ -230,7 +125,7 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
@ -238,129 +133,125 @@
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-94.210196,-206.61407)">
transform="translate(-69.668634,-48.140243)">
<g
style="display:inline"
id="g14606"
transform="matrix(0.28222223,0,0,0.28222223,-479.00124,-301.93764)">
<g
id="g925"
transform="translate(1.2646334,0.03223108)">
<g
style="stroke-width:2.7252984"
transform="matrix(0.3669323,0,0,0.3669323,1317.8763,1202.38)"
id="g980">
<path
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10.90119457;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 2039.9944,1634.1834 a 95.889395,95.889395 0 0 0 -95.8886,95.8887 95.889395,95.889395 0 0 0 95.8886,95.8906 95.889395,95.889395 0 0 0 42.7051,-10.0781 l 0.127,0.014 40.7929,15.832 c 3.2548,1.2632 7.5363,3.047 10.4551,0.2735 2.8333,-2.6923 2.1681,-5.4222 1.1875,-9.4121 l -11.3476,-46.168 v -0.01 0.01 a 95.889395,95.889395 0 0 0 11.9707,-46.3516 95.889395,95.889395 0 0 0 -95.8907,-95.8886 z"
id="path964"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.04530895"
d="m 2052.7212,1719.2884 a 11.53697,11.53697 0 0 1 -11.537,11.537 11.53697,11.53697 0 0 1 -11.537,-11.537 11.53697,11.53697 0 0 1 11.537,-11.5369 11.53697,11.53697 0 0 1 11.537,11.5369 z"
id="path984" />
<path
id="path986"
d="m 2103.4386,1719.2885 a 11.53697,11.53697 0 0 1 -11.537,11.5369 11.53697,11.53697 0 0 1 -11.537,-11.5369 11.53697,11.53697 0 0 1 11.537,-11.537 11.53697,11.53697 0 0 1 11.537,11.537 z"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.04530895"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="ccsc"
inkscape:connector-curvature="0"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.04530895"
d="m 2116.3676,1755.8129 c -12.7133,-0.329 -30.4545,38.6902 -29.8052,0.9297 0,-10.306 7.0406,-18.6606 15.7253,-18.6606 5.0651,0 14.5453,5.3309 14.0799,17.7309 z"
id="path988" />
<path
inkscape:connector-curvature="0"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.04530895"
d="m 1979.1343,1719.2885 a 11.53697,11.53697 0 0 0 11.537,11.5369 11.53697,11.53697 0 0 0 11.537,-11.5369 11.53697,11.53697 0 0 0 -11.537,-11.537 11.53697,11.53697 0 0 0 -11.537,11.537 z"
id="path990" />
<path
inkscape:original-d="m 2078.1216,1783.9664 c -8.7484,6.6613 -24.7952,13.3613 -37.8714,14.0385 -13.0762,0.6772 -23.1816,-4.6682 -36.5745,-13.8341 14.8965,10.484 19.8658,4.8954 20.9617,-7.412 0.5479,-6.1535 -1.1267,-25.0806 -0.2829,-28.2596 0.8437,-3.179 7.7475,-7.5824 16.8934,-7.5141 9.1459,0.068 15.9436,5.0338 16.6438,8.0452 0.7002,3.0115 -1.1553,21.0416 -0.7571,27.5128 0.7964,12.9425 5.5911,18.8599 20.987,7.4233 z"
inkscape:path-effect="#path-effect998"
sodipodi:nodetypes="cscsssssc"
inkscape:connector-curvature="0"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.04530907"
d="m 2078.1216,1783.9664 c -10.3552,9.0701 -24.1062,14.1675 -37.8714,14.0385 -13.3074,-0.1247 -26.5157,-5.1207 -36.5745,-13.8341 3.5313,2.5325 8.318,3.2125 12.415,1.7639 4.0969,-1.4487 7.3922,-4.9865 8.5467,-9.1759 0.6367,-2.3105 0.6496,-4.7548 0.375,-7.1357 -0.2746,-2.3808 -0.8297,-4.7188 -1.3173,-7.0653 -0.4876,-2.3465 -0.91,-4.7222 -0.9131,-7.1188 -0,-2.3967 0.4318,-4.832 1.5725,-6.9398 1.5437,-2.8524 4.3008,-4.9253 7.329,-6.087 3.0281,-1.1617 6.3215,-1.4775 9.5644,-1.4271 3.2431,0.05 6.5253,0.4683 9.5154,1.7251 2.9901,1.2569 5.68,3.418 7.1284,6.3201 1.0414,2.0866 1.4036,4.4624 1.3534,6.7939 -0.05,2.3314 -0.4967,4.6354 -1.0019,6.912 -0.5053,2.2766 -1.0719,4.5449 -1.3688,6.8579 -0.297,2.313 -0.3181,4.6899 0.2602,6.949 1.0858,4.2417 4.3854,7.8502 8.5132,9.3103 4.1278,1.46 8.9625,0.7286 12.4738,-1.887 z"
id="path996" />
<path
id="path1002"
d="m 1966.202,1755.8129 c 12.7133,-0.329 30.4544,38.6902 29.8051,0.9297 0,-10.306 -7.0405,-18.6606 -15.7252,-18.6606 -5.0651,0 -14.5453,5.3309 -14.0799,17.7309 z"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.04530895"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccsc" />
</g>
<g
style="stroke-width:0.84566534"
transform="matrix(1.1825009,0,0,1.1825009,-382.86422,-389.99169)"
id="g904">
<path
style="display:inline;opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.72006631px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6910-2-0)"
d="m 145.16406,11.183594 -5.13232,9.649402 c -0.77924,1.465076 -0.65974,2.41396 0.66876,3.18097 9.66686,5.488467 18.12303,12.874168 24.86104,21.711122 1.05534,1.616079 2.08054,1.713076 3.67763,0.571565 L 178.04883,40 C 169.45271,27.990203 158.19857,18.128379 145.16406,11.183594 Z"
id="path966"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csccscc"
transform="matrix(0.3108988,0,0,0.3108988,2040.1832,1853.6283)" />
<path
style="display:inline;opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.72006631px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6906-8-6)"
d="m 193.80469,75.615234 -9.62713,2.062751 c -2.66266,0.570512 -3.40763,1.172953 -2.90593,3.917433 0.85823,4.714633 1.30424,9.497137 1.33189,14.293254 -0.028,5.578758 -0.62194,11.137108 -1.77093,16.589918 -0.86591,3.23162 0.13682,3.77092 3.16149,4.58138 l 8.98639,2.30136 c 1.98177,-7.66828 3.00584,-15.55255 3.04883,-23.472658 -0.0187,-6.817681 -0.76446,-13.613926 -2.22461,-20.273438 z"
id="path968"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csccccccc"
transform="matrix(0.3108988,0,0,0.3108988,2040.1832,1853.6283)" />
<path
style="display:inline;opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.72006631px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6902-3-1)"
d="m 14.264281,102.76512 -10.2076406,0.87943 c 1.2093798,14.83154 5.8540346,29.17808 13.5664056,41.90429 l 8.544301,-5.23239 c 2.394983,-1.46665 1.895406,-3.37834 0.986202,-5.04513 -5.118253,-9.40257 -8.359018,-19.71635 -9.536202,-30.36553 0,-2.09418 -1.881577,-2.26744 -3.353066,-2.14067 z"
id="path970"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sccsccs"
transform="matrix(0.3108988,0,0,0.3108988,2040.1832,1853.6283)" />
<path
style="display:inline;opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.72006631px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6898-8-8)"
d="m 51.504371,166.60235 -5.82273,8.50898 c 12.710503,8.71282 27.333669,14.23394 42.630859,16.0957 l 1.220329,-9.90843 c 0.355066,-2.88295 -1.085712,-3.52946 -3.332252,-3.90256 -10.402329,-1.73697 -20.373956,-5.45322 -29.373754,-10.94516 -1.647505,-1.06744 -3.639993,-2.30718 -5.322452,0.15147 z"
id="path972"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sccsccs"
transform="matrix(0.3108988,0,0,0.3108988,2040.1832,1853.6283)" />
<path
style="display:inline;opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.72006631px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6894-1-5)"
d="M 32.208984,27.683594 C 21.779177,38.079001 13.883707,50.736882 9.1347656,64.675781 L 19.33617,68.090365 c 1.658147,0.55501 2.832564,-0.120955 3.374272,-1.591979 3.777598,-10.021698 9.470788,-19.210103 16.759132,-27.052307 1.561136,-1.561136 1.567283,-2.960058 0.447507,-4.076606 z"
id="path974"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccsccsc"
transform="matrix(0.3108988,0,0,0.3108988,2040.1832,1853.6283)" />
<path
style="display:inline;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.72006631px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6886-7-2)"
d="m 138.72416,168.48439 c -4.17634,2.25458 -8.55959,4.09055 -13.0504,5.63418 -1.00363,0.34498 -1.20742,1.18222 -0.8682,2.27372 l 3.44056,11.0706 c 4.92985,-1.53124 9.72799,-3.45808 14.34766,-5.76172 l 0.12695,0.0137 14.0293,5.44532 4.12174,-10.20577 c 0.7548,-1.86894 -0.0184,-2.7016 -1.59462,-3.31324 l -14.72114,-5.71251 c -1.86679,-0.7244 -3.68834,-0.60144 -5.83185,0.55572 z"
id="path976"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cssccccsssc"
transform="matrix(0.3108988,0,0,0.3108988,2040.1832,1853.6283)" />
<path
style="display:inline;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.72006631px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6882-4-9)"
d="m 186.53125,152.80469 -10.6386,2.70888 c -0.78879,0.20085 -1.67397,1.02386 -1.35494,2.33801 l 9.75918,40.15428 c 8.56713,5.97538 15.30408,3.06731 11.01563,-9.47266 z"
id="path978"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cssccc"
transform="matrix(0.3108988,0,0,0.3108988,2040.1832,1853.6283)" />
<path
style="display:inline;opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.72006631px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
clip-path="url(#clipPath6890-4-1)"
d="M 99.888672,-0.25 C 87.701045,-0.2239408 75.630114,2.1252837 64.322266,6.671875 l 3.530435,8.74898 c 1.063314,2.635062 1.616754,3.526314 4.973913,2.352259 8.692057,-3.031338 17.839027,-4.588849 27.062058,-4.599286 5.555828,0 6.486278,0.350026 6.780788,-3.4460223 l 0.74851,-9.64772758 C 104.9135,-0.12857239 102.40179,-0.23868346 99.888672,-0.25 Z"
id="path994"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccsccscc"
transform="matrix(0.3108988,0,0,0.3108988,2040.0741,1853.6334)" />
</g>
</g>
transform="matrix(0.28222223,0,0,0.28222223,-477.88455,-301.93764)">
<path
inkscape:connector-curvature="0"
id="path3415"
d="m 2039.9944,1240.4334 a 95.889395,95.889395 0 0 0 -95.8886,95.8887 95.889395,95.889395 0 0 0 95.8886,95.8906 95.889395,95.889395 0 0 0 42.7051,-10.0781 l 0.127,0.014 40.7929,15.832 c 3.2548,1.2632 7.5363,3.047 10.4551,0.2735 2.8333,-2.6923 2.1681,-5.4222 1.1875,-9.4121 l -11.3476,-46.168 v -0.01 0.01 a 95.889395,95.889395 0 0 0 11.9707,-46.3516 95.889395,95.889395 0 0 0 -95.8907,-95.8886 z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4.00000048;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
transform="translate(1940.1058,1240.4337)"
sodipodi:nodetypes="csccscc"
inkscape:connector-curvature="0"
id="path3417"
d="m 145.16406,11.183594 -5.13232,9.649402 c -0.77924,1.465076 -0.65974,2.41396 0.66876,3.18097 9.66686,5.488467 18.12303,12.874168 24.86104,21.711122 1.05534,1.616079 2.08054,1.713076 3.67763,0.571565 L 178.04883,40 C 169.45271,27.990203 158.19857,18.128379 145.16406,11.183594 Z"
clip-path="url(#clipPath6910-2)"
style="display:inline;opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
transform="translate(1940.1058,1240.4337)"
sodipodi:nodetypes="csccccccc"
inkscape:connector-curvature="0"
id="path3419"
d="m 193.80469,75.615234 -9.62713,2.062751 c -2.66266,0.570512 -3.40763,1.172953 -2.90593,3.917433 0.85823,4.714633 1.30424,9.497137 1.33189,14.293254 -0.028,5.578758 -0.62194,11.137108 -1.77093,16.589918 -0.86591,3.23162 0.13682,3.77092 3.16149,4.58138 l 8.98639,2.30136 c 1.98177,-7.66828 3.00584,-15.55255 3.04883,-23.472658 -0.0187,-6.817681 -0.76446,-13.613926 -2.22461,-20.273438 z"
clip-path="url(#clipPath6906-8)"
style="display:inline;opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
transform="translate(1940.1058,1240.4337)"
sodipodi:nodetypes="sccsccs"
inkscape:connector-curvature="0"
id="path3421"
d="m 14.264281,102.76512 -10.2076406,0.87943 c 1.2093798,14.83154 5.8540346,29.17808 13.5664056,41.90429 l 8.544301,-5.23239 c 2.394983,-1.46665 1.895406,-3.37834 0.986202,-5.04513 -5.118253,-9.40257 -8.359018,-19.71635 -9.536202,-30.36553 0,-2.09418 -1.881577,-2.26744 -3.353066,-2.14067 z"
clip-path="url(#clipPath6902-3)"
style="display:inline;opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
transform="translate(1940.1058,1240.4337)"
sodipodi:nodetypes="sccsccs"
inkscape:connector-curvature="0"
id="path3423"
d="m 51.504371,166.60235 -5.82273,8.50898 c 12.710503,8.71282 27.333669,14.23394 42.630859,16.0957 l 1.220329,-9.90843 c 0.355066,-2.88295 -1.085712,-3.52946 -3.332252,-3.90256 -10.402329,-1.73697 -20.373956,-5.45322 -29.373754,-10.94516 -1.647505,-1.06744 -3.639993,-2.30718 -5.322452,0.15147 z"
clip-path="url(#clipPath6898-8)"
style="display:inline;opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
transform="translate(1940.1058,1240.4337)"
sodipodi:nodetypes="ccsccsc"
inkscape:connector-curvature="0"
id="path3425"
d="M 32.208984,27.683594 C 21.779177,38.079001 13.883707,50.736882 9.1347656,64.675781 L 19.33617,68.090365 c 1.658147,0.55501 2.832564,-0.120955 3.374272,-1.591979 3.777598,-10.021698 9.470788,-19.210103 16.759132,-27.052307 1.561136,-1.561136 1.567283,-2.960058 0.447507,-4.076606 z"
clip-path="url(#clipPath6894-1)"
style="display:inline;opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
transform="translate(1940.1058,1240.4337)"
sodipodi:nodetypes="cssccccsssc"
inkscape:connector-curvature="0"
id="path3427"
d="m 138.72416,168.48439 c -4.17634,2.25458 -8.55959,4.09055 -13.0504,5.63418 -1.00363,0.34498 -1.20742,1.18222 -0.8682,2.27372 l 3.44056,11.0706 c 4.92985,-1.53124 9.72799,-3.45808 14.34766,-5.76172 l 0.12695,0.0137 14.0293,5.44532 4.12174,-10.20577 c 0.7548,-1.86894 -0.0184,-2.7016 -1.59462,-3.31324 l -14.72114,-5.71251 c -1.86679,-0.7244 -3.68834,-0.60144 -5.83185,0.55572 z"
clip-path="url(#clipPath6886-7)"
style="display:inline;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
transform="translate(1940.1058,1240.4337)"
sodipodi:nodetypes="cssccc"
inkscape:connector-curvature="0"
id="path3429"
d="m 186.53125,152.80469 -10.6386,2.70888 c -0.78879,0.20085 -1.67397,1.02386 -1.35494,2.33801 l 9.75918,40.15428 c 8.56713,5.97538 15.30408,3.06731 11.01563,-9.47266 z"
clip-path="url(#clipPath6882-4)"
style="display:inline;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<circle
r="10.470775"
style="opacity:1;fill:#f5f5f5;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.38355759"
id="circle3431"
cx="2041.2632"
cy="1331.7938" />
<circle
r="10.470775"
style="opacity:1;fill:#f5f5f5;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.38355759"
id="circle3433"
cx="2087.3342"
cy="1331.7938" />
<path
id="path3435"
d="m 2051.6642,1331.8339 a 10.470775,10.470775 0 0 1 -10.4708,10.4708 10.470775,10.470775 0 0 1 -10.4708,-10.4708 10.470775,10.470775 0 0 1 10.4708,-10.4707 10.470775,10.470775 0 0 1 10.4708,10.4707 z"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.38355759"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="cczc"
inkscape:connector-curvature="0"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.38355768"
d="m 2074.7172,1383.7461 c -15.8797,12.0914 -43.2556,14.948 -67.5659,-1.6895 27.0396,19.0303 0.8971,-35.2887 34.0999,-35.0406 33.2028,0.2482 5.5198,57.4893 33.466,36.7301 z"
id="path3437" />
<path
inkscape:connector-curvature="0"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.38355759"
d="m 2097.6945,1331.834 a 10.470775,10.470775 0 0 1 -10.4708,10.4707 10.470775,10.470775 0 0 1 -10.4708,-10.4707 10.470775,10.470775 0 0 1 10.4708,-10.4708 10.470775,10.470775 0 0 1 10.4708,10.4708 z"
id="path3439" />
<path
id="path3441"
d="m 2109.4287,1358.1944 c -11.5384,-0.2986 -27.64,38.1534 -27.0507,3.8825 0,-9.3535 6.3899,-16.936 14.272,-16.936 4.597,0 13.2011,1.7994 12.7787,13.0535 z"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.38355762"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccsc" />
<path
id="path3443"
d="m 1984.8779,1331.834 a 10.470775,10.470775 0 0 0 10.4708,10.4707 10.470775,10.470775 0 0 0 10.4708,-10.4707 10.470775,10.470775 0 0 0 -10.4708,-10.4708 10.470775,10.470775 0 0 0 -10.4708,10.4708 z"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.38355759"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="ccsc"
inkscape:connector-curvature="0"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.38355762"
d="m 1971.0018,1355.3574 c 15.2031,1.4783 27.0563,38.8786 29.1926,6.7197 0,-9.3537 -6.1191,-16.9362 -13.6673,-16.9362 -7.5483,0 -11.0905,1.2852 -15.5253,10.2165 z"
id="path3445" />
<path
transform="translate(1939.7548,1240.4502)"
sodipodi:nodetypes="ccsccscc"
inkscape:connector-curvature="0"
id="path3447"
d="M 99.888672,-0.25 C 87.701045,-0.2239408 75.630114,2.1252837 64.322266,6.671875 l 3.530435,8.74898 c 1.063314,2.635062 1.616754,3.526314 4.973913,2.352259 8.692057,-3.031338 17.839027,-4.588849 27.062058,-4.599286 5.555828,0 6.486278,0.350026 6.780788,-3.4460223 l 0.74851,-9.64772758 C 104.9135,-0.12857239 102.40179,-0.23868346 99.888672,-0.25 Z"
clip-path="url(#clipPath6890-4)"
style="display:inline;opacity:1;fill:#80d080;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 18 KiB

1
art/quicksy_launcher.svg Symbolic link
View File

@ -0,0 +1 @@
quicksy.svg

View File

@ -1,181 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="21.637424mm"
height="21.637424mm"
viewBox="0 0 21.637424 21.637426"
version="1.1"
id="svg14750"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="quicksy_mono.svg"
inkscape:export-filename="/home/smagnan/Work/osp/osp.cloud/osp.work.quicksy/package-quicksy/quicksy-adaptative.png"
inkscape:export-xdpi="175"
inkscape:export-ydpi="175">
<defs
id="defs14744">
<inkscape:path-effect
effect="spiro"
id="path-effect963"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect937"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect893"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect998"
effect="spiro" />
<inkscape:path-effect
is_visible="true"
id="path-effect890"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect852"
is_visible="true" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6910-2">
<path
inkscape:connector-curvature="0"
id="path6912-1"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6906-8">
<path
inkscape:connector-curvature="0"
id="path6908-0"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6902-3">
<path
inkscape:connector-curvature="0"
id="path6904-6"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6898-8">
<path
inkscape:connector-curvature="0"
id="path6900-9"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6894-1">
<path
inkscape:connector-curvature="0"
id="path6896-2"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6886-7">
<path
inkscape:connector-curvature="0"
id="path6888-5"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6882-4">
<path
inkscape:connector-curvature="0"
id="path6884-7"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6890-4">
<path
inkscape:connector-curvature="0"
id="path6892-8"
d="M 99.88867,-2.3837657e-4 A 95.889392,95.889392 0 0 0 4,95.888436 95.889392,95.889392 0 0 0 99.88867,191.77906 95.889392,95.889392 0 0 0 142.59375,181.70093 l 0.12695,0.0137 40.79297,15.83204 c 3.25479,1.26313 7.53628,3.04697 10.45508,0.27343 2.83326,-2.69222 2.16811,-5.42213 1.1875,-9.41211 l -11.34766,-46.16797 a 95.889392,95.889392 0 0 1 -0.002,0.002 v -0.008 l 0.002,0.006 A 95.889392,95.889392 0 0 0 195.7793,95.888466 95.889392,95.889392 0 0 0 99.88867,-2.0837657e-4 Z"
style="display:inline;opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="3.959798"
inkscape:cx="-19.000081"
inkscape:cy="39.577553"
inkscape:document-units="mm"
inkscape:current-layer="g14606"
showgrid="false"
fit-margin-top="0.5"
fit-margin-left="0.87888"
fit-margin-right="0.87888"
fit-margin-bottom="0.5"
inkscape:window-width="1916"
inkscape:window-height="1561"
inkscape:window-x="3840"
inkscape:window-y="18"
inkscape:window-maximized="0"
showguides="true"
inkscape:guide-bbox="true"
units="mm"
inkscape:pagecheckerboard="true">
<sodipodi:guide
position="-11.854979,103.32866"
orientation="0,1"
id="guide949"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata14747">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-157.69546,-207.72366)">
<g
style="display:inline"
id="g14606"
transform="matrix(0.28222223,0,0,0.28222223,-479.00124,-301.93764)">
<path
inkscape:connector-curvature="0"
id="path982"
d="m 2294.3053,1807.6581 a 35.179682,35.179682 0 0 0 -35.1793,35.1795 35.179682,35.179682 0 0 0 35.1793,35.1801 35.179682,35.179682 0 0 0 15.6671,-3.6974 l 0.047,0.016 14.9657,5.8081 c 1.1941,0.4634 2.765,1.1176 3.8357,0.1008 1.0396,-0.9878 0.7952,-1.9892 0.4353,-3.453 l -4.1628,-16.9381 a 35.179682,35.179682 0 0 0 4.3919,-17.0051 35.179682,35.179682 0 0 0 -35.18,-35.1796 z m -18.0955,26.9906 a 4.2326574,4.2326574 0 0 1 4.2322,4.2329 4.2326574,4.2326574 0 0 1 -4.2322,4.232 4.2326574,4.2326574 0 0 1 -4.2329,-4.232 4.2326574,4.2326574 0 0 1 4.2329,-4.2329 z m 18.5316,0 a 4.2326574,4.2326574 0 0 1 4.2327,4.2329 4.2326574,4.2326574 0 0 1 -4.2327,4.232 4.2326574,4.2326574 0 0 1 -4.2323,-4.232 4.2326574,4.2326574 0 0 1 4.2323,-4.2329 z m 18.6074,0 a 4.2326574,4.2326574 0 0 1 4.2327,4.2329 4.2326574,4.2326574 0 0 1 -4.2327,4.232 4.2326574,4.2326574 0 0 1 -4.2329,-4.232 4.2326574,4.2326574 0 0 1 4.2329,-4.2329 z m -40.9506,11.1272 c 3.1862,0 5.7692,3.0658 5.7692,6.8468 0.2382,13.8535 -6.2709,-0.4619 -10.9352,-0.3413 -0.1706,-4.5493 3.3077,-6.5055 5.166,-6.5055 z m 44.7609,0 c 1.8583,0 5.3366,1.9562 5.166,6.5055 -4.6643,-0.1203 -11.1734,14.1948 -10.9352,0.3413 0,-3.781 2.583,-6.8468 5.7692,-6.8468 z m -22.3942,1.0655 c 1.1897,0.018 2.3941,0.1717 3.4912,0.6327 1.0971,0.4611 2.0838,1.2544 2.6152,2.3191 0.3821,0.7655 0.5149,1.6369 0.4964,2.4922 -0.018,0.8553 -0.1822,1.7007 -0.3674,2.5359 -0.1854,0.8354 -0.3936,1.6673 -0.5026,2.5159 -0.1089,0.8485 -0.117,1.7206 0.096,2.5493 0.3984,1.5562 1.6087,2.8803 3.1231,3.416 1.5145,0.5358 3.2879,0.267 4.5762,-0.6926 -3.7992,3.3277 -8.8442,5.1979 -13.8944,5.1504 -4.8822,-0.046 -9.7276,-1.8785 -13.418,-5.0752 1.2957,0.9292 3.0515,1.1785 4.5546,0.647 1.5032,-0.5315 2.7124,-1.8292 3.1358,-3.3664 0.2337,-0.8475 0.2386,-1.7442 0.1381,-2.6177 -0.1007,-0.8736 -0.3048,-1.731 -0.4837,-2.5918 -0.1787,-0.8609 -0.3339,-1.7326 -0.3352,-2.6118 0,-0.8794 0.1593,-1.7727 0.5772,-2.546 0.5662,-1.0466 1.578,-1.8075 2.6889,-2.2337 1.1111,-0.4262 2.319,-0.5419 3.5089,-0.5233 z"
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4.00000048;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -11,6 +11,7 @@ resolutions = {
}
images = {
'quicksy_launcher.svg' => ['quicksy/launcher', 48],
'main_logo.svg' => ['conversations/main_logo', 200],
'quicksy_main_logo.svg' => ['quicksy/main_logo', 200],
'splash_logo.svg' => ['conversations/splash_logo', 144],
@ -23,11 +24,8 @@ images = {
'play_gif_white.svg' => ['play_gif_white', 128],
'play_video_black.svg' => ['play_video_black', 128],
'play_gif_black.svg' => ['play_gif_black', 128],
'open_pdf_black.svg' => ['open_pdf_black', 128],
'open_pdf_white.svg' => ['open_pdf_white', 128],
'conversations_mono.svg' => ['conversations/ic_notification', 24],
'quicksy_mono.svg' => ['quicksy/ic_notification', 24],
'flip_camera_android-black-24dp.svg' => ['ic_flip_camera_android_black_24dp', 24],
'conversations_mono.svg' => ['ic_notification', 24],
'ic_received_indicator.svg' => ['ic_received_indicator', 12],
'ic_send_text_offline.svg' => ['ic_send_text_offline', 36],
'ic_send_text_offline_white.svg' => ['ic_send_text_offline_white', 36],
'ic_send_text_online.svg' => ['ic_send_text_online', 36],
@ -119,7 +117,7 @@ images.each do |source_filename, settings|
else
path = "../src/#{output_parts[0]}/res/drawable-#{resolution}/#{output_parts[1]}.png"
end
execute_cmd "#{inkscape} #{source_filename} -C -w #{width} -h #{height} -o #{path}"
execute_cmd "#{inkscape} -f #{source_filename} -z -C -w #{width} -h #{height} -e #{path}"
top = []
right = []

View File

@ -3,10 +3,10 @@
buildscript {
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.3'
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
@ -14,104 +14,86 @@ apply plugin: 'com.android.application'
repositories {
google()
mavenCentral()
jcenter()
mavenCentral()
}
configurations {
playstoreImplementation
compatImplementation
conversationsFreeCompatImplementation
conversationsPlaystoreCompatImplementation
conversationsPlaystoreSystemImplementation
quicksyPlaystoreCompatImplementation
quicksyPlaystoreSystemImplementation
quicksyFreeCompatImplementation
quicksyImplementation
}
dependencies {
implementation 'androidx.viewpager:viewpager:1.0.0'
ext {
supportLibVersion = '28.0.0'
}
playstoreImplementation('com.google.firebase:firebase-messaging:22.0.0') {
dependencies {
playstoreImplementation('com.google.firebase:firebase-messaging:15.0.2') {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
}
conversationsPlaystoreCompatImplementation("com.android.installreferrer:installreferrer:2.2")
conversationsPlaystoreSystemImplementation("com.android.installreferrer:installreferrer:2.2")
quicksyPlaystoreCompatImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.1'
quicksyPlaystoreSystemImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.1'
implementation 'org.sufficientlysecure:openpgp-api:10.0'
implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.exifinterface:exifinterface:1.3.3'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.emoji:emoji:1.1.0'
implementation 'com.google.android.material:material:1.4.0'
compatImplementation 'androidx.emoji:emoji-appcompat:1.1.0'
conversationsFreeCompatImplementation 'androidx.emoji:emoji-bundled:1.1.0'
quicksyFreeCompatImplementation 'androidx.emoji:emoji-bundled:1.1.0'
implementation 'org.bouncycastle:bcmail-jdk15on:1.64'
//zxing stopped supporting Java 7 so we have to stick with 3.3.3
//https://github.com/zxing/zxing/issues/1170
implementation 'com.google.zxing:core:3.3.3'
implementation ('com.theartofdev.edmodo:android-image-cropper:2.7.+') {
exclude group: 'com.android.support', module: 'appcompat-v7'
exclude group: 'com.android.support', module: 'exifinterface'
}
implementation "com.android.support:support-v13:$supportLibVersion"
implementation "com.android.support:appcompat-v7:$supportLibVersion"
implementation "com.android.support:exifinterface:$supportLibVersion"
implementation "com.android.support:cardview-v7:$supportLibVersion"
implementation "com.android.support:support-emoji:$supportLibVersion"
implementation "com.android.support:design:$supportLibVersion"
compatImplementation "com.android.support:support-emoji-appcompat:$supportLibVersion"
conversationsFreeCompatImplementation "com.android.support:support-emoji-bundled:$supportLibVersion"
quicksyFreeCompatImplementation "com.android.support:support-emoji-bundled:$supportLibVersion"
implementation 'org.bouncycastle:bcmail-jdk15on:1.58'
implementation 'com.google.zxing:core:3.3.0'
implementation 'de.measite.minidns:minidns-hla:0.2.4'
implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
implementation 'org.whispersystems:signal-protocol-java:2.6.2'
implementation 'com.makeramen:roundedimageview:2.3.0'
implementation "com.wefika:flowlayout:0.4.1"
implementation 'com.otaliastudios:transcoder:0.10.4'
implementation 'org.jxmpp:jxmpp-jid:1.0.2'
implementation 'org.osmdroid:osmdroid-android:6.1.10'
implementation 'net.ypresto.androidtranscoder:android-transcoder:0.2.0'
implementation 'rocks.xmpp:xmpp-addr:0.8.0'
implementation 'org.osmdroid:osmdroid-android:6.0.1'
implementation 'org.hsluv:hsluv:0.2'
implementation 'org.conscrypt:conscrypt-android:2.5.2'
implementation 'org.conscrypt:conscrypt-android:1.3.0'
implementation 'me.drakeet.support:toastcompat:1.1.0'
implementation "com.leinardi.android:speed-dial:3.2.0"
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation "com.squareup.okhttp3:okhttp:4.9.2"
implementation 'com.google.guava:guava:30.1.1-android'
quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.36'
// implementation fileTree(include: ['libwebrtc-m92.aar'], dir: 'libs')
implementation 'org.webrtc:google-webrtc:1.0.32006'
quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.9.14'
}
ext {
travisBuild = System.getenv("TRAVIS") == "true"
preDexEnabled = System.getProperty("pre-dex", "true")
abiCodes = ['armeabi-v7a': 1, 'x86': 2, 'x86_64': 3, 'arm64-v8a': 4]
}
android {
compileSdkVersion 29
compileSdkVersion 28
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
versionCode 4202301
versionName "2.10.2"
minSdkVersion 19
targetSdkVersion 28
versionCode 305
versionName "2.3.8"
archivesBaseName += "-$versionName"
applicationId "eu.sum7.conversations"
applicationId "eu.siacs.conversations"
resValue "string", "applicationId", applicationId
def appName = "Conv6ations"
resValue "string", "app_name", appName
buildConfigField "String", "APP_NAME", "\"$appName\"";
}
configurations {
implementation.exclude group: 'org.jetbrains' , module:'annotations'
resValue "string", "app_name", "Conversations"
buildConfigField "String", "LOGTAG", "\"conversations\""
}
dataBinding {
enabled true
}
dexOptions {
// Skip pre-dexing when running on Travis CI or when disabled via -Dpre-dex=false.
preDexLibraries = preDexEnabled && !travisBuild
jumboMode true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@ -124,11 +106,9 @@ android {
quicksy {
dimension "mode"
applicationId = "im.quicksy.client"
resValue "string", "app_name", "Quicksy"
resValue "string", "applicationId", applicationId
def appName = "Quicksy"
resValue "string", "app_name", appName
buildConfigField "String", "APP_NAME", "\"$appName\"";
buildConfigField "String", "LOGTAG", "\"quicksy\""
}
conversations {
@ -137,34 +117,31 @@ android {
playstore {
dimension "distribution"
versionNameSuffix "+p"
}
free {
dimension "distribution"
versionNameSuffix "+f"
}
system {
dimension "emoji"
versionNameSuffix "s"
}
compat {
dimension "emoji"
versionNameSuffix "c"
}
}
sourceSets {
quicksyFreeSystem {
java {
srcDir 'src/quicksyFree/java'
}
}
quicksyFreeCompat {
java {
srcDir 'src/freeCompat/java'
srcDir 'src/quicksyFree/java'
srcDirs 'src/freeCompat/java'
}
}
quicksyPlaystoreCompat {
java {
srcDir 'src/playstoreCompat/java'
srcDir 'src/quicksyPlaystore/java'
srcDirs 'src/playstoreCompat/java'
}
res {
srcDir 'src/playstoreCompat/res'
@ -172,28 +149,18 @@ android {
}
}
quicksyPlaystoreSystem {
java {
srcDir 'src/quicksyPlaystore/java'
}
res {
srcDir 'src/quicksyPlaystore/res'
}
}
conversationsFreeCompat {
java {
srcDir 'src/freeCompat/java'
srcDir 'src/conversationsFree/java'
}
}
conversationsFreeSystem {
java {
srcDir 'src/conversationsFree/java'
srcDirs 'src/freeCompat/java'
}
}
conversationsPlaystoreCompat {
java {
srcDir 'src/playstoreCompat/java'
srcDir 'src/conversationsPlaystore/java'
srcDirs 'src/playstoreCompat/java'
}
res {
srcDir 'src/playstoreCompat/res'
@ -201,9 +168,6 @@ android {
}
}
conversationsPlaystoreSystem {
java {
srcDir 'src/conversationsPlaystore/java'
}
res {
srcDir 'src/conversationsPlaystore/res'
}
@ -215,15 +179,18 @@ android {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
versionNameSuffix "r"
}
debug {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
versionNameSuffix "d"
}
}
if (new File("signing.properties").exists()) {
Properties props = new Properties()
props.load(new FileInputStream(file("signing.properties")))
@ -240,8 +207,7 @@ android {
}
lintOptions {
disable 'MissingTranslation', 'InvalidPackage', 'AppCompatResource'
abortOnError false
disable 'ExtraTranslation', 'MissingTranslation', 'InvalidPackage', 'MissingQuantity', 'AppCompatResource'
}
subprojects {

View File

@ -1,472 +0,0 @@
<?xml version="1.0"?>
<?xml-stylesheet href="../style.xsl" type="text/xsl"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<Project xmlns="http://usefulinc.com/ns/doap#"
xmlns:foaf="http://xmlns.com/foaf/0.1/"
xmlns:xmpp="https://linkmauve.fr/ns/xmpp-doap#"
xmlns:schema="https://schema.org/">
<name>Conversations</name>
<created>2014-01-14</created>
<shortdesc xml:lang="en">Android XMPP Client</shortdesc>
<description xml:lang="en">Conversations is an open source XMPP/Jabber client for the Android platform</description>
<homepage rdf:resource="https://conversations.im/"/>
<download-page rdf:resource="https://play.google.com/store/apps/details?id=eu.siacs.conversations"/>
<bug-database rdf:resource="https://github.com/iNPUTmice/Conversations/issues"/>
<!-- See https://github.com/ewilderj/doap/issues/53 -->
<developer-forum rdf:resource="xmpp:conversations@siacs.conference.eu?join"/>
<support-forum rdf:resource="xmpp:conversations@siacs.conference.eu?join"/>
<license rdf:resource="https://github.com/iNPUTmice/Conversations/blob/master/LICENSE"/>
<!-- See https://github.com/ewilderj/doap/issues/49 -->
<language>en</language>
<schema:logo rdf:resource="https://raw.githubusercontent.com/iNPUTmice/Conversations/master/art/ic_launcher.svg"/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/08.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/09.png'/>
<programming-language>Java</programming-language>
<os>Android</os>
<category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-xmpp"/>
<category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-jabber"/>
<category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-client"/>
<maintainer>
<foaf:Person>
<foaf:name>Daniel Gultsch</foaf:name>
<foaf:homepage rdf:resource="https://gultsch.de/"/>
</foaf:Person>
</maintainer>
<repository>
<GitRepository>
<browse rdf:resource="https://github.com/iNPUTmice/Conversations"/>
<location rdf:resource="https://github.com/iNPUTmice/Conversations.git"/>
</GitRepository>
</repository>
<implements rdf:resource="https://xmpp.org/rfcs/rfc6120.html"/>
<implements rdf:resource="https://xmpp.org/rfcs/rfc6121.html"/>
<implements rdf:resource="https://xmpp.org/rfcs/rfc6122.html"/>
<implements rdf:resource="https://xmpp.org/rfcs/rfc7590.html"/>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0027.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.4</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0030.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>2.5rc3</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0045.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.32.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0048.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0048.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0049.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.2</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0054.html"/>
<xmpp:status>partial</xmpp:status>
<xmpp:version>1.2</xmpp:version>
<xmpp:note xml:lang='en'>Avatars only</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0084.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1.3</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0085.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>2.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0092.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0115.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.5.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0153.html"/>
<xmpp:status>partial</xmpp:status>
<xmpp:version>1.1</xmpp:version>
<xmpp:note xml:lang='en'>Read only. Publication via XEP-0398</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0163.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.2.1</xmpp:version>
<xmpp:note>Avatar, Nick, OMEMO</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0166.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1.2</xmpp:version>
<xmpp:note>File transfer + A/V calls</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0167.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.2.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0172.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1</xmpp:version>
<xmpp:note>read only</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0176.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0184.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.4.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0191.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.3</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0198.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.6</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0199.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>2.0.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0199.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>2.0.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0215.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.7</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0223.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0234.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.19.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0237.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.3</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0245.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0249.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.2</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0260.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.3</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0261.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0280.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.13.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0293.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0294.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0308.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.2.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0313.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.6.3</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0319.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.2</xmpp:version>
<xmpp:note>opt-in</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0320.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0333.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.3</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0338.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0339.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0352.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.3.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0353.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.3.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0357.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.4.0</xmpp:version>
<xmpp:note>Only available in the version distributed over Google Play</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0363.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0368.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0377.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.2</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0384.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.3.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0391.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.1.2</xmpp:version>
<xmpp:since>2.5.8</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0392.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.6.0</xmpp:version>
<xmpp:since>2.3.1</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0393.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.1.4</xmpp:version>
<xmpp:since>1.22.0</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0396.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.1</xmpp:version>
<xmpp:since>2.5.8</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0398.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.2.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0410.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.1</xmpp:version>
<xmpp:since>2.5.4</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0411.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.2.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0454.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.1.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<release>
<Version>
<revision>2.9.13</revision>
<created>2021-05-03</created>
<file-release rdf:resource="https://github.com/iNPUTmice/Conversations/archive/2.9.13.tar.gz"/>
</Version>
</release>
</Project>
</rdf:RDF>

25
docs/MISSION.md Normal file
View File

@ -0,0 +1,25 @@
Conversations is a messenger for the next decade. Based on already established
internet standards that have been around for over ten years Conversations isnt
trying to replace current commercial messengers. It will simply outlive them.
Commercial, closed source products are coming and going. 15 years ago we had ICQ
which was replaced by Skype. MySpace was replaced by Facebook. WhatsApp and
Hangouts will disappear soon. Internet standards however stick around. People
are still using IRC and e-mail even though these protocols have been around for
decades. Utilizing proven standards doesnt mean one can not evolve. GMail has
revolutionized the way we look at e-mail. Firefox and Chrome have changed the
way we use the Web. Conversations will change the way we look at instant
messaging. Being less obtrusive than a telephone call instant messaging has
always played an important role in modern society. Conversations will show that
instant messaging can be fast, reliable and private. Conversations will not
force its security and privacy aspects upon the user. For those willing to use
encryption Conversations will make it as uncomplicated as possible. However
Conversations is aware that end-to-end encryption by the very principle isnt
trivial. Instead of trying the impossible and making encryption easier than
comparing a fingerprint Conversations will try to educate the willing user and
explain the necessary steps and the reasons behind them. Those unwilling to
learn about encryption will still be protected by the design principals of
Conversations. Conversations will simply not share or generate certain
information for example by encouraging the use of federated servers.
Conversations will always utilize the best available standards for encryption
and media encoding instead of reinventing the wheel. However it isnt afraid to
break with behavior patterns that have been proven ineffective.

32
docs/XEPs.md Normal file
View File

@ -0,0 +1,32 @@
* XEP-0027: Current Jabber OpenPGP Usage
* XEP-0030: Service Discovery
* XEP-0045: Multi-User Chat
* XEP-0048: Bookmarks
* XEP-0084: User Avatar
* XEP-0085: Chat State Notifications
* XEP-0092: Software Version
* XEP-0115: Entity Capabilities
* XEP-0163: Personal Eventing Protocol (avatars and nicks)
* XEP-0166: Jingle (only used for file transfer)
* XEP-0172: User Nickname
* XEP-0184: Message Delivery Receipts (reply only)
* XEP-0191: Blocking command
* XEP-0198: Stream Management
* XEP-0199: XMPP Ping
* XEP-0234: Jingle File Transfer
* XEP-0237: Roster Versioning
* XEP-0245: The /me Command
* XEP-0249: Direct MUC Invitations (receiving only)
* XEP-0260: Jingle SOCKS5 Bytestreams Transport Method
* XEP-0261: Jingle In-Band Bytestreams Transport Method
* XEP-0280: Message Carbons
* XEP-0308: Last Message Correction
* XEP-0313: Message Archive Management
* XEP-0319: Last User Interaction in Presence
* XEP-0333: Chat Markers
* XEP-0352: Client State Indication
* XEP-0357: Push Notifications
* XEP-0363: HTTP File Upload
* XEP-0368: SRV records for XMPP over TLS
* XEP-0377: Spam Reporting
* XEP-0384: OMEMO Encryption

97
docs/observations.md Normal file
View File

@ -0,0 +1,97 @@
Observations on implementing XMPP
=================================
After spending the last two and a half month basically writing my own XMPP
library from scratch I decided to share some of the observations I made in the
process. In part this article can be seen as a response to a blog post made by
Dr. Ing. Georg Lukas. The blog post introduces a couple of XEP (XMPP Extensions)
which make the life on mobile devices a lot easier but states that they are
currently very few implementations of those XEPs. So I went ahead and
implemented all of them in my Android XMPP client.
### General observations
The first thing I noticed is that XMPP is actually okish designed. If you were
to design a new chat protocol today you probably wouldnt choose XML again
however the protocol basically consists of only three different packages which
are quickly hidden under some sort of abstraction layer within your library.
Getting from zero to sending messages to other users actually was very simple
and straight forward. But then came the XEPs.
### Multi-User Chat
The first one was XEP-0045 Multi-User Chat. This is the one XEP of the XEPs Im
going to mention in my article which is actually wildly adopted. Most clients
and servers I know of support MUC. However the level of completeness varies.
MUC actually introduces access and permission roles which are far more complex
than what some of us are used to from IRC but a lot of clients just dont
implement them. Im not implementing them myself (at least for now) because I
somewhat doubt that someone would actually use them (however this might be some
sort of chicken or egg problem). I did find some strange bugs though which might
be interesting for other library developers. In theory a MUC server
implementation can allow a single user (same jid) to join a conference room
multiple times with the same nick from different clients. This means if someone
wants to participate in a conference from two different devices (mobile and
desktop for example) one wouldnt have to name oneself `userDesktop` and
`userMobile` but just `user`. Both ejabberd and prosody support this but with
strange side effects. Prosody for example doesnt allow a user to change its
name once two clients are “merged” by having the same nick.
### Carbons and Stream Management
Two of the other XEPs Lukas mentions — Carbons (XEP-0280) and Stream Management
(XEP-0198) — were actually fairly easy to implement. The only challenges were to
find a server to support them (I ended up running my own Prosody server) and a
desktop client to test them with. For carbons there is a patched Mcabber version
and Gajim. After implementing stream management I had very good results on my
mobile device. I had sessions running for up to 24 hours with a walking outside,
loosing mobile coverage for a few minutes and so on. The only limitation was
that I had to keep on developing and reinstalling my app.
### Off the record
And then came OTR... This is were I spend the most time debugging stuff and
trying to get things right and compatible with other clients. This is the part
were I want to help other developers not to make the same mistakes and maybe
come to some sort of consent among XMPP developers to ultimately increase the
interoperability. OTR has some down sides which make it difficult or at times
even dangerous to implement within XMPP. First of all it is a synchronous
protocol which is tunneled through a different protocol (XMPP). Synchronous
means — among other things — auto replies. (An OTR session begins with “hi Im
speaking otr give me your key” “ok cool here is my key”) And auto replies — we
know that since the first time an out of office auto responder went postal — are
dangerous. Things really start to get messy when you use one of the best
features of XMPP — multiple clients. The way XMPP works is that clients are
encouraged to send their messages to the raw jid and let the server decide what
full jid the messages are routed to. If in doubt even all of them. So what
happens when Alice sends a start-otr-message to Bobs raw jid? Bob receives the
message on his notebook as well as his cell phone. Both of them answer. Alice
gets two different replies. Shit explodes. Even if Alice sends the message to
bob/notebook chances are that Bob has carbon messages enabled and still receives
the messages on both devices. Now assuming that Bobs client is clever enough not
to auto reply to carbonated messages Bob/cellphone will still end up with a lot
of garbage messages. (Essentially the entire conversation between Alice and
Bob/notebook but unreadable of course) Therefor it should be good practice to
tag OTR messages as both private and no-copy (private is part of the carbons
XEP, no-copy is a general hint). I found that prosody for some reasons doesnt
honor the private tag on outgoing messages. While this is easily fixed I presume
that having both the private and the no-copy tag will make it more compatible
with servers or clients I dont know about yet.
#### Rules to follow when implementing OTR
To summarize my observations on implementing OTR in XMPP let me make the
following three statements.
1. While it is good practice for unencrypted messages to be send to the raw jid
and have the receiving server or user decide how they should be routed OTR
messages must be send to a specific resource. To make this work the user should
be given the option to select the presence (which can be assisted with some
educated guessing by the client based on previous messages). Furthermore a
client should encourage a user to choose meaningful presences instead of the
clients name or even random ones. Something like `/mobile`, `/notebook`,
`/desktop` is a greater assist to any one who wants to start an otr session then
`/Gajim`, `/mcabber` or `/pidgin`.
2. Messages should be tagged private and no-copy to avoid unnecessary traffic or
otr error loops with faulty clients. This tagging should be done even if your
own client doesnt support carbons.
3. When dealing with “legacy clients” — meaning clients which dont follow my
advise — a client should be extra careful not to create message loops. This
means to not respond with otr errors if a client is not 100% sure it is the only
client which received the message

View File

@ -1,3 +0,0 @@
android.useAndroidX=true
android.enableJetifier=true
org.gradle.jvmargs=-Xmx4096m

View File

@ -1,6 +1,5 @@
#Sat Nov 14 09:59:55 CET 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip

View File

@ -1,2 +0,0 @@
• bug fixes for peer to peer file transfer (Jingle)
• fixed server info for unlimited/unknown max file size

View File

@ -1 +0,0 @@
• stability improvements for group chats and channels

View File

@ -1,2 +0,0 @@
• allow backups to be restored from anywhere
• bug fixes

View File

@ -1,3 +0,0 @@
• fix light themen composing fied
• fixes for Jingle file transfer
• fixed some rare crashes

View File

@ -1,3 +0,0 @@
• fixed crash when scanning QR codes on Android 6 and lower
• when sharing a message from and to Conversations insert it as quote

View File

@ -1,3 +0,0 @@
• fixed connection issues over Tor
• P2P file transfer (Jingle) now offers direct candidates
• Support XEP-0396: Jingle Encrypted Transports - OMEMO

View File

@ -1,4 +0,0 @@
• Fixed crash on Android <5.0
• Fixed crash on Xiaomi devices running Android 8.0 + 8.1
• fixed minor security issues
• Share XMPP uri from channel search by long pressing a result

View File

@ -1 +0,0 @@
• Fixed crash on internal database update

View File

@ -1,3 +0,0 @@
• rename app 'Conversations with IPv6' to 'Conv6ations for Sum7'
• Jingle file transfer fixes
• Fixed OMEMO self healing (after backup restore) on servers w/o MAM

View File

@ -1,4 +0,0 @@
• Introduce expert setting to perform channel discovery on local server instead of [search.jabber.network](https://search.jabber.network)
• Enable delivery check marks by default and remove setting
• Enable Send button indicates status by default and remove setting
• Move Backup and Foreground Service settings to main screen

View File

@ -1 +0,0 @@
• changes in Networkstack 'let OS decide IPv4 or IPv6' to own 'Happy Eyeball' for faster connection and fair to both IP version.

View File

@ -1,3 +0,0 @@
• fixes for Jingle IBB file transfer
• fixes for repeated corrections filling up the database
• switched to Last Message Correction v1.1

View File

@ -1,4 +0,0 @@
• support for ?register and ?register;preauth XMPP uri parameters
• let users set their own nick name
• resume download of OMEMO encrypted files
• channels now use '#' as symbol in avatar

View File

@ -1 +0,0 @@
• Support automatic theme switching on Android 10

View File

@ -1,2 +0,0 @@
• improve logging of happy eyeball
• fix several NullPointer bugs

View File

@ -1,5 +0,0 @@
• Provide PDF preview on Android 5+
• Use 12 byte IVs for OMEMO
• Happy Eyeball: cacheing of addresses
• Happy Eyeball: fix DNSSEC im special cases and NPE
• Happy Eyeball: revert dns-server selection

View File

@ -1,2 +0,0 @@
• Fix avatar selection on some Android 10 devices
• Fix file transfer for larger files

View File

@ -1,2 +0,0 @@
• Audio/Video calls (Requires server support in form of STUN and TURN servers discoverable via XEP-0215)
• Rename App to only Conv6sation

View File

@ -1,2 +0,0 @@
• Audible feedback (dialing, call started, call ended) for voice calls.
• Fixed issue with retrying failed video call

View File

@ -1,2 +0,0 @@
• Add button to switch camea during video call
• Fixed voice calls on tablets

View File

@ -1,3 +0,0 @@
• Move call icon to the left in order to keep other toolbar icons in a consistent place
• Show call duration during audio calls
• Tie breaking for A/V calls (the same two people calling each other at the same time)

View File

@ -1,2 +0,0 @@
• Rework Login with certificate UI
• Add ability to pin chats on top (add to favorites)

View File

@ -1,3 +0,0 @@
• Reduce echo during calls on some devices
• Fix login when passwords contains special characters
• Play dial and busy tones on speaker during video calls

View File

@ -1 +0,0 @@
• Offer to record voice message when callee is busy

View File

@ -1,3 +0,0 @@
• Show help button if A/V call fails
• Fixed some annoying crashes
• Fixed Jingle connections (file transfer + calls) with bare JIDs

View File

@ -1,2 +0,0 @@
• Fixed notifications not showing up under certain conditions
• Fixed compatibility issues and crashes related to A/V calls

View File

@ -1,3 +0,0 @@
• add 'Return to chat' to audio call screen
• Improve keyboard shortcuts
• bug fixes

View File

@ -1,3 +0,0 @@
• Handle GPX files
• Improve performance for backup restore
• bug fixes

View File

@ -1 +0,0 @@
• WebRTC update (with security fixes)

View File

@ -1,4 +0,0 @@
• Search individual conversations
• Notify user if message delivery fails
• Remember display names (nicks) from Quicksy users across restarts
• Add button to start Orbot (Tor) from notification if necessary

View File

@ -1,3 +0,0 @@
• Offer Easy Invite generation on supporting servers
• Display GIFs send from Movim
• store avatars in cache

View File

@ -1,4 +0,0 @@
• Fixed connectivity issues when different accounts used different SCRAM mechanisms
• Add support for SCRAM-SHA-512
• Allow P2P (Jingle) file transfer with self contact
• minor stability improvements for A/V calls

View File

@ -1,3 +0,0 @@
• Show call button for offline contacts if they previously announced support
• Back button no longer ends call when call is connected
• bug fixes

View File

@ -1 +0,0 @@
• fix crashes (error on internal database migration)

View File

@ -1,4 +0,0 @@
• Ability to select incoming call ringtone
• Fix OpenPGP key id discovery for OpenKeychain 5.6+
• Properly verify punycode TLS certificates
• Improve stability of RTP session establishment (calling)

View File

@ -1,2 +0,0 @@
• Verify A/V calls with preexisting OMEMO sessions
• Improve compatibility with non libwebrtc WebRTC implementations

View File

@ -1 +0,0 @@
• Various bug fixes around Tor support

View File

@ -1,3 +0,0 @@
• Improve call compatibility with Dino
• fix HTTP up/download for users that dont trust system CAs
• Fixed 'No Connectivity' issues on Android 7.1

View File

@ -1,3 +0,0 @@
• Always verify domain name. No user overwrite
• Support roster pre authentication
• minor A/V improvements

View File

@ -1,3 +0,0 @@
• Show black bars when remote video does not match aspect ratio of screen
• Improve search performance
• Add setting to prevent screenshots

View File

@ -1,2 +0,0 @@
• Fix issue with some videos not being compressed
• Fix rare crash when opening notification

View File

@ -1,2 +0,0 @@
• Fix crash when rendering some quotes
• Fix crash in welcome screen

View File

@ -1 +0,0 @@
• Fix usage directTLS of manuelle enter an address

View File

@ -1,45 +0,0 @@
Conv6ations for Sum7 is a fork of <a href="https://f-droid.org/packages/eu.siacs.conversations/">Conversations</a>.
Changes to origin:
• replace the hardcoded IPv4 preference to easy Happy Eyeball, for faster connection and fair to both IP version.
• rebrands it as chat.sum7.eu (to run both version together)
Easy to use, reliable, battery friendly. With built-in support for images, group chats and e2e encryption.
Design principles:
• Be as beautiful and easy to use as possible without sacrificing security or privacy
• Rely on existing, well established protocols
• Do not require a Google Account or specifically Google Cloud Messaging (GCM)
• Require as few permissions as possible
Features:
• End-to-end encryption with either <a href="https://conversations.im/omemo/">OMEMO</a> or <a href="https://openpgp.org/about/">OpenPGP</a>
• Sending and receiving images
• Encrypted audio and video calls (DLTS-SRTP)
• Intuitive UI that follows Android Design guidelines
• Pictures / Avatars for your Contacts
• Syncs with desktop client
• Conferences (with support for bookmarks)
• Address book integration
• Multiple accounts / unified inbox
• Very low impact on battery life
Conversations makes it very easy to create an account on the chat.sum7.eu server. However Conversations will work with any other XMPP server as well. A lot of XMPP servers are run by volunteers and are free of charge.
XMPP Features:
Conversations works with every XMPP server out there. However XMPP is an extensible protocol. These extensions are standardized as well in so called XEPs. Conversations supports a couple of those to make the overall user experience better. There is a chance that your current XMPP server does not support these extensions. Therefore to get the most out of Conversations you should consider either switching to an XMPP server that does or - even better - run your own XMPP server for you and your friends.
These XEPs are - as of now:
• XEP-0065: SOCKS5 Bytestreams (or mod_proxy65). Will be used to transfer files if both parties are behind a firewall (NAT).
• XEP-0163: Personal Eventing Protocol for avatars
• XEP-0191: Blocking command lets you blacklist spammers or block contacts without removing them from your roster.
• XEP-0198: Stream Management allows XMPP to survive small network outages and changes of the underlying TCP connection.
• XEP-0280: Message Carbons which automatically syncs the messages you send to your desktop client and thus allows you to switch seamlessly from your mobile client to your desktop client and back within one conversation.
• XEP-0237: Roster Versioning mainly to save bandwidth on poor mobile connections
• XEP-0313: Message Archive Management synchronize message history with the server. Catch up with messages that were sent while Conversations was offline.
• XEP-0352: Client State Indication lets the server know whether or not Conversations is in the background. Allows the server to save bandwidth by withholding unimportant packages.
• XEP-0363: HTTP File Upload allows you to share files in conferences and with offline contacts. Requires an additional component on your server.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@ -1 +0,0 @@
A Jabber/XMPP chat client which is fair to IPv6

53
proguard-rules.pro vendored
View File

@ -1,6 +1,6 @@
-dontobfuscate
-keep class eu.sum7.conversations.**
-keep class eu.siacs.conversations.**
-keep class org.whispersystems.**
@ -11,60 +11,9 @@
-keep class com.google.android.gms.**
-keep class org.openintents.openpgp.*
-keep class org.webrtc.** { *; }
-dontwarn javax.mail.internet.MimeMessage
-dontwarn javax.mail.internet.MimeBodyPart
-dontwarn javax.mail.internet.SharedInputStream
-dontwarn javax.activation.DataContentHandler
-dontwarn org.bouncycastle.mail.**
-dontwarn org.bouncycastle.x509.util.LDAPStoreHelper
-dontwarn org.bouncycastle.jce.provider.X509LDAPCertStoreSpi
-dontwarn org.bouncycastle.cert.dane.**
-dontwarn rocks.xmpp.addr.**
-dontwarn com.google.firebase.analytics.connector.AnalyticsConnector
-dontwarn java.lang.**
-dontwarn javax.lang.**
-dontwarn com.android.org.conscrypt.SSLParametersImpl
-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE
-keepclassmembers class eu.siacs.conversations.http.services.** {
!transient <fields>;
}
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod
# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}
# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**
# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit
# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*
# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>

BIN
screenshots.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

View File

@ -1,10 +1,9 @@
package eu.siacs.conversations.ui.widget;
import android.content.Context;
import android.support.text.emoji.widget.EmojiAppCompatEditText;
import android.util.AttributeSet;
import androidx.emoji.widget.EmojiAppCompatEditText;
public class EmojiWrapperEditText extends EmojiAppCompatEditText {
public EmojiWrapperEditText(Context context) {

View File

@ -29,7 +29,7 @@
package eu.siacs.conversations.utils;
import androidx.emoji.text.EmojiCompat;
import android.support.text.emoji.EmojiCompat;
public class EmojiWrapper {

View File

@ -11,71 +11,11 @@
<activity
android:name=".ui.WelcomeActivity"
android:label="@string/app_name"
android:launchMode="singleTask" />
<activity
android:name=".ui.PickServerActivity"
android:label="@string/create_new_account"
android:launchMode="singleTask" />
android:launchMode="singleTask"/>
<activity
android:name=".ui.MagicCreateActivity"
android:label="@string/create_new_account"
android:launchMode="singleTask" />
<activity
android:name=".ui.EasyOnboardingInviteActivity"
android:label="@string/invite_to_app"
android:launchMode="singleTask" />
<activity
android:name=".ui.ImportBackupActivity"
android:label="@string/restore_backup"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
android:label="@string/create_account"
android:launchMode="singleTask"/>
<data android:mimeType="application/vnd.conversations.backup" />
<data android:scheme="content" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/vnd.conversations.backup" />
<data android:scheme="file" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="content" />
<data android:host="*" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.ceb" />
<data android:pathPattern=".*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.ceb" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:host="*" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.ceb" />
<data android:pathPattern=".*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.ceb" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -1,50 +0,0 @@
package eu.siacs.conversations.entities;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.SerializedName;
import eu.siacs.conversations.xmpp.Jid;
public class AccountConfiguration {
private static final Gson GSON = new GsonBuilder().create();
public Protocol protocol;
public String address;
public String password;
public Jid getJid() {
return Jid.ofEscaped(address);
}
public static AccountConfiguration parse(final String input) {
final AccountConfiguration c;
try {
c = GSON.fromJson(input, AccountConfiguration.class);
} catch (JsonSyntaxException e) {
throw new IllegalArgumentException("Not a valid JSON string", e);
}
Preconditions.checkArgument(
c.protocol == Protocol.XMPP,
"Protocol must be XMPP"
);
Preconditions.checkArgument(
c.address != null && c.getJid().isBareJid() && !c.getJid().isDomainJid(),
"Invalid XMPP address"
);
Preconditions.checkArgument(
c.password != null && c.password.length() > 0,
"No password specified"
);
return c;
}
public enum Protocol {
@SerializedName("xmpp") XMPP,
}
}

View File

@ -1,387 +0,0 @@
package eu.siacs.conversations.services;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.provider.OpenableColumns;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import com.google.common.base.Charsets;
import com.google.common.base.Stopwatch;
import com.google.common.io.CountingInputStream;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.io.CipherInputStream;
import org.bouncycastle.crypto.modes.AEADBlockCipher;
import org.bouncycastle.crypto.modes.GCMBlockCipher;
import org.bouncycastle.crypto.params.AEADParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipException;
import javax.crypto.BadPaddingException;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.persistance.DatabaseBackend;
import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.ui.ManageAccountActivity;
import eu.siacs.conversations.utils.BackupFileHeader;
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
import eu.siacs.conversations.xmpp.Jid;
public class ImportBackupService extends Service {
private static final int NOTIFICATION_ID = 21;
private static final AtomicBoolean running = new AtomicBoolean(false);
private final ImportBackupServiceBinder binder = new ImportBackupServiceBinder();
private final SerialSingleThreadExecutor executor = new SerialSingleThreadExecutor(getClass().getSimpleName());
private final Set<OnBackupProcessed> mOnBackupProcessedListeners = Collections.newSetFromMap(new WeakHashMap<>());
private DatabaseBackend mDatabaseBackend;
private NotificationManager notificationManager;
private static int count(String input, char c) {
int count = 0;
for (char aChar : input.toCharArray()) {
if (aChar == c) {
++count;
}
}
return count;
}
@Override
public void onCreate() {
mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext());
notificationManager = (android.app.NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
return START_NOT_STICKY;
}
final String password = intent.getStringExtra("password");
final Uri data = intent.getData();
final Uri uri;
if (data == null) {
final String file = intent.getStringExtra("file");
uri = file == null ? null : Uri.fromFile(new File(file));
} else {
uri = data;
}
if (password == null || password.isEmpty() || uri == null) {
return START_NOT_STICKY;
}
if (running.compareAndSet(false, true)) {
executor.execute(() -> {
startForegroundService();
final boolean success = importBackup(uri, password);
stopForeground(true);
running.set(false);
if (success) {
notifySuccess();
}
stopSelf();
});
} else {
Log.d(Config.LOGTAG, "backup already running");
}
return START_NOT_STICKY;
}
public boolean getLoadingState() {
return running.get();
}
public void loadBackupFiles(final OnBackupFilesLoaded onBackupFilesLoaded) {
executor.execute(() -> {
final List<Jid> accounts = mDatabaseBackend.getAccountJids(false);
final ArrayList<BackupFile> backupFiles = new ArrayList<>();
final Set<String> apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name)));
for (String app : apps) {
final File directory = new File(FileBackend.getBackupDirectory(app));
if (!directory.exists() || !directory.isDirectory()) {
Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath());
continue;
}
final File[] files = directory.listFiles();
if (files == null) {
onBackupFilesLoaded.onBackupFilesLoaded(backupFiles);
return;
}
for (final File file : files) {
if (file.isFile() && file.getName().endsWith(".ceb")) {
try {
final BackupFile backupFile = BackupFile.read(file);
if (accounts.contains(backupFile.getHeader().getJid())) {
Log.d(Config.LOGTAG, "skipping backup for " + backupFile.getHeader().getJid());
} else {
backupFiles.add(backupFile);
}
} catch (IOException | IllegalArgumentException e) {
Log.d(Config.LOGTAG, "unable to read backup file ", e);
}
}
}
}
Collections.sort(backupFiles, (a, b) -> a.header.getJid().toString().compareTo(b.header.getJid().toString()));
onBackupFilesLoaded.onBackupFilesLoaded(backupFiles);
});
}
private void startForegroundService() {
startForeground(NOTIFICATION_ID, createImportBackupNotification(1, 0));
}
private void updateImportBackupNotification(final long total, final long current) {
final int max;
final int progress;
if (total == 0) {
max = 1;
progress = 0;
} else {
max = 100;
progress = (int) (current * 100 / total);
}
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
try {
notificationManager.notify(NOTIFICATION_ID, createImportBackupNotification(max, progress));
} catch (final RuntimeException e) {
Log.d(Config.LOGTAG, "unable to make notification", e);
}
}
private Notification createImportBackupNotification(final int max, final int progress) {
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
mBuilder.setContentTitle(getString(R.string.restoring_backup))
.setSmallIcon(R.drawable.ic_unarchive_white_24dp)
.setProgress(max, progress, max == 1 && progress == 0);
return mBuilder.build();
}
private boolean importBackup(final Uri uri, final String password) {
Log.d(Config.LOGTAG, "importing backup from " + uri);
final Stopwatch stopwatch = Stopwatch.createStarted();
try {
final SQLiteDatabase db = mDatabaseBackend.getWritableDatabase();
final InputStream inputStream;
final String path = uri.getPath();
final long fileSize;
if ("file".equals(uri.getScheme()) && path != null) {
final File file = new File(path);
inputStream = new FileInputStream(file);
fileSize = file.length();
} else {
final Cursor returnCursor = getContentResolver().query(uri, null, null, null, null);
if (returnCursor == null) {
fileSize = 0;
} else {
returnCursor.moveToFirst();
fileSize = returnCursor.getLong(returnCursor.getColumnIndex(OpenableColumns.SIZE));
returnCursor.close();
}
inputStream = getContentResolver().openInputStream(uri);
}
if (inputStream == null) {
synchronized (mOnBackupProcessedListeners) {
for (final OnBackupProcessed l : mOnBackupProcessedListeners) {
l.onBackupRestoreFailed();
}
}
return false;
}
final CountingInputStream countingInputStream = new CountingInputStream(inputStream);
final DataInputStream dataInputStream = new DataInputStream(countingInputStream);
final BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
Log.d(Config.LOGTAG, backupFileHeader.toString());
if (mDatabaseBackend.getAccountJids(false).contains(backupFileHeader.getJid())) {
synchronized (mOnBackupProcessedListeners) {
for (OnBackupProcessed l : mOnBackupProcessedListeners) {
l.onAccountAlreadySetup();
}
}
return false;
}
final byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt());
final AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
cipher.init(false, new AEADParameters(new KeyParameter(key), 128, backupFileHeader.getIv()));
final CipherInputStream cipherInputStream = new CipherInputStream(countingInputStream, cipher);
final GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream);
final BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, Charsets.UTF_8));
db.beginTransaction();
String line;
StringBuilder multiLineQuery = null;
while ((line = reader.readLine()) != null) {
int count = count(line, '\'');
if (multiLineQuery != null) {
multiLineQuery.append('\n');
multiLineQuery.append(line);
if (count % 2 == 1) {
db.execSQL(multiLineQuery.toString());
multiLineQuery = null;
updateImportBackupNotification(fileSize, countingInputStream.getCount());
}
} else {
if (count % 2 == 0) {
db.execSQL(line);
updateImportBackupNotification(fileSize, countingInputStream.getCount());
} else {
multiLineQuery = new StringBuilder(line);
}
}
}
db.setTransactionSuccessful();
db.endTransaction();
final Jid jid = backupFileHeader.getJid();
final Cursor countCursor = db.rawQuery("select count(messages.uuid) from messages join conversations on conversations.uuid=messages.conversationUuid join accounts on conversations.accountUuid=accounts.uuid where accounts.username=? and accounts.server=?", new String[]{jid.getEscapedLocal(), jid.getDomain().toEscapedString()});
countCursor.moveToFirst();
final int count = countCursor.getInt(0);
Log.d(Config.LOGTAG, String.format("restored %d messages in %s", count, stopwatch.stop().toString()));
countCursor.close();
stopBackgroundService();
synchronized (mOnBackupProcessedListeners) {
for (OnBackupProcessed l : mOnBackupProcessedListeners) {
l.onBackupRestored();
}
}
return true;
} catch (final Exception e) {
final Throwable throwable = e.getCause();
final boolean reasonWasCrypto = throwable instanceof BadPaddingException || e instanceof ZipException;
synchronized (mOnBackupProcessedListeners) {
for (OnBackupProcessed l : mOnBackupProcessedListeners) {
if (reasonWasCrypto) {
l.onBackupDecryptionFailed();
} else {
l.onBackupRestoreFailed();
}
}
}
Log.d(Config.LOGTAG, "error restoring backup " + uri, e);
return false;
}
}
private void notifySuccess() {
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
mBuilder.setContentTitle(getString(R.string.notification_restored_backup_title))
.setContentText(getString(R.string.notification_restored_backup_subtitle))
.setAutoCancel(true)
.setContentIntent(PendingIntent.getActivity(this, 145, new Intent(this, ManageAccountActivity.class), PendingIntent.FLAG_UPDATE_CURRENT))
.setSmallIcon(R.drawable.ic_unarchive_white_24dp);
notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
}
private void stopBackgroundService() {
Intent intent = new Intent(this, XmppConnectionService.class);
stopService(intent);
}
public void removeOnBackupProcessedListener(OnBackupProcessed listener) {
synchronized (mOnBackupProcessedListeners) {
mOnBackupProcessedListeners.remove(listener);
}
}
public void addOnBackupProcessedListener(OnBackupProcessed listener) {
synchronized (mOnBackupProcessedListeners) {
mOnBackupProcessedListeners.add(listener);
}
}
@Override
public IBinder onBind(Intent intent) {
return this.binder;
}
public interface OnBackupFilesLoaded {
void onBackupFilesLoaded(List<BackupFile> files);
}
public interface OnBackupProcessed {
void onBackupRestored();
void onBackupDecryptionFailed();
void onBackupRestoreFailed();
void onAccountAlreadySetup();
}
public static class BackupFile {
private final Uri uri;
private final BackupFileHeader header;
private BackupFile(Uri uri, BackupFileHeader header) {
this.uri = uri;
this.header = header;
}
private static BackupFile read(File file) throws IOException {
final FileInputStream fileInputStream = new FileInputStream(file);
final DataInputStream dataInputStream = new DataInputStream(fileInputStream);
BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
fileInputStream.close();
return new BackupFile(Uri.fromFile(file), backupFileHeader);
}
public static BackupFile read(final Context context, final Uri uri) throws IOException {
final InputStream inputStream = context.getContentResolver().openInputStream(uri);
if (inputStream == null) {
throw new FileNotFoundException();
}
final DataInputStream dataInputStream = new DataInputStream(inputStream);
BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
inputStream.close();
return new BackupFile(uri, backupFileHeader);
}
public BackupFileHeader getHeader() {
return header;
}
public Uri getUri() {
return uri;
}
}
public class ImportBackupServiceBinder extends Binder {
public ImportBackupService getService() {
return ImportBackupService.this;
}
}
}

View File

@ -1,10 +1,5 @@
package eu.siacs.conversations.services;
import android.content.Intent;
import android.util.Log;
import eu.siacs.conversations.Config;
public class QuickConversationsService extends AbstractQuickConversationsService {
QuickConversationsService(XmppConnectionService xmppConnectionService) {
@ -30,9 +25,4 @@ public class QuickConversationsService extends AbstractQuickConversationsService
public void considerSyncBackground(boolean force) {
}
@Override
public void handleSmsReceived(Intent intent) {
Log.d(Config.LOGTAG,"ignoring received SMS");
}
}

View File

@ -1,151 +0,0 @@
package eu.siacs.conversations.ui;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.databinding.DataBindingUtil;
import com.google.common.base.Strings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.ActivityEasyInviteBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.BarcodeProvider;
import eu.siacs.conversations.utils.EasyOnboardingInvite;
import eu.siacs.conversations.xmpp.Jid;
public class EasyOnboardingInviteActivity extends XmppActivity implements EasyOnboardingInvite.OnInviteRequested {
private ActivityEasyInviteBinding binding;
private EasyOnboardingInvite easyOnboardingInvite;
@Override
public void onCreate(final Bundle bundle) {
super.onCreate(bundle);
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_easy_invite);
setSupportActionBar(binding.toolbar);
configureActionBar(getSupportActionBar(), true);
this.binding.shareButton.setOnClickListener(v -> share());
if (bundle != null && bundle.containsKey("invite")) {
this.easyOnboardingInvite = bundle.getParcelable("invite");
if (this.easyOnboardingInvite != null) {
showInvite(this.easyOnboardingInvite);
return;
}
}
this.showLoading();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.easy_onboarding_invite, menu);
final MenuItem share = menu.findItem(R.id.action_share);
share.setVisible(easyOnboardingInvite != null);
return super.onCreateOptionsMenu(menu);
}
public boolean onOptionsItemSelected(MenuItem menuItem) {
if (menuItem.getItemId() == R.id.action_share) {
share();
return true;
} else {
return super.onOptionsItemSelected(menuItem);
}
}
private void share() {
final String shareText = getString(
R.string.easy_invite_share_text,
easyOnboardingInvite.getDomain(),
easyOnboardingInvite.getShareableLink()
);
final Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, shareText);
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, getString(R.string.share_invite_with)));
}
@Override
protected void refreshUiReal() {
invalidateOptionsMenu();
if (easyOnboardingInvite != null) {
showInvite(easyOnboardingInvite);
} else {
showLoading();
}
}
private void showLoading() {
this.binding.inProgress.setVisibility(View.VISIBLE);
this.binding.invite.setVisibility(View.GONE);
}
private void showInvite(final EasyOnboardingInvite invite) {
this.binding.inProgress.setVisibility(View.GONE);
this.binding.invite.setVisibility(View.VISIBLE);
this.binding.tapToShare.setText(getString(R.string.tap_share_button_send_invite, invite.getDomain()));
final Point size = new Point();
getWindowManager().getDefaultDisplay().getSize(size);
final int width = Math.min(size.x, size.y);
final Bitmap bitmap = BarcodeProvider.create2dBarcodeBitmap(invite.getShareableLink(), width);
binding.qrCode.setImageBitmap(bitmap);
}
@Override
public void onSaveInstanceState(Bundle bundle) {
super.onSaveInstanceState(bundle);
if (easyOnboardingInvite != null) {
bundle.putParcelable("invite", easyOnboardingInvite);
}
}
@Override
void onBackendConnected() {
if (easyOnboardingInvite != null) {
return;
}
final Intent launchIntent = getIntent();
final String accountExtra = launchIntent.getStringExtra(EXTRA_ACCOUNT);
final Jid jid = accountExtra == null ? null : Jid.ofEscaped(accountExtra);
if (jid == null) {
return;
}
final Account account = xmppConnectionService.findAccountByJid(jid);
xmppConnectionService.requestEasyOnboardingInvite(account, this);
}
public static void launch(final Account account, final Activity context) {
final Intent intent = new Intent(context, EasyOnboardingInviteActivity.class);
intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
context.startActivity(intent);
}
@Override
public void inviteRequested(EasyOnboardingInvite invite) {
this.easyOnboardingInvite = invite;
Log.d(Config.LOGTAG, "invite requested");
refreshUi();
}
@Override
public void inviteRequestFailed(final String message) {
runOnUiThread(() -> {
if (!Strings.isNullOrEmpty(message)) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
finish();
});
}
}

View File

@ -1,249 +0,0 @@
package eu.siacs.conversations.ui;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.databinding.DataBindingUtil;
import com.google.android.material.snackbar.Snackbar;
import java.io.IOException;
import java.util.List;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.ActivityImportBackupBinding;
import eu.siacs.conversations.databinding.DialogEnterPasswordBinding;
import eu.siacs.conversations.services.ImportBackupService;
import eu.siacs.conversations.ui.adapter.BackupFileAdapter;
import eu.siacs.conversations.ui.util.SettingsUtils;
import eu.siacs.conversations.utils.ThemeHelper;
public class ImportBackupActivity extends ActionBarActivity implements ServiceConnection, ImportBackupService.OnBackupFilesLoaded, BackupFileAdapter.OnItemClickedListener, ImportBackupService.OnBackupProcessed {
private ActivityImportBackupBinding binding;
private BackupFileAdapter backupFileAdapter;
private ImportBackupService service;
private boolean mLoadingState = false;
private int mTheme;
@Override
protected void onCreate(final Bundle savedInstanceState) {
this.mTheme = ThemeHelper.find(this);
setTheme(this.mTheme);
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_import_backup);
setSupportActionBar(binding.toolbar);
setLoadingState(savedInstanceState != null && savedInstanceState.getBoolean("loading_state", false));
this.backupFileAdapter = new BackupFileAdapter();
this.binding.list.setAdapter(this.backupFileAdapter);
this.backupFileAdapter.setOnItemClickedListener(this);
}
@Override
protected void onResume(){
super.onResume();
SettingsUtils.applyScreenshotPreventionSetting(this);
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.import_backup, menu);
final MenuItem openBackup = menu.findItem(R.id.action_open_backup_file);
openBackup.setVisible(!this.mLoadingState);
return true;
}
@Override
public void onSaveInstanceState(Bundle bundle) {
bundle.putBoolean("loading_state", this.mLoadingState);
super.onSaveInstanceState(bundle);
}
@Override
public void onStart() {
super.onStart();
final int theme = ThemeHelper.find(this);
if (this.mTheme != theme) {
recreate();
} else {
bindService(new Intent(this, ImportBackupService.class), this, Context.BIND_AUTO_CREATE);
}
final Intent intent = getIntent();
if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction()) && !this.mLoadingState) {
Uri uri = intent.getData();
if (uri != null) {
openBackupFileFromUri(uri, true);
}
}
}
@Override
public void onStop() {
super.onStop();
if (this.service != null) {
this.service.removeOnBackupProcessedListener(this);
}
unbindService(this);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
ImportBackupService.ImportBackupServiceBinder binder = (ImportBackupService.ImportBackupServiceBinder) service;
this.service = binder.getService();
this.service.addOnBackupProcessedListener(this);
setLoadingState(this.service.getLoadingState());
this.service.loadBackupFiles(this);
}
@Override
public void onServiceDisconnected(ComponentName name) {
this.service = null;
}
@Override
public void onBackupFilesLoaded(final List<ImportBackupService.BackupFile> files) {
runOnUiThread(() -> backupFileAdapter.setFiles(files));
}
@Override
public void onClick(final ImportBackupService.BackupFile backupFile) {
showEnterPasswordDialog(backupFile, false);
}
private void openBackupFileFromUri(final Uri uri, final boolean finishOnCancel) {
try {
final ImportBackupService.BackupFile backupFile = ImportBackupService.BackupFile.read(this, uri);
showEnterPasswordDialog(backupFile, finishOnCancel);
} catch (final IOException | IllegalArgumentException e) {
Log.d(Config.LOGTAG, "unable to open backup file " + uri, e);
Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG).show();
}
}
private void showEnterPasswordDialog(final ImportBackupService.BackupFile backupFile, final boolean finishOnCancel) {
final DialogEnterPasswordBinding enterPasswordBinding = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.dialog_enter_password, null, false);
Log.d(Config.LOGTAG, "attempting to import " + backupFile.getUri());
enterPasswordBinding.explain.setText(getString(R.string.enter_password_to_restore, backupFile.getHeader().getJid().toString()));
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setView(enterPasswordBinding.getRoot());
builder.setTitle(R.string.enter_password);
builder.setNegativeButton(R.string.cancel, (dialog, which) -> {
if (finishOnCancel) {
finish();
}
});
builder.setPositiveButton(R.string.restore, null);
builder.setCancelable(false);
final AlertDialog dialog = builder.create();
dialog.setOnShowListener((d) -> {
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> {
final String password = enterPasswordBinding.accountPassword.getEditableText().toString();
if (password.isEmpty()) {
enterPasswordBinding.accountPasswordLayout.setError(getString(R.string.please_enter_password));
return;
}
final Uri uri = backupFile.getUri();
Intent intent = new Intent(this, ImportBackupService.class);
intent.setAction(Intent.ACTION_SEND);
intent.putExtra("password", password);
if ("file".equals(uri.getScheme())) {
intent.putExtra("file", uri.getPath());
} else {
intent.setData(uri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
setLoadingState(true);
ContextCompat.startForegroundService(this, intent);
d.dismiss();
});
});
dialog.show();
}
private void setLoadingState(final boolean loadingState) {
binding.coordinator.setVisibility(loadingState ? View.GONE : View.VISIBLE);
binding.inProgress.setVisibility(loadingState ? View.VISIBLE : View.GONE);
setTitle(loadingState ? R.string.restoring_backup : R.string.restore_backup);
configureActionBar(getSupportActionBar(), !loadingState);
this.mLoadingState = loadingState;
invalidateOptionsMenu();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode == RESULT_OK) {
if (requestCode == 0xbac) {
openBackupFileFromUri(intent.getData(), false);
}
}
}
@Override
public void onAccountAlreadySetup() {
runOnUiThread(() -> {
setLoadingState(false);
Snackbar.make(binding.coordinator, R.string.account_already_setup, Snackbar.LENGTH_LONG).show();
});
}
@Override
public void onBackupRestored() {
runOnUiThread(() -> {
Intent intent = new Intent(this, ConversationActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
});
}
@Override
public void onBackupDecryptionFailed() {
runOnUiThread(() -> {
setLoadingState(false);
Snackbar.make(binding.coordinator, R.string.unable_to_decrypt_backup, Snackbar.LENGTH_LONG).show();
});
}
@Override
public void onBackupRestoreFailed() {
runOnUiThread(() -> {
setLoadingState(false);
Snackbar.make(binding.coordinator, R.string.unable_to_restore_backup, Snackbar.LENGTH_LONG).show();
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_open_backup_file) {
openBackupFile();
return true;
}
return super.onOptionsItemSelected(item);
}
private void openBackupFile() {
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(Intent.createChooser(intent, getString(R.string.open_backup)), 0xbac);
}
}

View File

@ -6,157 +6,111 @@ import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.databinding.DataBindingUtil;
import java.security.SecureRandom;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.MagicCreateBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.InstallReferrerUtils;
import eu.siacs.conversations.xmpp.Jid;
import rocks.xmpp.addr.Jid;
public class MagicCreateActivity extends XmppActivity implements TextWatcher {
public static final String EXTRA_DOMAIN = "domain";
public static final String EXTRA_PRE_AUTH = "pre_auth";
public static final String EXTRA_USERNAME = "username";
private TextView mFullJidDisplay;
private EditText mUsername;
private MagicCreateBinding binding;
private String domain;
private String username;
private String preAuth;
@Override
protected void refreshUiReal() {
@Override
protected void refreshUiReal() {
}
}
@Override
void onBackendConnected() {
@Override
void onBackendConnected() {
}
}
@Override
public void onStart() {
super.onStart();
final int theme = findTheme();
if (this.mTheme != theme) {
recreate();
}
}
@Override
public void onStart() {
super.onStart();
final int theme = findTheme();
if (this.mTheme != theme) {
recreate();
}
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
if (getResources().getBoolean(R.bool.portrait_only)) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.magic_create);
setSupportActionBar(findViewById(R.id.toolbar));
configureActionBar(getSupportActionBar());
mFullJidDisplay = findViewById(R.id.full_jid);
mUsername = findViewById(R.id.username);
Button next = findViewById(R.id.create_account);
next.setOnClickListener(v -> {
try {
String username = mUsername.getText().toString();
Jid jid = Jid.of(username.toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null);
if (!jid.getEscapedLocal().equals(jid.getLocal())|| username.length() < 3) {
mUsername.setError(getString(R.string.invalid_username));
mUsername.requestFocus();
} else {
mUsername.setError(null);
Account account = xmppConnectionService.findAccountByJid(jid);
if (account == null) {
account = new Account(jid, CryptoHelper.createPassword(new SecureRandom()));
account.setOption(Account.OPTION_REGISTER, true);
account.setOption(Account.OPTION_DISABLED, true);
account.setOption(Account.OPTION_MAGIC_CREATE, true);
xmppConnectionService.createAccount(account);
}
Intent intent = new Intent(MagicCreateActivity.this, EditAccountActivity.class);
intent.putExtra("jid", account.getJid().asBareJid().toString());
intent.putExtra("init", true);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
Toast.makeText(MagicCreateActivity.this, R.string.secure_password_generated, Toast.LENGTH_SHORT).show();
StartConversationActivity.addInviteUri(intent, getIntent());
startActivity(intent);
}
} catch (IllegalArgumentException e) {
mUsername.setError(getString(R.string.invalid_username));
mUsername.requestFocus();
}
});
mUsername.addTextChangedListener(this);
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
final Intent data = getIntent();
this.domain = data == null ? null : data.getStringExtra(EXTRA_DOMAIN);
this.preAuth = data == null ? null : data.getStringExtra(EXTRA_PRE_AUTH);
this.username = data == null ? null : data.getStringExtra(EXTRA_USERNAME);
if (getResources().getBoolean(R.bool.portrait_only)) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
super.onCreate(savedInstanceState);
this.binding = DataBindingUtil.setContentView(this, R.layout.magic_create);
setSupportActionBar(this.binding.toolbar);
configureActionBar(getSupportActionBar(), this.domain == null);
if (username != null && domain != null) {
binding.title.setText(R.string.your_server_invitation);
binding.instructions.setText(getString(R.string.magic_create_text_fixed, domain));
binding.username.setEnabled(false);
binding.username.setText(this.username);
updateFullJidInformation(this.username);
} else if (domain != null) {
binding.instructions.setText(getString(R.string.magic_create_text_on_x, domain));
}
binding.createAccount.setOnClickListener(v -> {
try {
final String username = binding.username.getText().toString();
final Jid jid;
final boolean fixedUsername;
if (this.domain != null && this.username != null) {
fixedUsername = true;
jid = Jid.ofLocalAndDomainEscaped(this.username, this.domain);
} else if (this.domain != null) {
fixedUsername = false;
jid = Jid.ofLocalAndDomainEscaped(username, this.domain);
} else {
fixedUsername = false;
jid = Jid.ofLocalAndDomainEscaped(username, Config.MAGIC_CREATE_DOMAIN);
}
if (!jid.getEscapedLocal().equals(jid.getLocal()) || (this.username == null && username.length() < 3)) {
binding.username.setError(getString(R.string.invalid_username));
binding.username.requestFocus();
} else {
binding.username.setError(null);
Account account = xmppConnectionService.findAccountByJid(jid);
if (account == null) {
account = new Account(jid, CryptoHelper.createPassword(new SecureRandom()));
account.setOption(Account.OPTION_REGISTER, true);
account.setOption(Account.OPTION_DISABLED, true);
account.setOption(Account.OPTION_MAGIC_CREATE, true);
account.setOption(Account.OPTION_FIXED_USERNAME, fixedUsername);
if (this.preAuth != null) {
account.setKey(Account.PRE_AUTH_REGISTRATION_TOKEN, this.preAuth);
}
xmppConnectionService.createAccount(account);
}
Intent intent = new Intent(MagicCreateActivity.this, EditAccountActivity.class);
intent.putExtra("jid", account.getJid().asBareJid().toString());
intent.putExtra("init", true);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
Toast.makeText(MagicCreateActivity.this, R.string.secure_password_generated, Toast.LENGTH_SHORT).show();
StartConversationActivity.addInviteUri(intent, getIntent());
startActivity(intent);
}
} catch (IllegalArgumentException e) {
binding.username.setError(getString(R.string.invalid_username));
binding.username.requestFocus();
}
});
binding.username.addTextChangedListener(this);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@Override
public void onDestroy() {
InstallReferrerUtils.markInstallReferrerExecuted(this);
super.onDestroy();
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
@Override
public void afterTextChanged(Editable s) {
if (s.toString().trim().length() > 0) {
try {
mFullJidDisplay.setVisibility(View.VISIBLE);
Jid jid = Jid.of(s.toString().toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null);
mFullJidDisplay.setText(getString(R.string.your_full_jid_will_be, jid.toEscapedString()));
} catch (IllegalArgumentException e) {
mFullJidDisplay.setVisibility(View.INVISIBLE);
}
}
@Override
public void afterTextChanged(final Editable s) {
updateFullJidInformation(s.toString());
}
private void updateFullJidInformation(final String username) {
if (username.trim().isEmpty()) {
binding.fullJid.setVisibility(View.INVISIBLE);
} else {
try {
binding.fullJid.setVisibility(View.VISIBLE);
final Jid jid;
if (this.domain == null) {
jid = Jid.ofLocalAndDomainEscaped(username, Config.MAGIC_CREATE_DOMAIN);
} else {
jid = Jid.ofLocalAndDomainEscaped(username, this.domain);
}
binding.fullJid.setText(getString(R.string.your_full_jid_will_be, jid.toEscapedString()));
} catch (IllegalArgumentException e) {
binding.fullJid.setVisibility(View.INVISIBLE);
}
}
}
} else {
mFullJidDisplay.setVisibility(View.INVISIBLE);
}
}
}

View File

@ -5,20 +5,20 @@ import android.content.Intent;
import android.os.Bundle;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.util.Pair;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import org.openintents.openpgp.util.OpenPgpApi;
import java.util.ArrayList;
@ -32,398 +32,373 @@ import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.ui.adapter.AccountAdapter;
import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.XmppConnection;
import rocks.xmpp.addr.Jid;
import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated {
public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated, AccountAdapter.OnTglAccountState {
private final String STATE_SELECTED_ACCOUNT = "selected_account";
private final String STATE_SELECTED_ACCOUNT = "selected_account";
protected Account selectedAccount = null;
protected Jid selectedAccountJid = null;
private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
protected final List<Account> accountList = new ArrayList<>();
protected ListView accountListView;
protected AccountAdapter mAccountAdapter;
protected AtomicBoolean mInvokedAddAccount = new AtomicBoolean(false);
protected Account selectedAccount = null;
protected Jid selectedAccountJid = null;
protected Pair<Integer, Intent> mPostponedActivityResult = null;
protected final List<Account> accountList = new ArrayList<>();
protected ListView accountListView;
protected AccountAdapter mAccountAdapter;
protected AtomicBoolean mInvokedAddAccount = new AtomicBoolean(false);
@Override
public void onAccountUpdate() {
refreshUi();
}
protected Pair<Integer, Intent> mPostponedActivityResult = null;
@Override
protected void refreshUiReal() {
synchronized (this.accountList) {
accountList.clear();
accountList.addAll(xmppConnectionService.getAccounts());
}
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setHomeButtonEnabled(this.accountList.size() > 0);
actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0);
}
invalidateOptionsMenu();
mAccountAdapter.notifyDataSetChanged();
}
@Override
public void onAccountUpdate() {
refreshUi();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
@Override
protected void refreshUiReal() {
synchronized (this.accountList) {
accountList.clear();
accountList.addAll(xmppConnectionService.getAccounts());
}
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setHomeButtonEnabled(this.accountList.size() > 0);
actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0);
}
invalidateOptionsMenu();
mAccountAdapter.notifyDataSetChanged();
}
super.onCreate(savedInstanceState);
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_manage_accounts);
setSupportActionBar(findViewById(R.id.toolbar));
configureActionBar(getSupportActionBar());
if (savedInstanceState != null) {
String jid = savedInstanceState.getString(STATE_SELECTED_ACCOUNT);
if (jid != null) {
try {
this.selectedAccountJid = Jid.of(jid);
} catch (IllegalArgumentException e) {
this.selectedAccountJid = null;
}
}
}
super.onCreate(savedInstanceState);
accountListView = (ListView) findViewById(R.id.account_list);
this.mAccountAdapter = new AccountAdapter(this, accountList);
accountListView.setAdapter(this.mAccountAdapter);
accountListView.setOnItemClickListener(new OnItemClickListener() {
setContentView(R.layout.activity_manage_accounts);
setSupportActionBar(findViewById(R.id.toolbar));
configureActionBar(getSupportActionBar());
if (savedInstanceState != null) {
String jid = savedInstanceState.getString(STATE_SELECTED_ACCOUNT);
if (jid != null) {
try {
this.selectedAccountJid = Jid.ofEscaped(jid);
} catch (IllegalArgumentException e) {
this.selectedAccountJid = null;
}
}
}
@Override
public void onItemClick(AdapterView<?> arg0, View view,
int position, long arg3) {
switchToAccount(accountList.get(position));
}
});
registerForContextMenu(accountListView);
}
accountListView = findViewById(R.id.account_list);
this.mAccountAdapter = new AccountAdapter(this, accountList);
accountListView.setAdapter(this.mAccountAdapter);
accountListView.setOnItemClickListener((arg0, view, position, arg3) -> switchToAccount(accountList.get(position)));
registerForContextMenu(accountListView);
}
@Override
protected void onStart() {
super.onStart();
final int theme = findTheme();
if (this.mTheme != theme) {
recreate();
}
}
@Override
protected void onStart() {
super.onStart();
final int theme = findTheme();
if (this.mTheme != theme) {
recreate();
}
}
@Override
public void onSaveInstanceState(final Bundle savedInstanceState) {
if (selectedAccount != null) {
savedInstanceState.putString(STATE_SELECTED_ACCOUNT, selectedAccount.getJid().asBareJid().toString());
}
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onSaveInstanceState(final Bundle savedInstanceState) {
if (selectedAccount != null) {
savedInstanceState.putString(STATE_SELECTED_ACCOUNT, selectedAccount.getJid().asBareJid().toEscapedString());
}
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
ManageAccountActivity.this.getMenuInflater().inflate(
R.menu.manageaccounts_context, menu);
AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
this.selectedAccount = accountList.get(acmi.position);
if (this.selectedAccount.isEnabled()) {
menu.findItem(R.id.mgmt_account_enable).setVisible(false);
menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(Config.supportOpenPgp());
} else {
menu.findItem(R.id.mgmt_account_disable).setVisible(false);
menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(false);
menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false);
}
menu.setHeaderTitle(this.selectedAccount.getJid().asBareJid().toString());
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
ManageAccountActivity.this.getMenuInflater().inflate(
R.menu.manageaccounts_context, menu);
AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
this.selectedAccount = accountList.get(acmi.position);
if (this.selectedAccount.isEnabled()) {
menu.findItem(R.id.mgmt_account_enable).setVisible(false);
menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(Config.supportOpenPgp());
} else {
menu.findItem(R.id.mgmt_account_disable).setVisible(false);
menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(false);
menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false);
}
menu.setHeaderTitle(this.selectedAccount.getJid().asBareJid().toEscapedString());
}
@Override
void onBackendConnected() {
if (selectedAccountJid != null) {
this.selectedAccount = xmppConnectionService.findAccountByJid(selectedAccountJid);
}
refreshUiReal();
if (this.mPostponedActivityResult != null) {
this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
}
if (Config.X509_VERIFICATION && this.accountList.size() == 0) {
if (mInvokedAddAccount.compareAndSet(false, true)) {
addAccountFromKey();
}
}
}
@Override
void onBackendConnected() {
if (selectedAccountJid != null) {
this.selectedAccount = xmppConnectionService.findAccountByJid(selectedAccountJid);
}
refreshUiReal();
if (this.mPostponedActivityResult != null) {
this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
}
if (Config.X509_VERIFICATION && this.accountList.size() == 0) {
if (mInvokedAddAccount.compareAndSet(false, true)) {
addAccountFromKey();
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.manageaccounts, menu);
MenuItem enableAll = menu.findItem(R.id.action_enable_all);
MenuItem addAccount = menu.findItem(R.id.action_add_account);
MenuItem addAccountWithCertificate = menu.findItem(R.id.action_add_account_with_cert);
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.manageaccounts, menu);
MenuItem enableAll = menu.findItem(R.id.action_enable_all);
MenuItem addAccount = menu.findItem(R.id.action_add_account);
MenuItem addAccountWithCertificate = menu.findItem(R.id.action_add_account_with_cert);
if (Config.X509_VERIFICATION) {
addAccount.setVisible(false);
addAccountWithCertificate.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
if (Config.X509_VERIFICATION) {
addAccount.setVisible(false);
addAccountWithCertificate.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
if (!accountsLeftToEnable()) {
enableAll.setVisible(false);
}
MenuItem disableAll = menu.findItem(R.id.action_disable_all);
if (!accountsLeftToDisable()) {
disableAll.setVisible(false);
}
return true;
}
if (!accountsLeftToEnable()) {
enableAll.setVisible(false);
}
MenuItem disableAll = menu.findItem(R.id.action_disable_all);
if (!accountsLeftToDisable()) {
disableAll.setVisible(false);
}
return true;
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.mgmt_account_publish_avatar:
publishAvatar(selectedAccount);
return true;
case R.id.mgmt_account_disable:
disableAccount(selectedAccount);
return true;
case R.id.mgmt_account_enable:
enableAccount(selectedAccount);
return true;
case R.id.mgmt_account_delete:
deleteAccount(selectedAccount);
return true;
case R.id.mgmt_account_announce_pgp:
publishOpenPGPPublicKey(selectedAccount);
return true;
default:
return super.onContextItemSelected(item);
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.mgmt_account_publish_avatar:
publishAvatar(selectedAccount);
return true;
case R.id.mgmt_account_disable:
disableAccount(selectedAccount);
return true;
case R.id.mgmt_account_enable:
enableAccount(selectedAccount);
return true;
case R.id.mgmt_account_delete:
deleteAccount(selectedAccount);
return true;
case R.id.mgmt_account_announce_pgp:
publishOpenPGPPublicKey(selectedAccount);
return true;
default:
return super.onContextItemSelected(item);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (MenuDoubleTabUtil.shouldIgnoreTap()) {
return false;
}
switch (item.getItemId()) {
case R.id.action_add_account:
startActivity(new Intent(getApplicationContext(),
EditAccountActivity.class));
break;
case R.id.action_disable_all:
disableAllAccounts();
break;
case R.id.action_enable_all:
enableAllAccounts();
break;
case R.id.action_add_account_with_cert:
addAccountFromKey();
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (MenuDoubleTabUtil.shouldIgnoreTap()) {
return false;
}
switch (item.getItemId()) {
case R.id.action_add_account:
startActivity(new Intent(this, EditAccountActivity.class));
break;
case R.id.action_import_backup:
if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
startActivity(new Intent(this, ImportBackupActivity.class));
}
break;
case R.id.action_disable_all:
disableAllAccounts();
break;
case R.id.action_enable_all:
enableAllAccounts();
break;
case R.id.action_add_account_with_cert:
addAccountFromKey();
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onNavigateUp() {
if (xmppConnectionService.getConversations().size() == 0) {
Intent contactsIntent = new Intent(this,
StartConversationActivity.class);
contactsIntent.setFlags(
// if activity exists in stack, pop the stack and go back to it
Intent.FLAG_ACTIVITY_CLEAR_TOP |
// otherwise, make a new task for it
Intent.FLAG_ACTIVITY_NEW_TASK |
// don't use the new activity animation; finish
// animation runs instead
Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(contactsIntent);
finish();
return true;
} else {
return super.onNavigateUp();
}
}
public void onClickTglAccountState(Account account, boolean enable) {
if (enable) {
enableAccount(account);
} else {
disableAccount(account);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults.length > 0) {
if (allGranted(grantResults)) {
switch (requestCode) {
case REQUEST_IMPORT_BACKUP:
startActivity(new Intent(this, ImportBackupActivity.class));
break;
}
} else {
Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
}
}
if (writeGranted(grantResults, permissions)) {
if (xmppConnectionService != null) {
xmppConnectionService.restartFileObserver();
}
}
}
private void addAccountFromKey() {
try {
KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG).show();
}
}
@Override
public boolean onNavigateUp() {
if (xmppConnectionService.getConversations().size() == 0) {
Intent contactsIntent = new Intent(this,
StartConversationActivity.class);
contactsIntent.setFlags(
// if activity exists in stack, pop the stack and go back to it
Intent.FLAG_ACTIVITY_CLEAR_TOP |
// otherwise, make a new task for it
Intent.FLAG_ACTIVITY_NEW_TASK |
// don't use the new activity animation; finish
// animation runs instead
Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(contactsIntent);
finish();
return true;
} else {
return super.onNavigateUp();
}
}
private void publishAvatar(Account account) {
Intent intent = new Intent(getApplicationContext(),
PublishProfilePictureActivity.class);
intent.putExtra(EXTRA_ACCOUNT, account.getJid().toString());
startActivity(intent);
}
@Override
public void onClickTglAccountState(Account account, boolean enable) {
if (enable) {
enableAccount(account);
} else {
disableAccount(account);
}
}
private void disableAllAccounts() {
List<Account> list = new ArrayList<>();
synchronized (this.accountList) {
for (Account account : this.accountList) {
if (account.isEnabled()) {
list.add(account);
}
}
}
for (Account account : list) {
disableAccount(account);
}
}
private void addAccountFromKey() {
try {
KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG).show();
}
}
private boolean accountsLeftToDisable() {
synchronized (this.accountList) {
for (Account account : this.accountList) {
if (account.isEnabled()) {
return true;
}
}
return false;
}
}
private void publishAvatar(Account account) {
Intent intent = new Intent(getApplicationContext(),
PublishProfilePictureActivity.class);
intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
startActivity(intent);
}
private boolean accountsLeftToEnable() {
synchronized (this.accountList) {
for (Account account : this.accountList) {
if (!account.isEnabled()) {
return true;
}
}
return false;
}
}
private void disableAllAccounts() {
List<Account> list = new ArrayList<>();
synchronized (this.accountList) {
for (Account account : this.accountList) {
if (account.isEnabled()) {
list.add(account);
}
}
}
for (Account account : list) {
disableAccount(account);
}
}
private void enableAllAccounts() {
List<Account> list = new ArrayList<>();
synchronized (this.accountList) {
for (Account account : this.accountList) {
if (!account.isEnabled()) {
list.add(account);
}
}
}
for (Account account : list) {
enableAccount(account);
}
}
private boolean accountsLeftToDisable() {
synchronized (this.accountList) {
for (Account account : this.accountList) {
if (account.isEnabled()) {
return true;
}
}
return false;
}
}
private void disableAccount(Account account) {
account.setOption(Account.OPTION_DISABLED, true);
if (!xmppConnectionService.updateAccount(account)) {
Toast.makeText(this,R.string.unable_to_update_account,Toast.LENGTH_SHORT).show();
}
}
private boolean accountsLeftToEnable() {
synchronized (this.accountList) {
for (Account account : this.accountList) {
if (!account.isEnabled()) {
return true;
}
}
return false;
}
}
private void enableAccount(Account account) {
account.setOption(Account.OPTION_DISABLED, false);
final XmppConnection connection = account.getXmppConnection();
if (connection != null) {
connection.resetEverything();
}
if (!xmppConnectionService.updateAccount(account)) {
Toast.makeText(this,R.string.unable_to_update_account,Toast.LENGTH_SHORT).show();
}
}
private void enableAllAccounts() {
List<Account> list = new ArrayList<>();
synchronized (this.accountList) {
for (Account account : this.accountList) {
if (!account.isEnabled()) {
list.add(account);
}
}
}
for (Account account : list) {
enableAccount(account);
}
}
private void publishOpenPGPPublicKey(Account account) {
if (ManageAccountActivity.this.hasPgp()) {
announcePgp(selectedAccount, null,null, onOpenPGPKeyPublished);
} else {
this.showInstallPgpDialog();
}
}
private void disableAccount(Account account) {
account.setOption(Account.OPTION_DISABLED, true);
if (!xmppConnectionService.updateAccount(account)) {
Toast.makeText(this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
}
}
private void deleteAccount(final Account account) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.mgmt_account_are_you_sure));
builder.setIconAttribute(android.R.attr.alertDialogIcon);
builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text));
builder.setPositiveButton(getString(R.string.delete),
(dialog, which) -> {
xmppConnectionService.deleteAccount(account);
selectedAccount = null;
if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) {
WelcomeActivity.launch(this);
}
});
builder.setNegativeButton(getString(R.string.cancel), null);
builder.create().show();
}
private void enableAccount(Account account) {
account.setOption(Account.OPTION_DISABLED, false);
final XmppConnection connection = account.getXmppConnection();
if (connection != null) {
connection.resetEverything();
}
if (!xmppConnectionService.updateAccount(account)) {
Toast.makeText(this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (xmppConnectionServiceBound) {
if (requestCode == REQUEST_CHOOSE_PGP_ID) {
if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
selectedAccount.setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID));
announcePgp(selectedAccount, null, null, onOpenPGPKeyPublished);
} else {
choosePgpSignId(selectedAccount);
}
} else if (requestCode == REQUEST_ANNOUNCE_PGP) {
announcePgp(selectedAccount, null, data, onOpenPGPKeyPublished);
}
this.mPostponedActivityResult = null;
} else {
this.mPostponedActivityResult = new Pair<>(requestCode, data);
}
}
}
private void publishOpenPGPPublicKey(Account account) {
if (ManageAccountActivity.this.hasPgp()) {
announcePgp(selectedAccount, null, null, onOpenPGPKeyPublished);
} else {
this.showInstallPgpDialog();
}
}
@Override
public void alias(String alias) {
if (alias != null) {
xmppConnectionService.createAccountFromKey(alias, this);
}
}
private void deleteAccount(final Account account) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.mgmt_account_are_you_sure));
builder.setIconAttribute(android.R.attr.alertDialogIcon);
builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text));
builder.setPositiveButton(getString(R.string.delete),
(dialog, which) -> {
xmppConnectionService.deleteAccount(account);
selectedAccount = null;
if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) {
WelcomeActivity.launch(this);
}
});
builder.setNegativeButton(getString(R.string.cancel), null);
builder.create().show();
}
@Override
public void onAccountCreated(Account account) {
Intent intent = new Intent(this, EditAccountActivity.class);
intent.putExtra("jid", account.getJid().asBareJid().toString());
intent.putExtra("init", true);
startActivity(intent);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (xmppConnectionServiceBound) {
if (requestCode == REQUEST_CHOOSE_PGP_ID) {
if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
selectedAccount.setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID));
announcePgp(selectedAccount, null, null, onOpenPGPKeyPublished);
} else {
choosePgpSignId(selectedAccount);
}
} else if (requestCode == REQUEST_ANNOUNCE_PGP) {
announcePgp(selectedAccount, null, data, onOpenPGPKeyPublished);
}
this.mPostponedActivityResult = null;
} else {
this.mPostponedActivityResult = new Pair<>(requestCode, data);
}
}
}
@Override
public void alias(final String alias) {
if (alias != null) {
xmppConnectionService.createAccountFromKey(alias, this);
}
}
@Override
public void onAccountCreated(final Account account) {
final Intent intent = new Intent(this, EditAccountActivity.class);
intent.putExtra("jid", account.getJid().asBareJid().toString());
intent.putExtra("init", true);
startActivity(intent);
}
@Override
public void informUser(final int r) {
runOnUiThread(() -> Toast.makeText(ManageAccountActivity.this, r, Toast.LENGTH_LONG).show());
}
@Override
public void informUser(final int r) {
runOnUiThread(() -> Toast.makeText(ManageAccountActivity.this, r, Toast.LENGTH_LONG).show());
}
}

View File

@ -1,104 +0,0 @@
package eu.siacs.conversations.ui;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import java.util.List;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.ActivityPickServerBinding;
import eu.siacs.conversations.entities.Account;
public class PickServerActivity extends XmppActivity {
@Override
protected void refreshUiReal() {
}
@Override
void onBackendConnected() {
}
@Override
public void onStart() {
super.onStart();
final int theme = findTheme();
if (this.mTheme != theme) {
recreate();
}
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
if (item.getItemId() == android.R.id.home) {
startActivity(new Intent(this, WelcomeActivity.class));
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
startActivity(new Intent(this, WelcomeActivity.class));
super.onBackPressed();
}
@Override
public void onNewIntent(Intent intent) {
if (intent != null) {
setIntent(intent);
}
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
if (getResources().getBoolean(R.bool.portrait_only)) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
super.onCreate(savedInstanceState);
ActivityPickServerBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_pick_server);
setSupportActionBar(binding.toolbar);
configureActionBar(getSupportActionBar());
binding.useCim.setOnClickListener(v -> {
final Intent intent = new Intent(this, MagicCreateActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
addInviteUri(intent);
startActivity(intent);
});
binding.useOwnProvider.setOnClickListener(v -> {
List<Account> accounts = xmppConnectionService.getAccounts();
Intent intent = new Intent(this, EditAccountActivity.class);
intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, true);
if (accounts.size() == 1) {
intent.putExtra("jid", accounts.get(0).getJid().asBareJid().toString());
intent.putExtra("init", true);
} else if (accounts.size() >= 1) {
intent = new Intent(this, ManageAccountActivity.class);
}
addInviteUri(intent);
startActivity(intent);
});
}
public void addInviteUri(Intent intent) {
StartConversationActivity.addInviteUri(intent, getIntent());
}
public static void launch(AppCompatActivity activity) {
Intent intent = new Intent(activity, PickServerActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
activity.startActivity(intent);
activity.overridePendingTransition(0, 0);
}
}

View File

@ -1,6 +1,10 @@
package eu.siacs.conversations.ui;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import java.util.ArrayList;
@ -10,7 +14,7 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.ui.adapter.AccountAdapter;
import eu.siacs.conversations.xmpp.Jid;
import rocks.xmpp.addr.Jid;
public class ShareViaAccountActivity extends XmppActivity {
public static final String EXTRA_CONTACT = "contact";
@ -26,6 +30,11 @@ public class ShareViaAccountActivity extends XmppActivity {
accountList.clear();
accountList.addAll(xmppConnectionService.getAccounts());
}
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setHomeButtonEnabled(this.accountList.size() > 0);
actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0);
}
mAccountAdapter.notifyDataSetChanged();
}

Some files were not shown because too many files have changed in this diff Show More