diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e9eb28bd..a198eb7f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,1147 +1,72 @@ # Changelog -### Version 2.13.0 - -* Easier access to 'Show QR code' -* Support PEP Native Bookmarks -* Add support for SDP Offer / Answer Model (Used by SIP gateways) -* Raise target API to Android 14 - -### Version 2.12.12 - -* Support Private DNS (DNS over TLS) -* Support themed launcher icon -* Fix rare permission issue when sharing files on Android 11+ - -### Version 2.12.11 - -* Bump libwebrtc dependency to M117 and bump libvpx -* Go back to AAC for voice messages -* Support per app language settings - -### Version 2.12.10 - -* support per conversation notification settings -* use opus for voice messages on Android 10 - -### Version 2.12.9 - -* Introduce new backup file format - -### Version 2.12.8 - -* Disable opening backup files (.ceb) from file manager - -### Version 2.12.7 - -* Remove channel discovery feature from Google Play version - -### Version 2.12.6 - -* Fix 'q' falsely being recognized as cyrillic - -### Version 2.12.5 - -* Bump Target SDK to 33 again -* Fix issues on servers supporting SASL2 w/o inline Stream Management - -### Version 2.12.4 - -* Revert Target SDK bump (back to 32) to fix various issues on Android 13 - -### Version 2.12.3 - -* Improve support for new emojis -* Add ability to remove account from server -* Show timestamp for calls - -### Version 2.12.2 - -* Increase corner radius on profile pictures - -### Version 2.12.1 - -* Fix crash in UnifiedPush Distributor - -### Version 2.12.0 - -* Integrate UnifiedPush Distributor to facilitate push messages to other UnifiedPush enabled apps like Tusky and Fedilab - -### Version 2.11.3 - -* Fix messages getting resend when using SASL2 -* Fix black video between some devices -* Fix crash on empty passwords - -### Version 2.11.2 - -* Fixed regression in P2P file transfer - -### Version 2.11.1 - -* Fix resend loop on servers that support only sm:2 -* Show 'Switch to video' only if other party supports video - -### Version 2.11.0 - -* Implement Extensible SASL Profile, Bind 2.0 and Fast for faster reconnects -* Implement Channel Binding -* Add ability to switch from audio call to video call -* Add ability to delete own avatar -* Add notification for missed calls - -### Version 2.10.10 - -* Minor bug fixes -* Restore ability to call out via JMP and other services (Playstore version) - -### Version 2.10.9 - -* Ask for Bluetooth permissions when making A/V calls (You can reject this if you don’t use Bluetooth headsets) -* Fix bug when calling Movim - -### Version 2.10.8 - -* Fix wrong avatar being shown for group chats - -### Version 2.10.7 - -* always ask for battery optimizations opt-out -* set local only flag on 'x connected accounts' notifications -* Minor bug fixes - -### Version 2.10.6 - -* Minor bug fixes - -### Version 2.10.5 - -* Security: Stop downloading files that exceed advertised file size -* Security: Limit POSH files to 10K - -### Version 2.10.4 - -* Fix interaction with Google Maps Share Location Plugin -* Remove footnote with regards to server fee - -### Version 2.10.3 - -* Store files in location appropriate for Android 11 -* Attempt to reconnect call after network switch -* Show caller JID and account JID in incoming call screen - -### 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 don’t 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 - -### Version 2.3.7 -* long press on 'allow' or 'add back' snackbar to bring up 'reject' -* bug fixes for Android 9 - -### Version 2.3.6 -* Improved handling of bookmark nicks -* Show send PM menu entry in anonymous MUCs - -### Version 2.3.5 -* Fixed group chat mentions when nick ends in . (dot) -* Fixed Conversations not asking for permissions after direct share -* Fixed CVE-2018-18467 - -### Version 2.3.4 -* Fixed sending OMEMO files to ChatSecure - -### Version 2.3.3 -* Fixed connection issues with user@ip type JIDs - -### Version 2.3.2 -* Fixed OMEMO on Android 5.1 & 6.0 -* Added setting for video quality -* bug fixes - -### Version 2.3.1 -* Stronger compression for video files -* Use SNI on STARTTLS to fix gtalk -* Fix Quiet Hours on Android 8+ -* Use Consistent Color Generation (XEP-0392) - -### Version 2.3.0 -* Preview and ask for confirmation before sending media files -* View per conversation media files in contact and conference details screens -* Enable foreground service by default for Android 8 (notification can be disabled by long pressing it) -* Audio player: disable screen and switch to ear piece -* Support TLSv1.3 (ejabberd ≤ 18.06 is incompatible with openssl 1.1.1 - Update ejabberd or downgrade openssl if you get ›Stream opening error‹) - - -### Version 2.2.9 -* Store bookmarks in PEP if server has ability to convert to old bookmarks -* Show Jabber IDs from address book in Start Conversation screen - -### Version 2.2.8 -* fixed regression that broke XMPP uris - -### Version 2.2.7 -* stability improvements - -### Version 2.2.6 -* support old MAM version to work with Prosody - -### Version 2.2.5 -* Persist MUC avatar across restarts / show in bookmarks -* Offer Paste as quote for HTML content - -### Version 2.2.4 -* Use group chat name as primary identifier -* Show group name and subject in group chat details -* Upload group chat avatar on compatible servers - -### Version 2.2.3 -* Introduce Expert Setting to enable direct search -* Introduce Paste As Quote on Android 6+ -* Fixed issues with HTTP Upload - -### Version 2.2.2 -* Fixed connection problems with TLS1.3 servers -* Attempt to delete broken bundles from PEP -* Use FCM instead of GCM - -### Version 2.2.1 -* improved recording quality -* load map tiles over Tor if enabled - -### Version 2.2.0 -* Integrate Voice Recorder -* Integrate Share Location -* Added ability to search messages - -### Version 2.1.4 -* bug fixes - -### Version 2.1.3 -* Do not process stanzas with invalid JIDs - -### Version 2.1.2 -* Fixed avatars not being displayed on new installs - -### Version 2.1.1 -* Improved start up performance -* bug fixes - -### Version 2.1.0 -* Added configurable font size -* Added global OMEMO preference -* Added scroll to bottom button -* Only mark visible messages as read - - -### Version 2.0.0 -* OMEMO by default for everything but public group chats -* Integrate QR code scanner (requires camera permission) -* Removed support for OTR -* Removed support for customizable resources -* Removed slide out panel for conversation overview -* Add ability to change status message -* Highlight irregular unicode code blocks in Jabber IDs -* Conversations now requires Android 4.4+ - -### Version 1.23.8 -* bug fixes - -### Version 1.23.7 -* Improved MAM support + bug fixes - -### Version 1.23.6 -* Fixed crash on receiving invalid HTTP slot response - -### Version 1.23.5 -* improved self chat - -### Version 1.23.4 -* keep screen on while playing audio -* send delivery receipts after MAM catch-up -* reduce number of wake locks - -### Version 1.23.3 -* Fixed OMEMO device list not being announced - -### Version 1.23.2 -* Removed NFC support -* upload Avatars as JPEG -* reduce APK size - -### Version 1.23.1 -* Show icon instead of image preview in conversation overview -* fixed loop when trying to decrypt with YubiKey - -### Version 1.23.0 -* Support for read markers in private, non-anonymous group chats - -### Version 1.22.1 -* Disable swipe to left to end conversation -* Fixed 'No permission to access …' when opening files shared from the SD card -* Always open URLs in new tab - -### Version 1.22.0 -* Text markup *bold*, _italic_,`monospace` and ~strikethrough~ -* Use same emoji style on all Android versions -* Display emojis slightly larger within continuous text - -### Version 1.21.0 -* Inline player for audio messages -* Stronger compression for long videos -* Long press the 'add back' button to show block menu - -### Version 1.20.1 -* fixed OTR encrypted file transfer - -### Version 1.20.0 -* presence subscription no longer required for OMEMO on compatible servers -* display emoji-only messages slightly larger - -### Version 1.19.5 -* fixed connection loop on Android <4.4 - -### Version 1.19.4 -* work around for OpensFire’s self signed certs -* use VPN’s DNS servers first - -### Version 1.19.3 -* Do not create foreground service when all accounts are disabled -* bug fixes - -### Version 1.19.2 -* bug fixes - -### Version 1.19.1 -* Made DNSSEC hostname validation opt-in - -### Version 1.19.0 -* Added 'App Shortcuts' to quickly access frequent contacts -* Use DNSSEC to verify hostname instead of domain in certificate -* Setting to enable Heads-up notifications -* Added date separators in message view - -### Version 1.18.5 -* colorize send button only after history is caught up -* improved MAM catchup strategy - -### Version 1.18.4 -* fixed UI freezes during connection timeout -* fixed notification sound playing twice -* fixed conversations being marked as read -* removed 'copy text' in favor of 'select text' and 'share with' - -### Version 1.18.3 -* limited GPG encryption for MUC offline members - -### Version 1.18.2 -* added support for Android Auto -* fixed HTTP Download over Tor -* work around for nimbuzz.com MUCs - -### Version 1.18.1 -* bug fixes - -### Version 1.18.0 -* Conversations <1.16.0 will be unable to receive OMEMO encrypted messages -* OMEMO: put auth tag into key (verify auth tag as well) -* offer to block entire domain in message from stranger snackbar -* treat URL as file if URL is in oob or contains key - -### Version 1.17.1 -* Switch Aztec to QR for faster scans -* Fixed unread counter for image messages - -### Version 1.17.0 -* Do not notify for messages from strangers by default -* Blocking a JID closes the corresponding conversation -* Show message sender in conversation overview -* Show unread counter for every conversation -* Send typing notifications in private, non-anonymous MUCs -* Support for the latest MAM namespace -* Icons for attach menu - -### Version 1.16.2 -* change mam catchup strategy. support mam:1 -* bug fixes - -### Version 1.16.1 -* UI performance fixes -* bug fixes - -### Version 1.16.0 -* configurable client side message retention period -* compress videos before sending them - -### Version 1.15.5 -* show nick as bold text when mentioned in conference -* bug fixes - -### Version 1.15.4 -* bug fixes - -### Version 1.15.3 -* show offline contacts in MUC as grayed-out -* don't transcode gifs. add overlay indication to gifs -* bug fixes - -### Version 1.15.2 -* bug fixes - -### Version 1.15.1 -* support for POSH (RFC7711) -* support for quoting messages (via select text) -* verified messages show shield icon. unverified messages show lock - -### Version 1.15.0 -* New [Blind Trust Before Verification](https://gultsch.de/trust.html) mode -* Easily share Barcode and XMPP uri from Account details -* Automatically deactivate own devices after 7 day of inactivity -* Improvements fo doze/push mode -* bug fixes - -### Version 1.14.9 -* warn in account details when data saver is enabled -* automatically enable foreground service after detecting frequent restarts -* bug fixes - -### Version 1.14.8 -* bug fixes - -### Version 1.14.7 -* error message accessible via context menu for failed messages -* don't include pgp signature in anonymous mucs -* bug fixes - -### Version 1.14.6 -* make error notification dismissible -* bug fixes - - -### Version 1.14.5 -* expert setting to delete OMEMO identities -* bug fixes - -### Version 1.14.4 -* bug fixes - -### Version 1.14.3 -* XEP-0377: Spam Reporting -* fix rare start up crashes - -### Version 1.14.2 -* support ANONYMOUS SASL -* bug fixes - -### Version 1.14.1 -* Press lock icon to see why OMEMO is deactivated -* bug fixes - -### Version 1.14.0 -* Improvements for N -* Quick Reply to Notifications on N -* Don't download avatars and files when data saver is on -* bug fixes - -### Version 1.13.9 -* bug fixes - -### Version 1.13.8 -* show identities instead of resources in selection dialog -* allow TLS direct connect when port is set to 5223 -* bug fixes - -### Version 1.13.7 -* bug fixes - -### Version 1.13.6 -* thumbnails for videos -* bug fixes - -### Version 1.13.5 -* bug fixes - -### Version 1.13.4 -* support jingle ft:4 -* show contact as DND if one resource is -* bug fixes - -### Version 1.13.3 -* bug fixes - -### Version 1.13.2 -* new PGP decryption logic -* bug fixes - -### Version 1.13.1 -* changed some colors in dark theme -* fixed fall-back message for OMEMO - -### Version 1.13.0 -* configurable dark theme -* opt-in to share Last User Interaction - -### Version 1.12.9 -* make grace period configurable - -### Version 1.12.8 -* more bug fixes :-( - -### Version 1.12.7 -* bug fixes - -### Version 1.12.6 -* bug fixes - -### Version 1.12.5 -* new create conference dialog -* show first unread message on top -* show geo uri as links -* circumvent long message DOS - -### Version 1.12.4 -* show offline members in conference (needs server support) -* various bug fixes - -### Version 1.12.3 -* make omemo default when all resources support it -* show presence of other resources as template -* start typing in StartConversationsActivity to search -* various bug fixes and improvements - -### Version 1.12.2 -* fixed pgp presence signing - -### Version 1.12.1 -* small bug fixes - -### Version 1.12.0 -* new welcome screen that makes it easier to register account -* expert setting to modify presence - -### Version 1.11.7 -* Share xmpp uri from conference details -* add setting to allow quick sharing -* various bug fixes - -### Version 1.11.6 -* added preference to disable notification light -* various bug fixes - -### Version 1.11.5 -* check file ownership to not accidentally share private files - -### Version 1.11.4 -* fixed a bug where contacts are shown as offline -* improved broken PEP detection - -### Version 1.11.3 -* check maximum file size when using HTTP Upload -* properly calculate caps hash - -### Version 1.11.2 -* only add image files to media scanner -* allow to delete files -* various bug fixes - -### Version 1.11.1 -* fixed some bugs when sharing files with Conversations - -### Version 1.11.0 -* OMEMO encrypted conferences - -### Version 1.10.1 -* made message correction opt-in -* various bug fixes - -### Version 1.10.0 -* Support for XEP-0357: Push Notifications -* Support for XEP-0308: Last Message Correction -* introduced build flavors to make dependence on play-services optional - -### Version 1.9.4 -* prevent cleared Conversations from reloading history with MAM -* various MAM fixes - -### Version 1.9.3 -* expert setting that enables host and port configuration -* expert setting opt-out of bookmark autojoin handling -* offer to rejoin a conference after server sent unavailable -* internal rewrites - -### Version 1.9.2 -* prevent startup crash on Sailfish OS -* minor bug fixes - -### Version 1.9.1 -* minor bug fixes incl. a workaround for nimbuzz.com - -### Version 1.9.0 -* Per conference notification settings -* Let user decide whether to compress pictures -* Support for XEP-0368 -* Ask user to exclude Conversations from battery optimizations - -### Version 1.8.4 -* prompt to trust own OMEMO devices -* fixed rotation issues in avatar publication -* invite non-contact JIDs to conferences - -### Version 1.8.3 -* brought text selection back - -### Version 1.8.2 -* fixed stuck at 'connecting...' bug -* make message box behave correctly with multiple links - -### Version 1.8.1 -* enabled direct share on Android 6.0 -* ask for permissions on Android 6.0 -* notify on MAM catchup messages -* bug fixes - -### Version 1.8.0 -* TOR/ORBOT support in advanced settings -* show vcard avatars of participants in a conference - -### Version 1.7.3 -* fixed PGP encrypted file transfer -* fixed repeating messages in slack conferences - -### Version 1.7.2 -* decode PGP messages in background - -### Version 1.7.1 -* performance improvements when opening a conversation - -### Version 1.7.0 -* CAPTCHA support -* SASL EXTERNAL (client certificates) -* fetching MUC history via MAM -* redownload deleted files from HTTP hosts -* Expert setting to automatically set presence -* bug fixes - -### Version 1.6.11 -* tab completion for MUC nicks -* history export -* bug fixes - -### Version 1.6.10 -* fixed facebook login -* fixed bug with ejabberd mam -* use official HTTP File Upload namespace - -### Version 1.6.9 -* basic keyboard support - -### Version 1.6.8 -* reworked 'enter is send' setting -* reworked DNS server discovery on lolipop devices -* various bug fixes - -### Version 1.6.7 -* bug fixes - -### Version 1.6.6 -* best 1.6 release yet - -### Version 1.6.5 -* more OMEMO fixes - -### Version 1.6.4 -* setting to enable white chat bubbles -* limit OMEMO key publish attempts to work around broken PEP -* various bug fixes - -### Version 1.6.3 -* bug fixes - -### Version 1.6.2 -* fixed issues with connection time out when server does not support ping - -### Version 1.6.1 -* fixed crashes - -### Version 1.6.0 -* new multi-end-to-multi-end encryption method -* redesigned chat bubbles -* show unexpected encryption changes as red chat bubbles -* always notify in private/non-anonymous conferences - -### Version 1.5.1 -* fixed rare crashes -* improved otr support - -### Version 1.5.0 -* upload files to HTTP host and share them in MUCs. requires new [HttpUploadComponent](https://github.com/siacs/HttpUploadComponent) on server side - -### Version 1.4.5 -* fixes to message parser to not display some ejabberd muc status messages - -### Version 1.4.4 -* added unread count badges on supported devices -* rewrote message parser - -### Version 1.4.0 -* send button turns into quick action button to offer faster access to take photo, send location or record audio -* visually separate merged messages -* faster reconnects of failed accounts after network switches -* r/o vcard avatars for contacts -* various bug fixes - -### Version 1.3.0 -* swipe conversations to end them -* quickly enable / disable account via slider -* share multiple images at once -* expert option to distrust system CAs -* mlink compatibility -* bug fixes - -### Version 1.2.0 -* Send current location. (requires [plugin](https://play.google.com/store/apps/details?id=eu.siacs.conversations.sharelocation)) -* Invite multiple contacts at once -* performance improvements -* bug fixes - -### Version 1.1.0 -* Typing notifications (must be turned on in settings) -* Various UI performance improvements -* bug fixes - -### Version 1.0.4 -* load avatars asynchronously on start up -* support for XEP-0092: Software Version - -### Version 1.0.3 -* load messages asynchronously on start up -* bug fixes - -### Version 1.0.2 -* skipped - -### Version 1.0.1 -* accept more ciphers - -### Version 1.0 -* MUC controls (Affiliation changes) -* Added download button to notification -* Added check box to hide offline contacts -* Use Material theme and icons on Android L -* Improved security -* bug fixes + code clean up - -### Version 0.10 -* Support for Message Archive Management -* Dynamically load message history -* Ability to block contacts -* New UI to verify fingerprints -* Ability to change password on server -* removed stream compression -* quiet hours -* fixed connection issues on ipv6 servers - -### Version 0.9.3 -* bug fixes - -### Version 0.9.2 -* more bug fixes - -### Version 0.9.1 -* bug fixes including some that caused Conversations to crash on start - -### Version 0.9 -* arbitrary file transfer -* more options to verify OTR (SMP, QR Codes, NFC) -* ability to create instant conferences -* r/o dynamic tags (presence and roster groups) -* optional foreground service (expert option) -* added SCRAM-SHA1 login method -* bug fixes - -### Version 0.8.4 -* bug fixes - -### Version 0.8.3 -* increased UI performance -* fixed rotation bugs - -### Version 0.8.2 -* Share contacts via QR codes or NFC -* Slightly improved UI -* minor bug fixes - -### Version 0.8.1 -* minor bug fixes - -### Version 0.8 -* Download HTTP images -* Show avatars in MUC tiles -* Disabled SSLv3 -* Performance improvements -* bug fixes - -### Version 0.7.3 -* revised tablet ui -* internal rewrites -* bug fixes - -### Version 0.7.2 -* show full timestamp in messages -* brought back option to use JID to identify conferences -* optionally request delivery receipts (expert option) -* more languages -* bug fixes - -### Version 0.7.1 -* Optionally use send button as status indicator - -### Version 0.7 -* Ability to disable notifications for single conversations -* Merge messages in chat bubbles -* Fixes for OpenPGP and OTR (please republish your public key) -* Improved reliability on sending messages -* Join password protected Conferences -* Configurable font size -* Expert options for encryption - -### Version 0.6 -* Support for server side avatars -* save images in gallery -* show contact name and picture in non-anonymous conferences -* reworked account creation -* various bug fixes - -### Version 0.5.2 -* minor bug fixes - -### Version 0.5.1 -* couple of small bug fixes that have been missed in 0.5 -* complete translations for Swedish, Dutch, German, Spanish, French, Russian - -### Version 0.5 -* UI overhaul -* MUC / Conference bookmarks -* A lot of bug fixes - -### Version 0.4 -* OTR file encryption -* keep OTR messages and files on device until both parties or online at the same time -* 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 -* Mark the last used presence in presence selection dialog - -### Version 0.3 -* Mostly bug fixes and internal rewrites -* Touch contact picture in conference to highlight -* Long press on received image to share -* made OTR more reliable -* improved issues with occasional message lost -* experimental conference encryption. (see FAQ) - -### Version 0.2.3 -* regression fix with receiving encrypted images - -### Version 0.2.2 -* Ability to take photos directly -* Improved openPGP offline handling -* Various bug fixes -* Updated Translations - -### Version 0.2.1 -* Various bug fixes -* Updated Translations - -### Version 0.2 -* Image file transfer -* Better integration with OpenKeychain (PGP encryption) -* Nicer conversation tiles for conferences -* Ability to clear conversation history -* A lot of bug fixes and code clean up - -### Version 0.1.3 -* Switched to minidns library to resolve SRV records -* Faster DNS in some cases -* Enabled stream compression -* Added permanent notification when an account fails to connect -* Various bug fixes involving message notifications -* Added support for DIGEST-MD5 auth - -### Version 0.1.2 -* Various bug fixes relating to conferences -* Further DNS lookup improvements - -### Version 0.1.1 -* Fixed the 'server not found' bug - -### Version 0.1 -* Initial release +### another.im 1.0.0 +* Implementation of OTR chats +* Rebranding from Conversation Clasic to another.im +* UI improvements and bug fixes + +### Conversations Classic 2.3.1 +* Added account indicator +* Group chats are now marked with an additional icon +* Saved messages now have a brighter background color +* Added nickname in reply in Multi-User Chats(MUC) +* Contact status is now displayed separately +* Updated Russian and Ukrainian localizations + +### Conversations Classic 2.3.0 +* Add Cheogram mention on about screen +* Fix save to downloads menu item visibility +* Update Russian and Ukrainian localizations +* Update caps node field +* Notifications throttling setting for all conversations +* Fix gestures handling conflict in commands +* Support navigation via nav bar +* Per conversation custom backgrounds +* Use original file name in chat attachements +* Support avatars shape customization +* Medium font for unread conversation title +* New settings screen +* Show MUC subject as a pinned message + +### Conversations Classic 2.2.4 +* Get rid of outdated kernel version calculation code +* Fix contact long click crash +* Clean code and optimize imports +* Render reply message author name on the top of replied text +* Fix conversation position handling while grouping enabled +* Properly show contact name in MUC + +### Conversations Classic 2.2.2 + +_The app was forked from Conversations Version 2.13.0 and renamed to Conversations Classic._ + +* Numerous small fixes +* Info about PM on MUC details screen +* Temporary disable domain JID sanity check +* Support 'Save to downloads' action for attachments +* Reactions +* New UI category in expert settings +* Better self contact handling +* Split roster by different accounts +* Group conversation by tags +* Save expanded items in persistent storage +* Fix not working accent color +* Support conversations grouping on ShareWith screen +* LED color changed to blue +* Show real reply text instead of fallback +* New launcher icons +* Fix multiline reply quote +* Fix swipe to reply handling +* Add leakCanary +* Log reason for SM resume failure +* Fix notifications fallback color +* Get rid of command blicks +* Add checkbox for auto downloading all files when connected to Wi-Fi +* Improve scroll to bottom button behavior +* Add notification sound throttling setting, per each contact separately +* Get rid of upstream bug reports system +* Add contact JID copy button +* Move restore backup button from context menu on welcome screen +* Improve replies on file or image messages +* Fix reactions handling in case of missing reply stanza +* Fix replies to audio messages diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 000000000..0e8b4e9eb --- /dev/null +++ b/FAQ.md @@ -0,0 +1,224 @@ +# FAQ + +## General + +### How do I install another.im? + +another.im 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. + +### 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 [another.im](https://another.im). If you don’t like to use *another.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 another.im 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. + +#### 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. + +As of 2023 XMPP has reached a level of maturity where all major XMPP servers ([ejabberd](https://ejabberd.im), [Prosody](https://prosody.im), [Openfire](https://www.igniterealtime.org/projects/openfire/), [Tigase](https://tigase.net/xmpp-server/)) should work well with another.im. + +Interoperability with Prosody and ejabberd is tested fairly regularly just because of their market share but we occasionally test with other servers too and fix issues as soon as we are being made aware of them. + +### Where can I set up a custom hostname / port +another.im will automatically look up the SRV records for your domain name +which can point to any hostname port combination. If your server doesn’t provide +those please contact your admin and have them read +[this](http://prosody.im/doc/dns#srv_records). If your server operator is unwilling +to fix this you can enable advanced server settings in the expert settings of +another.im. + +### I get 'Incompatible Server' + +As regular user you should be picking a different server. The server you selected +is probably insecure and/or very old. + +If you are a server administrator you should make sure that your server provides +either STARTTLS or [XEP-0368: SRV records for XMPP over TLS](https://xmpp.org/extensions/xep-0368.html). + +On rare occasions this error message might also be caused by a server not providing +a login (SASL) mechanism that another.im is able to handle. another.im supports +SCRAM-SHA1, PLAIN, EXTERNAL (client certs) and DIGEST-MD5. + +### I get 'Bind failure'. What does that mean? + +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 another.im doesn’t 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`. another.im doesn’t like that. +This can be fixed by creating a new account in another.im 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` doesn’t point to the correct host. In that case you might have to enable the extended connection settings in the expert settings of another.im 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). + +**The battery consumption and the entire behavior of another.im 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 <= 7.1 or another.im from F-Droid (all Android versions) +The foreground notification is still controlled over the expert settings within another.im 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 another.im (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. + +### another.im doesn’t work for me. Where can I get help? + +You can join our conference room on [`xmppclient-dev@conference.another.im`](xmpp:xmppclient-dev@conference.another.im). +A lot of people in there are able to answer basic questions about the usage of +another.im 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. + +### How does the address book integration work? + +The address book integration was designed to protect your privacy. another.im +neither uploads contacts from your address book to your server nor fills your +address book with unnecessary contacts from your online roster. If you manually +add a Jabber ID to your phones address book another.im will use the name and +the profile picture of this contact. To make the process of adding Jabber IDs to +your address book easier you can click on the profile picture in the contact +details within another.im. This will start an "add to address book" intent +with the JID as the payload. This doesn't require another.im to have write +permissions on your address book but also doesn't require you to copy/paste a +JID from one app to another. + +### I get 'delivery failed' on my messages + +If you get delivery failed on images it's probably because the recipient lost +network connectivity during reception. In that case you can try it again at a +later time. + +For text messages the answer to your question is a little bit more complex. +When you see 'delivery failed' on text messages, it is always something that is +being reported by the server. The most common reason for this is that the +recipient failed to resume a connection. When a client loses connectivity for a +short time the client usually has a five minute window to pick up that +connection again. When the client fails to do so because the network +connectivity is out for longer than that all messages sent to that client will +be returned to the sender resulting in a delivery failed. + +Instead of returning a message to the sender both ejabberd and prosody have the +ability to store messages in offline storage when the disconnecting client is +the only client. In prosody this is available via an extra module called +```mod_smacks_offline```. In ejabberd this is available via some configuration +settings. + +Other less common reasons are that the message you sent didn't meet some +criteria enforced by the server (too large, too many). Another reason could be +that the recipient is offline and the server doesn't provide offline storage. + +Usually you are able to distinguish between these two groups in the fact that +the first one happens always after some time and the second one happens almost +instantly. + +### Where can I see the status of my contacts? How can I set a status or priority? + +Statuses are a horrible metric. Setting them manually to a proper value rarely +works because users are either lazy or just forget about them. Setting them +automatically does not provide quality results either. Keyboard or mouse +activity as indicator for example fails when the user is just looking at +something (reading an article, watching a movie). Furthermore automatic setting +of status always implies an impact on your privacy (are you sure you want +everybody in your contact list to know that you have been using your computer at +4am‽). + +In the past status has been used to judge the likelihood of whether or not your +messages are being read. This is no longer necessary. With Chat Markers +(XEP-0333, supported by Conversations since 0.4) we have the ability to **know** +whether or not your messages are being read. Similar things can be said for +priorities. In the past priorities have been used (by servers, not by clients!) +to route your messages to one specific client. With carbon messages (XEP-0280, +supported by Conversations since 0.1) this is no longer necessary. Using +priorities to route OTR messages isn't practical either because they are not +changeable on the fly. Metrics like last active client (the client which sent +the last message) are much better. + +Unfortunately these modern replacements for legacy XMPP features are not widely +adopted. However another.im should be an instant messenger for the future and +instead of making another.im compatible with the past we should work on +implementing new, improved technologies and getting them into other XMPP clients +as well. + +Making these status and priority optional isn't a solution either because +another.im is trying to get rid of old behaviours and set an example for +other clients. + + +### How do I backup / move another.im to a new device? + +Use the Backup button in the Settings. + +### another.im is missing a certain feature + +Please report it to our XMPP conference [`xmppclient-dev@conference.another.im`](xmpp:xmppclient-dev@conference.another.im). + +## Security + +### Why are there two end-to-end encryption methods and which one should I choose? + +* OMEMO works even when a contact is offline, and works with multiple devices. It also allows asynchronous file-transfer when the server has [HTTP File Upload](http://xmpp.org/extensions/xep-0363.html). However, OMEMO not widely support and is currently implemented only [by a handful of clients](https://omemo.top). +* OpenPGP (XEP-0027) is a very old encryption method that has some advantages over OMEMO but should only be used by people who know what they are doing. + +### How do I use OpenPGP + +Before you continue reading you should note that the OpenPGP support in +another.im is experimental. This is not because it will make the app unstable +but because the fundamental concepts of PGP aren't ready for widespread use. +The way PGP works is that you trust Key IDs instead of JID's or email addresses. +So in theory your contact list should consist of Public-Key-IDs instead of +JID's. But of course no email or XMPP client out there implements these +concepts. Plus PGP in the context of instant messaging has a couple of +downsides: It is vulnerable to replay attacks and it is rather verbose. + +To use OpenPGP you have to install the open source app +[OpenKeychain](http://www.openkeychain.org) and then long press on the account in +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 couldn’t 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 doesn’t 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 group chats work? + +#### 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 another.im. (see 'OMEMO is grayed out') + +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.) + +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*. + +#### OpenPGP + +Every participant has to announce their OpenPGP key (see answer above). +If you would like to send encrypted messages to a conference you have to make +sure that you have every participant's public key in your OpenKeychain. +Right now there is no check in another.im to ensure that. +You have to take care of that yourself. Go to the conference details and +touch every key id (The hexadecimal number below a contact). This will send you +to OpenKeychain which will assist you on adding the key. This works best in +very small conferences with contacts you are already using OpenPGP with. This +feature is regarded experimental. another.im is the only client that uses +XEP-0027 with conferences. (The XEP neither specifically allows nor disallows +this.) + +### What is Blind Trust Before Verification / why are messages marked with a red lock? + +Read more about the concept on https://gultsch.de/trust.html + +### I found a bug + +Please report it to our XMPP conference [`xmppclient-dev@conference.narayana.im`](xmpp:xmppclient-dev@conference.narayana.im). diff --git a/README.md b/README.md index 8b7a76c3d..a7da9be02 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,44 @@ -

Conversations Classic

+

another.im

-

Conversations Classic: the very last word in instant messaging

+

another.im: the very last word in instant messaging

+ +

+ Get it on F-Droid +

+ +## About app and us + +the Another Instant Messenger is not a messenger, +> don't believe the words. + + +we just offer you a standardized xmpp-client with predicted features which work equally on all platforms.\ +in our clients we have realized pure XMPP and you won't step on a hedgehog unexpectedly when you communicate with another xmpp client or server. + + +because you buy our open-sourced bitcoin,\ +we decided to offer you to buy our open-sourced messenger.\ +ha-ha joke.\ +it's free, really. GPLv3, whatever. + + +for Android, it's just an improved fork of Conversations,\ +for iOS, it's our development from scratch,\ +for Desktops we're polako [looking](xmpp:xmppclient-dev@conference.another.im). for devs. + + +all necessary XEP's was realized by Conversations devs but we added a cherry on top of this.\ +on the server side we are offering free to use Prosody server,\ +just connect to [another.im](xmpp:xmppclient-dev@conference.another.im), but we are encouroge you to don't trust us, self-host. + +OTR encryption is also supported as 'secret chats' due to otr has been designed for one-time sessions and the concept of 'secret chats' fully corresponds to the idea of the OTR. + +also you can just download the source code, compile it and install. + +only those who seeks will realize the way to pay us,\ +we can offer you additional server-side features and some telecommunication magic on our narayana.im ## Design principles @@ -28,11 +66,11 @@ ### XMPP Features -Conversations Classic works with every XMPP server out there. However XMPP is an +another.im works with every XMPP server out there. However XMPP is an extensible protocol. These extensions are standardized as well in so called -XEP's. Conversations Classic supports a couple of these to make the overall user +XEP's. another.im supports a couple of these 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 Classic you +support these extensions; therefore to get the most out of another.im 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 XEP's are: @@ -51,7 +89,7 @@ run your own XMPP server for you and your friends. These XEP's are: client to your desktop client and back within one conversation. * [XEP-0308: Last Message Correction](https://xmpp.org/extensions/xep-0308.html) allows you to edit last message as well as retract it * [XEP-0313: Message Archive Management](http://xmpp.org/extensions/xep-0313.html) synchronize message history with the - server. Catch up with messages that were sent while Conversations Classic was + server. Catch up with messages that were sent while another.im was offline. * [XEP-0352: Client State Indication](http://xmpp.org/extensions/xep-0352.html) lets the server know whether or not Conversations is in the background. Allows the server to save bandwidth by @@ -59,229 +97,10 @@ run your own XMPP server for you and your friends. These XEP's are: * [XEP-0363: HTTP File Upload](http://xmpp.org/extensions/xep-0363.html) allows you to share files in conferences and with offline contacts. * [XEP-0461: Message Replies](https://xmpp.org/extensions/xep-0461.html) provides support of native replies, which also works in many transports (gateways) as well +* [XEP-0364: Current Off-the-Record Messaging Usage](https://xmpp.org/extensions/xep-0364.html) is also supported as 'secret chats' due to otr has been designed for one-time sessions and the concept of 'secret chats' fully corresponds to the idea of the OTR. +### FAQ -## FAQ +[*FAQ*](/FAQ.md) is located separately and may contain links to upstream. -### General - -#### How do I install Conversations? - -Conversations Classic 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. - -#### 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 [conversations.im](https://account.conversations.im). If you don’t 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. - -##### 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. - -As of 2023 XMPP has reached a level of maturity where all major XMPP servers ([ejabberd](https://ejabberd.im), [Prosody](https://prosody.im), [Openfire](https://www.igniterealtime.org/projects/openfire/), [Tigase](https://tigase.net/xmpp-server/)) should work well with Conversations. - -Interoperability with Prosody and ejabberd is tested fairly regularly just because of their market share but we occasionally test with other servers too and fix issues as soon as we are being made aware of them. - -#### Where can I set up a custom hostname / port -Conversations Classic will automatically look up the SRV records for your domain name -which can point to any hostname port combination. If your server doesn’t provide -those please contact your admin and have them read -[this](http://prosody.im/doc/dns#srv_records). If your server operator is unwilling -to fix this you can enable advanced server settings in the expert settings of -Conversations Classic. - -#### I get 'Incompatible Server' - -As regular user you should be picking a different server. The server you selected -is probably insecure and/or very old. - -If you are a server administrator you should make sure that your server provides -either STARTTLS or [XEP-0368: SRV records for XMPP over TLS](https://xmpp.org/extensions/xep-0368.html). - -On rare occasions this error message might also be caused by a server not providing -a login (SASL) mechanism that Conversations Classic is able to handle. Conversations Classic supports -SCRAM-SHA1, PLAIN, EXTERNAL (client certs) and DIGEST-MD5. - -#### I get 'Bind failure'. What does that mean? - -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 Classic doesn’t 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 Classic doesn’t like that. -This can be fixed by creating a new account in Conversations Classic 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` doesn’t 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). - -**The battery consumption and the entire behavior of Conversations Classic 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 <= 7.1 or Conversations Classic from F-Droid (all Android versions) -The foreground notification is still controlled over the expert settings within Conversations Classic 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. - -#### Conversations doesn’t work for me. Where can I get help? - -You can join our conference room on [`xmppclient-dev@conference.narayana.im`](xmpp:xmppclient-dev@conference.narayana.im). -A lot of people in there are able to answer basic questions about the usage of -Conversations Classic 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. - -#### How does the address book integration work? - -The address book integration was designed to protect your privacy. Conversations Classic -neither uploads contacts from your address book to your server nor fills your -address book with unnecessary contacts from your online roster. If you manually -add a Jabber ID to your phones address book Conversations Classic will use the name and -the profile picture of this contact. To make the process of adding Jabber IDs to -your address book easier you can click on the profile picture in the contact -details within Conversations Classic. This will start an "add to address book" intent -with the JID as the payload. This doesn't require Conversations Classic to have write -permissions on your address book but also doesn't require you to copy/paste a -JID from one app to another. - -#### I get 'delivery failed' on my messages - -If you get delivery failed on images it's probably because the recipient lost -network connectivity during reception. In that case you can try it again at a -later time. - -For text messages the answer to your question is a little bit more complex. -When you see 'delivery failed' on text messages, it is always something that is -being reported by the server. The most common reason for this is that the -recipient failed to resume a connection. When a client loses connectivity for a -short time the client usually has a five minute window to pick up that -connection again. When the client fails to do so because the network -connectivity is out for longer than that all messages sent to that client will -be returned to the sender resulting in a delivery failed. - -Instead of returning a message to the sender both ejabberd and prosody have the -ability to store messages in offline storage when the disconnecting client is -the only client. In prosody this is available via an extra module called -```mod_smacks_offline```. In ejabberd this is available via some configuration -settings. - -Other less common reasons are that the message you sent didn't meet some -criteria enforced by the server (too large, too many). Another reason could be -that the recipient is offline and the server doesn't provide offline storage. - -Usually you are able to distinguish between these two groups in the fact that -the first one happens always after some time and the second one happens almost -instantly. - -#### Where can I see the status of my contacts? How can I set a status or priority? - -Statuses are a horrible metric. Setting them manually to a proper value rarely -works because users are either lazy or just forget about them. Setting them -automatically does not provide quality results either. Keyboard or mouse -activity as indicator for example fails when the user is just looking at -something (reading an article, watching a movie). Furthermore automatic setting -of status always implies an impact on your privacy (are you sure you want -everybody in your contact list to know that you have been using your computer at -4am‽). - -In the past status has been used to judge the likelihood of whether or not your -messages are being read. This is no longer necessary. With Chat Markers -(XEP-0333, supported by Conversations since 0.4) we have the ability to **know** -whether or not your messages are being read. Similar things can be said for -priorities. In the past priorities have been used (by servers, not by clients!) -to route your messages to one specific client. With carbon messages (XEP-0280, -supported by Conversations since 0.1) this is no longer necessary. Using -priorities to route OTR messages isn't practical either because they are not -changeable on the fly. Metrics like last active client (the client which sent -the last message) are much better. - -Unfortunately these modern replacements for legacy XMPP features are not widely -adopted. However Conversations Classic should be an instant messenger for the future and -instead of making Conversations Classic compatible with the past we should work on -implementing new, improved technologies and getting them into other XMPP clients -as well. - -Making these status and priority optional isn't a solution either because -Conversations Classic is trying to get rid of old behaviours and set an example for -other clients. - - -#### How do I backup / move Conversations Classic to a new device? - -Use the Backup button in the Settings. - -#### Conversations Classic is missing a certain feature - -Please report it to our XMPP conference [`xmppclient-dev@conference.narayana.im`](xmpp:xmppclient-dev@conference.narayana.im) - -### Security - -#### Why are there two end-to-end encryption methods and which one should I choose? - -* OMEMO works even when a contact is offline, and works with multiple devices. It also allows asynchronous file-transfer when the server has [HTTP File Upload](http://xmpp.org/extensions/xep-0363.html). However, OMEMO not widely support and is currently implemented only [by a handful of clients](https://omemo.top). -* OpenPGP (XEP-0027) is a very old encryption method that has some advantages over OMEMO but should only be used by people who know what they are doing. - -#### How do I use OpenPGP - -Before you continue reading you should note that the OpenPGP support in -Conversations Classic is experimental. This is not because it will make the app unstable -but because the fundamental concepts of PGP aren't ready for widespread use. -The way PGP works is that you trust Key IDs instead of JID's or email addresses. -So in theory your contact list should consist of Public-Key-IDs instead of -JID's. But of course no email or XMPP client out there implements these -concepts. Plus PGP in the context of instant messaging has a couple of -downsides: It is vulnerable to replay attacks and it is rather verbose. - -To use OpenPGP you have to install the open source app -[OpenKeychain](http://www.openkeychain.org) and then long press on the account in -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 couldn’t 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 doesn’t 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 group chats work? - -##### 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') - -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.) - -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*. - -##### OpenPGP - -Every participant has to announce their OpenPGP key (see answer above). -If you would like to send encrypted messages to a conference you have to make -sure that you have every participant's public key in your OpenKeychain. -Right now there is no check in Conversations Classic to ensure that. -You have to take care of that yourself. Go to the conference details and -touch every key id (The hexadecimal number below a contact). This will send you -to OpenKeychain which will assist you on adding the key. This works best in -very small conferences with contacts you are already using OpenPGP with. This -feature is regarded experimental. Conversations Classic is the only client that uses -XEP-0027 with conferences. (The XEP neither specifically allows nor disallows -this.) - -#### What is Blind Trust Before Verification / why are messages marked with a red lock? - -Read more about the concept on https://gultsch.de/trust.html - -#### I found a bug - -Please report it to our XMPP conference [`xmppclient-dev@conference.narayana.im`](xmpp:xmppclient-dev@conference.narayana.im). +*In case of issues, bugs, suggestions please contact us directly [`xmppclient-dev@conference.another.im`](xmpp:xmppclient-dev@conference.another.im).* \ No newline at end of file diff --git a/build.gradle b/build.gradle index c6e5f252d..b4e41d6fb 100644 --- a/build.gradle +++ b/build.gradle @@ -71,6 +71,7 @@ dependencies { implementation 'com.google.guava:guava:32.1.3-android' implementation 'io.michaelrocks:libphonenumber-android:8.13.17' implementation 'im.conversations.webrtc:webrtc-android:119.0.0' + implementation 'org.jitsi:org.otr4j:0.23' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation "androidx.recyclerview:recyclerview:1.2.1" @@ -83,6 +84,7 @@ dependencies { implementation 'com.github.singpolyma:TokenAutoComplete:bfa93780e0' + implementation 'com.github.kizitonwose.colorpreference:core:1.1.0' implementation 'com.github.kizitonwose.colorpreference:support:1.1.0' implementation 'com.caverock:androidsvg-aar:1.4' implementation 'com.github.singpolyma:Better-Link-Movement-Method:4df081e1e4' @@ -102,12 +104,12 @@ android { defaultConfig { minSdkVersion 24 targetSdkVersion 34 - versionCode 42115 - versionName "2.3.1" + versionCode 1 + versionName "1.0.0" archivesBaseName += "-$versionName" - applicationId "eu.siacs.conversations.classic" + applicationId "im.narayana.another" resValue "string", "applicationId", applicationId - def appName = "Conversations Classic" + def appName = "another.im" resValue "string", "app_name", appName buildConfigField "String", "APP_NAME", "\"$appName\"" } diff --git a/conversations.doap b/conversations.doap index 4d492f9b3..fdf3f2d19 100644 --- a/conversations.doap +++ b/conversations.doap @@ -5,22 +5,22 @@ xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:xmpp="https://linkmauve.fr/ns/xmpp-doap#" xmlns:schema="https://schema.org/"> - Conversations + another.im 2014-01-14 Android XMPP Client - Conversations is an open source XMPP/Jabber client for the Android platform + another.im is an open source XMPP/Jabber client for the Android platform forked from Conversations - - - + + + - - + + - + en @@ -46,8 +46,8 @@ - Daniel Gultsch - + kosyak + @@ -63,6 +63,14 @@ + + + + Deferred + 0.3.2 + + + diff --git a/fastlane/metadata/android/en-US/changelogs/104.txt b/fastlane/metadata/android/en-US/changelogs/104.txt new file mode 100644 index 000000000..11961d351 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/104.txt @@ -0,0 +1,3 @@ +* Implementation of OTR chats +* Rebranding from Conversation Clasic to another.im +* UI improvements and bug fixes \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/4211004.txt b/fastlane/metadata/android/en-US/changelogs/4211004.txt index b1ceab468..c6027656c 100644 --- a/fastlane/metadata/android/en-US/changelogs/4211004.txt +++ b/fastlane/metadata/android/en-US/changelogs/4211004.txt @@ -1,4 +1,4 @@ -The app is renamed to Conversations Classic. +The app is renamed to another.im. * Numerous small fixes * Info about PM on MUC details screen diff --git a/fastlane/metadata/android/en-US/changelogs/4211504.txt b/fastlane/metadata/android/en-US/changelogs/4211504.txt new file mode 100644 index 000000000..c8af84748 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4211504.txt @@ -0,0 +1,6 @@ +* Added account indicator +* Group chats are now marked with an additional icon +* Saved messages now have a brighter background color +* Added nickname in reply in Multi-User Chats(MUC) +* Contact status is now displayed separately +* Updated Russian and Ukrainian localizations diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 7892ba73e..81cea7fc6 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -1,42 +1,23 @@ -Easy to use, reliable, battery friendly. With built-in support for images, group chats and e2e encryption. +the Another Instant Messenger is not a messenger, +don't believe the words. -Design principles: +we just offer you a standardized xmpp-client with predicted features which work equally on all platforms. +in our clients we have realized pure XMPP and you won't step on a hedgehog unexpectedly when you communicate with another xmpp client or server. -* 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 +because you buy our open-sourced bitcoin, +we decided to offer you to buy our open-sourced messenger. +ha-ha joke. +it's free, really. GPLv3, whatever. -Features: +for Android, it's just an improved fork of Conversations, -* End-to-end encryption with either OMEMO or OpenPGP -* Sending and receiving images -* Encrypted audio and video calls (DTLS-SRTP) with DTMF dialpad -* 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 -* Builtin image editor -* Very low impact on battery life +all necessary XEP's was realized by Conversations devs but we added a cherry on top of this. +on the server side we are offering free to use Prosody server, +just connect to [another.im](xmpp:xmppclient-dev@conference.another.im), but we are encouroge you to don't trust us, self-host. -XMPP Features: +OTR encryption is also supported as 'secret chats' due to otr has been designed for one-time sessions and the concept of 'secret chats' fully corresponds to the idea of the OTR. -Conversations Classic works with every XMPP server out there. However XMPP is an extensible protocol. These extensions are standardized as well in so called XEP’s. Conversations Classic 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 Classic you should consider either switching to an XMPP server that does or - even better - run your own XMPP server for you and your friends. +also you can just download the source code, compile it and install. -These XEPs are - as of now: - -* XEP-0050: Ad-Hoc Commands lets to interact with gateways. -* 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-0215: External Service Discovery will be used to discover STUN and TURN servers which facilitate P2P A/V calls. -* XEP-0237: Roster Versioning mainly to save bandwidth on poor mobile connections. -* 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-0308: Last Message Correction allows you to edit last message as well as retract it. -* XEP-0313: Message Archive Management synchronize message history with the server. Catch up with messages that were sent while Conversations Classic was offline. -* XEP-0352: Client State Indication lets the server know whether or not Conversations Classic 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. -* XEP-0461: Message Replies provides support of native replies, which also works in many transports (gateways) as well. +only those who seeks will realize the way to pay us, +we can offer you additional server-side features and some telecommunication magic on our narayana.im \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png index d698391ea..ec01ff49b 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png index 6489618e6..b48255cc3 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png index 72568ff3d..5733737d1 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png index a34fa37b9..c49d655f5 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png index e83ee828d..df6fd862a 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png index e95cb3442..206135690 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png index 218a69565..a0892a1b2 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/08.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/08.png index dad3b1def..d073956d3 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/08.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/08.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/09.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/09.png index 3bea6c68f..ee344efdf 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/09.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/09.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/10.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/10.png index 2b601d6a5..26a2dcca9 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/10.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/10.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/11.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/11.png index a8424923a..792d59e5a 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/11.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/11.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/12.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/12.png index 59d1d8b85..dcdc9f215 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/12.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/12.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/13.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/13.png new file mode 100644 index 000000000..f0cb443bc Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/13.png differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt index 7a2205c2b..94b80310b 100644 --- a/fastlane/metadata/android/en-US/short_description.txt +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -1 +1 @@ -Encrypted, easy-to-use XMPP instant messenger for your mobile device +Yet Another Conversations fork but... \ No newline at end of file diff --git a/fastlane/metadata/android/ru/changelogs/104.txt b/fastlane/metadata/android/ru/changelogs/104.txt new file mode 100644 index 000000000..2ec5df7a0 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/104.txt @@ -0,0 +1,3 @@ +* Внедрение OTR-чатов +* Ребрендинг с Conversation Clasic на another.im +* Улучшения пользовательского интерфейса и исправление багов \ No newline at end of file diff --git a/fastlane/metadata/android/ru/changelogs/4211004.txt b/fastlane/metadata/android/ru/changelogs/4211004.txt index 11b67b8d3..773e9a385 100644 --- a/fastlane/metadata/android/ru/changelogs/4211004.txt +++ b/fastlane/metadata/android/ru/changelogs/4211004.txt @@ -1,4 +1,4 @@ -Приложение переименовано в Conversations Classic. +Приложение переименовано в another.im. * Многочисленные мелкие исправления * Информация о личных сообщениях в групповом чате на экране сведений diff --git a/fastlane/metadata/android/ru/changelogs/4211504.txt b/fastlane/metadata/android/ru/changelogs/4211504.txt new file mode 100644 index 000000000..7a77fb0e0 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/4211504.txt @@ -0,0 +1,6 @@ +* Добавлен индикатор учетной записи +* Групповые чаты теперь отмечены дополнительным значком +* Закрепленные сообщения теперь имеют более яркий цвет фона +* Добавлен никнейм в ответ в многопользовательских чатах(MUC) +* Статус контакта теперь отображается отдельно +* Обновлены русская и украинская локализации diff --git a/fastlane/metadata/android/ru/full_description.txt b/fastlane/metadata/android/ru/full_description.txt index 39c6708dd..7d2feb9ac 100644 --- a/fastlane/metadata/android/ru/full_description.txt +++ b/fastlane/metadata/android/ru/full_description.txt @@ -1,42 +1,23 @@ -Надёжный, простой в использовании, сберегающий заряд аккумулятора. Обладает встроенной поддержкой изображений, групповых чатов и сквозного шифрования. +Another Instant Messenger - это не просто мессенджер, +не верьте слухам. -Принципы проектирования: +Мы просто предлагаем вам стандартный XMPP-клиент с предсказуемым функционалом, который одинаково работает на всех платформах. +В наших клиентах мы реализовали чистый XMPP, и вы не столкнетесь в неожиданными проблемами, при общении с другими XMPP-клиентами или серверами. -* Быть предельно красивым и простым в использовании, не жертвуя безопасностью или конфиденциальностью -* Полагаться на существующие, устоявшиеся протоколы -* Не требовать учётной записи Google, в частности Google Cloud Messaging (GCM) -* Требовать как можно меньше разрешений +когда то вы купили биткойн из-за его открытого кода, +поэтому мы решили предложить вам купить наш мессенджер с открытым исходным кодом. +Шутка. +Он бесплатный, правда. GPLv3, по... -Функции: +Для андройд это просто улучшенный форк Conversations, -* Сквозное шифрование (от отправителя к получателю) при помощи OMEMO или OpenPGP -* Отправка и получение изображений -* Зашифрованные голосовые и видеозвонки (DTLS-SRTP) с DTMF-клавиатурой -* Интуитивно понятный интерфейс пользователя, соответствующий указаниям Android Design -* Изображения / Аватары для Ваших контактов -* Синхронизация с настольным клиентом -* Конференции (с поддержкой закладок) -* Интеграция адресной книги -* Несколько учётных записей / единая папка входящих -* Встроенный редактор изображений -* Крайне низкое влияние на время жизни от аккумулятора +Все необходимые XEP'ы были реализованы разработчиками Conversations, но мы добавили вишенку сверху. +На стороне сервера мы предлагаем бесплатный сервер на базе Prosody, +просто подключитесь к [another.im](xmpp:xmppclient-dev@conference.another.im), но мы призываем вас не доверять нам, используйте собственный хостинг. -Функции XMPP: +Шифрование OTR также поддерживается как «секретные чаты», поскольку OTR был разработан для одноразовых сессий, а концепция «секретных чатов» полностью соответствует идее OTR. -Conversations Classic работает с любым сервером XMPP. Однако, XMPP — расширяемый протокол. Расширения также стандартизированы в так называемых XEP. Conversations Classic поддерживает некоторые из них, дабы улучшить общий опыт использования. Может оказаться, что Ваш текущий сервер XMPP не поддерживает эти расширения. Поэтому, чтобы получить максимум от Conversations Classic, рассмотрите переход на XMPP-сервер с поддержкой этих расширений, или — ещё лучше — запускайте собственный сервер XMPP для себя и своих друзей. +также вы можете просто скачать исходный код, скомпилировать его и установить. -В настоящее время поддерживаются такие XEP: - -* XEP-0050: Контекстные Команды, позволяет взаимодействовать с мостами. -* XEP-0065: Байтовые Потоки SOCKS5 (либо mod_proxy65). Используется для передачи файлов, если обе стороны находятся за брандмауэром (NAT). -* XEP-0163: Персональный Протокол Событий, для аватаров. -* XEP-0191: Команда Блокировки, позволяет Вам заносить спамеров в чёрный список или блокировать контакты, не удаляя их из своего списка. -* XEP-0198: Управление Потоками, позволяет XMPP выдерживать небольшие перебои в сети и смены основного TCP-соединения. -* XEP-0215: Поиск Внешних Сервисов, позволяет находить STUN- и TURN-сервера, когда аудио-/видеозвонок невозможно осуществить напрямую. -* XEP-0237: Версионирование Списка Контактов, прежде всего для сберегания мобильного трафика. -* XEP-0280: Сообщения под Копирку, автоматически синхронизирует отправленные сообщений на настольный клиент, чем позволяет плавно переключаться между мобильным и настольным клиентами в рамках одного разговора. -* XEP-0308: Исправление Последнего Сообщения, позволяет отредактировать или отозвать сообщение. -* XEP-0313: Управление Архивом Сообщений, синхронизирует историю сообщений с сервером. Узнавайте о сообщениях, отправленных, пока Conversations Classic находился оффлайн. -* XEP-0352: Индикация Состояния Клиента, сообщает серверу, работает ли Conversations Classic в фоновом режиме. Позволяет серверу сберегать пропускную способность, удерживая неважные пакеты. -* XEP-0363: Загрузка Файлов по HTTP, позволяет обмениваться файлами в конференциях и с оффлайн-контактами. Требует дополнительного компонента на Вашем сервере. -* XEP-0461: Ответы на Сообщения, предоставляет поддержку привязанных к сообщению ответов, которые также работают со многими транспортами (мостами). +только те, кто ищет, поймут, как нам платить, +мы можем предложить вам дополнительные услуги и немного телекоммуникационной магии на нашем narayana.im. \ No newline at end of file diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/01.png b/fastlane/metadata/android/ru/images/phoneScreenshots/01.png index c949baff5..1a63bbb70 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/01.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/01.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/02.png b/fastlane/metadata/android/ru/images/phoneScreenshots/02.png index 2b1ec9cfd..ff08d5482 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/02.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/02.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/03.png b/fastlane/metadata/android/ru/images/phoneScreenshots/03.png index 3700ad9c6..307ef39a7 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/03.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/03.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/04.png b/fastlane/metadata/android/ru/images/phoneScreenshots/04.png index 587ed0dca..6c4bbbd4b 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/04.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/04.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/05.png b/fastlane/metadata/android/ru/images/phoneScreenshots/05.png index efdaff600..960411dfa 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/05.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/05.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/06.png b/fastlane/metadata/android/ru/images/phoneScreenshots/06.png index e95cb3442..518b17e9b 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/06.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/06.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/07.png b/fastlane/metadata/android/ru/images/phoneScreenshots/07.png index d00172292..a0892a1b2 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/07.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/07.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/08.png b/fastlane/metadata/android/ru/images/phoneScreenshots/08.png index dad3b1def..0cc1b31f1 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/08.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/08.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/09.png b/fastlane/metadata/android/ru/images/phoneScreenshots/09.png index 517cffba4..ee344efdf 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/09.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/09.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/10.png b/fastlane/metadata/android/ru/images/phoneScreenshots/10.png index 494f8f012..c2c464910 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/10.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/10.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/11.png b/fastlane/metadata/android/ru/images/phoneScreenshots/11.png index a8424923a..c7c13c217 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/11.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/11.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/12.png b/fastlane/metadata/android/ru/images/phoneScreenshots/12.png index db013f06d..dcdc9f215 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/12.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/12.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/13.png b/fastlane/metadata/android/ru/images/phoneScreenshots/13.png new file mode 100644 index 000000000..44c50d2c5 Binary files /dev/null and b/fastlane/metadata/android/ru/images/phoneScreenshots/13.png differ diff --git a/fastlane/metadata/android/ru/short_description.txt b/fastlane/metadata/android/ru/short_description.txt index 2c0b5fe0c..3fa8b4f8f 100644 --- a/fastlane/metadata/android/ru/short_description.txt +++ b/fastlane/metadata/android/ru/short_description.txt @@ -1 +1 @@ -Простой в использовании XMPP-клиент с поддержкой шифрования для Вашого телефона +Очередной клон Conversations, но... \ No newline at end of file diff --git a/fastlane/metadata/android/uk/changelogs/104.txt b/fastlane/metadata/android/uk/changelogs/104.txt new file mode 100644 index 000000000..f0554e37b --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/104.txt @@ -0,0 +1,3 @@ +* Впровадження OTR-чатів +* Ребрендинг з Conversation Classic на another.im +* Покращення користувацького інтерфейсу та виправлення помилок \ No newline at end of file diff --git a/fastlane/metadata/android/uk/changelogs/4211004.txt b/fastlane/metadata/android/uk/changelogs/4211004.txt index b08f436dd..56447144d 100644 --- a/fastlane/metadata/android/uk/changelogs/4211004.txt +++ b/fastlane/metadata/android/uk/changelogs/4211004.txt @@ -1,4 +1,4 @@ -Застосунок перейменовано на Conversations Classic. +Застосунок перейменовано на another.im. * Численні дрібні виправлення * Інформація про особисті повідомлення в груповому чаті на екрані деталей diff --git a/fastlane/metadata/android/uk/changelogs/4211504.txt b/fastlane/metadata/android/uk/changelogs/4211504.txt new file mode 100644 index 000000000..ea82804cc --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/4211504.txt @@ -0,0 +1,6 @@ +* Додано індикатор облікового запису +* Групові чати тепер позначено додатковою піктограмою +* Закріплені повідомлення тепер мають яскравіший колір тла +* Додано нікнейм у відповідь у групових чатах +* Статус контакту тепер відображається окремо +* Оновлено російську й українську локалізації diff --git a/fastlane/metadata/android/uk/full_description.txt b/fastlane/metadata/android/uk/full_description.txt index 55c7fcc8b..cac4c1442 100644 --- a/fastlane/metadata/android/uk/full_description.txt +++ b/fastlane/metadata/android/uk/full_description.txt @@ -1,42 +1,23 @@ -Надійний, простий у використанні, ощадливо витрачає заряд акумулятора. Має вбудовану підтримку зображень, групових чатів і наскрізного шифрування. +Another Instant Messenger - це не просто месенджер, +не вірте чуткам. -Принципи проєктування: +Ми просто пропонуємо вам стандартний XMPP-клієнт із передбачуваним функціоналом, який однаково працює на всіх платформах. +У наших клієнтах ми реалізували чистий XMPP, і ви не зіштовхнетеся з неочікуваними проблемами, при спілкуванні з іншими XMPP-клієнтами чи серверами. -* Бути максимально красивим та простим у використанні, не жертвуючи безпекою чи конфіденційністю -* Покладатися на існуючі, добре встановлені протоколи -* Не вимагати облікового запису Google, зокрема Google Cloud Messaging (GCM) -* Вимагати якомога менше дозволів +колись ви купили біткойн через його відкритий код, +тому ми вирішили запропонувати вам купити наш месенджер із відкритим вихідним кодом. +Жарт. +Він безкоштовний, правда. GPLv3, по... -Функції: +Для андройд це просто покращений форк Conversations, -* Наскрізне шифрування (від відправника до одержувача) за допомогою OMEMO або OpenPGP -* Надсилання та отримання зображень -* Зашифровані голосові та відеодзвінки (DTLS-SRTP) з DTMF-клавіатурою -* Інтуїтивно зрозумілий інтерфейс користувача, який відповідає вказівкам Android Design -* Зображення / Аватари для Ваших контактів -* Синхронізація з настільним клієнтом -* Конференції (з підтримкою закладок) -* Інтеграція адресної книги -* Кілька облікових записів / єдина папка вхідних -* Вбудований редактор зображень -* Дуже низький вплив на термін служби акумулятора +Всі необхідні XEP'и були реалізовані розробниками Conversations, але ми додали зверху вишні (дивовижні). +На боці сервера ми пропонуємо безкоштовний сервер на базі Prosody, +просто підключіться до [another.im](xmpp:xmppclient-dev@conference.another.im), але ми закликаємо вас не довірять нам, використовуйте власний хостинг. -Функції XMPP: +Шифрування OTR також підтримується як «секретні чати», оскільки OTR був розроблений для одноразових сесій, а концепція «секретних чатів» цілком відповідає ідеї OTR. -Conversations Classic працює з будь-яким сервером XMPP. Проте XMPP — розширюваний протокол. Розширення також стандартизовані в так званих XEP. Conversations Classic підтримує кілька з них, щоб покращити загальний досвід користування. Може виявитися, що Ваш поточний сервер XMPP не підтримує цих розширень. Тому, щоб отримати максимум від Conversations Classic, розгляньте перехід на XMPP-сервер з підтримкою цих розширень або — ще краще — запускайте власний сервер XMPP для себе і своїх друзів. +також ви можете просто скачати [вихідний код](https://dev.narayana.im/narayana/anotherim), скомпілювати його і встановити. -На даний час підтримуються такі XEP: - -* XEP-0050: Контекстові Команди, дозволяє взаємодіяти з мостами. -* XEP-0065: Байтові Потоки SOCKS5 (або mod_proxy65). Використовується для передачі файлів, якщо обидві сторони знаходяться за брандмауером (NAT). -* XEP-0163: Персональний Протокол Подій, для аватарів. -* XEP-0191: Команда Блокування, дозволяє Вам заносити спамерів у чорний список або блокувати контакти, не видаляючи їх зі свого списку. -* XEP-0198: Керування Потоками, дозволяє XMPP витримувати невеликі перебої в мережі та зміни основного TCP-з'єднання. -* XEP-0215: Пошук Зовнішніх Сервісів, дозволяє знаходити STUN- та TURN-сервери, коли аудіо-/відеодзвінок неможливо здійснити напряму. -* XEP-0237: Версіонування Списку Контактів, передусім для заощадження мобільного трафіку. -* XEP-0280: Повідомлення під Копірку, автоматично синхронізує надіслані повідомлення на настільний клієнт, чим дозволяє плавно перемикатися між мобільним і настільним клієнтами в рамках однієї розмови. -* XEP-0308: Виправлення Останнього Повідомлення, дозволяє відредагувати чи відкликати повідомлення. -* XEP-0313: Керування Архівом Повідомлень, синхронізує історію повідомлень із сервером. Дізнавайтеся про повідомлення, надіслані, поки Conversations Classic був офлайн. -* XEP-0352: Індикація Стану Клієнта, повідомляє серверу, чи працює Conversations Classic у фоновому режимі. Дозволяє серверу заощаджувати пропускну здатність, утримуючи неважливі пакети. -* XEP-0363: Завантаження Файлів за HTTP, дозволяє обмінюватися файлами в конференціях і з офлайн-контактами. Потребує додаткового компонента на Вашому сервері. -* XEP-0461: Відповіді на Повідомлення, надає підтримку привʼязаних до повідомлення відповідей, які також працюють із багатьма транспортами (мостами). +тільки ті, хто шукають, збагнуть, куди в нас гроші запихувати, +ми можемо запропонувати вам додаткові послуги і трішки телекомунікаційної магії на нашому [narayana.im](https://narayana.im). diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/01.png b/fastlane/metadata/android/uk/images/phoneScreenshots/01.png index cda0673cf..69702c76d 100644 Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/01.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/01.png differ diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/02.png b/fastlane/metadata/android/uk/images/phoneScreenshots/02.png index 70d2e9d07..320e9f03e 100644 Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/02.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/02.png differ diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/03.png b/fastlane/metadata/android/uk/images/phoneScreenshots/03.png index 1ab5ffd16..496b8164f 100644 Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/03.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/03.png differ diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/04.png b/fastlane/metadata/android/uk/images/phoneScreenshots/04.png index 0ebef1ba9..7923746d2 100644 Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/04.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/04.png differ diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/05.png b/fastlane/metadata/android/uk/images/phoneScreenshots/05.png index 7035dba4c..b95408943 100644 Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/05.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/05.png differ diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/06.png b/fastlane/metadata/android/uk/images/phoneScreenshots/06.png index e95cb3442..3d5c194df 100644 Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/06.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/06.png differ diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/07.png b/fastlane/metadata/android/uk/images/phoneScreenshots/07.png index e997d979a..a0892a1b2 100644 Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/07.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/07.png differ diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/08.png b/fastlane/metadata/android/uk/images/phoneScreenshots/08.png index dad3b1def..c7a8f570e 100644 Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/08.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/08.png differ diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/09.png b/fastlane/metadata/android/uk/images/phoneScreenshots/09.png index ae5acf37b..ee344efdf 100644 Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/09.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/09.png differ diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/10.png b/fastlane/metadata/android/uk/images/phoneScreenshots/10.png index 455e66d6c..df9be9544 100644 Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/10.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/10.png differ diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/11.png b/fastlane/metadata/android/uk/images/phoneScreenshots/11.png index 2812ee435..e29d4d03a 100644 Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/11.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/11.png differ diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/12.png b/fastlane/metadata/android/uk/images/phoneScreenshots/12.png index 05bd0304a..dcdc9f215 100644 Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/12.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/12.png differ diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/13.png b/fastlane/metadata/android/uk/images/phoneScreenshots/13.png new file mode 100644 index 000000000..1c707a9b1 Binary files /dev/null and b/fastlane/metadata/android/uk/images/phoneScreenshots/13.png differ diff --git a/screenshots.png b/screenshots.png deleted file mode 100644 index af411ba3e..000000000 Binary files a/screenshots.png and /dev/null differ diff --git a/screenshots.xcf b/screenshots.xcf deleted file mode 100644 index 45f79ca5a..000000000 Binary files a/screenshots.xcf and /dev/null differ diff --git a/settings.gradle b/settings.gradle index 4193570fa..044568f81 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'Conversations' +rootProject.name = 'anotherim' diff --git a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java index aab32220d..67803b387 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -36,6 +36,7 @@ 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.utils.UIHelper; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; @@ -44,8 +45,14 @@ import static eu.siacs.conversations.utils.PermissionUtils.writeGranted; import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.navigation.NavigationBarView; +import com.kizitonwose.colorpreference.ColorDialog; +import com.kizitonwose.colorpreference.ColorShape; -public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated, AccountAdapter.OnTglAccountState { +public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, + KeyChainAliasCallback, + XmppConnectionService.OnAccountCreated, + AccountAdapter.OnTglAccountState, + ColorDialog.OnColorSelectedListener { private final String STATE_SELECTED_ACCOUNT = "selected_account"; @@ -61,6 +68,18 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda protected Pair mPostponedActivityResult = null; + private AccountAdapter.ColorSelectorListener colorSelectorListener = new AccountAdapter.ColorSelectorListener() { + @Override + public void onColorPickerRequested(Jid accountJid, int currentColor) { + new ColorDialog.Builder(ManageAccountActivity.this) + .setColorShape(ColorShape.CIRCLE) + .setColorChoices(R.array.themeColorsOverride) + .setSelectedColor(currentColor) + .setTag(accountJid.asBareJid().toEscapedString()) + .show(); + } + }; + @Override public void onAccountUpdate() { refreshUi(); @@ -102,7 +121,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } accountListView = findViewById(R.id.account_list); - this.mAccountAdapter = new AccountAdapter(this, accountList); + this.mAccountAdapter = new AccountAdapter(this, accountList, colorSelectorListener); accountListView.setAdapter(this.mAccountAdapter); accountListView.setOnItemClickListener((arg0, view, position, arg3) -> switchToAccount(accountList.get(position))); registerForContextMenu(accountListView); @@ -158,6 +177,13 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda super.onSaveInstanceState(savedInstanceState); } + @Override + protected void onDestroy() { + super.onDestroy(); + colorSelectorListener = null; + mAccountAdapter.colorSelectorListener = null; + } + @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); @@ -349,6 +375,12 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } } + @Override + public void onColorSelected(int newColor, String tag) { + UIHelper.overrideAccountColor(this, tag, newColor); + refreshUiReal(); + } + private void addAccountFromKey() { try { KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null); diff --git a/src/conversations/res/.DS_Store b/src/conversations/res/.DS_Store new file mode 100644 index 000000000..746f05cb8 Binary files /dev/null and b/src/conversations/res/.DS_Store differ diff --git a/src/conversations/res/drawable-anydpi/ic_notification.xml b/src/conversations/res/drawable-anydpi/ic_notification.xml new file mode 100644 index 000000000..c469e6d3f --- /dev/null +++ b/src/conversations/res/drawable-anydpi/ic_notification.xml @@ -0,0 +1,22 @@ + + > + + + + + + + + diff --git a/src/conversations/res/drawable-hdpi/ic_notification.png b/src/conversations/res/drawable-hdpi/ic_notification.png index 0d4f42f12..dbca4adfc 100644 Binary files a/src/conversations/res/drawable-hdpi/ic_notification.png and b/src/conversations/res/drawable-hdpi/ic_notification.png differ diff --git a/src/conversations/res/drawable-hdpi/main_logo.png b/src/conversations/res/drawable-hdpi/main_logo.png deleted file mode 100644 index 98dde6e9d..000000000 Binary files a/src/conversations/res/drawable-hdpi/main_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-hdpi/splash_logo.png b/src/conversations/res/drawable-hdpi/splash_logo.png deleted file mode 100644 index c7da96af9..000000000 Binary files a/src/conversations/res/drawable-hdpi/splash_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-mdpi/ic_notification.png b/src/conversations/res/drawable-mdpi/ic_notification.png index 6e9aaeee3..32a22558e 100644 Binary files a/src/conversations/res/drawable-mdpi/ic_notification.png and b/src/conversations/res/drawable-mdpi/ic_notification.png differ diff --git a/src/conversations/res/drawable-mdpi/main_logo.png b/src/conversations/res/drawable-mdpi/main_logo.png deleted file mode 100644 index 5378f5b82..000000000 Binary files a/src/conversations/res/drawable-mdpi/main_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-mdpi/splash_logo.png b/src/conversations/res/drawable-mdpi/splash_logo.png deleted file mode 100644 index 1e237e487..000000000 Binary files a/src/conversations/res/drawable-mdpi/splash_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-xhdpi/ic_notification.png b/src/conversations/res/drawable-xhdpi/ic_notification.png index 0497abdb2..e390243ef 100644 Binary files a/src/conversations/res/drawable-xhdpi/ic_notification.png and b/src/conversations/res/drawable-xhdpi/ic_notification.png differ diff --git a/src/conversations/res/drawable-xhdpi/main_logo.png b/src/conversations/res/drawable-xhdpi/main_logo.png deleted file mode 100644 index af5f69f4c..000000000 Binary files a/src/conversations/res/drawable-xhdpi/main_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-xhdpi/splash_logo.png b/src/conversations/res/drawable-xhdpi/splash_logo.png deleted file mode 100644 index aa3812b0a..000000000 Binary files a/src/conversations/res/drawable-xhdpi/splash_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-xxhdpi/ic_notification.png b/src/conversations/res/drawable-xxhdpi/ic_notification.png index 9e59b6a0a..286e42251 100644 Binary files a/src/conversations/res/drawable-xxhdpi/ic_notification.png and b/src/conversations/res/drawable-xxhdpi/ic_notification.png differ diff --git a/src/conversations/res/drawable-xxhdpi/main_logo.png b/src/conversations/res/drawable-xxhdpi/main_logo.png deleted file mode 100644 index 4fed66ae6..000000000 Binary files a/src/conversations/res/drawable-xxhdpi/main_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-xxhdpi/splash_logo.png b/src/conversations/res/drawable-xxhdpi/splash_logo.png deleted file mode 100644 index a029dc131..000000000 Binary files a/src/conversations/res/drawable-xxhdpi/splash_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-xxxhdpi/ic_notification.png b/src/conversations/res/drawable-xxxhdpi/ic_notification.png deleted file mode 100644 index ed864641b..000000000 Binary files a/src/conversations/res/drawable-xxxhdpi/ic_notification.png and /dev/null differ diff --git a/src/conversations/res/drawable-xxxhdpi/main_logo.png b/src/conversations/res/drawable-xxxhdpi/main_logo.png deleted file mode 100644 index db72f5724..000000000 Binary files a/src/conversations/res/drawable-xxxhdpi/main_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-xxxhdpi/splash_logo.png b/src/conversations/res/drawable-xxxhdpi/splash_logo.png deleted file mode 100644 index 6a01ae89a..000000000 Binary files a/src/conversations/res/drawable-xxxhdpi/splash_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable/ic_launcher_foreground.xml b/src/conversations/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 5851e5f2c..000000000 --- a/src/conversations/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/src/conversations/res/drawable/ic_launcher_monochrome.xml b/src/conversations/res/drawable/ic_launcher_monochrome.xml deleted file mode 100644 index 56895d605..000000000 --- a/src/conversations/res/drawable/ic_launcher_monochrome.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/src/conversations/res/drawable/ic_main_logo.xml b/src/conversations/res/drawable/ic_main_logo.xml new file mode 100644 index 000000000..751d609d6 --- /dev/null +++ b/src/conversations/res/drawable/ic_main_logo.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/src/conversations/res/drawable/ic_main_logo_bg.xml b/src/conversations/res/drawable/ic_main_logo_bg.xml new file mode 100644 index 000000000..4c94d5500 --- /dev/null +++ b/src/conversations/res/drawable/ic_main_logo_bg.xml @@ -0,0 +1,4 @@ + + + diff --git a/src/conversations/res/drawable/main_logo.xml b/src/conversations/res/drawable/main_logo.xml new file mode 100644 index 000000000..f2e8872b1 --- /dev/null +++ b/src/conversations/res/drawable/main_logo.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/conversations/res/drawable/new_launcher_background.xml b/src/conversations/res/drawable/new_launcher_background.xml new file mode 100644 index 000000000..224f1a36e --- /dev/null +++ b/src/conversations/res/drawable/new_launcher_background.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/src/conversations/res/drawable/new_launcher_foreground.xml b/src/conversations/res/drawable/new_launcher_foreground.xml new file mode 100644 index 000000000..2e4c5653a --- /dev/null +++ b/src/conversations/res/drawable/new_launcher_foreground.xml @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/src/conversations/res/drawable/new_launcher_monochrome.xml b/src/conversations/res/drawable/new_launcher_monochrome.xml new file mode 100644 index 000000000..11bb13e18 --- /dev/null +++ b/src/conversations/res/drawable/new_launcher_monochrome.xml @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/src/conversations/res/drawable/splash_logo.xml b/src/conversations/res/drawable/splash_logo.xml new file mode 100644 index 000000000..59b81c018 --- /dev/null +++ b/src/conversations/res/drawable/splash_logo.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/conversations/res/mipmap-anydpi-v26/new_launcher.xml b/src/conversations/res/mipmap-anydpi-v26/new_launcher.xml new file mode 100644 index 000000000..a7c225307 --- /dev/null +++ b/src/conversations/res/mipmap-anydpi-v26/new_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/conversations/res/mipmap-anydpi-v26/new_launcher_round.xml b/src/conversations/res/mipmap-anydpi-v26/new_launcher_round.xml new file mode 100644 index 000000000..a7c225307 --- /dev/null +++ b/src/conversations/res/mipmap-anydpi-v26/new_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/conversations/res/mipmap-hdpi/new_launcher.png b/src/conversations/res/mipmap-hdpi/new_launcher.png deleted file mode 100644 index 70a2e4ae9..000000000 Binary files a/src/conversations/res/mipmap-hdpi/new_launcher.png and /dev/null differ diff --git a/src/conversations/res/mipmap-hdpi/new_launcher.webp b/src/conversations/res/mipmap-hdpi/new_launcher.webp new file mode 100644 index 000000000..1adbc6293 Binary files /dev/null and b/src/conversations/res/mipmap-hdpi/new_launcher.webp differ diff --git a/src/conversations/res/mipmap-hdpi/new_launcher_round.png b/src/conversations/res/mipmap-hdpi/new_launcher_round.png deleted file mode 100644 index 9d4efa725..000000000 Binary files a/src/conversations/res/mipmap-hdpi/new_launcher_round.png and /dev/null differ diff --git a/src/conversations/res/mipmap-hdpi/new_launcher_round.webp b/src/conversations/res/mipmap-hdpi/new_launcher_round.webp new file mode 100644 index 000000000..8fb6d66b4 Binary files /dev/null and b/src/conversations/res/mipmap-hdpi/new_launcher_round.webp differ diff --git a/src/conversations/res/mipmap-mdpi/new_launcher.png b/src/conversations/res/mipmap-mdpi/new_launcher.png deleted file mode 100644 index 30e6c86e1..000000000 Binary files a/src/conversations/res/mipmap-mdpi/new_launcher.png and /dev/null differ diff --git a/src/conversations/res/mipmap-mdpi/new_launcher.webp b/src/conversations/res/mipmap-mdpi/new_launcher.webp new file mode 100644 index 000000000..afa8d7218 Binary files /dev/null and b/src/conversations/res/mipmap-mdpi/new_launcher.webp differ diff --git a/src/conversations/res/mipmap-mdpi/new_launcher_round.png b/src/conversations/res/mipmap-mdpi/new_launcher_round.png deleted file mode 100644 index a196fd228..000000000 Binary files a/src/conversations/res/mipmap-mdpi/new_launcher_round.png and /dev/null differ diff --git a/src/conversations/res/mipmap-mdpi/new_launcher_round.webp b/src/conversations/res/mipmap-mdpi/new_launcher_round.webp new file mode 100644 index 000000000..5db1773e3 Binary files /dev/null and b/src/conversations/res/mipmap-mdpi/new_launcher_round.webp differ diff --git a/src/conversations/res/mipmap-xhdpi/new_launcher.png b/src/conversations/res/mipmap-xhdpi/new_launcher.png deleted file mode 100644 index 39b674cb9..000000000 Binary files a/src/conversations/res/mipmap-xhdpi/new_launcher.png and /dev/null differ diff --git a/src/conversations/res/mipmap-xhdpi/new_launcher.webp b/src/conversations/res/mipmap-xhdpi/new_launcher.webp new file mode 100644 index 000000000..2fddff0e5 Binary files /dev/null and b/src/conversations/res/mipmap-xhdpi/new_launcher.webp differ diff --git a/src/conversations/res/mipmap-xhdpi/new_launcher_round.png b/src/conversations/res/mipmap-xhdpi/new_launcher_round.png deleted file mode 100644 index 039a09fdd..000000000 Binary files a/src/conversations/res/mipmap-xhdpi/new_launcher_round.png and /dev/null differ diff --git a/src/conversations/res/mipmap-xhdpi/new_launcher_round.webp b/src/conversations/res/mipmap-xhdpi/new_launcher_round.webp new file mode 100644 index 000000000..33d603117 Binary files /dev/null and b/src/conversations/res/mipmap-xhdpi/new_launcher_round.webp differ diff --git a/src/conversations/res/mipmap-xxhdpi/new_launcher.png b/src/conversations/res/mipmap-xxhdpi/new_launcher.png deleted file mode 100644 index a6273f142..000000000 Binary files a/src/conversations/res/mipmap-xxhdpi/new_launcher.png and /dev/null differ diff --git a/src/conversations/res/mipmap-xxhdpi/new_launcher.webp b/src/conversations/res/mipmap-xxhdpi/new_launcher.webp new file mode 100644 index 000000000..53b761687 Binary files /dev/null and b/src/conversations/res/mipmap-xxhdpi/new_launcher.webp differ diff --git a/src/conversations/res/mipmap-xxhdpi/new_launcher_round.png b/src/conversations/res/mipmap-xxhdpi/new_launcher_round.png deleted file mode 100644 index 6c10228ec..000000000 Binary files a/src/conversations/res/mipmap-xxhdpi/new_launcher_round.png and /dev/null differ diff --git a/src/conversations/res/mipmap-xxhdpi/new_launcher_round.webp b/src/conversations/res/mipmap-xxhdpi/new_launcher_round.webp new file mode 100644 index 000000000..904abc5a9 Binary files /dev/null and b/src/conversations/res/mipmap-xxhdpi/new_launcher_round.webp differ diff --git a/src/conversations/res/mipmap-xxxhdpi/new_launcher.png b/src/conversations/res/mipmap-xxxhdpi/new_launcher.png deleted file mode 100644 index 1a1105fd7..000000000 Binary files a/src/conversations/res/mipmap-xxxhdpi/new_launcher.png and /dev/null differ diff --git a/src/conversations/res/mipmap-xxxhdpi/new_launcher.webp b/src/conversations/res/mipmap-xxxhdpi/new_launcher.webp new file mode 100644 index 000000000..e403364f0 Binary files /dev/null and b/src/conversations/res/mipmap-xxxhdpi/new_launcher.webp differ diff --git a/src/conversations/res/mipmap-xxxhdpi/new_launcher_round.png b/src/conversations/res/mipmap-xxxhdpi/new_launcher_round.png deleted file mode 100644 index 914cad2c4..000000000 Binary files a/src/conversations/res/mipmap-xxxhdpi/new_launcher_round.png and /dev/null differ diff --git a/src/conversations/res/mipmap-xxxhdpi/new_launcher_round.webp b/src/conversations/res/mipmap-xxxhdpi/new_launcher_round.webp new file mode 100644 index 000000000..c0f134223 Binary files /dev/null and b/src/conversations/res/mipmap-xxxhdpi/new_launcher_round.webp differ diff --git a/src/conversations/res/values-ar/strings.xml b/src/conversations/res/values-ar/strings.xml deleted file mode 100644 index 6483bc9df..000000000 --- a/src/conversations/res/values-ar/strings.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - اختر مزود خدمة XMPP الخاص بك - استخدِم conversations.im - أنشئ حسابًا جديدًا - هل تملك حساب XMPP؟؟ قد يكون ذلك ممكنا لو كنت تستعمل خدمة XMPP أخرى أو إستعملت تطبيق Conversations سابقا. أو يمكنك صنع حساب XMPP جديد الآن. -\nملاحظة: بعض خدمات البريد الإلكتروني تقدم حسابات XMPP. - XMPP هو مزود مستقل لشبكة المراسلة الفورية. يمكنك استخدام هذا العميل مع أي خادم XMPP تختاره. -\nولكن من أجل راحتك ، فقد جعلنا من السهل إنشاء حساب على موقع chat. مزود مناسب بشكل خاص للاستخدام مع المحادثات. - لقد تمت دعوتك إلى%1$s. سنوجهك خلال عملية إنشاء حساب. -\nعند اختيار%1$s كموفر ، ستتمكن من التواصل مع مستخدمي مقدمي الخدمات الآخرين من خلال منحهم عنوان XMPP الكامل الخاص بك. - لقد تمت دعوتك إلى%1$s. تم بالفعل اختيار اسم مستخدم لك. سنوجهك خلال عملية إنشاء حساب. -\nستتمكن من التواصل مع مستخدمي مقدمي الخدمات الآخرين من خلال منحهم عنوان XMPP الكامل الخاص بك. - سيرفر دعوتك - لم يتم التقاط الكود بطريقة جيّدة - إضغط على زر مشاركة لترسل إلى المتصل بك دعوة إلى %1$s. - إذا كان المتصل بك قريبا منك، يمكنه فحص الكود بالأسفل ليقبل دعوتك. - إنظم %1$s وتحدّث معي: %2$s - شارك الدعوة مع… - \ No newline at end of file diff --git a/src/conversations/res/values-bg/strings.xml b/src/conversations/res/values-bg/strings.xml deleted file mode 100644 index 92667523d..000000000 --- a/src/conversations/res/values-bg/strings.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - Изберете своя XMPP доставчик - Използвайте conversations.im - Създаване не нов профил - Имате ли вече XMPP профил? Може да имате, ако вече използвате друг клиент на XMPP или сте използвали Conversations и преди. Ако не, можете да създадете нов XMPP профил сега.\nСъвет: някои доставчици на е-поща също предоставят XMPP профили. -  - XMPP е мрежа за общуване чрез мигновени съобщения, която не е обвързана с конкретен доставчик. Можете да използвате клиента с всеки сървър, който работи с XMPP.\nЗа Ваше удобство, обаче, ние предоставяме лесен начин да си създадете профил в conversations.im — сървър, пригоден да работи най-добре с Conversations. - Получихте покана за %1$s. Ще Ви преведем през процеса на създаване на профил.\nИзбирайки %1$s за доставчик, Вие ще можете да общувате и с потребители на други доставчици, като им предоставите своя пълен XMPP адрес. - Получихте покана за %1$s. Вече Ви избрахме потребителско име. Ще Ви преведем през процеса на създаване на профил.\nЩе можете да общувате и с потребители на други доставчици, като им предоставите своя пълен XMPP адрес. - Вашата покана за сървъра - Неправилно форматиран код за достъп - Докоснете бутона за споделяне, за да изпратите на контакта си покана за %1$s. - Ако контактът Ви е наблизо, може да сканира кода по-долу, за да приеме поканата Ви. - Присъедини се в %1$s и си пиши с мен: %2$s - Споделяне на поканата чрез… - \ No newline at end of file diff --git a/src/conversations/res/values-bn-rIN/strings.xml b/src/conversations/res/values-bn-rIN/strings.xml deleted file mode 100644 index 382343a37..000000000 --- a/src/conversations/res/values-bn-rIN/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - XMPP সার্ভার নির্বাচন করুন - conversations.im-ই ব্যবহার করা যাক - নতুন অ্যকাউন্ট তৈরী করা যাক - আপনার কি একটা XMPP অ্যকাউন্ট ইতিমধ্যে করা আছে? সেরকমটা হতেই পারে যদি এর আগে আপনি কোনো অন্য XMPP প্রোগ্রাম বা অ্যাপ ব্যবহার করে থাকেন। এই মুহুর্তে আরেকটা অ্যকাউন্ট তৈরী করা সম্ভব না।‌\nHint: মাঝে মাঝে ইমেল অ্যকাউন্ট খুললেও এরকম অ্যকাউন্ট নিজে থেকেই তৈরী হয়ে যায়। - XMPP কোনো একটি নির্দিষ্ট সংস্থার উপরে নির্ভরশীল নয়। এই অ্যপটি আপনি যেকোনো সংস্থার XMPP সার্ভারের সাথে ব্যবহার করতে পারেন।\nমনে রাখবেন, সুধুমাত্র আপনার সুবিধার্থেই conversations.im -এ আপনার জন্যে একটি অ্যকাউন্ট তৈরী করে দেওয়া হয়েছে। Conversations অ্যপটি এই সার্ভারের সাথে সবথেকে বেশী কার্যকারী। - আপনাকে %1$s-এ আমন্ত্রিত করা হয়েছে। অ্যকাউন্ট তৈরী করার সময় আপনাকে সাহায্য করা হবে।\n%1$s ব্যবহার করলেও, অন্য সেবা-প্রদানকারী সংস্থার ব্যবহারকারীদের সাথে আপনি কথা বলতে পারবেন, আপনার সম্পূর্ণ XMPP অ্যড্রেস তাদেরকে বলে দিয়ে। - আপনাকে %1$s-এ নিমন্ত্রণ করা হয়েছে। একটি username-ও আপনার জন্যে নির্দিষ্ট করে রাখা হয়েছে। অ্যকাউন্ট তৈরী করার সময় আপনাকে সাহায্য করা হবে।\nঅন্য XMPP সেবা প্রদানকারী সংস্থার ব্যবহারকারীদের সাথে আপনিও কথা বলতে পারবেন, আপনার সম্পূর্ণ XMPP অ্যড্রেস তাদেরকে বলে দিয়ে। - আপনার নিমন্ত্রণপত্র, সার্ভার থেকে - Provisioning code-এ গরমিল আছে - Share বোতামটা টিপে %1$s-কে একটি আমন্ত্রপত্র পাঠান - পরিচিত ব্যক্তি যদি নিকটেই থাকেন, তাহলে তারা এই কোডটাও স্ক্যান করে নিতে পারেন - %1$sতে এসো, আর আমার সাথে কথা বলো: %2$s - একটি আমন্ত্রণপত্র দেওয়া যাক... - \ No newline at end of file diff --git a/src/conversations/res/values-ca/strings.xml b/src/conversations/res/values-ca/strings.xml deleted file mode 100644 index 7606e4708..000000000 --- a/src/conversations/res/values-ca/strings.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - Triï el seu proveïdor de XMPP - - Fer servir conversations.im - Crear un compte nou - Ja tens un compte XMPP? Aquest podria ser el cas si ja estàs usant un client XMPP diferent o has usat Converses abans. Si no, pots crear un nou compte XMPP ara mateix.\nPista: Alguns proveïdors de correu electrònic també proporcionen comptes XMPP. - XMPP és una xarxa de missatgeria instantània independent del proveïdor. Pots usar aquest client amb qualsevol servidor XMPP que triïs. No obstant això, per a la teva conveniència, hem fet fàcil la creació d\'un compte en Conversaciones.im; un proveïdor especialment adequat per a l\'ús amb Conversations. - Has estat convidat a %1$s. Et guiarem a través del procés de creació d\'un compte.\nEn triar%1$s com a proveïdor podràs comunicar-se amb els usuaris d\'altres proveïdors donant-los la seva adreça XMPP completa. - Has estat convidat a %1$s . Ja s\'ha triat un nom d\'usuari per a tu. Et guiarem en el procés de creació d\'un compte. Podràs comunicar-te amb usuaris d\'altres proveïdors donant-los la teva adreça XMPP completa. - La teva invitació al servidor - Codi d\'aprovisionament mal formatat - Toca el botó de compartir per a enviar al teu contacte una invitació a %1$s . - Si el teu contacte està a prop, també pot escanejar el codi de baix per a acceptar la teva invitació. - Uneix-te %1$s i xerra amb mi: %2$s - Comparteix la invitació amb... - \ No newline at end of file diff --git a/src/conversations/res/values-da-rDK/strings.xml b/src/conversations/res/values-da-rDK/strings.xml deleted file mode 100644 index f79a92078..000000000 --- a/src/conversations/res/values-da-rDK/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Vælg din XMPP udbyder - Brug conversations.im - Opret ny konto - Har du allerede en XMPP-konto? Dette kan være tilfældet, hvis du allerede bruger en anden XMPP-klient eller har brugt Conversations før. Hvis ikke, kan du lige nu oprette en ny XMPP-konto.\nTip: Nogle e-mail-udbydere leverer også XMPP-konti. - XMPP er et udbyderuafhængigt onlinemeddelelsesnetværk. Du kan bruge denne klient med hvilken XMPP-server du end vælger.\nMen for din nemhedsskyld har vi gjort vi det let at oprette en konto på conversations.im; en udbyder, der er specielt velegnet til brug med Conversations. - Du er blevet inviteret til %1$s. Vi guider dig gennem processen med at oprette en konto.\nNår du vælger %1$s som udbyder, kan du kommunikere med brugere fra andre udbydere ved at give dem din fulde XMPP-adresse. - Du er blevet inviteret til %1$s. Der er allerede valgt et brugernavn til dig. Vi guider dig gennem processen med at oprette en konto.\nDu vil være i stand til at kommunikere med brugere fra andre udbydere ved at give dem din fulde XMPP-adresse. - Din server invitation - Forkert formateret klargøringskode - Tryk på deleknappen for at sende din kontakt en invitation til %1$s. - Hvis din kontakt er i nærheden, kan de også skanne koden nedenfor for at acceptere din invitation. - Deltag med %1$s og chat med mig: %2$s - Del invitation med… - \ No newline at end of file diff --git a/src/conversations/res/values-de/strings.xml b/src/conversations/res/values-de/strings.xml deleted file mode 100644 index 2fd0319a9..000000000 --- a/src/conversations/res/values-de/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Wähle deinen XMPP-Provider - Benutze conversations.im - Neues Konto erstellen - Hast du bereits ein XMPP-Konto? Dies kann der Fall sein, wenn du bereits einen anderen XMPP-Client verwendest oder bereits Conversations verwendet hast. Wenn nicht, kannst du jetzt ein neues XMPP-Konto erstellen.\nTipp: Einige E-Mail-Anbieter bieten auch XMPP-Konten an. - XMPP ist ein anbieterunabhängiges Instant Messaging Netzwerk. Du kannst diesen Client mit jedem beliebigen XMPP-Server nutzen.\nUm es dir leicht zu machen, haben wir die Möglichkeit geschaffen, ein Konto auf conversations.im anzulegen; ein Anbieter, der speziell für die Verwendung mit Conversations geeignet ist. - Du wurdest zu %1$s eingeladen. Wir führen dich durch den Prozess der Kontoerstellung.\nWenn du %1$s als Provider wählst, kannst du mit Nutzern anderer Anbieter kommunizieren, indem du ihnen deine vollständige XMPP-Adresse gibst. - Du wurdest zu %1$seingeladen. Ein Benutzername ist bereits für dich ausgewählt worden. Wir führen dich durch den Prozess der Kontoerstellung.\nDu kannst mit Nutzern anderer Anbieter kommunizieren, indem du ihnen deine vollständige XMPP-Adresse gibst. - Deine Einladung für den Server - Falsch formatierter Provisionierungscode - Tippe auf die \"Teilen\"-Schaltfläche, um deinem Kontakt eine Einladung an %1$s zu senden. - Wenn dein Kontakt in der Nähe ist, kann er auch den untenstehenden Code einscannen, um deine Einladung anzunehmen. - Komme zu %1$s und chatte mit mir: %2$s - Einladung teilen mit… - \ No newline at end of file diff --git a/src/conversations/res/values-el/strings.xml b/src/conversations/res/values-el/strings.xml deleted file mode 100644 index c64e3d68e..000000000 --- a/src/conversations/res/values-el/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Επιλέξτε τον πάροχο XMPP σας - Χρήση του conversations.im - Δημιουργία νέου λογαριασμού - Έχετε ήδη λογαριασμό XMPP; Αυτό μπορεί να συμβαίνει αν ήδη χρησιμοποιείτε ένα άλλο πρόγραμμα XMPP ή έχετε χρησιμοποιήσει το Conversations παλιότερα. Αν όχι, μπορείτε να δημιουργήσετε ένα νέο λογαριασμό XMPP τώρα.\nΧρήσιμη πληροφορία: Κάποιοι πάροχοι e-mail παρέχουν επίσης και λογαριασμούς XMPP. - Το XMPP είναι ένα δίκτυο άμεσης ανταλλαγής μηνυμάτων ανεξάρτητο παρόχου. Μπορείτε να χρησιμοποιήσετε αυτό το πρόγραμμα με όποιον διακομιστή XMPP επιθυμείτε.\nΓια διευκόλυνση πάντως μπορείτε να δημιουργήσετε έναν λογαριασμό στο conversations.im, έναν πάροχο ειδικά σχεδιασμένο για χρήση με το Conversations. - Έχετε προσκληθεί στο %1$s. Θα σας καθοδηγήσουμε στη διαδικασία δημιουργίας λογαριασμού.\nΕπιλέγοντας τον %1$s ως πάροχο θα μπορείτε να επικοινωνείτε με χρήστες άλλων παρόχων δίνοντάς τους την πλήρη διεύθυνση XMPP σας. - Έχετε προσκληθεί στο %1$s. Ένα όνομα χρήστη έχει ήδη επιλεγεί για εσάς. Θα σας καθοδηγήσουμε στη διαδικασία δημιουργίας λογαριασμού.\nΘα μπορείτε να επικοινωνείτε με χρήστες άλλων παρόχων δίνοντάς τους την πλήρη διεύθυνση XMPP σας. - Η πρόσκλησή σας στον διακομιστή - Λάθος μορφοποίηση κώδικα παροχής - Πατήστε το πλήκτρο διαμοιρασμού για να στείλετε στην επαφή σας μια πρόσκληση στο %1$s. - Αν η επαφή σας βρίσκεται κοντά σας, μπορεί επίσης να σαρώσει τον κωδικό παρακάτω για να αποδεχτεί την πρόσκλησή σας. - Μπείτε στο %1$s και συνομιλήστε μαζί μου: %2$s - Διαμοιρασμός πρόσκλησης με… - \ No newline at end of file diff --git a/src/conversations/res/values-eo/strings.xml b/src/conversations/res/values-eo/strings.xml deleted file mode 100644 index d5fc7d80e..000000000 --- a/src/conversations/res/values-eo/strings.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - Uzi conversations.im - Aliĝu al %1$s kaj babilu kun mi: %2$s - Vi estis invitita al %1$s. Ni gvidos vin tra la procezo de kreado de konto. -\nElektante %1$s kiel provizanton vi povos komuniki kun uzantoj de aliaj provizantoj donante al ili vian plenan XMPP-adreson. - Ĉu vi jam havas XMPP-konton\? Ĉi tio povus esti la kazo se vi jam uzas alian XMPP-klienton aŭ antaŭe uzis Conversations. Se ne, vi povas krei novan XMPP-konton nun. -\nKonsileto: Iuj retpoŝtaj provizantoj ankaŭ provizas XMPP-kontojn. - Se via kontakto estas proksime, ili ankaŭ povas skani la suban kodon por akcepti vian inviton. - Elekti vian XMPP-provizanton - Kunhavigi inviton kun… - XMPP estas provizanta sendependa tujmesaĝa reto. Vi povas uzi ĉi tiun klienton per kia ajn XMPP-servilo, kiun vi elektas. -\nTamen por via komforto ni faciligis krei konton ĉe conversations.im; provizanto speciale taŭga por la uzo kun Conversations. - Vi estis invitita al %1$s. Uzantnomo jam estas elektita por vi. Ni gvidos vin tra la procezo de kreado de konto. -\nVi povos komuniki kun uzantoj de aliaj provizantoj donante al ili vian plenan XMPP-adreson. - Nedece formatita provizokodo - Premu la kunhavigi butonon por sendi al via kontakto inviton al %1$s. - Via servila invito - Krei novan konton - \ No newline at end of file diff --git a/src/conversations/res/values-es/strings.xml b/src/conversations/res/values-es/strings.xml deleted file mode 100644 index 80958fadc..000000000 --- a/src/conversations/res/values-es/strings.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - Elige tu proveedor XMPP - Usa conversations.im - Crear nueva cuenta - ¿Ya tienes una cuenta XMPP? Este puede ser el caso si ya estás usando un cliente XMPP diferente o has usado Conversations anteriormente. Si no es así, puedes crear una nueva cuenta XMPP ahora mismo.\nConsejo: Algunos proveedores de email también ofrecen una cuenta XMPP. - XMPP es una red de mensajería instantánea independiente del proveedor. Puedes usar este cliente con cualquier servidor XMPP que elijas. -\nSin embargo, para tu conveniencia, hacemos de forma sencilla la creación de una cuenta en conversations.im; un proveedor especializado para el uso con Conversations. - Has sido invitado a %1$s. Te guiaremos durante el proceso de creación de la cuenta.\nCuando selecciones %1$s como proveedor podrás comunicarte con usuarios de otros servidores proporcionándoles tu dirección XMPP completa. - Has sido invitado a %1$s. Un nombre de usuario ya ha sido escogido para ti. Te guiaremos durante el proceso de creación de la cuenta.\nPodrás comunicarte con otros usuarios de otros servidores proporcionándoles tu dirección XMPP completa. - Tu invitación al servidor - Código de abastecimiento formateado incorrectamente - Pulsa el botón de compartir para enviar a tu contacto una invitación a %1$s. - Si tu contacto está cerca, también puede escanear el código mostrado debajo para aceptar tu invitación. - Únete a %1$s y chatea conmigo: %2$s - Comparte la invitación con… - \ No newline at end of file diff --git a/src/conversations/res/values-eu/strings.xml b/src/conversations/res/values-eu/strings.xml deleted file mode 100644 index bf9555311..000000000 --- a/src/conversations/res/values-eu/strings.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - Hautatu zure XMPP hornitzailea - Erabili conversations.im - Kontu berria sortu - XMPP kontu bat badaukazu dagoeneko? Horrela izan daiteke beste XMPP aplikazio bat erabiltzen baduzu edo Conversations lehenago erabili baduzu. Bestela XMPP kontu berri bat sortu dezakezu oraintxe bertan.\nIradokizuna: email hornitzaile batzuek XMPP kontuak hornitzen dituzte ere. - XMPP hornitzailez independientea den bat-bateko mezularitza sare bat da. Aplikazio hau nahi duzun XMPP zerbitzariarekin erabili dezakezu.\nHala ere zure erosotasunerako conversations.im-en, Conversationsekin bereziki erabiltzeko egokia den hornitzaile batean, kontu bat sortzea erraz egin dugu. - \ No newline at end of file diff --git a/src/conversations/res/values-fa-rIR/strings.xml b/src/conversations/res/values-fa-rIR/strings.xml deleted file mode 100644 index 0f3362506..000000000 --- a/src/conversations/res/values-fa-rIR/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - لطفا سرویس دهنده پیام خود را انتخاب نمائید. برای مثال artalk.im - از Conversations.im استفاده کنید - حساب کاربری جدیدی بسازید - \ No newline at end of file diff --git a/src/conversations/res/values-fi/strings.xml b/src/conversations/res/values-fi/strings.xml deleted file mode 100644 index 17c75a297..000000000 --- a/src/conversations/res/values-fi/strings.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - Valitse XMPP-palveluntarjoaja - Käytä conversations.im:ää - Luo uusi tili - Onko sinulla jo XMPP-tunnus? Jos käytät jo toista XMPP-sovellusta tai olet käyttänyt Conversationsia aiemmin, niin voi olla. Jos ei, voit tehdä uuden XMPP-tilin saman tien.\nVinkki: Jotkin sähköpostipalvelut tarjoavat myös XMPP-tilin. - XMPP on tietystä palveluntarjoasta riippumaton pikaviestiverkosto. Voit käyttää tätä asiakasohjelmaa minkä tahansa haluamasi XMPP-palvelimen kanssa.\nHelppouden nimissä olemme kuitenkin helpottaneet tilin luomista conversations.im:iin. - Sinut on kutsuttu %1$s:iin. Opastamme sinua tilin luomisen kanssa.\nValitessasi palvelimen %1$s palveluntarjoajaksesi voit jutella muiden palveluntajoajien käyttäjien kanssa kertomalla heille koko XMPP-osoitteesi. - Sinut on kutsuttu palvelimelle %1$s. Käyttäjänimesi on valittu valmiiksi puolestasi. Opastamme sinua tilin luomisen kanssa.\nVoit jutella muiden palveluntarjoajien käyttäjien kanssa kertomalle heille koko XMPP-osoitteesi. - Kutsusi palvelimelle - Virheellisesti muotoiltu koodi - Jos henkilö on lähellä, hän voi myös hyväksyä kutsun lukemalla allaolevan koodin. - Jaa kutsu sovelluksella... - \ No newline at end of file diff --git a/src/conversations/res/values-fr/strings.xml b/src/conversations/res/values-fr/strings.xml deleted file mode 100644 index f0c96726f..000000000 --- a/src/conversations/res/values-fr/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Choisissez votre fournisseur XMPP - Utiliser conversations.im - Créer un nouveau compte - Avez-vous déjà un compte XMPP ? Cela peut être le cas si vous utilisez déjà un autre client XMPP ou si vous avez déjà utilisé Conversations auparavant. Sinon, vous pouvez créer un nouveau compte XMPP dès maintenant.\nRemarque : Certains fournisseurs de messagerie proposent également des comptes XMPP. - XMPP est un réseau de messagerie instantanée indépendant du fournisseur. Vous pouvez utiliser ce client avec n’importe quel serveur XMPP de votre choix.\nToutefois, pour votre commodité, nous avons facilité la création d’un compte sur conversations.im ; un fournisseur spécialement conçu pour Conversations. - Vous avez été invité à %1$s. Nous allons vous guider à travers le processus de création d’un compte.\nEn choisissant %1$s comme fournisseur, vous pourrez communiquer avec les utilisateurs des autres fournisseurs en leur donnant votre adresse XMPP complète. - Vous avez été invité à %1$s. Un nom d’utilisateur a déjà été choisi pour vous. Nous allons vous guider à travers le processus de création d’un compte.\nVous pourrez communiquer avec les utilisateurs des autres fournisseurs en leur donnant votre adresse XMPP complète. - Votre invitation au serveur - Code de provisionnement mal formaté - Appuyez sur le bouton partager pour envoyer à votre contact une invitation pour %1$s. - Si vos contacts sont à proximité, ils peuvent aussi scanner le code ci-dessous pour accepter votre invitation. - Rejoignez %1$set discutez avec moi : %2$s - Partager une invitation avec … - \ No newline at end of file diff --git a/src/conversations/res/values-gl/strings.xml b/src/conversations/res/values-gl/strings.xml deleted file mode 100644 index 2becd8bea..000000000 --- a/src/conversations/res/values-gl/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Elixe o teu provedor XMPP - Utilizar conversations.im - Crear nova conta - Xa posúes unha conta XMPP? Este pode ser o caso se xa estás a utilizar outro cliente XMPP ou utilizaches Conversations previamente. Se non é así podes crear unha nova conta agora mesmo.\nTruco: Algúns provedores de correo tamén proporcionan contas XMPP. - XMPP é unha rede de mensaxería independente do provedor. Podes utilizar este cliente con calquera provedor XMPP da túa elección.\nMais para a tua conveniencia fixemos que fose doado crear unha conta en conversations.im; un provedor especialmente axeitado para utilizar con Conversations. - Convidáronte a %1$s. Guiarémoste no proceso para crear unha conta.\nAo elexir %1$s como provedor poderás comunicarte con usuarias doutros provedores cando lles deas o teu enderezo XMPP completo. - Convidáronte a %1$s. Xa eleximos un nome de usuaria para ti. Guiarémoste no proceso de crear unha conta.\nPoderás comunicarte con usuarias doutros provedores cando lles digas o teu enderezo XMPP completo. - O convite do teu servidor - Código de aprovisionamento con formato non válido - Toca no botón compartir para convidar ao teu contacto a %1$s. - Se o contacto está preto de ti, pode escanear o código inferior para aceptar o teu convite. - Únete a %1$s e conversa conmigo: %2$s - Enviar convite a… - \ No newline at end of file diff --git a/src/conversations/res/values-hr/strings.xml b/src/conversations/res/values-hr/strings.xml deleted file mode 100644 index 093c6d0b9..000000000 --- a/src/conversations/res/values-hr/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Odaberite svog XMPP davatelja usluga. - Koristite conversations.im - Napravi novi račun - Već imate XMPP račun? To može biti slučaj ako već koristite drugi XMPP klijent ili ste prije koristili Razgovore. Ako niste, možete odmah stvoriti novi XMPP račun.\nSavjet: Neki pružatelji usluga e-pošte također nude XMPP račune. - XMPP je mreža za razmjenu izravnih poruka neovisna o pružatelju usluga. Možete koristiti ovaj klijent s bilo kojim XMPP poslužiteljem koji odaberete.\nMeđutim, radi vaše udobnosti olakšali smo kreiranje računa na conversations.im; pružatelj usluga posebno prilagođen za korištenje s Conversations. - Pozvani ste na %1$s. Vodit ćemo vas kroz postupak kreiranja računa.\nPrilikom odabira %1$s pružatelja moći ćete komunicirati s korisnicima drugih pružatelja dajući im svoju punu XMPP adresu. - Pozvani ste na %1$s. Korisničko ime je već odabrano za vas. Vodit ćemo vas kroz postupak kreiranja računa.\nMoći ćete komunicirati s korisnicima drugih pružatelja tako da im date svoju punu XMPP adresu. - Vaša pozivnica za poslužitelj - Neispravno formatiran kod za dodjelu - Dodirnite gumb za dijeljenje kako biste svom kontaktu poslali pozivnicu na %1$s. - Ako je vaš kontakt u blizini, također može skenirati kod u nastavku kako bi prihvatio vašu pozivnicu. - Pridružite se %1$s i razgovarajte sa mnom: %2$s - Podijelite pozivnicu s... - \ No newline at end of file diff --git a/src/conversations/res/values-hu/strings.xml b/src/conversations/res/values-hu/strings.xml deleted file mode 100644 index f4c180889..000000000 --- a/src/conversations/res/values-hu/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Válassza ki az XMPP szolgáltatóját - A conversations.im használata - Új fiók létrehozása - Már rendelkezik XMPP-fiókkal? Ez az eset állhat fenn, ha már egy másik XMPP-klienst használ, vagy ha már korábban használta a Conversations alkalmazást. Ha nem, akkor most létrehozhat egy új XMPP-fiókot.\nTipp: egyes e-mail szolgáltatók is biztosítanak XMPP-fiókokat. - Az XMPP egy szolgáltatófüggetlen, azonnali üzenetküldő hálózat. Ezt a kliensprogramot bármely XMPP-kiszolgálóhoz használhatja.\nAzonban a kényelem érdekében megkönnyítettük a conversations.im szolgáltatón való fióklétrehozást, ami kifejezetten a Conversations alkalmazással történő használatra lett tervezve. - Meghívást kapott a(z) %1$s kiszolgálóra. Végig fogjuk vezetni egy fiók létrehozásának folyamatán.\nHa a(z) %1$s kiszolgálót választja szolgáltatóként, akkor képes lesz más szolgáltatók felhasználóival is kommunikálni, ha megadja nekik a teljes XMPP-címét. - Meghívást kapott a(z) %1$s kiszolgálóra. Már kiválasztottak Önnek egy felhasználónevet. Végig fogjuk vezetni egy fiók létrehozásának folyamatán.\nKépes lesz más szolgáltatók felhasználóival is kommunikálni, ha megadja nekik a teljes XMPP-címét. - Az Ön kiszolgálómeghívása - Helytelenül formázott kiépítési kód - Koppintson a megosztás gombra, hogy meghívót küldjön a partnerének erre: %1$s. - Ha a partnere a közelben van, akkor a meghívás elfogadásához leolvashatja a lenti kódot. - Csatlakozzon ehhez: %1$s, és csevegjen velem: %2$s - Meghívás megosztása… - \ No newline at end of file diff --git a/src/conversations/res/values-id/strings.xml b/src/conversations/res/values-id/strings.xml deleted file mode 100644 index a316ee848..000000000 --- a/src/conversations/res/values-id/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Pilih XMPP server anda - Gunakan conversations.im - Buat akun baru - Anda sudah memiliki akun XMPP\? Ini mungkin terjadi jika Anda sudah menggunakan aplikasi XMPP yang berbeda atau pernah menggunakan Conversations sebelumnya. Jika tidak, Anda dapat membuat akun XMPP baru. NPetunjuk: Beberapa penyedia layanan email juga menyediakan akun XMPP. - XMPP adalah jaringan penyedia pesan instan independen. Anda dapat menggunakan aplikasi ini dengan server XMPP pilihan Anda. NNamun demi kenyamanan Anda, kami permudah untuk membuat akun di Conversations.im; provider yang sangat cocok digunakan dengan Conversations. - Anda telah diundang ke %1$s. Kami akan memandu Anda melalui proses pembuatan akun. \nSaat memilih %1$s sebagai penyedia, Anda akan dapat berkomunikasi dengan pengguna provider lain dengan memberikan alamat XMPP lengkap Anda kepada mereka. - Anda telah diundang ke%1$s. Username telah dipilihkan untuk Anda. Kami akan memandu Anda melalui proses pembuatan akun. \nAnda dapat berkomunikasi dengan pengguna provider lain dengan memberi mereka alamat XMPP lengkap Anda. - Undangan server Anda - Kode provisioning tidak diformat dengan benar - Klik tombol bagikan untuk mengirim undangan ke kontak Anda %1$s. - Jika kontak Anda di dekat Anda, mereka juga dapat memindai kode di bawah ini untuk menerima undangan Anda - Bergabung %1$s dan mengobrol dengan saya: %2$s - Bagikan undangan dengan... - \ No newline at end of file diff --git a/src/conversations/res/values-it/strings.xml b/src/conversations/res/values-it/strings.xml deleted file mode 100644 index 428a2b032..000000000 --- a/src/conversations/res/values-it/strings.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - Scegli il tuo fornitore XMPP - Usa conversations.im - Crea un nuovo profilo - Hai già un profilo XMPP\? Può accadere se stai già usando un client XMPP diverso o hai già usato prima Conversations. In caso negativo, puoi creare un profilo XMPP adesso. -\nNota: alcuni fornitori di email offrono anche account XMPP. - XMPP è una rete di messaggistica istantanea indipendente dal fornitore. Puoi usare questo client con qualsiasi server XMPP. -\nTuttavia, per comodità, puoi creare facilmente un account su conversations.im; un fornitore pensato apposta per essere usato con Conversations. - Hai ricevuto un invito per %1$s. Ti guideremo nel procedimento per creare un profilo.\nQuando scegli %1$s come fornitore sarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo. - Hai ricevuto un invito per %1$s. È già stato scelto un nome utente per te. Ti guideremo nel procedimento per creare un profilo.\nSarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo. - Il tuo invito al server - Codice di approvvigionamento formattato male - Tocca il pulsante condividi per inviare al contatto un invito per %1$s. - Se il contatto è vicino, può anche scansionare il codice sottostante per accettare il tuo invito. - Unisciti a %1$s e chatta con me: %2$s - Condividi invito con… - \ No newline at end of file diff --git a/src/conversations/res/values-ja/strings.xml b/src/conversations/res/values-ja/strings.xml deleted file mode 100644 index 2d240bedc..000000000 --- a/src/conversations/res/values-ja/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - XMPP プロバイダーを選択してください - conversations.im を利用する - 新規アカウントを作成 - XMPP アカウントをお持ちですか?既にほかの XMPP クライアントを利用しているか、 Conversations を利用したことがある場合はこちら。初めての方は、今すぐ新規 XMPP アカウントを作成できます。\nヒント: e メールのプロバイダーが XMPP アカウントも提供している場合があります。 - XMPP は、プロバイダーに依存しないインスタントメッセージのプロトコルです。 XMPP サーバーならどこでも、このクライアントを使用することができます。\nよろしければ、 Conversations に最適化されたプロバイダー conversations.im で簡単にアカウントを作成することもできます。 - %1$s へ招待されました。アカウント作成手順をご案内します。 \n%1$s をプロバイダーに選択してほかのプロバイダーのユーザーと会話するには、 XMPP のフルアドレスを相手にお知らせください。 - %1$s へ招待されました。ユーザー名は既に選択されています。アカウント作成手順をご案内します。 \nほかのプロバイダーのユーザーと会話するには、 XMPP のフルアドレスを相手にお知らせください。 - サーバーの招待 - 仮コードの書式が不正です - 共有ボタンを叩いて、連絡先の %1$s に招待を送信する。 - あなたの連絡先が近くにいる場合は、下のコードをスキャンして、あなたの招待を受け取ることもできます。 - %1$s に参加して私とお話しましょう: %2$s - …で招待を共有 - \ No newline at end of file diff --git a/src/conversations/res/values-nl/strings.xml b/src/conversations/res/values-nl/strings.xml deleted file mode 100644 index f04a6b2de..000000000 --- a/src/conversations/res/values-nl/strings.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - Kies je XMPP-dienst - Conversations.im gebruiken - Nieuwe account registreren - Heb je al een XMPP-account? Als je al een andere XMPP-cliënt gebruikt, of Conversations vroeger al eens hebt gebruikt, is dit waarschijnlijk het geval. Zo niet, kan je nu een nieuwe XMPP-account aanmaken.\nTip: sommige e-mailproviders bieden ook XMPP-accounts aan. - XMPP is een provider-onafhankelijk berichtennetwerk. Je kan deze cliënt gebruiken met eender welke XMPP-server.\nOm het je gemakkelijker te maken kun je simpelweg een account aanmaken op conversations.im; een provider speciaal geschikt voor Conversations. - Je ontving een uitnodiging voor %1$s. We zullen je helpen een account aan te maken.\nWanneer je %1$s als je provider kiest kan je met gebruikers van andere providers communiceren door hen je volledige XMPP-adres te geven. - Je ontving een uitnodiging voor %1$s. Er werd reeds een gebruikersnaam voor jou gekozen. We zullen je helpen een account aan te maken.\nJe zal met gebruikers van andere providers communiceren door hen je volledige XMPP-adres te geven. - Je server uitnodiging - Tik op de delen knop om een uitnodiging te versturen naar %1$s - Als je contactpersoon in de buurt is, kan deze ook onderstaande code scannen om de uitnodiging te aanvaarden. - Deel de uitnodiging met ... - \ No newline at end of file diff --git a/src/conversations/res/values-pl/strings.xml b/src/conversations/res/values-pl/strings.xml deleted file mode 100644 index f3771aed2..000000000 --- a/src/conversations/res/values-pl/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Wybierz dostawcę XMPP - Użyj conversations.im - Utwórz nowe konto - Czy masz już konto XMPP? Tak może być jeśli używasz już innego klienta XMPP lub używałeś już Conversations. Jeśli nie możesz stworzyć nowe konto XMPP teraz.\nPodpowiedź: Niektórzy dostawcy poczty oferują również konta XMPP. - XMPP to niezależna od dostawcy sieć komunikacji błyskawicznej. Możesz użyć tego klienta z dowolnym serwerem XMPP.\nDla twojej wygody jednak ułatwiliśmy stworzenie konta na conversations.im; dostawcy specjalnie dostosowanego do pracy z Conversations. - Zostałeś zaproszony do %1$s. Poprowadzimy ciebie przez proces tworzenia konta.\nWybierając %1$s jako dostawcę będziesz mógł komunikować się z innymi użytkownikami podając swój pełny adres XMPP. - Zostałeś zaproszony do %1$s. Nazwa użytkownika została już dla ciebie wybrana. Poprowadzimy ciebie przez proces tworzenia konta.\nBęziesz mógł komunikować się z innymi użytkownikami podając swój adres XMPP. - Zaproszenie twojego serwera - Niepoprawnie sformatowany kod zaopatrywania - Użyj przycisku udostępniania aby wysłać swojemu kontaktowi zaproszenie do %1$s. - Jeśli twój kontakt jest blisko może przeskanować kod poniżej aby zaakceptować twoje zaproszenie. - Dołącz do %1$s aby porozmawiać ze mną: %2$s - Udostępnij zaproszenie… - \ No newline at end of file diff --git a/src/conversations/res/values-pt-rBR/strings.xml b/src/conversations/res/values-pt-rBR/strings.xml deleted file mode 100644 index 0a4b54191..000000000 --- a/src/conversations/res/values-pt-rBR/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Selecione o seu provedor XMPP - Usar o conversations.im - Criar uma nova conta - Você já possui uma conta XMPP? Esse pode ser o seu caso caso já esteja usando um outro cliente XMPP ou tenha usado o Conversations antes. Caso contrário, você pode criar uma nova conta XMPP agora.\nDica: alguns provedores de e-mail também fornecem contas XMPP. - O XMPP é uma rede de mensageria instantânea independente de provedor. Você pode usar esse cliente com qualquer servidor XMPP que você escolher.\nEntretanto, para sua conveniência, nós simplificamos o processo de criação de uma conta em conversations.im, um provedor especialmente configurado para se usar com o Conversations. - Você foi convidado para %1$s. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nAo escolher %1$s como um provedor você conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo. - Você foi convidado para %1$s. Um nome de usuário já foi escolhido para você. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nVocê conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo. - Seu convite do servidor - Código de provisionamento formatado de maneira imprópria - Toque no botão compartilhar para enviar, para seu contato, um convite para %1$s. - Se seu contato estiver por perto, ele também pode escanear o código abaixo para aceitar seu convite. - Junte-se a %1$s e converse comigo: %2$s - Compartilhe o convite com... - \ No newline at end of file diff --git a/src/conversations/res/values-pt/strings.xml b/src/conversations/res/values-pt/strings.xml deleted file mode 100644 index a6b3daec9..000000000 --- a/src/conversations/res/values-pt/strings.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/src/conversations/res/values-ro-rRO/strings.xml b/src/conversations/res/values-ro-rRO/strings.xml deleted file mode 100644 index baefb00c6..000000000 --- a/src/conversations/res/values-ro-rRO/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Alegeți-vă furnizorul XMPP - Folosește conversations.im - Creează un cont nou - Aveți deja un cont XMPP? S-ar putea să fie așa dacă deja utilizați un alt client XMPP sau dacă ați folosit Conversations în trecut. Dacă nu, puteți crea un cont nou XMPP chiar acum.\nIdee: Unii furnizori de e-mail oferă de asemenea și conturi XMPP. - XMPP este o rețea de mesagerie instant ce nu depinde de un anumit furnizor. Aveți posibilitatea să utilizați acest client cu orice server XMPP doriți.\nTotuși, pentru confortul dumneavoastră, am facilitat crearea unui cont pe conversations.im; un furnizor potrivit pentru utilizarea cu aplicația Conversations. - Ați fost invitați la %1$s. Vă vom ghida prin procesul de creare al unui cont.\nCând alegeți %1$s ca furnizor veți putea comunica cu utilizatorii altor furnizori oferindu-le adresa dumneavoastră completă XMPP. - Ați fost invitați la %1$s. Un nume de utilizator a fost deja ales pentru dumneavoastră. Vă vom ghida prin procesul de creare al unui cont.\nVeți putea comunica cu utilizatorii altor furnizori oferindu-le adresa dumneavoastră completă XMPP. - Invitația serverului dumneavoastră - Cod de acces formatat necorespunzător - Atingeți butonul de partajare pentru a trimite contactului o invitație la %1$s. - Dacă e în apropiere, contactul poate scana codul de mai jos pentru a vă accepta invitația. - Alătură-te %1$s și discută cu mine: %2$s - Partajează invitația cu… - \ No newline at end of file diff --git a/src/conversations/res/values-ru/strings.xml b/src/conversations/res/values-ru/strings.xml index 20b99a7b3..cc0c49719 100644 --- a/src/conversations/res/values-ru/strings.xml +++ b/src/conversations/res/values-ru/strings.xml @@ -1,11 +1,11 @@ Выберите своего XMPP-провайдера - Использовать conversations.im + Использовать another.im Создать новый аккаунт - У вас есть аккаунт XMPP\? Если вы использовали Conversations или другой XMPP-клиент в прошлом, то скорее всего, он у вас есть. Если у вас нет аккаунта, вы можете создать его прямо сейчас. + У вас есть аккаунт XMPP\? Если вы использовали another.im или другой XMPP-клиент в прошлом, то скорее всего, он у вас есть. Если у вас нет аккаунта, вы можете создать его прямо сейчас. \nПодсказка: Некоторые провайдеры электронной почты также регистрируют аккаунты XMPP. - XMPP - это независимая сеть обмена сообщениями. Conversations позволяет вам подключиться к любому XMPP-серверу на ваш выбор.\nЕсли у вас нет сервера, предлагаем вам зарегистрировать аккаунт на conversations.im, сервере, специально предназначенном для работы с Conversations. + XMPP - это независимая сеть обмена сообщениями. another.im позволяет вам подключиться к любому XMPP-серверу на ваш выбор.\nЕсли у вас нет сервера, предлагаем вам зарегистрировать аккаунт на another.im, сервере, специально предназначенном для работы с another.im. Вас пригласили на %1$s. Мы проведём вас через процесс создания аккаунта. \nАккаунт на %1$s позволит вам общаться с пользователями и на этом, и на других серверах, используя ваш полный XMPP-адрес. Вас пригласили на %1$s. Вам уже назначили имя пользователя. Мы проведём вас через процесс создания аккаунта. @@ -16,4 +16,4 @@ Если ваш контакт находится поблизости, он также может отсканировать приведенный ниже код, чтобы принять ваше приглашение. Присоединяйтесь к %1$s и пообщайтесь со мной: %2$s Поделиться приглашением с… - \ No newline at end of file + diff --git a/src/conversations/res/values-sk/strings.xml b/src/conversations/res/values-sk/strings.xml deleted file mode 100644 index e280344c4..000000000 --- a/src/conversations/res/values-sk/strings.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - Vyberte si svojho XMPP poskytovateľa - Použiť conversations.im - Vytvoriť nové konto - Máte už svoje XMPP konto? Môže to tak byť v prípade, že už používate iného klienta XMPP alebo ste predtým používali Conversations. Ak nie, môžete si vytvoriť nové XMPP konto práve teraz.\nHint: Niektorí poskytovatelia emailu zároveň poskytujú aj XMPP kontá. - XMPP je sieť pre okamžité správy nezávislá od poskytovateľa. Tohto klienta môžete používať s akýmkoľvek XMPP serverom, ktorý si vyberiete..\nAvšak pre vaše pohodlie sme zjednodušili vytvorenie konta na conversations.im; poskytovateľ špeciálne vhodný na používanie s Conversations. - Boli ste pozvaný do %1$s. Prevedieme vás procesom vytvorenia konta..\nPo výbere %1$s ako poskytovateľa, budete môcť komunikovať s užívateľmi iných poskytovateľov tak, že im dáte vašu úplnú XMPP adresu. - Boli ste pozvaný do %1$s . Užívateľské meno vám už bolo vopred vybrané. Prevedieme vás procesom vytvorenia konta..\nBudete môcť komunikovať s užívateľmi iných poskytovateľov tak, že im dáte vašu úplnú XMPP adresu. - Ťuknite na tlačidlo zdieľať na odoslanie pozvánky do %1$s vášmu kontaktu. - Ak je váš kontakt blízko, na prijatie vašej pozvánky si môže nasnímať kód nižšie. - Pripojte sa k %1$sa rozprávajte sa so mnou: %2$s - Zdieľať pozvánku s… - \ No newline at end of file diff --git a/src/conversations/res/values-sq/strings.xml b/src/conversations/res/values-sq/strings.xml deleted file mode 100644 index 1e3f34b5b..000000000 --- a/src/conversations/res/values-sq/strings.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - XMPP është një rrjet shkëmbimi mesazhesh të atypëratyshëm i pavarur nga shërbimet. Këtë klient mund ta përdorni me cilindo shërbyes XMPP që zgjidhni. -\nMegjithatë, për lehtësi, e kemi bërë të kollajshme të krijohet një llogari te conversations.im, një shërbim posaçërisht i përshtatshëm për përdorim me Conversations. - Jeni ftuar te %1$s. Do t’ju udhëheqim përmes procesit të krijimit të një llogarie. -\nKur zgjidhet %1$s si shërbim, do të jeni në gjendje të komunikoni me përdorues nga shërbime të tjera duke u dhënë adresën tuaj të plotë XMPP. - Jeni ftuar te %1$s. Për ju është zgjedhur tashmë një emër përdoruesi. Do t’ju udhëheqim përmes procesit të krijimit të një llogarie. -\nDo të jeni në gjendje të komunikoni me përdorues nga shërbime të tjera duke u dhënë adresën tuaj të plotë XMPP. - Prekni butonin e ndarjes me të tjerë që t’i dërgoni kontaktit tuaj një ftesë për te %1$s. - Nëse kontakti juaj është atypari, mund të skanojë gjithashtu kodin më poshtë, që të pranojë ftesën tuaj. - Bëhuni pjesë e %1$s dhe bisedoni me: %2$s - Ndajeni ftesën me… - Krijoni llogari të re - Zgjidhni shërbimin tuaj XMPP - Përdor conversations.im - Keni tashmë një llogari XMPP\? Mund të jetë kështu nëse përdorni tashmë një klient tjetër XMPP, ose e keni përdorur Conversations më parë. Nëse jo, mund të krijoni një llogari të re XMPP që tani. -\nNdihmëz: Disa shërbime email-i ofrojnë gjithashtu llogari XMPP. - Ftesë nga shërbyesi juaj - Kod i formatuar jo saktësisht - \ No newline at end of file diff --git a/src/conversations/res/values-sr/strings.xml b/src/conversations/res/values-sr/strings.xml deleted file mode 100644 index e668ed7e6..000000000 --- a/src/conversations/res/values-sr/strings.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - Одаберите вашег ИксМПП провајдера - Користи conversations.im - Направи нови налог - Да ли већ имате ИксМПП налог? Извесно је да га имате ако користите неки ИксМПП клијент или сте раније користили Конверзацију. Ако немате, сада можете направити нови ИксМПП налог.\nСавет: неки поштански провајдери такође омогућавају и ИксМПП налоге. - ИксМПП је мрежа брзих порука, независна од провајдера. Овај клијент можете користити уз било који сервер по вашем избору.\nДа бисмо вам олакшали, омогућили смо креирање налога на conversations.im; провајдеру специјално прилаг.ођеном за коришћење уз Конверзацију - Ваша серверска позивница - \ No newline at end of file diff --git a/src/conversations/res/values-sv/strings.xml b/src/conversations/res/values-sv/strings.xml deleted file mode 100644 index 062a0c26f..000000000 --- a/src/conversations/res/values-sv/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - Välj din XMPP-leverantör - Använd conversations.im - Skapa ett nytt konto - Har du redan ett XMPP-konto? Detta kan vara fallet om du redan använder en annan XMPP-klient eller om du har använt Conversations tidigare. Om inte, kan du skapa ett nytt XMPP-konto på en gång.\nTips: Vissa e-postleverantörer tillhandahåller även XMPP-konton. - Din serverinbjudan - Felaktigt formaterad provisioneringskod - Tryck på dela-knappen för att skicka en inbjudan till din kontakt till %1$s. - Om din kontakt är i närheten, kan de också skanna koden nedan för att acceptera din inbjudan. - Gå med %1$s och chatta med mig: %2$s - Dela inbjudan med… - Du har blivit inbjuden till %1$s. Ett användarnamn har redan valts åt dig. Vi guidar dig genom processen för att skapa ett konto. -\nDu kommer att kunna kommunicera med användare av andra leverantörer genom att ge dem din fullständiga XMPP-adress. - XMPP är ett leverantörsoberoende snabbmeddelandenätverk. Du kan använda den här klienten med vilken XMPP-server du än väljer. -\nMen för din bekvämlighet har vi gjort det enkelt att skapa ett konto på conversations.im; en leverantör som är speciellt lämpad för användning med Conversations. - Du har blivit inbjuden till %1$s. Vi guidar dig genom processen för att skapa ett konto. -\nNär du väljer %1$s som leverantör kommer du att kunna kommunicera med användare av andra leverantörer genom att ge dem din fullständiga XMPP-adress. - \ No newline at end of file diff --git a/src/conversations/res/values-szl/strings.xml b/src/conversations/res/values-szl/strings.xml deleted file mode 100644 index 6e0134d06..000000000 --- a/src/conversations/res/values-szl/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Wybier liferanta XMPP - Użyj conversations.im - Stwōrz nowe kōnto - Mosz już kōnto XMPP? Tak może być, jeźli już używosz inkszego klijynta XMPP aboś używoł abo używała wcześnij Conversations. Jak niy, to możesz stworzić teroz nowe kōnto XMPP.\nDorada: Niykerzi liferańcio emaili dowajōm tyż kōnta XMPP. - XMPP to je nec wartkich wiadōmości niyzależny ôd liferanta. Możesz używać tego klijynta ze serwerym XMPP, jaki sie wybieresz.\nAle dlo twojij wygody ułacniyli my tworzynie kōnt na conversations.im; liferańcie ekstra dopasowanym do używanio ze Conversations. - Mosz zaproszynie na %1$s. Pokludzymy cie bez proces tworzynio kōnta.\nPo wybraniu %1$s za liferanta, poradzisz kōmunikować sie ze używoczami ôd inkszych liferantōw bez danie im swojij połnyj adresy XMPP. - Mosz zaproszynie na %1$s. Miano ôd używocza już je do ciebie wybrane. Pokludzymy cie bez proces tworzynio kōnta.\nBydzie szło kōmunikować sie ze używoczami ôd inkszych liferantōw bez danie im swojij połnyj adresy XMPP. - Twoje zaproszynie na serwer - Niynoleżnie sformatowany kod lifrowanio - Tyknij knefla dzielynio sie, żeby posłać kōntaktowi zaproszynie na %1$s. - Jeźli kōntakt je blisko, to może tyż zeskanować kod niżyj, żeby zaakceptować twoje zaproszynie. - Pōdź na %1$s i pogodej zy mnōm: %2$s - Poślij zaproszynie do… - \ No newline at end of file diff --git a/src/conversations/res/values-tr-rTR/strings.xml b/src/conversations/res/values-tr-rTR/strings.xml deleted file mode 100644 index 415bc89e0..000000000 --- a/src/conversations/res/values-tr-rTR/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - XMPP sağlayıcınızı seçin - conversations.im kullan - Yeni hesap oluştur - Zaten bir XMPP hesabınız var mı? Bunun sebebi, zaten başka bir XMPP istemcisi kullanıyor oluşunuz veya Conversations\'ı önceden kullanmış olmanız olabilir. Eğer durum bu değilse şimdi yeni bir XMPP hesabı oluşturabilirsiniz.\nİpucu: Bağzı e-posta sağlayıcıları da XMPP hesapları kullanabilir. - XMPP; anlık yazışmalar için bağımsız bir sağlayıcıdır. Bu istemciyi istediğiniz herhangi bir XMPP sunucusu ile birlikte kullanabilirsiniz.\nAncak kullanım rahatlığı adına sizin için conversations.im; Conversations için özellikle tasarlanmış bir sağlayıcıda hesap açmanızı kolaylaştırdık. - %1$s sağlayıcısına davet edildiniz. Sizi hesap oluşturulması konusunda yönlendireceğiz.\n%1$s bir sağlayıcı olark seçildiğinde, başka sağlayıcılar kullanan kullanıcılarla, onlara tam XMPP adresinizi vererek iletişim kurabileceksiniz. - %1$s sağlayıcısına davet edildiniz. Sizin için zaten bir kullanıcı adı seçildi. Sizi hesap oluşturulması konusunda yönlendireceğiz.\nBaşka sağlayıcılar kullanan kullanıcılarla, onlara tam XMPP adresinizi vererek iletişim kurabileceksiniz. - Sunucu davetiyeniz - Yanlış ayarlanmış düzenleme kodu - Kişinize, %1$s grubuna davet etmek için Paylaş düğmesine basın. - Kişiniz yakınınızda ise, aşağıdaki kodu tarayak daveti kabul edebilirler. - %1$s grubuna katıl ve benimle sohbet et: %2$s - Daveti şununla paylaş… - \ No newline at end of file diff --git a/src/conversations/res/values-uk/strings.xml b/src/conversations/res/values-uk/strings.xml index f9e37cea7..eb2f3c7dd 100644 --- a/src/conversations/res/values-uk/strings.xml +++ b/src/conversations/res/values-uk/strings.xml @@ -1,12 +1,12 @@ Виберіть постачальника послуг обміну повідомленнями XMPP - Скористатися conversations.im + Скористатися another.im Створити новий обліковий запис - Уже маєте обліковий запис XMPP\? Можливо, користуєтеся іншою програмою XMPP або користувалися Conversations раніше. Якщо ні, можете створити новий обліковий запис XMPP просто зараз. + Уже маєте обліковий запис XMPP\? Можливо, користуєтеся іншою програмою XMPP або користувалися another.im раніше. Якщо ні, можете створити новий обліковий запис XMPP просто зараз. \nЗверніть увагу, що деякі постачальники електронної пошти у той же час надають облікові записи XMPP. XMPP — це мережа обміну повідомленнями, незалежна від постачальників. Можете використовувати цю програму з будь-яким XMPP-сервером, який оберете. -\nПроте для зручності ми спростили створення облікового запису на conversations.im — у постачальника, спеціально налаштованого на роботу з Conversations. +\nПроте для зручності ми спростили створення облікового запису на another.im — у постачальника, спеціально налаштованого на роботу з another.im. Вас запросили до %1$s. Ми проведемо Вас крок за кроком, щоб створити обліковий запис. \nОбравши %1$s в якості свого постачальника, Ви зможете спілкуватися з користувачами інших постачальників, для цього повідомте їм свою повну адресу XMPP. Вас запросили до %1$s. Для Вас створено ім\'я користувача. Ми проведемо Вас крок за кроком, щоб створити обліковий запис. @@ -17,4 +17,4 @@ Приєднуйтеся до %1$s і спілкуйтеся зі мною: %2$s Запросити… Натисніть «Поділитися», щоб надіслати Вашому контакту запрошення до %1$s. - \ No newline at end of file + diff --git a/src/conversations/res/values-vi/strings.xml b/src/conversations/res/values-vi/strings.xml deleted file mode 100644 index 851ad0927..000000000 --- a/src/conversations/res/values-vi/strings.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - Chọn nhà cung cấp XMPP của bạn - Sử dụng “conversations.im” - Tạo tài khoản mới - Bạn đã có sẵn một tài khoản XMPP chưa\? Nếu bạn đang dùng một ứng dụng XMPP khác dành cho máy khách (client) hoặc đã sử dụng Conversations trước đó. Nếu chưa có, bạn có thể tạo tài khoản XMPP mới ngay bây giờ. -\nGợi ý: Một số nhà cung cấp dịch vụ email cũng cung cấp tài khoản XMPP. - XMPP là một dịch vụ mạng tin nhắn không phụ thuộc vào nhà cung cấp nào. Bạn có thể sử dụng ứng dụng máy khách này với bất kỳ máy chủ XMPP nào mà bạn chọn. -\nĐể thuận tiện hơn cho bạn, chúng tôi đã đơn giản hóa khâu tạo tài khoản trên conversations.im – một nhà cung cấp đặc biệt phù hợp cho việc sử dụng Conversations. - Bạn đã được mời vào “ %1$s”. Chúng tôi sẽ hướng dẫn bạn xuyên suốt quá trình tạo tài khoản. -\nKhi chọn “%1$s” làm nhà cung cấp, bạn sẽ có thể liên lạc với những người dùng của các nhà cung cấp khác bằng cách đưa cho họ địa chỉ XMPP đầy đủ của bạn. - Bạn đã được mời vào “%1$s.” Một tên người dùng đã được chọn sẵn cho bạn. Chúng tôi sẽ hướng dẫn bạn xuyên suốt quá trình tạo tài khoản. -\nBạn sẽ có thể với những người dùng của các nhà cung cấp khác bằng cách đưa cho họ địa chỉ XMPP đầy đủ của bạn. - Lời mời vào máy chủ của bạn - Mã cung cấp sai định dạng - Nhấn nút chia sẻ để gửi đến liên hệ của bạn một lời mời vào “%1$s”. - Nếu liên hệ của bạn đang ở gần bên bạn, họ có thể quét mã ở dưới để chấp nhận lời mời của bạn. - Hãy tham gia vào “%1$s” và trò chuyện với tôi: %2$s - Chia sẻ lời mời với… - \ No newline at end of file diff --git a/src/conversations/res/values-zh-rCN/strings.xml b/src/conversations/res/values-zh-rCN/strings.xml deleted file mode 100644 index 34cf35734..000000000 --- a/src/conversations/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - 选择您的 XMPP 提供者 - 使用 conversations.im - 创建新账号 - 您已经有 XMPP 账号了吗?如果您之前使用过 Conversations 或其他 XMPP 客户端,那么您已经有账号了。如果没有,您可以立即创建一个。 -\n提示:一些电子邮件服务也提供 XMPP 账号。 - XMPP 是独立于提供者的即时通讯网络。您选择的任何 XMPP 服务器都可以使用此客户端。 -\n不过,您可以轻松地在 conversations.im 上创建账号;特别适合与 Conversations 使用的提供者。 - 您已受邀加入 %1$s。我们将指导您创建账号。 -\n当选择 %1$s 作为提供者时,向其他 XMPP 用户提供您的完整地址,就能和对方交流。 - 您已受邀加入 %1$s。已为您选择了用户名。我们将指导您创建账号。 -\n向其他 XMPP 用户提供您的完整地址,就能和对方交流。 - 您的服务器邀请 - 配置代码格式不正确 - 轻击分享按钮,向您的联系人发送加入 %1$s 的邀请。 - 如果您的联系人在附近,对方也可以扫描下方二维码接受邀请。 - 加入 %1$s 和我聊天:%2$s - 分享邀请至… - \ No newline at end of file diff --git a/src/conversations/res/values-zh-rTW/strings.xml b/src/conversations/res/values-zh-rTW/strings.xml deleted file mode 100644 index de09fe867..000000000 --- a/src/conversations/res/values-zh-rTW/strings.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - 挑選您的 XMPP 提供者 - 使用 conversations.im - 建立新帳戶 - 您已經擁有一個 XMPP 帳戶了嗎?如果您之前使用過其他 XMPP 用戶端或 Conversations 的話,那麼您已經擁有 XMPP 帳戶了。若沒有,您現在就建立一個新的 XMPP 帳戶。 -\n提示:部分電子郵件提供者也會提供 XMPP 帳戶。 - XMPP 是提供者無關的即時訊息網路。任何您選擇的 XMPP 伺服器都可在此用戶端上使用。 -\n不過,我們令它在 Coversations.im 中建立帳戶變得更方便;conversations.im 是特別適合 Conversations 的提供者。 - 你已受邀參加 %1$s 。我們將指引您完成建立帳戶的過程。 -\n選擇 %1$s 作為提供者後,您可以將您完整的 XMPP 位址交給使用其他提供者的使用者,以便能與他們進行交流。 - 你已受邀參加 %1$s 。我們已經為您挑選了一個使用者名稱。我們將指引您完成建立帳戶的過程。 -\n您可以將您完整的 XMPP 位址交給使用其他提供者的使用者,以便能與他們進行交流。 - 您的伺服器邀請 - 佈建代碼格式不正確 - 輕觸分享按鍵以向您的聯絡人傳送加入 %1$s 的邀請。 - 如果您的聯絡人就在附近,他們也可以掃描下面的代碼以接受您的邀請。 - 加入 %1$s 與我聊天:%2$s - 分享邀請至… - \ No newline at end of file diff --git a/src/conversations/res/values/strings.xml b/src/conversations/res/values/strings.xml index fffee31d6..44776bf9f 100644 --- a/src/conversations/res/values/strings.xml +++ b/src/conversations/res/values/strings.xml @@ -1,10 +1,10 @@ Pick your XMPP provider - Use conversations.im + Use another.im Create new account - Do you already have an XMPP account? This might be the case if you are already using a different XMPP client or have used Conversations before. If not you can create a new XMPP account right now.\nHint: Some email providers also provide XMPP accounts. - XMPP is a provider independent instant messaging network. You can use this client with what ever XMPP server you choose.\nHowever for your convenience we made it easy to create an account on conversations.im; a provider specially suited for the use with Conversations. + Do you already have an XMPP account? This might be the case if you are already using a different XMPP client or have used another.im before. If not you can create a new XMPP account right now.\nHint: Some email providers also provide XMPP accounts. + XMPP is a provider independent instant messaging network. You can use this client with what ever XMPP server you choose.\nHowever for your convenience we made it easy to create an account on another.im; a provider specially suited for the use with another.im. You have been invited to %1$s. We will guide you through the process of creating an account.\nWhen picking %1$s as a provider you will be able to communicate with users of other providers by giving them your full XMPP address. You have been invited to %1$s. A username has already been picked for you. We will guide you through the process of creating an account.\nYou will be able to communicate with users of other providers by giving them your full XMPP address. Your server invitation @@ -13,4 +13,4 @@ If your contact is nearby, they can also scan the code below to accept your invitation. Join %1$s and chat with me: %2$s Share invite with… - \ No newline at end of file + diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index d848e4027..247ca8b98 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -216,7 +216,7 @@ - + @@ -299,6 +299,11 @@ + bookmarks = new HashMap<>(); private Presence.Status presenceStatus; private String presenceStatusMessage; @@ -535,6 +544,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable } public void initAccountServices(final XmppConnectionService context) { + this.mOtrService = new OtrService(context, this); this.axolotlService = new AxolotlService(this, context); this.pgpDecryptionService = new PgpDecryptionService(context); if (xmppConnection != null) { @@ -542,6 +552,10 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable } } + public OtrService getOtrService() { + return this.mOtrService; + } + public PgpDecryptionService getPgpDecryptionService() { return this.pgpDecryptionService; } @@ -554,6 +568,27 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable this.xmppConnection = connection; } + public String getOtrFingerprint() { + if (this.otrFingerprint == null) { + try { + if (this.mOtrService == null) { + return null; + } + final PublicKey publicKey = this.mOtrService.getPublicKey(); + if (publicKey == null || !(publicKey instanceof DSAPublicKey)) { + return null; + } + this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey).toLowerCase(Locale.US); + return this.otrFingerprint; + } catch (final OtrCryptoException ignored) { + return null; + } + } else { + return this.otrFingerprint; + } + } + + public String getRosterVersion() { if (this.rosterVersion == null) { return ""; @@ -710,7 +745,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable public String getShareableLink() { List fingerprints = this.getFingerprints(); String uri = - "https://conversations.im/i/" + "https://another.im/i/" + XmppUri.lameUrlEncode(this.getJid().asBareJid().toEscapedString()); if (fingerprints.size() > 0) { return XmppUri.getFingerprintUri(uri, fingerprints, '&'); @@ -721,6 +756,10 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable private List getFingerprints() { ArrayList fingerprints = new ArrayList<>(); + final String otr = this.getOtrFingerprint(); + if (otr != null) { + fingerprints.add(new XmppUri.Fingerprint(XmppUri.FingerprintType.OTR, otr)); + } if (axolotlService == null) { return fingerprints; } diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index 6b0554240..53f0134bf 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -348,6 +348,47 @@ public class Contact implements ListItem, Blockable { return groups; } + public ArrayList getOtrFingerprints() { + synchronized (this.keys) { + final ArrayList fingerprints = new ArrayList(); + try { + if (this.keys.has("otr_fingerprints")) { + final JSONArray prints = this.keys.getJSONArray("otr_fingerprints"); + for (int i = 0; i < prints.length(); ++i) { + final String print = prints.isNull(i) ? null : prints.getString(i); + if (print != null && !print.isEmpty()) { + fingerprints.add(prints.getString(i).toLowerCase(Locale.US)); + } + } + } + } catch (final JSONException ignored) { + + } + return fingerprints; + } + } + + public boolean addOtrFingerprint(String print) { + synchronized (this.keys) { + if (getOtrFingerprints().contains(print)) { + return false; + } + try { + JSONArray fingerprints; + if (!this.keys.has("otr_fingerprints")) { + fingerprints = new JSONArray(); + } else { + fingerprints = this.keys.getJSONArray("otr_fingerprints"); + } + fingerprints.put(print); + this.keys.put("otr_fingerprints", fingerprints); + return true; + } catch (final JSONException ignored) { + return false; + } + } + } + public long getPgpKeyId() { synchronized (this.keys) { if (this.keys.has("pgp_keyid")) { diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index e3c147320..3f9648d92 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -71,6 +71,7 @@ import org.json.JSONException; import org.json.JSONObject; import java.lang.ref.WeakReference; +import java.security.interfaces.DSAPublicKey; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -81,6 +82,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.ListIterator; +import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.Timer; @@ -132,6 +134,12 @@ import me.saket.bettermovementmethod.BetterLinkMovementMethod; import static eu.siacs.conversations.entities.Bookmark.printableValue; +import net.java.otr4j.OtrException; +import net.java.otr4j.crypto.OtrCryptoException; +import net.java.otr4j.session.SessionID; +import net.java.otr4j.session.SessionImpl; +import net.java.otr4j.session.SessionStatus; + public class Conversation extends AbstractEntity implements Blockable, Comparable, Conversational, AvatarService.Avatarable { public static final String TABLENAME = "conversations"; @@ -180,10 +188,16 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl private int mode; private JSONObject attributes; private Jid nextCounterpart; + private boolean hasPermanentCounterpart; + private transient SessionImpl otrSession; + private transient String otrFingerprint = null; + private Smp mSmp = new Smp(); private transient MucOptions mucOptions = null; + private byte[] symmetricKey; private boolean messagesLeftOnServer = true; private ChatState mOutgoingChatState = Config.DEFAULT_CHAT_STATE; private ChatState mIncomingChatState = Config.DEFAULT_CHAT_STATE; + private String mLastReceivedOtrMessageId = null; private String mFirstMamReference = null; protected Message replyTo = null; protected int mCurrentTab = -1; @@ -216,6 +230,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl this.attributes = new JSONObject(); } this.nextCounterpart = nextCounterpart; + if (nextCounterpart != null) { + hasPermanentCounterpart = true; + } } public String getContactUuid() { @@ -490,6 +507,17 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl } } + public void findUnsentMessagesWithEncryption(int encryptionType, OnMessageFound onMessageFound) { + synchronized (this.messages) { + for (Message message : this.messages) { + if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING) + && (message.getEncryption() == encryptionType)) { + onMessageFound.onMessageFound(message); + } + } + } + } + public void findUnsentTextMessages(OnMessageFound onMessageFound) { final ArrayList results = new ArrayList<>(); synchronized (this.messages) { @@ -662,6 +690,14 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl return getContact().getBlockedJid(); } + public String getLastReceivedOtrMessageId() { + return this.mLastReceivedOtrMessageId; + } + + public void setLastReceivedOtrMessageId(String id) { + this.mLastReceivedOtrMessageId = id; + } + public int countMessages() { synchronized (this.messages) { return this.messages.size(); @@ -905,7 +941,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl values.put(STATUS, status); values.put(MODE, mode); - if (nextCounterpart != null) { + if (nextCounterpart != null && hasPermanentCounterpart) { values.put(NEXT_COUNTERPART, nextCounterpart.toString()); } @@ -923,6 +959,124 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl this.mode = mode; } + public SessionImpl startOtrSession(String presence, boolean sendStart) { + if (this.otrSession != null) { + return this.otrSession; + } else { + final SessionID sessionId = new SessionID(this.getJid().asBareJid().toString(), + presence, + "xmpp"); + this.otrSession = new SessionImpl(sessionId, getAccount().getOtrService()); + try { + if (sendStart) { + this.otrSession.startSession(); + return this.otrSession; + } + return this.otrSession; + } catch (OtrException e) { + return null; + } + } + + } + + public SessionImpl getOtrSession() { + return this.otrSession; + } + + public void resetOtrSession() { + this.otrFingerprint = null; + this.otrSession = null; + this.mSmp.hint = null; + this.mSmp.secret = null; + this.mSmp.status = Smp.STATUS_NONE; + } + + public Smp smp() { + return mSmp; + } + + public boolean startOtrIfNeeded() { + if (this.otrSession != null && this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) { + try { + this.otrSession.startSession(); + return true; + } catch (OtrException e) { + this.resetOtrSession(); + return false; + } + } else { + return true; + } + } + + public boolean endOtrIfNeeded() { + if (this.otrSession != null) { + if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) { + try { + this.otrSession.endSession(); + this.resetOtrSession(); + return true; + } catch (OtrException e) { + this.resetOtrSession(); + return false; + } + } else { + this.resetOtrSession(); + return false; + } + } else { + return false; + } + } + + public boolean hasValidOtrSession() { + return this.otrSession != null; + } + + public synchronized String getOtrFingerprint() { + if (this.otrFingerprint == null) { + try { + if (getOtrSession() == null || getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) { + return null; + } + DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession().getRemotePublicKey(); + this.otrFingerprint = getAccount().getOtrService().getFingerprint(remotePubKey).toLowerCase(Locale.US); + } catch (final OtrCryptoException ignored) { + return null; + } catch (final UnsupportedOperationException ignored) { + return null; + } + } + return this.otrFingerprint; + } + + public boolean verifyOtrFingerprint() { + final String fingerprint = getOtrFingerprint(); + if (fingerprint != null) { + getContact().addOtrFingerprint(fingerprint); + return true; + } else { + return false; + } + } + + public boolean isOtrFingerprintVerified() { + return getContact().getOtrFingerprints().contains(getOtrFingerprint()); + } + + public class Smp { + public static final int STATUS_NONE = 0; + public static final int STATUS_CONTACT_REQUESTED = 1; + public static final int STATUS_WE_REQUESTED = 2; + public static final int STATUS_FAILED = 3; + public static final int STATUS_VERIFIED = 4; + + public String secret = null; + public String hint = null; + public int status = 0; + } + /** * short for is Private and Non-anonymous */ @@ -959,14 +1113,23 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl return this.nextCounterpart; } + public boolean hasPermanentCounterpart() { + return hasPermanentCounterpart; + } + public void setNextCounterpart(Jid jid) { this.nextCounterpart = jid; } public int getNextEncryption() { - if (!Config.supportOmemo() && !Config.supportOpenPgp()) { + if (!Config.supportOmemo() && !Config.supportOpenPgp() && !Config.supportOtr()) { return Message.ENCRYPTION_NONE; } + + if (Config.supportOtr() && nextCounterpart != null && getMode() == MODE_SINGLE && hasPermanentCounterpart) { + return Message.ENCRYPTION_OTR; + } + if (OmemoSetting.isAlways()) { return suitableForOmemoByDefault(this) ? Message.ENCRYPTION_AXOLOTL : Message.ENCRYPTION_NONE; } @@ -977,7 +1140,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl defaultEncryption = Message.ENCRYPTION_NONE; } int encryption = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, defaultEncryption); - if (encryption == Message.ENCRYPTION_OTR || encryption < 0) { + if (encryption < 0) { return defaultEncryption; } else { return encryption; @@ -993,6 +1156,10 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl return nextMessage == null ? "" : nextMessage; } + public boolean smpRequested() { + return smp().status == Smp.STATUS_CONTACT_REQUESTED; + } + public @Nullable Draft getDraft() { long timestamp = getLongAttribute(ATTRIBUTE_NEXT_MESSAGE_TIMESTAMP, 0); @@ -1015,6 +1182,14 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl return changed; } + public void setSymmetricKey(byte[] key) { + this.symmetricKey = key; + } + + public byte[] getSymmetricKey() { + return this.symmetricKey; + } + public Bookmark getBookmark() { return this.account.getBookmark(this.contactJid); } @@ -1231,14 +1406,14 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl String res2 = nextCounterpart == null ? null : nextCounterpart.getResource(); if (nextCounterpart == null) { - if (!message.isPrivateMessage()) { + if (!message.isPrivateMessage() && message.encryption != Message.ENCRYPTION_OTR) { synchronized (this.messages) { this.messages.add(message); actualizeReplyMessages(this.messages, List.of(message)); } } } else { - if (message.isPrivateMessage() && Objects.equals(res1, res2)) { + if ((message.isPrivateMessage() || message.encryption == Message.ENCRYPTION_OTR) && Objects.equals(res1, res2)) { synchronized (this.messages) { this.messages.add(message); actualizeReplyMessages(this.messages, List.of(message)); @@ -1260,14 +1435,14 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl } if (nextCounterpart == null) { - if (!message.isPrivateMessage()) { + if (!message.isPrivateMessage() && message.encryption != Message.ENCRYPTION_OTR) { synchronized (this.messages) { properListToAdd.add(Math.min(offset, properListToAdd.size()), message); actualizeReplyMessages(properListToAdd, List.of(message)); } } } else { - if (message.isPrivateMessage() && Objects.equals(res1, res2)) { + if ((message.isPrivateMessage() || message.encryption == Message.ENCRYPTION_OTR) && Objects.equals(res1, res2)) { synchronized (this.messages) { properListToAdd.add(Math.min(offset, properListToAdd.size()), message); actualizeReplyMessages(properListToAdd, List.of(message)); @@ -1291,7 +1466,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl if (nextCounterpart == null) { for(Message m : messages) { - if (!m.isPrivateMessage()) { + if (!m.isPrivateMessage() && m.encryption != Message.ENCRYPTION_OTR) { newM.add(m); } } @@ -1302,7 +1477,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl String res2 = nextCounterpart == null ? null : nextCounterpart.getResource(); - if (m.isPrivateMessage() && Objects.equals(res1, res2)) { + if ((m.isPrivateMessage() || m.encryption == Message.ENCRYPTION_OTR) && Objects.equals(res1, res2)) { newM.add(m); } } diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index ba7dc1bdb..4150d20ac 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -352,7 +352,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } public String replyId() { - return conversation.getMode() == Conversation.MODE_MULTI || getRemoteMsgId() == null ? getServerMsgId() : getRemoteMsgId(); + if (conversation.getMode() == Conversation.MODE_MULTI) return getServerMsgId(); + final String remote = getRemoteMsgId(); + if (remote == null && getStatus() > STATUS_RECEIVED) return getUuid(); + return remote; } public Message reply() { diff --git a/src/main/java/eu/siacs/conversations/entities/Transferable.java b/src/main/java/eu/siacs/conversations/entities/Transferable.java index 5c833f603..58297d26a 100644 --- a/src/main/java/eu/siacs/conversations/entities/Transferable.java +++ b/src/main/java/eu/siacs/conversations/entities/Transferable.java @@ -6,7 +6,7 @@ import java.util.List; public interface Transferable { List VALID_IMAGE_EXTENSIONS = Arrays.asList("webp", "jpeg", "jpg", "png", "jpe"); - List VALID_CRYPTO_EXTENSIONS = Arrays.asList("pgp", "gpg"); + List VALID_CRYPTO_EXTENSIONS = Arrays.asList("pgp", "gpg", "otr"); int STATUS_UNKNOWN = 0x200; int STATUS_CHECKING = 0x201; diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index 706b50043..1088427fb 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java @@ -57,6 +57,9 @@ public abstract class AbstractGenerator { private final String[] PRIVACY_SENSITIVE = { "urn:xmpp:time" //XEP-0202: Entity Time leaks time zone }; + private final String[] OTR = { + "urn:xmpp:otr:0" + }; private final String[] VOIP_NAMESPACES = { Namespace.JINGLE_TRANSPORT_ICE_UDP, Namespace.JINGLE_FEATURE_AUDIO, @@ -125,6 +128,9 @@ public abstract class AbstractGenerator { features.addAll(Arrays.asList(PRIVACY_SENSITIVE)); features.addAll(Arrays.asList(VOIP_NAMESPACES)); } + if (Config.supportOtr()) { + features.addAll(Arrays.asList(OTR)); + } if (mXmppConnectionService.broadcastLastActivity()) { features.add(Namespace.IDLE); } diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 944e36d15..dcf91868c 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -1,5 +1,8 @@ package eu.siacs.conversations.generator; +import net.java.otr4j.OtrException; +import net.java.otr4j.session.Session; + import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; @@ -24,7 +27,8 @@ import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; public class MessageGenerator extends AbstractGenerator { - private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://conversations.im/omemo"; + public static final String OTR_FALLBACK_MESSAGE = "I would like to start a private (OTR encrypted) conversation but your client doesn’t seem to support that"; + private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://another.im/omemo"; private static final String PGP_FALLBACK_MESSAGE = "I sent you a PGP encrypted message but your client doesn’t seem to support that."; public MessageGenerator(XmppConnectionService service) { @@ -102,6 +106,36 @@ public class MessageGenerator extends AbstractGenerator { return packet; } + public static void addMessageHints(MessagePacket packet) { + packet.addChild("private", "urn:xmpp:carbons:2"); + packet.addChild("no-copy", "urn:xmpp:hints"); + packet.addChild("no-permanent-store", "urn:xmpp:hints"); + packet.addChild("no-permanent-storage", "urn:xmpp:hints"); //do not copy this. this is wrong. it is *store* + } + + public MessagePacket generateOtrChat(Message message) { + Conversation conversation = (Conversation) message.getConversation(); + Session otrSession = conversation.getOtrSession(); + if (otrSession == null) { + return null; + } + MessagePacket packet = preparePacket(message); + addMessageHints(packet); + try { + String content; + if (message.hasFileOnRemoteHost()) { + content = message.getFileParams().url.toString(); + } else { + content = message.getBody(); + } + packet.setBody(otrSession.transformSending(content)[0]); + packet.addChild("encryption", "urn:xmpp:eme:0").setAttribute("namespace", "urn:xmpp:otr:0"); + return packet; + } catch (OtrException e) { + return null; + } + } + public MessagePacket generateChat(Message message) { MessagePacket packet = preparePacket(message); String content; @@ -233,6 +267,19 @@ public class MessageGenerator extends AbstractGenerator { return packet; } + public MessagePacket generateOtrError(Jid to, String id, String errorText) { + MessagePacket packet = new MessagePacket(); + packet.setType(MessagePacket.TYPE_ERROR); + packet.setAttribute("id", id); + packet.setTo(to); + Element error = packet.addChild("error"); + error.setAttribute("code", "406"); + error.setAttribute("type", "modify"); + error.addChild("not-acceptable", "urn:ietf:params:xml:ns:xmpp-stanzas"); + error.addChild("text").setContent("?OTR Error:" + errorText); + return packet; + } + public MessagePacket sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) { final MessagePacket packet = new MessagePacket(); packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 2ec1bacf2..ea7261b3e 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -1,8 +1,15 @@ package eu.siacs.conversations.parser; +import android.os.Build; +import android.text.Html; import android.util.Log; import android.util.Pair; +import com.google.common.base.Strings; + +import net.java.otr4j.session.Session; +import net.java.otr4j.session.SessionStatus; + import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -16,6 +23,7 @@ import java.util.UUID; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.OtrService; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.BrokenSessionException; import eu.siacs.conversations.crypto.axolotl.NotEncryptedForThisDeviceException; @@ -28,9 +36,11 @@ import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; +import eu.siacs.conversations.entities.Presence; import eu.siacs.conversations.entities.ReadByMarker; import eu.siacs.conversations.entities.ReceiptRequest; import eu.siacs.conversations.entities.RtpSessionStatus; +import eu.siacs.conversations.entities.ServiceDiscoveryResult; import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.QuickConversationsService; @@ -49,6 +59,7 @@ import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; public class MessageParser extends AbstractParser implements OnMessagePacketReceived { + private static final List CLIENTS_SENDING_HTML_IN_OTR = Arrays.asList("Pidgin", "Adium", "Trillian"); private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH); @@ -95,6 +106,30 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece return result != null ? result : fallback; } + private static boolean clientMightSendHtml(Account account, Jid from) { + String resource = from.getResource(); + if (resource == null) { + return false; + } + Presence presence = account.getRoster().getContact(from).getPresences().getPresencesMap().get(resource); + ServiceDiscoveryResult disco = presence == null ? null : presence.getServiceDiscoveryResult(); + if (disco == null) { + return false; + } + return hasIdentityKnowForSendingHtml(disco.getIdentities()); + } + + private static boolean hasIdentityKnowForSendingHtml(List identities) { + for (ServiceDiscoveryResult.Identity identity : identities) { + if (identity.getName() != null) { + if (CLIENTS_SENDING_HTML_IN_OTR.contains(identity.getName())) { + return true; + } + } + } + return false; + } + private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final MessagePacket packet) { ChatState state = ChatState.parse(packet); if (state != null && c != null) { @@ -126,6 +161,70 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece return false; } + private Message parseOtrChat(String body, Jid from, String id, Conversation conversation) { + String presence; + if (from.isBareJid()) { + presence = ""; + } else { + presence = from.getResource(); + } + if (body.matches("^\\?OTRv\\d{1,2}\\?.*")) { + conversation.endOtrIfNeeded(); + } + if (!conversation.hasValidOtrSession()) { + conversation.startOtrSession(presence, false); + } else { + String foreignPresence = conversation.getOtrSession().getSessionID().getUserID(); + if (!foreignPresence.equals(presence)) { + conversation.endOtrIfNeeded(); + conversation.startOtrSession(presence, false); + } + } + try { + conversation.setLastReceivedOtrMessageId(id); + Session otrSession = conversation.getOtrSession(); + body = otrSession.transformReceiving(body); + SessionStatus status = otrSession.getSessionStatus(); + if (body == null && status == SessionStatus.ENCRYPTED) { + mXmppConnectionService.onOtrSessionEstablished(conversation); + return null; + } else if (body == null && status == SessionStatus.FINISHED) { + conversation.resetOtrSession(); + mXmppConnectionService.updateConversationUi(); + return null; + } else if (body == null || (body.isEmpty())) { + return null; + } + if (body.startsWith(CryptoHelper.FILETRANSFER)) { + String key = body.substring(CryptoHelper.FILETRANSFER.length()); + conversation.setSymmetricKey(CryptoHelper.hexToBytes(key)); + return null; + } + if (clientMightSendHtml(conversation.getAccount(), from)) { + Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": received OTR message from bad behaving client. escaping HTML…"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + body = Html.fromHtml(body, Html.FROM_HTML_MODE_LEGACY).toString(); + } else { + body = Html.fromHtml(body).toString(); + } + } + + final OtrService otrService = conversation.getAccount().getOtrService(); + Message finishedMessage = new Message(conversation, body, Message.ENCRYPTION_OTR, Message.STATUS_RECEIVED); + finishedMessage.setFingerprint(otrService.getFingerprint(otrSession.getRemotePublicKey())); + conversation.setLastReceivedOtrMessageId(null); + + if (body.startsWith("?OTR")) { + return null; + } + + return finishedMessage; + } catch (Exception e) { + conversation.resetOtrSession(); + return null; + } + } + private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, final boolean checkedForDuplicates, boolean postpone) { final AxolotlService service = conversation.getAccount().getAxolotlService(); final XmppAxolotlMessage xmppAxolotlMessage; @@ -327,6 +426,11 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece final Jid from = packet.getFrom(); final String id = packet.getId(); if (from != null && id != null) { + final Message message = mXmppConnectionService.markMessage(account, + from.asBareJid(), + packet.getId(), + Message.STATUS_SEND_FAILED, + extractErrorMessage(packet)); if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) { final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length()); mXmppConnectionService.getJingleConnectionManager() @@ -335,8 +439,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX)) { final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX.length()); - final String message = extractErrorMessage(packet); - mXmppConnectionService.getJingleConnectionManager().failProceed(account, from, sessionId, message); + final String errorMessage = extractErrorMessage(packet); + mXmppConnectionService.getJingleConnectionManager().failProceed(account, from, sessionId, errorMessage); return true; } mXmppConnectionService.markMessage(account, @@ -355,6 +459,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } } } + + if (message != null) { + if (message.getEncryption() == Message.ENCRYPTION_OTR) { + Conversation conversation = (Conversation) message.getConversation(); + conversation.endOtrIfNeeded(); + } + } } return true; } @@ -368,6 +479,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } final MessagePacket packet; Long timestamp = null; + final boolean isForwarded; boolean isCarbon = false; String serverMsgId = null; final Element fin = original.findChild("fin", MessageArchiveService.Version.MAM_0.namespace); @@ -385,7 +497,9 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } timestamp = f.second; packet = f.first; + isForwarded = true; serverMsgId = result.getAttribute("id"); + query.incrementMessageCount(); if (handleErrorMessage(account, packet)) { return; @@ -403,8 +517,10 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } timestamp = f != null ? f.second : null; isCarbon = f != null; + isForwarded = isCarbon; } else { packet = original; + isForwarded = false; } if (timestamp == null) { @@ -449,6 +565,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping"); return; } + boolean isProperlyAddressed = (to != null) && (!to.isBareJid() || account.countPresences() == 0); boolean isMucStatusMessage = InvalidJid.hasValidFrom(packet) && from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status"); boolean selfAddressed; if (packet.fromAccount(account)) { @@ -483,7 +600,10 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServersWithholdAccount().contains(counterpart.getDomain().toEscapedString()); - if (conversationIsProbablyMuc && !isTypeGroupChat) { + final boolean isOTR = body != null && body.content.startsWith("?OTR") && Config.supportOtr(); + final boolean correctOTR = !isForwarded && !isTypeGroupChat && isProperlyAddressed; + + if ((conversationIsProbablyMuc && !isTypeGroupChat) || (!Strings.isNullOrEmpty(counterpart.getResource()) && isOTR && correctOTR)) { nextCounterpart = counterpart; } @@ -508,10 +628,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } } - if (nextCounterpart != null && mXmppConnectionService.checkIsArchived(account, counterpart.asBareJid(), nextCounterpart)) { - return; - } - if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null) && !isMucStatusMessage) { final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), null, conversationIsProbablyMuc, nextCounterpart != null, false, nextCounterpart); final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI; @@ -551,7 +667,17 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } } final Message message; - if (pgpEncrypted != null && Config.supportOpenPgp()) { + if (isOTR) { + if (correctOTR && !conversationMultiMode) { + message = parseOtrChat(body.content, from, remoteMsgId, conversation); + if (message == null) { + return; + } + } else { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring OTR message from " + from + " isForwarded=" + Boolean.toString(isForwarded) + ", isProperlyAddressed=" + Boolean.valueOf(isProperlyAddressed)); + return; + } + } else if (pgpEncrypted != null && Config.supportOpenPgp()) { message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status); } else if (axolotlEncrypted != null && Config.supportOmemo()) { Jid origin; @@ -800,6 +926,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece processMessageReceipts(account, packet, remoteMsgId, query); } + if (message.getStatus() == Message.STATUS_RECEIVED + && conversation.getOtrSession() != null + && !conversation.getOtrSession().getSessionID().getUserID() + .equals(message.getCounterpart().getResource())) { + conversation.endOtrIfNeeded(); + } + mXmppConnectionService.databaseBackend.createMessage(message); final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager(); if (message.trusted() && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) { diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 41ab36dff..dcc0ff2aa 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -50,6 +50,7 @@ import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.PresenceTemplate; import eu.siacs.conversations.entities.Roster; @@ -887,30 +888,40 @@ public class DatabaseBackend extends SQLiteOpenHelper { String comparsionOperation = isForward ? ">?" : " markMessage(message1, Message.STATUS_SEND_FAILED)); + } + final boolean inProgressJoin = isJoinInProgress(conversation); @@ -1736,6 +1760,30 @@ public class XmppConnectionService extends Service { packet = mMessageGenerator.generatePgpChat(message); } break; + case Message.ENCRYPTION_OTR: + SessionImpl otrSession = conversation.getOtrSession(); + if (otrSession != null && otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) { + try { + message.setCounterpart(OtrJidHelper.fromSessionID(otrSession.getSessionID())); + } catch (IllegalArgumentException e) { + break; + } + if (message.needsUploading()) { + mJingleConnectionManager.startJingleFileTransfer(message); + } else { + packet = mMessageGenerator.generateOtrChat(message); + } + } else if (otrSession == null) { + if (message.fixCounterpart()) { + conversation.startOtrSession(message.getCounterpart().getResource(), true); + } else { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not fix counterpart for OTR message to contact " + message.getCounterpart()); + break; + } + } else { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + " OTR session with " + message.getContact() + " is in wrong state: " + otrSession.getSessionStatus().toString()); + } + break; case Message.ENCRYPTION_AXOLOTL: message.setFingerprint(account.getAxolotlService().getOwnFingerprint()); if (message.needsUploading()) { @@ -1789,6 +1837,12 @@ public class XmppConnectionService extends Service { } } break; + case Message.ENCRYPTION_OTR: + if (!conversation.hasValidOtrSession() && message.getCounterpart() != null) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": create otr session without starting for " + message.getContact().getJid()); + conversation.startOtrSession(message.getCounterpart().getResource(), false); + } + break; case Message.ENCRYPTION_AXOLOTL: message.setFingerprint(account.getAxolotlService().getOwnFingerprint()); break; @@ -2446,7 +2500,9 @@ public class XmppConnectionService extends Service { query.setCallback(callback); callback.informUser(R.string.fetching_history_from_server); } else { - callback.informUser(R.string.not_fetching_history_retention_period); + if (conversation.getMode() != Conversational.MODE_SINGLE || !conversation.hasPermanentCounterpart()) { + callback.informUser(R.string.not_fetching_history_retention_period); + } } } @@ -2496,6 +2552,7 @@ public class XmppConnectionService extends Service { if ((account == null || conversation.getAccount() == account) && (conversation.getJid().asBareJid().equals(jid.asBareJid())) && Objects.equal(conversation.getNextCounterpart(), counterpart) + && conversation.hasPermanentCounterpart() ) { return conversation; } @@ -2504,7 +2561,7 @@ public class XmppConnectionService extends Service { for (final Conversation conversation : haystack) { if ((account == null || conversation.getAccount() == account) && (conversation.getJid().asBareJid().equals(jid.asBareJid())) - && conversation.getNextCounterpart() == null + && (conversation.getNextCounterpart() == null || !conversation.hasPermanentCounterpart()) ) { return conversation; } @@ -2566,6 +2623,7 @@ public class XmppConnectionService extends Service { if (conversation != null) { return conversation; } + conversation = databaseBackend.findConversation(account, jid, counterpart); final boolean loadMessagesFromDb; if (conversation != null) { @@ -2647,6 +2705,18 @@ public class XmppConnectionService extends Service { archiveConversation(conversation, true); } + public void destroyConversation(Conversation conversation) { + archiveConversation(conversation); + final Runnable runnable = () -> { + databaseBackend.deleteMessagesInConversation(conversation); + + if (!databaseBackend.deleteConversation(conversation.getAccount(), conversation.getContactJid().asBareJid(), conversation.getNextCounterpart())) { + Log.d(Config.LOGTAG, conversation.getJid().asBareJid() + ": unable to delete conversation"); + } + }; + mDatabaseWriterExecutor.execute(runnable); + } + private void archiveConversation(Conversation conversation, final boolean maySynchronizeWithBookmarks) { getNotificationService().clear(conversation); conversation.setStatus(Conversation.STATUS_ARCHIVED); @@ -2675,6 +2745,7 @@ public class XmppConnectionService extends Service { stopPresenceUpdatesTo(conversation.getContact()); } } + conversation.endOtrIfNeeded(); updateConversation(conversation); this.conversations.remove(conversation); updateConversationUi(); @@ -3922,6 +3993,12 @@ public class XmppConnectionService extends Service { if (conversation.getAccount() == account) { if (conversation.getMode() == Conversation.MODE_MULTI) { leaveMuc(conversation, true); + } else { + if (conversation.endOtrIfNeeded()) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + + ": ended otr session with " + + conversation.getJid()); + } } } } @@ -3978,6 +4055,39 @@ public class XmppConnectionService extends Service { pushContactToServer(contact, preAuth); } + public void onOtrSessionEstablished(Conversation conversation) { + final Account account = conversation.getAccount(); + final Session otrSession = conversation.getOtrSession(); + Log.d(Config.LOGTAG, + account.getJid().asBareJid() + " otr session established with " + + conversation.getJid() + "/" + + otrSession.getSessionID().getUserID()); + conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, new Conversation.OnMessageFound() { + + @Override + public void onMessageFound(Message message) { + SessionID id = otrSession.getSessionID(); + try { + message.setCounterpart(Jid.of(id.getAccountID() + "/" + id.getUserID())); + } catch (IllegalArgumentException e) { + return; + } + if (message.needsUploading()) { + mJingleConnectionManager.startJingleFileTransfer(message); + } else { + MessagePacket outPacket = mMessageGenerator.generateOtrChat(message); + if (outPacket != null) { + mMessageGenerator.addDelay(outPacket, message.getTimeSent()); + message.setStatus(Message.STATUS_SEND); + databaseBackend.updateMessage(message, false); + sendMessagePacket(account, outPacket); + } + } + updateConversationUi(); + } + }); + } + public void pushContactToServer(final Contact contact) { pushContactToServer(contact, null); } @@ -4503,6 +4613,7 @@ public class XmppConnectionService extends Service { return false; } else { final Message message = conversation.findSentMessageWithUuid(uuid); + if (message != null) { if (message.getServerMsgId() == null) { message.setServerMsgId(serverMessageId); @@ -4805,6 +4916,11 @@ public class XmppConnectionService extends Service { setMemorizingTrustManager(tm); } + public void syncRosterToDisk(final Account account) { + Runnable runnable = () -> databaseBackend.writeRoster(account.getRoster()); + mDatabaseWriterExecutor.execute(runnable); + } + public LruCache getBitmapCache() { return this.mBitmapCache; } @@ -5272,10 +5388,14 @@ public class XmppConnectionService extends Service { } public boolean verifyFingerprints(Contact contact, List fingerprints) { + boolean needsRosterWrite = false; boolean performedVerification = false; final AxolotlService axolotlService = contact.getAccount().getAxolotlService(); for (XmppUri.Fingerprint fp : fingerprints) { - if (fp.type == XmppUri.FingerprintType.OMEMO) { + if (fp.type == XmppUri.FingerprintType.OTR) { + performedVerification |= contact.addOtrFingerprint(fp.fingerprint); + needsRosterWrite |= performedVerification; + } else if (fp.type == XmppUri.FingerprintType.OMEMO) { String fingerprint = "05" + fp.fingerprint.replaceAll("\\s", ""); FingerprintStatus fingerprintStatus = axolotlService.getFingerprintTrust(fingerprint); if (fingerprintStatus != null) { @@ -5288,6 +5408,11 @@ public class XmppConnectionService extends Service { } } } + + if (needsRosterWrite) { + syncRosterToDisk(contact.getAccount()); + } + return performedVerification; } diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 7768c55f6..c1f75ac5e 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -403,7 +403,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers protected String getShareableUri(boolean http) { if (mConversation != null) { if (http) { - return "https://conversations.im/j/" + XmppUri.lameUrlEncode(mConversation.getJid().asBareJid().toEscapedString()); + return "https://another.im/j/" + XmppUri.lameUrlEncode(mConversation.getJid().asBareJid().toEscapedString()); } else { return "xmpp:" + mConversation.getJid().asBareJid() + "?join"; } diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index cc7773ad2..2b812da6d 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -204,7 +204,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp @Override protected String getShareableUri(boolean http) { if (http) { - return "https://conversations.im/i/" + XmppUri.lameUrlEncode(contact.getJid().asBareJid().toEscapedString()); + return "https://another.im/i/" + XmppUri.lameUrlEncode(contact.getJid().asBareJid().toEscapedString()); } else { return "xmpp:" + Uri.encode(contact.getJid().asBareJid().toEscapedString(), "@/+"); } @@ -504,7 +504,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp && contact.getLastseen() > 0 && contact.getPresences().allOrNonSupport(Namespace.IDLE)) { binding.detailsLastseen.setVisibility(View.VISIBLE); - binding.detailsLastseen.setText(UIHelper.lastseen(getApplicationContext(), contact.isActive(), contact.getLastseen())); + binding.detailsLastseen.setText(UIHelper.lastseen(getApplicationContext(), contact.isActive(), contact.getLastseen(), false)); } else { binding.detailsLastseen.setVisibility(View.GONE); } @@ -523,7 +523,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp AvatarWorkerTask.loadAvatar(contact, binding.detailsContactBadge, R.dimen.avatar_on_details_screen_size); binding.detailsContactBadge.setOnClickListener(this::onBadgeClick); - binding.presenceIndicator.setStatus(contact.getShownStatus()); + binding.presenceIndicator.setStatus(contact); binding.detailsContactKeys.removeAllViews(); boolean hasKeys = false; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 457ff8807..84906ce04 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -79,6 +79,8 @@ import androidx.viewpager.widget.PagerAdapter; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; +import net.java.otr4j.session.SessionStatus; + import org.jetbrains.annotations.NotNull; import java.io.File; @@ -223,6 +225,14 @@ public class ConversationFragment extends XmppFragment private ConversationsActivity activity; private Vibrator vibrator; private boolean reInitRequiredOnStart = true; + + protected OnClickListener clickToVerify = new OnClickListener() { + @Override + public void onClick(View v) { + activity.verifyOtrSessionDialog(conversation, v); + } + }; + @ColorInt private int primaryColor = -1; @@ -534,6 +544,20 @@ public class ConversationFragment extends XmppFragment } } }; + + private OnClickListener mAnswerSmpClickListener = new OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(activity, VerifyOTRActivity.class); + intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); + intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toString()); + intent.putExtra(VerifyOTRActivity.EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toString()); + intent.putExtra("mode", VerifyOTRActivity.MODE_ANSWER_QUESTION); + startActivity(intent); + activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out); + } + }; + protected OnClickListener clickToDecryptListener = new OnClickListener() { @@ -590,7 +614,7 @@ public class ConversationFragment extends XmppFragment public void onClick(View v) { stopScrolling(); - if (previousClickedReply != null) { + /*if (previousClickedReply != null) { int lastVisiblePosition = binding.messagesView.getLastVisiblePosition(); Message lastVisibleMessage = messageListAdapter.getItem(lastVisiblePosition); Message jump = previousClickedReply; @@ -602,7 +626,7 @@ public class ConversationFragment extends XmppFragment return; } } - } + }*/ if (conversation.isInHistoryPart()) { conversation.jumpToLatest(); @@ -1064,6 +1088,9 @@ public class ConversationFragment extends XmppFragment message.setUuid(UUID.randomUUID().toString()); } switch (conversation.getNextEncryption()) { + case Message.ENCRYPTION_OTR: + sendOtrMessage(message); + break; case Message.ENCRYPTION_PGP: sendPgpMessage(message); break; @@ -1361,7 +1388,7 @@ public class ConversationFragment extends XmppFragment public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); - if (savedInstanceState == null) { + if (savedInstanceState == null && conversation != null) { conversation.jumpToLatest(); } } @@ -1380,8 +1407,16 @@ public class ConversationFragment extends XmppFragment final MenuItem menuVideoCall = menu.findItem(R.id.action_video_call); final MenuItem menuTogglePinned = menu.findItem(R.id.action_toggle_pinned); final MenuItem deleteCustomBg = menu.findItem(R.id.action_delete_custom_bg); + final MenuItem startSecretChat = menu.findItem(R.id.action_start_secret_chat); + final MenuItem destroySecretChat = menu.findItem(R.id.action_destroy_secret_chat); + final MenuItem encryption = menu.findItem(R.id.action_security); if (conversation != null) { + boolean considerAsSecretChat = conversation.getMode() == Conversational.MODE_SINGLE && + conversation.getNextCounterpart() != null && conversation.hasPermanentCounterpart(); + + + destroySecretChat.setVisible(false); if (conversation.getMode() == Conversation.MODE_MULTI) { menuContactDetails.setVisible(false); menuInviteContact.setVisible(conversation.getMucOptions().canInvite() && conversation.getNextCounterpart() == null); @@ -1391,7 +1426,12 @@ public class ConversationFragment extends XmppFragment : R.string.channel_details); menuCall.setVisible(false); menuOngoingCall.setVisible(false); + startSecretChat.setVisible(false); } else { + if (considerAsSecretChat) { + startSecretChat.setVisible(false); + destroySecretChat.setVisible(true); + } menuMucParticipants.setVisible(false); final XmppConnectionService service = activity == null ? null : activity.xmppConnectionService; @@ -1432,6 +1472,10 @@ public class ConversationFragment extends XmppFragment menuTogglePinned.setTitle(R.string.add_to_favorites); } + if (considerAsSecretChat) { + encryption.setVisible(false); + } + deleteCustomBg.setVisible(ChatBackgroundHelper.getBgFile(activity, conversation.getUuid()).exists()); } @@ -2022,6 +2066,12 @@ public class ConversationFragment extends XmppFragment case R.id.action_archive: activity.xmppConnectionService.archiveConversation(conversation); break; + case R.id.action_start_secret_chat: + startOtrChat(); + break; + case R.id.action_destroy_secret_chat: + destroySecrectChat(); + break; case R.id.action_contact_details: activity.switchToContactDetails(conversation.getContact()); break; @@ -3183,10 +3233,19 @@ public class ConversationFragment extends XmppFragment .setOpenConversation(this.conversation); if (commandAdapter != null && conversation != originalConversation) { + View currentFocus = null; + if (activity != null) { + currentFocus = activity.getCurrentFocus(); + } conversation.setupViewPager(binding.conversationViewPager, binding.tabLayout, originalConversation); refreshCommands(); + maybeRestoreMessageInputFocus(currentFocus); } if (commandAdapter == null && conversation != null) { + View currentFocus = null; + if (activity != null) { + currentFocus = activity.getCurrentFocus(); + } conversation.setupViewPager(binding.conversationViewPager, binding.tabLayout, null); commandAdapter = new CommandAdapter((XmppActivity) getActivity()); binding.commandsView.setAdapter(commandAdapter); @@ -3197,6 +3256,7 @@ public class ConversationFragment extends XmppFragment activity.startCommand(conversation.getAccount(), command.getAttributeAsJid("jid"), command.getAttribute("node")); }); refreshCommands(); + maybeRestoreMessageInputFocus(currentFocus); } previousClickedReply = null; @@ -3241,6 +3301,12 @@ public class ConversationFragment extends XmppFragment } } + private void maybeRestoreMessageInputFocus(View currentFocus) { + if (currentFocus == this.binding.textinput) { + this.binding.textinput.requestFocus(); + } + } + private void resetUnreadMessagesCount() { lastMessageUuid = null; hideUnreadMessagesCount(); @@ -3550,6 +3616,14 @@ public class ConversationFragment extends XmppFragment } } else if (account.hasPendingPgpIntent(conversation)) { showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener); + } else if (mode == Conversation.MODE_SINGLE + && conversation.smpRequested()) { + showSnackbar(R.string.smp_requested, R.string.verify, this.mAnswerSmpClickListener); + } else if (mode == Conversation.MODE_SINGLE + && conversation.hasValidOtrSession() + && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) + && (!conversation.isOtrFingerprintVerified())) { + showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify); } else if (connection != null && connection.getFeatures().blocking() && conversation.countMessages() != 0 @@ -3610,7 +3684,7 @@ public class ConversationFragment extends XmppFragment conversation.refreshSessions(); - if (conversation != null && conversation.getMode() == Conversational.MODE_MULTI) { + if (conversation != null && conversation.getMode() == Conversational.MODE_MULTI && conversation.getNextCounterpart() == null) { String subject = conversation.getMucOptions().getSubject(); Boolean hidden = conversation.getMucOptions().subjectHidden(); @@ -3650,6 +3724,11 @@ public class ConversationFragment extends XmppFragment new Handler() .post( () -> { + if (conversation.isInHistoryPart()) { + conversation.jumpToLatest(); + refresh(false); + } + int size = messageList.size(); this.binding.messagesView.setSelection(size - 1); }); @@ -3937,6 +4016,31 @@ public class ConversationFragment extends XmppFragment messageSent(); } + protected void sendOtrMessage(final Message message) { + final ConversationsActivity activity = (ConversationsActivity) getActivity(); + final XmppConnectionService xmppService = activity.xmppConnectionService; + message.setCounterpart(conversation.getNextCounterpart()); + xmppService.sendMessage(message); + messageSent(); + } + + protected void startOtrChat() { + final ConversationsActivity activity = (ConversationsActivity) getActivity(); + activity.selectPresence(conversation, + () -> { + Conversation c = activity.xmppConnectionService.findOrCreateConversation(conversation.getAccount(), conversation.getJid(), null, false, false, false, conversation.getNextCounterpart()); + conversation.setNextCounterpart(null); + if (c != conversation) { + activity.switchToConversation(c); + } + }); + } + + private void destroySecrectChat() { + conversation.endOtrIfNeeded(); + activity.xmppConnectionService.destroyConversation(conversation); + } + protected void sendPgpMessage(final Message message) { final XmppConnectionService xmppService = activity.xmppConnectionService; final Contact contact = message.getConversation().getContact(); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index b225d8404..51f7f5891 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -42,10 +42,14 @@ import android.app.FragmentTransaction; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.preference.PreferenceManager; import android.provider.Settings; import android.util.Log; import android.view.KeyEvent; @@ -59,9 +63,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.PopupMenu; import androidx.core.app.ActivityCompat; import androidx.databinding.DataBindingUtil; +import net.java.otr4j.session.SessionStatus; + import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.navigation.NavigationBarView; @@ -95,10 +102,13 @@ import eu.siacs.conversations.ui.util.PendingItem; import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.PhoneNumberUtilWrapper; import eu.siacs.conversations.utils.SignupUtils; +import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.XmppUri; +import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import io.michaelrocks.libphonenumber.android.NumberParseException; +import me.drakeet.support.toast.ToastCompat; public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnAffiliationChanged { @@ -135,6 +145,10 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio private boolean mActivityPaused = true; private final AtomicBoolean mRedirectInProcess = new AtomicBoolean(false); + private final Handler handler = new Handler(Looper.getMainLooper()); + private final Runnable refreshTitleRunnable = this::invalidateActionBarTitle; + private boolean showLastSeen = false; + private static boolean isViewOrShareIntent(Intent i) { Log.d(Config.LOGTAG, "action: " + (i == null ? null : i.getAction())); return i != null && VIEW_AND_SHARE_ACTIONS.contains(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION); @@ -660,6 +674,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio this.mSkipBackgroundBinding = false; } mRedirectInProcess.set(false); + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + this.showLastSeen = preferences.getBoolean("last_activity", false); BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation); bottomNavigationView.setSelectedItemId(R.id.chats); @@ -735,13 +751,18 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (actionBar == null) { return; } + final FragmentManager fragmentManager = getFragmentManager(); final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); if (mainFragment instanceof ConversationFragment) { final Conversation conversation = ((ConversationFragment) mainFragment).getConversation(); if (conversation != null) { - if (conversation.getNextCounterpart() != null) { - actionBar.setTitle(getString(R.string.muc_private_conversation_title, conversation.getNextCounterpart().getResource(), conversation.getName())); + if (conversation.getNextCounterpart() != null && conversation.hasPermanentCounterpart()) { + if (conversation.getMode() == Conversational.MODE_MULTI) { + actionBar.setTitle(getString(R.string.muc_private_conversation_title, conversation.getNextCounterpart().getResource(), conversation.getName())); + } else { + actionBar.setTitle(getString(R.string.secret_chat_title_no_resource, conversation.getName())); + } } else { actionBar.setTitle(conversation.getName()); } @@ -750,10 +771,40 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio binding.toolbar, (v) -> openConversationDetails(conversation) ); + + if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getNextCounterpart() == null) { + int usersCount = conversation.getMucOptions().getUserCount(); + if (usersCount > 0) { + actionBar.setSubtitle(getResources().getQuantityString(R.plurals.x_participants, conversation.getMucOptions().getUserCount(), conversation.getMucOptions().getUserCount())); + } else { + actionBar.setSubtitle(""); + } + + handler.postDelayed(refreshTitleRunnable, 5000L); + } else if (conversation.getMode() == Conversation.MODE_SINGLE) { + Contact contact = conversation.getContact(); + List statuses = contact.getPresences().getStatusMessages(); + if (conversation.getNextCounterpart() != null && conversation.hasPermanentCounterpart()) { + actionBar.setSubtitle(conversation.getNextCounterpart().getResource()); + } else if (!statuses.isEmpty() && !statuses.get(0).isBlank()) { + actionBar.setSubtitle(statuses.get(0)); + handler.postDelayed(refreshTitleRunnable, 5000L); + } else { + actionBar.setSubtitle(""); + handler.removeCallbacks(refreshTitleRunnable); + } + } else { + actionBar.setSubtitle(""); + handler.removeCallbacks(refreshTitleRunnable); + } + return; } } + + handler.removeCallbacks(refreshTitleRunnable); actionBar.setTitle(R.string.app_name); + actionBar.setSubtitle(""); actionBar.setDisplayHomeAsUpEnabled(false); ActionBarUtil.resetActionBarOnClickListeners(binding.toolbar); } @@ -771,6 +822,41 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } } + public void verifyOtrSessionDialog(final Conversation conversation, View view) { + if (!conversation.hasValidOtrSession() || conversation.getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) { + ToastCompat.makeText(this, R.string.otr_session_not_started, Toast.LENGTH_LONG).show(); + return; + } + if (view == null) { + return; + } + PopupMenu popup = new PopupMenu(this, view); + popup.inflate(R.menu.verification_choices); + popup.setOnMenuItemClickListener(menuItem -> { + if (menuItem.getItemId() == R.id.blind_trust) { + conversation.verifyOtrFingerprint(); + xmppConnectionService.syncRosterToDisk(conversation.getAccount()); + refreshUiReal(); + return true; + } + + Intent intent = new Intent(ConversationsActivity.this, VerifyOTRActivity.class); + intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); + intent.putExtra("contact", conversation.getContact().getJid().asBareJid().toString()); + intent.putExtra("counterpart", conversation.getNextCounterpart().toString()); + intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toString()); + switch (menuItem.getItemId()) { + case R.id.ask_question: + intent.putExtra("mode", VerifyOTRActivity.MODE_ASK_QUESTION); + break; + } + startActivity(intent); + overridePendingTransition(R.animator.fade_in, R.animator.fade_out); + return true; + }); + popup.show(); + } + @Override public void onConversationArchived(Conversation conversation) { if (performRedirectIfNecessary(conversation, false)) { diff --git a/src/main/java/eu/siacs/conversations/ui/SendLogActivity.java b/src/main/java/eu/siacs/conversations/ui/SendLogActivity.java index 8dabc77cd..ec537d703 100644 --- a/src/main/java/eu/siacs/conversations/ui/SendLogActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/SendLogActivity.java @@ -265,7 +265,6 @@ public class SendLogActivity extends ActionBarActivity { log.insert(0, mAdditonalInfo); } - android.util.Log.e("35fd", log.toString()); writer.write(log.toString()); } catch (IOException e){ diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 076956815..b18de5822 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -1310,6 +1310,21 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } } + protected void startOtrChat() { + int position = contact_context_id; + Contact contact = (Contact) contacts.get(position); + + Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), null, false, false, false, null); + + selectPresence(conversation, + () -> { + Conversation c = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), null, false, false, false, conversation.getNextCounterpart()); + conversation.setNextCounterpart(null); + if (c != null) { + switchToConversation(c); + } + }); + } private void setRefreshing(boolean refreshing) { MyListFragment fragment = (MyListFragment) mListPagerAdapter.getItem(0); @@ -1451,9 +1466,12 @@ public class StartConversationActivity extends XmppActivity implements XmppConne final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock); final MenuItem showContactDetailsItem = menu.findItem(R.id.context_contact_details); final MenuItem deleteContactMenuItem = menu.findItem(R.id.context_delete_contact); + final MenuItem startSecrectChat = menu.findItem(R.id.context_contact_start_secrect_chat); if (contact.isSelf()) { showContactDetailsItem.setVisible(false); + startSecrectChat.setVisible(false); } + deleteContactMenuItem.setVisible(contact.showInRoster() && !contact.getOption(Contact.Options.SYNCED_VIA_OTHER)); final XmppConnection xmpp = contact.getAccount().getXmppConnection(); if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) { @@ -1492,6 +1510,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne break; case R.id.context_delete_conference: activity.deleteConference(); + case R.id.context_contact_start_secrect_chat: + activity.startOtrChat(); } return true; } diff --git a/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java b/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java new file mode 100644 index 000000000..dd9ec29df --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java @@ -0,0 +1,450 @@ +package eu.siacs.conversations.ui; + +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; + +import net.java.otr4j.OtrException; +import net.java.otr4j.session.Session; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.XmppUri; +import eu.siacs.conversations.xmpp.Jid; +import me.drakeet.support.toast.ToastCompat; + +public class VerifyOTRActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate { + + public static final String ACTION_VERIFY_CONTACT = "verify_contact"; + public static final int MODE_SCAN_FINGERPRINT = -0x0502; + public static final int MODE_ASK_QUESTION = 0x0503; + public static final int MODE_ANSWER_QUESTION = 0x0504; + public static final int MODE_MANUAL_VERIFICATION = 0x0505; + + private LinearLayout mManualVerificationArea; + private LinearLayout mSmpVerificationArea; + private TextView mRemoteFingerprint; + private TextView mYourFingerprint; + private TextView mVerificationExplain; + private TextView mStatusMessage; + private TextView mSharedSecretHint; + private EditText mSharedSecretHintEditable; + private EditText mSharedSecretSecret; + private Button mLeftButton; + private Button mRightButton; + private Account mAccount; + private Conversation mConversation; + private int mode = MODE_MANUAL_VERIFICATION; + private XmppUri mPendingUri = null; + + private DialogInterface.OnClickListener mVerifyFingerprintListener = new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialogInterface, int click) { + mConversation.verifyOtrFingerprint(); + xmppConnectionService.syncRosterToDisk(mConversation.getAccount()); + ToastCompat.makeText(VerifyOTRActivity.this, R.string.verified, Toast.LENGTH_SHORT).show(); + finish(); + } + }; + + private View.OnClickListener mCreateSharedSecretListener = new View.OnClickListener() { + @Override + public void onClick(final View view) { + if (isAccountOnline()) { + final String question = mSharedSecretHintEditable.getText().toString(); + final String secret = mSharedSecretSecret.getText().toString(); + if (question.trim().isEmpty()) { + mSharedSecretHintEditable.requestFocus(); + mSharedSecretHintEditable.setError(getString(R.string.shared_secret_hint_should_not_be_empty)); + } else if (secret.trim().isEmpty()) { + mSharedSecretSecret.requestFocus(); + mSharedSecretSecret.setError(getString(R.string.shared_secret_can_not_be_empty)); + } else { + mSharedSecretSecret.setError(null); + mSharedSecretHintEditable.setError(null); + initSmp(question, secret); + updateView(); + } + } + } + }; + private View.OnClickListener mCancelSharedSecretListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + if (isAccountOnline()) { + abortSmp(); + updateView(); + } + } + }; + private View.OnClickListener mRespondSharedSecretListener = new View.OnClickListener() { + + @Override + public void onClick(View view) { + if (isAccountOnline()) { + final String question = mSharedSecretHintEditable.getText().toString(); + final String secret = mSharedSecretSecret.getText().toString(); + respondSmp(question, secret); + updateView(); + } + } + }; + private View.OnClickListener mRetrySharedSecretListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + mConversation.smp().status = Conversation.Smp.STATUS_NONE; + mConversation.smp().hint = null; + mConversation.smp().secret = null; + updateView(); + } + }; + private View.OnClickListener mFinishListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + mConversation.smp().status = Conversation.Smp.STATUS_NONE; + finish(); + } + }; + + protected boolean initSmp(final String question, final String secret) { + final Session session = mConversation.getOtrSession(); + if (session != null) { + try { + session.initSmp(question, secret); + mConversation.smp().status = Conversation.Smp.STATUS_WE_REQUESTED; + mConversation.smp().secret = secret; + mConversation.smp().hint = question; + return true; + } catch (OtrException e) { + return false; + } + } else { + return false; + } + } + + protected boolean abortSmp() { + final Session session = mConversation.getOtrSession(); + if (session != null) { + try { + session.abortSmp(); + mConversation.smp().status = Conversation.Smp.STATUS_NONE; + mConversation.smp().hint = null; + mConversation.smp().secret = null; + return true; + } catch (OtrException e) { + return false; + } + } else { + return false; + } + } + + protected boolean respondSmp(final String question, final String secret) { + final Session session = mConversation.getOtrSession(); + if (session != null) { + try { + session.respondSmp(question, secret); + return true; + } catch (OtrException e) { + return false; + } + } else { + return false; + } + } + + protected boolean verifyWithUri(XmppUri uri) { + Contact contact = mConversation.getContact(); + if (this.mConversation.getContact().getJid().equals(uri.getJid()) && uri.hasFingerprints()) { + xmppConnectionService.verifyFingerprints(contact, uri.getFingerprints()); + ToastCompat.makeText(this, R.string.verified, Toast.LENGTH_SHORT).show(); + updateView(); + return true; + } else { + ToastCompat.makeText(this, R.string.could_not_verify_fingerprint, Toast.LENGTH_SHORT).show(); + return false; + } + } + + protected boolean isAccountOnline() { + if (this.mAccount.getStatus() != Account.State.ONLINE) { + ToastCompat.makeText(this, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show(); + return false; + } else { + return true; + } + } + + protected boolean handleIntent(Intent intent) { + if (intent != null && intent.getAction().equals(ACTION_VERIFY_CONTACT)) { + this.mAccount = extractAccount(intent); + if (this.mAccount == null) { + return false; + } + try { + this.mConversation = this.xmppConnectionService.find(this.mAccount, Jid.of(intent.getExtras().getString("contact")), Jid.of(intent.getExtras().getString("counterpart"))); + if (this.mConversation == null) { + return false; + } + } catch (final IllegalArgumentException ignored) { + ignored.printStackTrace(); + return false; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + this.mode = intent.getIntExtra("mode", MODE_MANUAL_VERIFICATION); + // todo scan OTR fingerprint + if (this.mode == MODE_SCAN_FINGERPRINT) { + Log.d(Config.LOGTAG, "Scan OTR fingerprint is not implemented in this version"); + //new IntentIntegrator(this).initiateScan(); + return false; + } + return true; + } else { + return false; + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + // todo onActivityResult for OTR scan + Log.d(Config.LOGTAG, "Scan OTR fingerprint result is not implemented in this version"); + /*if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) { + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); + if (scanResult != null && scanResult.getFormatName() != null) { + String data = scanResult.getContents(); + XmppUri uri = new XmppUri(data); + if (xmppConnectionServiceBound) { + verifyWithUri(uri); + finish(); + } else { + this.mPendingUri = uri; + } + } else { + finish(); + } + }*/ + super.onActivityResult(requestCode, requestCode, intent); + } + + @Override + protected void onBackendConnected() { + if (handleIntent(getIntent())) { + updateView(); + } else if (mPendingUri != null) { + verifyWithUri(mPendingUri); + finish(); + mPendingUri = null; + } + setIntent(null); + } + + protected void updateView() { + if (this.mConversation != null && this.mConversation.hasValidOtrSession()) { + final ActionBar actionBar = getSupportActionBar(); + this.mVerificationExplain.setText(R.string.no_otr_session_found); + invalidateOptionsMenu(); + switch (this.mode) { + case MODE_ASK_QUESTION: + if (actionBar != null) { + actionBar.setTitle(R.string.ask_question); + } + this.updateViewAskQuestion(); + break; + case MODE_ANSWER_QUESTION: + if (actionBar != null) { + actionBar.setTitle(R.string.smp_requested); + } + this.updateViewAnswerQuestion(); + break; + case MODE_MANUAL_VERIFICATION: + default: + if (actionBar != null) { + actionBar.setTitle(R.string.manually_verify); + } + this.updateViewManualVerification(); + break; + } + } else { + this.mManualVerificationArea.setVisibility(View.GONE); + this.mSmpVerificationArea.setVisibility(View.GONE); + } + } + + protected void updateViewManualVerification() { + this.mVerificationExplain.setText(R.string.manual_verification_explanation); + this.mManualVerificationArea.setVisibility(View.VISIBLE); + this.mSmpVerificationArea.setVisibility(View.GONE); + this.mYourFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mAccount.getOtrFingerprint())); + this.mRemoteFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mConversation.getOtrFingerprint())); + if (this.mConversation.isOtrFingerprintVerified()) { + deactivateButton(this.mRightButton, R.string.verified); + activateButton(this.mLeftButton, R.string.cancel, this.mFinishListener); + } else { + activateButton(this.mLeftButton, R.string.cancel, this.mFinishListener); + activateButton(this.mRightButton, R.string.verify, new View.OnClickListener() { + @Override + public void onClick(View view) { + showManuallyVerifyDialog(); + } + }); + } + } + + protected void updateViewAskQuestion() { + this.mManualVerificationArea.setVisibility(View.GONE); + this.mSmpVerificationArea.setVisibility(View.VISIBLE); + this.mVerificationExplain.setText(R.string.smp_explain_question); + final int smpStatus = this.mConversation.smp().status; + switch (smpStatus) { + case Conversation.Smp.STATUS_WE_REQUESTED: + this.mStatusMessage.setVisibility(View.GONE); + this.mSharedSecretHintEditable.setVisibility(View.VISIBLE); + this.mSharedSecretSecret.setVisibility(View.VISIBLE); + this.mSharedSecretHintEditable.setText(this.mConversation.smp().hint); + this.mSharedSecretSecret.setText(this.mConversation.smp().secret); + this.activateButton(this.mLeftButton, R.string.cancel, this.mCancelSharedSecretListener); + this.deactivateButton(this.mRightButton, R.string.in_progress); + break; + case Conversation.Smp.STATUS_FAILED: + this.mStatusMessage.setVisibility(View.GONE); + this.mSharedSecretHintEditable.setVisibility(View.VISIBLE); + this.mSharedSecretSecret.setVisibility(View.VISIBLE); + this.mSharedSecretSecret.requestFocus(); + this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match)); + this.deactivateButton(this.mLeftButton, R.string.cancel); + this.activateButton(this.mRightButton, R.string.try_again, this.mRetrySharedSecretListener); + break; + case Conversation.Smp.STATUS_VERIFIED: + this.mSharedSecretHintEditable.setText(""); + this.mSharedSecretHintEditable.setVisibility(View.GONE); + this.mSharedSecretSecret.setText(""); + this.mSharedSecretSecret.setVisibility(View.GONE); + this.mStatusMessage.setVisibility(View.VISIBLE); + this.deactivateButton(this.mLeftButton, R.string.cancel); + this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener); + break; + default: + this.mStatusMessage.setVisibility(View.GONE); + this.mSharedSecretHintEditable.setVisibility(View.VISIBLE); + this.mSharedSecretSecret.setVisibility(View.VISIBLE); + this.activateButton(this.mLeftButton, R.string.cancel, this.mFinishListener); + this.activateButton(this.mRightButton, R.string.ask_question, this.mCreateSharedSecretListener); + break; + } + } + + protected void updateViewAnswerQuestion() { + this.mManualVerificationArea.setVisibility(View.GONE); + this.mSmpVerificationArea.setVisibility(View.VISIBLE); + this.mVerificationExplain.setText(R.string.smp_explain_answer); + this.mSharedSecretHintEditable.setVisibility(View.GONE); + this.mSharedSecretHint.setVisibility(View.VISIBLE); + this.deactivateButton(this.mLeftButton, R.string.cancel); + final int smpStatus = this.mConversation.smp().status; + switch (smpStatus) { + case Conversation.Smp.STATUS_CONTACT_REQUESTED: + this.mStatusMessage.setVisibility(View.GONE); + this.mSharedSecretHint.setText(this.mConversation.smp().hint); + this.activateButton(this.mRightButton, R.string.respond, this.mRespondSharedSecretListener); + break; + case Conversation.Smp.STATUS_VERIFIED: + this.mSharedSecretHintEditable.setText(""); + this.mSharedSecretHintEditable.setVisibility(View.GONE); + this.mSharedSecretHint.setVisibility(View.GONE); + this.mSharedSecretSecret.setText(""); + this.mSharedSecretSecret.setVisibility(View.GONE); + this.mStatusMessage.setVisibility(View.VISIBLE); + this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener); + break; + case Conversation.Smp.STATUS_FAILED: + default: + this.mSharedSecretSecret.requestFocus(); + this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match)); + this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener); + break; + } + } + + protected void activateButton(Button button, int text, View.OnClickListener listener) { + button.setEnabled(true); + button.setText(text); + button.setOnClickListener(listener); + } + + protected void deactivateButton(Button button, int text) { + button.setEnabled(false); + button.setText(text); + button.setOnClickListener(null); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_verify_otr); + this.mRemoteFingerprint = findViewById(R.id.remote_fingerprint); + this.mYourFingerprint = findViewById(R.id.your_fingerprint); + this.mLeftButton = findViewById(R.id.left_button); + this.mRightButton = findViewById(R.id.right_button); + this.mVerificationExplain = findViewById(R.id.verification_explanation); + this.mStatusMessage = findViewById(R.id.status_message); + this.mSharedSecretSecret = findViewById(R.id.shared_secret_secret); + this.mSharedSecretHintEditable = findViewById(R.id.shared_secret_hint_editable); + this.mSharedSecretHint = findViewById(R.id.shared_secret_hint); + this.mManualVerificationArea = findViewById(R.id.manual_verification_area); + this.mSmpVerificationArea = findViewById(R.id.smp_verification_area); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.verify_otr, menu); + return true; + } + + private void showManuallyVerifyDialog() { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.manually_verify); + builder.setMessage(R.string.are_you_sure_verify_fingerprint); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.verify, mVerifyFingerprintListener); + builder.create().show(); + } + + @Override + protected String getShareableUri() { + if (mAccount != null) { + return mAccount.getShareableUri(); + } else { + return ""; + } + } + + public void onConversationUpdate() { + refreshUi(); + } + + @Override + protected void refreshUiReal() { + updateView(); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 47ce3be01..3e1f878a9 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -56,6 +56,8 @@ import androidx.databinding.DataBindingUtil; import com.google.common.base.Strings; +import net.java.otr4j.session.SessionID; + import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -446,7 +448,18 @@ public abstract class XmppActivity extends ActionBarActivity { public void selectPresence(final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) { final Contact contact = conversation.getContact(); - if (contact.showInRoster() || contact.isSelf()) { + + if (conversation.hasValidOtrSession()) { + SessionID id = conversation.getOtrSession().getSessionID(); + Jid jid; + try { + jid = Jid.of(id.getAccountID() + "/" + id.getUserID()); + } catch (IllegalArgumentException e) { + jid = null; + } + conversation.setNextCounterpart(jid); + listener.onPresenceSelected(); + } else if (contact.showInRoster() || contact.isSelf()) { final Presences presences = contact.getPresences(); if (presences.size() == 0) { if (contact.isSelf()) { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java index a074fd6c8..b4109bfdb 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java @@ -9,6 +9,11 @@ import android.widget.ArrayAdapter; import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; +import com.kizitonwose.colorpreference.ColorDialog; +import com.kizitonwose.colorpreference.ColorPreference; +import com.kizitonwose.colorpreference.ColorShape; +import com.kizitonwose.colorpreference.ColorUtils; + import java.util.List; import eu.siacs.conversations.Config; @@ -19,22 +24,29 @@ import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.ui.util.AvatarWorkerTask; import eu.siacs.conversations.ui.util.StyledAttributes; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.Jid; public class AccountAdapter extends ArrayAdapter { private final XmppActivity activity; private final boolean showStateButton; + private final boolean showColorSelector; + + public ColorSelectorListener colorSelectorListener = null; public AccountAdapter(XmppActivity activity, List objects, boolean showStateButton) { super(activity, 0, objects); this.activity = activity; this.showStateButton = showStateButton; + this.showColorSelector = false; } - public AccountAdapter(XmppActivity activity, List objects) { + public AccountAdapter(XmppActivity activity, List objects, ColorSelectorListener listener) { super(activity, 0, objects); this.activity = activity; this.showStateButton = true; + this.showColorSelector = true; + colorSelectorListener = listener; } @Override @@ -77,18 +89,28 @@ public class AccountAdapter extends ArrayAdapter { } else { viewHolder.binding.tglAccountStatus.setVisibility(View.GONE); } + viewHolder.binding.tglAccountStatus.setOnCheckedChangeListener((compoundButton, b) -> { if (b == isDisabled && activity instanceof OnTglAccountState) { ((OnTglAccountState) activity).onClickTglAccountState(account, b); } }); - if (activity.xmppConnectionService.getAccounts().size() > 1) { - viewHolder.binding.accountIndicator.setBackgroundColor(UIHelper.getColorForName(account.getJid().asBareJid().getEscapedLocal())); + if (this.showColorSelector && activity.xmppConnectionService.getAccounts().size() > 1 && + activity.xmppConnectionService.getPreferences().getBoolean("show_account_indicator", activity.getResources().getBoolean(R.bool.show_account_indicator))) { + int color = UIHelper.getAccountColor(activity, account.getJid()); + viewHolder.binding.colorView.setVisibility(View.VISIBLE); + ColorUtils.setColorViewValue(viewHolder.binding.colorView, color, false, ColorShape.CIRCLE); + viewHolder.binding.colorView.setOnClickListener(v -> { + if (colorSelectorListener != null) { + colorSelectorListener.onColorPickerRequested(account.getJid(), color); + } + }); } else { - viewHolder.binding.accountIndicator.setBackgroundColor(Color.TRANSPARENT); + viewHolder.binding.colorView.setVisibility(View.GONE); } + return view; } @@ -106,4 +128,7 @@ public class AccountAdapter extends ArrayAdapter { void onClickTglAccountState(Account account, boolean state); } + public interface ColorSelectorListener { + void onColorPickerRequested(Jid accountJid, int currentColor); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index 69d4c7df4..0a8f328ab 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -338,8 +338,12 @@ public class ConversationAdapter } CharSequence name = conversation.getName(); - if (conversation.getNextCounterpart() != null) { - name = viewHolder.binding.getRoot().getResources().getString(R.string.muc_private_conversation_title, conversation.getNextCounterpart().getResource(), conversation.getName()); + if (conversation.getNextCounterpart() != null && conversation.hasPermanentCounterpart()) { + if (conversation.getMode() == Conversational.MODE_MULTI) { + name = viewHolder.binding.getRoot().getResources().getString(R.string.muc_private_conversation_title, conversation.getNextCounterpart().getResource(), conversation.getName()); + } else { + name = viewHolder.binding.getRoot().getResources().getString(R.string.secret_chat_title, conversation.getName(), conversation.getNextCounterpart().getResource()); + } } if (conversation.withSelf()) { @@ -386,6 +390,10 @@ public class ConversationAdapter int drId = activity.getThemeResource(R.attr.ic_group_16, R.drawable.ic_group_selected_black_16); Drawable dr = AppCompatResources.getDrawable(activity, drId); viewHolder.binding.conversationName.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, dr, null); + } else if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.hasPermanentCounterpart()) { + int drId = activity.getThemeResource(R.attr.ic_secret_chat_16, R.drawable.ic_secret_chat_16dp_black); + Drawable dr = AppCompatResources.getDrawable(activity, drId); + viewHolder.binding.conversationName.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, dr, null); } else { viewHolder.binding.conversationName.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null); } @@ -393,7 +401,7 @@ public class ConversationAdapter Contact contact = conversation.getContact(); if (contact != null) { - viewHolder.binding.presenceIndicator.setStatus(contact.getShownStatus()); + viewHolder.binding.presenceIndicator.setStatus(contact); } else { viewHolder.binding.presenceIndicator.setStatus(null); } @@ -401,7 +409,7 @@ public class ConversationAdapter Account account = conversation.getAccount(); if (account != null && activity.xmppConnectionService.getAccounts().size() > 1) { - viewHolder.binding.accountIndicator.setBackgroundColor(UIHelper.getColorForName(account.getJid().asBareJid().getEscapedLocal())); + viewHolder.binding.accountIndicator.setBackgroundColor(UIHelper.getAccountColor(activity, account.getJid())); } else { viewHolder.binding.accountIndicator.setBackgroundColor(Color.TRANSPARENT); } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java index b342ccf0d..f9444a06c 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java @@ -94,7 +94,7 @@ public class ListItemAdapter extends ArrayAdapter { AvatarWorkerTask.loadAvatar(item, viewHolder.avatar, R.dimen.avatar); if (item instanceof Contact) { - viewHolder.presenceIndicator.setStatus(((Contact) item).getShownStatus()); + viewHolder.presenceIndicator.setStatus(((Contact) item)); } else { viewHolder.presenceIndicator.setStatus(null); } @@ -108,7 +108,7 @@ public class ListItemAdapter extends ArrayAdapter { } if (account != null && activity.xmppConnectionService.getAccounts().size() > 1) { - viewHolder.accountIndicator.setBackgroundColor(UIHelper.getColorForName(account.getJid().asBareJid().getEscapedLocal())); + viewHolder.accountIndicator.setBackgroundColor(UIHelper.getAccountColor(activity, account.getJid())); } else { viewHolder.accountIndicator.setBackgroundColor(Color.TRANSPARENT); } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/UserAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/UserAdapter.java index 525fec6f6..e1fbfa283 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/UserAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/UserAdapter.java @@ -91,7 +91,7 @@ public class UserAdapter extends ListAdapter 1 && Arrays.asList("j","i").contains(uri.getPathSegments().get(0))); + final boolean candidateToProcessDirectly = "xmpp".equals(uri.getScheme()) || ("https".equals(uri.getScheme()) && "another.im".equals(uri.getHost()) && uri.getPathSegments().size() > 1 && Arrays.asList("j","i").contains(uri.getPathSegments().get(0))); if (candidateToProcessDirectly && context instanceof ConversationsActivity) { if (((ConversationsActivity) context).onXmppUriClicked(uri)) { widget.playSoundEffect(0); diff --git a/src/main/java/eu/siacs/conversations/ui/widget/AccountIndicator.kt b/src/main/java/eu/siacs/conversations/ui/widget/AccountIndicator.kt new file mode 100644 index 000000000..f7343aeca --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/widget/AccountIndicator.kt @@ -0,0 +1,37 @@ +package eu.siacs.conversations.ui.widget + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import eu.siacs.conversations.R +import eu.siacs.conversations.ui.XmppActivity + +class AccountIndicator : View { + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) + + constructor( + context: Context?, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + val enabled = (context as? XmppActivity) + ?.xmppConnectionService?.preferences + ?.getBoolean("show_account_indicator", context.resources.getBoolean(R.bool.show_account_indicator)) ?: false + + visibility = if (enabled) { + VISIBLE + } else { + INVISIBLE + } + } +} \ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/ui/widget/PresenceIndicator.kt b/src/main/java/eu/siacs/conversations/ui/widget/PresenceIndicator.kt index 4704c3ef5..27af31796 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/PresenceIndicator.kt +++ b/src/main/java/eu/siacs/conversations/ui/widget/PresenceIndicator.kt @@ -8,9 +8,13 @@ import android.graphics.Paint import android.util.AttributeSet import android.view.View import android.view.ViewOutlineProvider +import eu.siacs.conversations.R +import eu.siacs.conversations.entities.Contact import eu.siacs.conversations.entities.Presence +import eu.siacs.conversations.ui.XmppActivity import eu.siacs.conversations.ui.util.StyledAttributes import eu.siacs.conversations.utils.UIHelper +import eu.siacs.conversations.xml.Namespace class PresenceIndicator : View { private var paint: Paint = Paint().also { @@ -19,13 +23,9 @@ class PresenceIndicator : View { it.strokeWidth = 1 * Resources.getSystem().displayMetrics.density } - var status: Presence.Status? = null - set(value) { - if (field != value) { - field = value - invalidate() - } - } + private var status: Presence.Status? = null + + private var enabled = false constructor(context: Context?) : super(context) constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) @@ -51,7 +51,28 @@ class PresenceIndicator : View { } } + fun setStatus(contact: Contact?) { + val status = contact?.shownStatus?.takeIf { + contact.account?.isOnlineAndConnected == true + } + if (status != this.status) { + this.status = status + invalidate() + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + enabled = (context as? XmppActivity) + ?.xmppConnectionService?.preferences + ?.getBoolean("show_contact_status", context.resources.getBoolean(R.bool.show_contact_status)) ?: false + } + override fun onDraw(canvas: Canvas) { + if (!enabled) { + return + } + super.onDraw(canvas) val color: Int? = UIHelper.getColorForStatus(status); diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java index 85ea2bb86..59399fbca 100644 --- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java @@ -41,6 +41,7 @@ public final class CryptoHelper { private static final int PW_LENGTH = 12; private static final char[] VOWELS = "aeiou".toCharArray(); private static final char[] CONSONANTS = "bcfghjklmnpqrstvwxyz".toCharArray(); + public static final String FILETRANSFER = "?FILETRANSFERv1:"; private final static char[] hexArray = "0123456789abcdef".toCharArray(); public static String bytesToHex(byte[] bytes) { diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index 5680a5e60..d3f99e3bc 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -1,6 +1,7 @@ package eu.siacs.conversations.utils; import android.content.Context; +import android.content.SharedPreferences; import android.text.SpannableStringBuilder; import android.text.format.DateFormat; import android.text.format.DateUtils; @@ -204,26 +205,60 @@ public class UIHelper { .get(Calendar.DAY_OF_YEAR); } - public static String lastseen(Context context, boolean active, long time) { + public static String lastseen(Context context, boolean active, long time, boolean shortText) { long difference = (System.currentTimeMillis() - time) / 1000; if (active) { - return context.getString(R.string.online_right_now); + if (shortText) { + return context.getString(R.string.online_right_now_short); + } else { + return context.getString(R.string.online_right_now); + } } else if (difference < 60) { - return context.getString(R.string.last_seen_now); + if (shortText) { + return context.getString(R.string.last_seen_now_short); + } else { + return context.getString(R.string.last_seen_now); + } } else if (difference < 60 * 2) { - return context.getString(R.string.last_seen_min); + if (shortText) { + return context.getString(R.string.last_seen_min_short); + } else { + return context.getString(R.string.last_seen_min); + } } else if (difference < 60 * 60) { - return context.getString(R.string.last_seen_mins, Math.round(difference / 60.0)); + if (shortText) { + return context.getString(R.string.last_seen_mins_short, Math.round(difference / 60.0)); + } else { + return context.getString(R.string.last_seen_mins, Math.round(difference / 60.0)); + } } else if (difference < 60 * 60 * 2) { - return context.getString(R.string.last_seen_hour); + if (shortText) { + return context.getString(R.string.last_seen_hour_short); + } else { + return context.getString(R.string.last_seen_hour); + } } else if (difference < 60 * 60 * 24) { - return context.getString(R.string.last_seen_hours, - Math.round(difference / (60.0 * 60.0))); + if (shortText) { + return context.getString(R.string.last_seen_hours_short, + Math.round(difference / (60.0 * 60.0))); + } else { + return context.getString(R.string.last_seen_hours, + Math.round(difference / (60.0 * 60.0))); + } } else if (difference < 60 * 60 * 48) { - return context.getString(R.string.last_seen_day); + if (shortText) { + return context.getString(R.string.last_seen_day_short); + } else { + return context.getString(R.string.last_seen_day); + } } else { - return context.getString(R.string.last_seen_days, - Math.round(difference / (60.0 * 60.0 * 24.0))); + if (shortText) { + return context.getString(R.string.last_seen_days_short, + Math.round(difference / (60.0 * 60.0 * 24.0))); + } else { + return context.getString(R.string.last_seen_days, + Math.round(difference / (60.0 * 60.0 * 24.0))); + } } } @@ -231,6 +266,23 @@ public class UIHelper { return getColorForName(name, false); } + public static int getAccountColor(Context context, Jid accountJid) { + SharedPreferences prefs = context.getSharedPreferences("accountColorsPrefs", Context.MODE_PRIVATE); + String name = accountJid.asBareJid().toEscapedString(); + int overrideColor = prefs.getInt(name, -1); + + if (overrideColor != -1) { + return overrideColor; + } else { + return getColorForName(name); + } + } + + public static void overrideAccountColor(Context context, String accountName, int color) { + SharedPreferences prefs = context.getSharedPreferences("accountColorsPrefs", Context.MODE_PRIVATE); + prefs.edit().putInt(accountName, color).apply(); + } + public static int getColorForName(String name, boolean safe) { if (Config.XEP_0392) { return XEP0392Helper.rgbFromNick(name); @@ -292,6 +344,8 @@ public class UIHelper { return new Pair<>(context.getString(R.string.file_deleted), true); } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { return new Pair<>(context.getString(R.string.pgp_message), true); + } else if (message.getEncryption() == Message.ENCRYPTION_OTR) { + return new Pair<>(context.getString(R.string.otr_message), true); } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { return new Pair<>(context.getString(R.string.decryption_failed), true); } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) { @@ -557,6 +611,8 @@ public class UIHelper { } else { return context.getString(R.string.send_message_to_x, conversation.getName()); } + case Message.ENCRYPTION_OTR: + return context.getString(R.string.send_otr_message); case Message.ENCRYPTION_AXOLOTL: AxolotlService axolotlService = conversation.getAccount().getAxolotlService(); if (axolotlService != null && axolotlService.trustedSessionVerified(conversation)) { diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java index 6c3075be9..2cca3e560 100644 --- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java +++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java @@ -29,13 +29,14 @@ public class XmppUri { public static final String PARAMETER_PRE_AUTH = "preauth"; public static final String PARAMETER_IBR = "ibr"; private static final String OMEMO_URI_PARAM = "omemo-sid-"; + private static final String OTR_URI_PARAM = "otr-fingerprint"; protected Uri uri; protected String jid; private List fingerprints = new ArrayList<>(); private Map parameters = Collections.emptyMap(); private boolean safeSource = true; - public static final String INVITE_DOMAIN = "conversations.im"; + public static final String INVITE_DOMAIN = "another.im"; public XmppUri(final String uri) { try { @@ -111,6 +112,8 @@ public class XmppUri { if (type == XmppUri.FingerprintType.OMEMO) { builder.append(XmppUri.OMEMO_URI_PARAM); builder.append(fingerprints.get(i).deviceId); + } else if (type == XmppUri.FingerprintType.OTR) { + builder.append(XmppUri.OTR_URI_PARAM); } builder.append('='); builder.append(fingerprints.get(i).fingerprint); @@ -143,14 +146,14 @@ public class XmppUri { List segments = uri.getPathSegments(); if ("https".equalsIgnoreCase(scheme) && INVITE_DOMAIN.equalsIgnoreCase(host)) { if (segments.size() >= 2 && segments.get(1).contains("@")) { - // sample : https://conversations.im/i/foo@bar.com + // sample : https://another.im/i/foo@bar.com try { jid = Jid.ofEscaped(lameUrlDecode(segments.get(1))).toEscapedString(); } catch (Exception e) { jid = null; } } else if (segments.size() >= 3) { - // sample : https://conversations.im/i/foo/bar.com + // sample : https://another.im/i/foo/bar.com jid = segments.get(1) + "@" + segments.get(2); } if (segments.size() > 1 && "j".equalsIgnoreCase(segments.get(0))) { @@ -241,7 +244,8 @@ public class XmppUri { } public enum FingerprintType { - OMEMO + OMEMO, + OTR } public static class Fingerprint { @@ -249,6 +253,10 @@ public class XmppUri { public final String fingerprint; final int deviceId; + public Fingerprint(FingerprintType type, String fingerprint) { + this(type, fingerprint, 0); + } + public Fingerprint(FingerprintType type, String fingerprint, int deviceId) { this.type = type; this.fingerprint = fingerprint; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jid/OtrJidHelper.java b/src/main/java/eu/siacs/conversations/xmpp/jid/OtrJidHelper.java new file mode 100644 index 000000000..09c4dec53 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/jid/OtrJidHelper.java @@ -0,0 +1,17 @@ + +package eu.siacs.conversations.xmpp.jid; + +import net.java.otr4j.session.SessionID; + +import eu.siacs.conversations.xmpp.Jid; + +public final class OtrJidHelper { + + public static Jid fromSessionID(final SessionID id) throws IllegalArgumentException { + if (id.getUserID().isEmpty()) { + return Jid.of(id.getAccountID()); + } else { + return Jid.of(id.getAccountID() + "/" + id.getUserID()); + } + } +} \ No newline at end of file diff --git a/src/main/res/drawable-v24/ic_launcher_background.xml b/src/main/res/drawable-v24/ic_launcher_background.xml index cd1a4b5a3..224f1a36e 100644 --- a/src/main/res/drawable-v24/ic_launcher_background.xml +++ b/src/main/res/drawable-v24/ic_launcher_background.xml @@ -10,24 +10,6 @@ android:translateY="17.28"> - - - - - - - - - - - + android:fillColor="#52617a"/> diff --git a/src/main/res/drawable/background.xml b/src/main/res/drawable/background.xml index 215c0eae4..611c135a6 100644 --- a/src/main/res/drawable/background.xml +++ b/src/main/res/drawable/background.xml @@ -3,9 +3,10 @@ - - + diff --git a/src/main/res/drawable/ic_secret_chat_16dp_black.xml b/src/main/res/drawable/ic_secret_chat_16dp_black.xml new file mode 100644 index 000000000..44fa7de58 --- /dev/null +++ b/src/main/res/drawable/ic_secret_chat_16dp_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/main/res/drawable/ic_secret_chat_16dp_white.xml b/src/main/res/drawable/ic_secret_chat_16dp_white.xml new file mode 100644 index 000000000..0f704750e --- /dev/null +++ b/src/main/res/drawable/ic_secret_chat_16dp_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/main/res/drawable/ic_subject_black.xml b/src/main/res/drawable/ic_subject_black.xml new file mode 100644 index 000000000..6edbae1ae --- /dev/null +++ b/src/main/res/drawable/ic_subject_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/main/res/drawable/ic_subject_white.xml b/src/main/res/drawable/ic_subject_white.xml new file mode 100644 index 000000000..8c6262226 --- /dev/null +++ b/src/main/res/drawable/ic_subject_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/main/res/layout/account_row.xml b/src/main/res/layout/account_row.xml index ee3f7c138..93d6bd8d7 100644 --- a/src/main/res/layout/account_row.xml +++ b/src/main/res/layout/account_row.xml @@ -6,17 +6,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?selectableItemBackground" - android:clipToPadding="false" android:paddingLeft="8dp" android:paddingBottom="8dp" android:paddingTop="8dp"> - - + android:layout_toLeftOf="@+id/controls" + android:layout_toStartOf="@+id/controls"> - + android:layout_alignParentRight="true" + android:layout_centerVertical="true"> + + + + + + \ No newline at end of file diff --git a/src/main/res/layout/activity_verify_otr.xml b/src/main/res/layout/activity_verify_otr.xml new file mode 100644 index 000000000..5c15dd3e6 --- /dev/null +++ b/src/main/res/layout/activity_verify_otr.xml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +