Compare commits

..

115 Commits

Author SHA1 Message Date
adbenitez 7f89ecea5c Merge remote-tracking branch 'upstream/adb/improve-notifications' 2024-11-30 13:17:01 +01:00
adbenitez 30a63dd13b allow to toggle direct mentions notifications in muted chats 2024-11-30 13:16:05 +01:00
adbenitez a5d2642c37 do not notify mentions in muted 1:1 chats 2024-11-30 13:13:50 +01:00
adbenitez 79e5eedab0 Merge remote-tracking branch 'upstream/adb/improve-notifications' 2024-11-30 12:21:20 +01:00
adbenitez 27da217eb7 Merge remote-tracking branch 'upstream/adb/fix-callJavascriptFunction' 2024-11-30 12:20:07 +01:00
adbenitez a0c71a5387 fix WebxdcActivity.callJavaScriptFunction()
now even if internet access is enabled, the index.html is wrapped in
an iframe
2024-11-30 12:03:44 +01:00
adbenitez 4d19a59cb6 improve notifications handling 2024-11-29 20:10:49 +01:00
adbenitez 819a9f415a Merge remote-tracking branch 'upstream/main' 2024-11-29 19:38:43 +01:00
adbenitez dff1ef3778 update CI 2024-11-29 16:49:15 +01:00
B. Petersen 7131f5774e adapt to changed deltachat-pages output folder 2024-11-27 20:36:46 +01:00
adb aa572508dc prepare 1.49.0 (#3453)
* update changelog

* update build.gradle

* update changelog

* update scripts/upload-beta.sh
2024-11-27 20:36:30 +01:00
adb 1f880efb3f Merge pull request #3454 from deltachat/adb/update-link-generation
remove Util.QrDataToInviteURL()
2024-11-27 19:15:41 +01:00
adbenitez 72ace5c156 remove Util.QrDataToInviteURL() 2024-11-27 19:01:49 +01:00
B. Petersen 2f34a6ffa4 do not quote a dedicated message; current android shows them grouped and you do not really know what the message refers to 2024-11-27 18:02:07 +01:00
adbenitez afdfe7bbaa update strings 2024-11-27 14:37:29 +01:00
adbenitez 14f43f2a79 update deltachat-core-rust to 'chore(release): prepare for 1.151.2 (#6267)' of 'v1.151.2' 2024-11-27 14:37:29 +01:00
B. Petersen f3353cf6e9 update local help 2024-11-27 14:36:33 +01:00
adb 722f2cad1e Merge pull request #3449 from deltachat/adb/update-deeplink-api-impl
update to new deep-link API specs
2024-11-26 16:23:09 +01:00
adbenitez b135093628 update to new deep-link API specs 2024-11-26 13:24:35 +01:00
adb be44789b01 Merge pull request #3447 from deltachat/adb/issue-3444
add "show in chat" to webxdc menu
2024-11-26 12:35:22 +01:00
B. Petersen 233197095b remove dead code 2024-11-25 20:42:46 +01:00
adb 494cb728c3 Update src/main/res/menu/webxdc.xml
Co-authored-by: bjoern <r10s@b44t.com>
2024-11-25 19:59:51 +01:00
Hocuri 60e9a9101a Don't always show the sender in notifications (#3441), Always show "~" before overridden sender names (#3442)
* Don't always show the sender in notifications

In 1:1 chats, don't prepend the sender name to every line. The exception is when the display name is not equal to the chat name (i.e. there is an OverwriteSenderDisplayname), in this case we still prepend it.

* Always show the "~" before the sender name if it's overridden

When we introduced this, I assume that we weren't sure whether we should
do it and only showed it in some places. But I think it's nicer to
show the same sender name everywhere, i.e. always add the "~".
2024-11-25 14:44:31 +01:00
adbenitez c23dac6f99 add "show in chat" to webxdc menu 2024-11-25 00:57:45 +01:00
adb 3965319ef4 Merge pull request #3446 from deltachat/adb/deprecate-desc-param-of-sendUpdate
remove 'description' parameter from webxdc's sendUpdate()
2024-11-25 00:56:16 +01:00
adb c4db7c515f Merge pull request #3443 from deltachat/adb/issue-3439
notify DC_EVENT_INCOMING_WEBXDC_NOTIFY
2024-11-24 21:37:15 +01:00
adbenitez fbf88e102b remove 'description' parameter from webxdc's sendUpdate()
the 'description' parameter is about to be deprecated,
see https://github.com/deltachat/deltachat-core-rust/issues/6245
2024-11-24 21:34:22 +01:00
adb d5ea043893 Merge pull request #3440 from deltachat/hoc/reverse-notification-inbox
Reverse order of the tiny inbox in notifications
2024-11-24 21:20:34 +01:00
adbenitez f70c79ba4a fix notifyWebxdc 2024-11-24 03:46:50 +01:00
adb 0b2a26e2f1 Merge pull request #3438 from deltachat/adb/webxdc-deeplink
allow to open deep-link to webxdc
2024-11-24 00:33:59 +01:00
adbenitez b968ba67b3 avoid NullPointerException 2024-11-24 00:33:22 +01:00
adbenitez e234cc864b don't play in-chat sound 2024-11-23 23:11:34 +01:00
adbenitez b47e75eed6 use new selfAddr 2024-11-23 23:11:34 +01:00
adbenitez 6b684bf030 notify DC_EVENT_INCOMING_WEBXDC_NOTIFY 2024-11-23 23:11:34 +01:00
adbenitez ea89d37461 open webxdc when clicking info-message, pass deeplink/href 2024-11-23 23:09:01 +01:00
adbenitez 814af177d1 fix: make getWebxdcHref public 2024-11-23 23:09:01 +01:00
adbenitez 1ccbb7177f add DcMsg.getWebxdcHref() 2024-11-23 23:09:01 +01:00
adbenitez 3118dbd32b allow to open deep-link to webxdc 2024-11-23 23:09:01 +01:00
adb 1a5e062dac Merge pull request #3445 from deltachat/update-core-to-1.151.0
update core to 1.151.0
2024-11-23 23:04:44 +01:00
B. Petersen 0bcaea6f01 update deltachat-core-rust to 'chore(release): prepare for 1.151.0' of 'v1.151.0' 2024-11-23 23:01:56 +01:00
Hocuri 8283a6c4ad Reverse order of the tiny inbox in notifications 2024-11-23 20:01:46 +01:00
adb cd159bac6c Merge pull request #3377 from deltachat/r10s/notify-reactions
notify reactions added
2024-11-23 18:22:49 +01:00
adb 21f917004f Merge pull request #3436 from deltachat/adb/remove-android-4-code
remove android 4 dead code (SDK<21)
2024-11-22 23:07:12 +01:00
adb 0abf2ff00c Merge pull request #3435 from deltachat/adb/allow-to-open-http-link
allow to open in browser http links that look like proxies
2024-11-21 01:31:11 +01:00
adbenitez 0ca92ec6b5 remove android 4 compatibility code 2024-11-21 01:16:20 +01:00
adbenitez e66fa53537 allow to open in browser http links that look like proxies 2024-11-20 19:36:18 +01:00
Hocuri a51013cc00 fix: Show composite emojis enlarged, too (#3427)
fix: Show composite emojis enlarged, too

There are a lot of composite emojis like the flags and the family emojis.
Here, the grapheme starts with an emoji, but then come some other
characters that modify the first emoji (e.g. all flags start with the
flag emoji).

This PR shows those as big, too, by checking whether a grapheme _starts_
with an emoji to determine whether it's an emoji.


---------

Co-authored-by: adb <adb@merlinux.eu>
2024-11-20 13:59:50 +01:00
Hocuri 171500a97d fix: Don't change display name when starting to create a new account and going back (#3434) 2024-11-19 13:24:10 +01:00
link2xt 9a4851961e fix: check draft.isOk() instead of comparing to null
dcContext.getDraft() never returns null.
2024-11-19 10:38:08 +00:00
adbenitez e5ddbacb2e update build.gradle 2024-11-15 22:45:17 +01:00
adbenitez 03c7d78e2b Merge remote-tracking branch 'upstream/main' 2024-11-15 22:45:10 +01:00
adb 00498b02b3 Merge pull request #3424 from deltachat/adb/issue-3423
remove internal font scaling preference
2024-11-15 22:01:51 +01:00
adbenitez ad6e2d1a9f fix typo 2024-11-15 21:20:40 +01:00
adbenitez 5cfa71cd72 add some protection against theoretical 0 value from ViewUtil.pxToSp() 2024-11-15 20:57:33 +01:00
adbenitez 0551044ef9 remove internal font size setting 2024-11-15 18:02:42 +01:00
adb 5126b3eeb2 Merge pull request #3422 from deltachat/adb/issue-3421
change proxy protocol label position
2024-11-15 04:27:31 +01:00
B. Petersen 8d7078f96f fix playing inChatSounds 2024-11-14 14:10:17 +01:00
B. Petersen c446440537 save one database call 2024-11-14 14:10:17 +01:00
B. Petersen 333e4c9eca notify reactions 2024-11-14 14:10:17 +01:00
B. Petersen fb1f5df234 fix ticker line 2024-11-14 14:10:17 +01:00
Hocuri cc419a9f62 perf: Return early in getTextScale() if text is empty or starts with letter (#3426)
Not sure why, but `setText()` seems to be called with an empty string
very often - for this case it's nice if getTextScale() returns early.

If the text starts with a letter, we can also return early.
2024-11-13 20:08:31 +01:00
adbenitez 2469b6efef update originalFontSize on setTextSize() 2024-11-13 17:20:14 +01:00
adbenitez b7446c9a93 update build.gradle 2024-11-13 04:29:58 +01:00
adbenitez 0c9b5bbaf4 Merge remote-tracking branch 'upstream/main' 2024-11-13 04:29:08 +01:00
adbenitez 3126544f00 fix "show full message" button 2024-11-13 04:28:13 +01:00
adbenitez ccdad0469d Merge remote-tracking branch 'upstream/main' 2024-11-13 01:45:40 +01:00
B. Petersen 9f3eeb3d0d calculate account size in background
calculation may take a moment,
do this in background.

it is totally fine to just display nothing before,
that avoids flickering (WHAT was this? i cannot look so fast!) -
and in most cases, it is close to instant.
2024-11-13 00:16:52 +01:00
adbenitez 92bc53b672 change proxy protocol label position 2024-11-12 23:12:37 +01:00
adb 1355303360 Merge pull request #3411 from deltachat/adb/issue-3399
remove old emojis
2024-11-12 20:34:59 +01:00
adbenitez 7227f513a5 don't use unnecessary "" in build.gradle 2024-11-12 20:33:41 +01:00
adb 4933d66000 Update src/main/res/layout/reactions_pill.xml
Co-authored-by: bjoern <r10s@b44t.com>
2024-11-12 20:23:55 +01:00
adbenitez 977b21618c increase accepted lenght for scalling to 21
also avoid unnecessary iteration over graphemes if limit is exceeded already
2024-11-12 19:45:33 +01:00
adb 5cecd5bec3 Update src/main/java/org/thoughtcrime/securesms/components/InputPanel.java
Co-authored-by: Hocuri <hocuri@gmx.de>
2024-11-12 19:29:38 +01:00
adbenitez d8db39f9c7 add comment on regex 2024-11-12 19:28:25 +01:00
adbenitez 736fc44870 fix onEmojiPicked() 2024-11-12 19:23:49 +01:00
adb 537127ec4e Merge pull request #3419 from deltachat/adb/issue-3291
don't increase reaction selector's font size according to system
2024-11-12 15:29:17 +01:00
B. Petersen a5167f3739 log a warning if index gets out of bounds 2024-11-12 10:02:15 +01:00
B. Petersen d481541ad8 crash precaution by checking Spinner bounds
Spinner is very picky about bad selections,
this is an accident waiting to happen -
eg. if new values are introduces
or someone thinks, the "deprecated" case can be removed.

better do not trust incoming data.
2024-11-12 10:02:15 +01:00
adbenitez 7ab19fb8af scale emojis 2024-11-12 05:41:54 +01:00
adbenitez 715a2c0653 don't increase reaction selector's font size according to system 2024-11-12 03:33:36 +01:00
link2xt 350cd0539c build: update NDK to r27
If we are dropping Android 4 support anyway,
can as well upgrade to the current LTS NDK
which requires API level 21 (Android 5.0).
2024-11-11 16:22:55 +00:00
adb 9f8fe08c75 Merge pull request #3400 from deltachat/adb/issue-3395
improve msg_action_button
2024-11-11 16:11:44 +01:00
Hocuri 5048239563 Rename "Back up Chats to External Storage" to "Export Backup" (#3416) 2024-11-11 16:08:59 +01:00
link2xt 6e135e06a9 Opt out of webview metrics collection 2024-11-11 15:05:06 +00:00
B. Petersen d52647b288 tweak phased rollout instructions 2024-11-09 17:32:46 +01:00
B. Petersen c0a729978c update huawei instructions 2024-11-08 12:54:26 +01:00
adbenitez 041c95c6d2 remove jemoji, it adds +13MB 2024-11-06 16:39:17 +01:00
adbenitez 8de0fed18e update build.gradle 2024-11-05 23:33:09 +01:00
adbenitez dd75a8e858 enlarge font size when text is only a few emojis 2024-11-05 23:04:25 +01:00
adbenitez 2e77db23d5 remove more unused code 2024-11-05 22:24:29 +01:00
adbenitez 04a4a02235 remove transparency from emoji in pills and reactions details 2024-11-05 19:36:42 +01:00
adbenitez 2910730bc1 remove unused emoji attrs and layouts 2024-11-05 19:18:32 +01:00
Hocuri 23458a6c64 Don't show "0%" at the beginning when being added as second device (#3409) 2024-11-05 15:32:34 +01:00
Hocuri 9679b22072 fix: Accept the deprecated 3 as an alias for 2 (#3410) 2024-11-05 15:28:50 +01:00
adbenitez 3fae301ec2 remove old emojis components 2024-11-05 02:00:42 +01:00
adbenitez d08069f7b0 remove old emojis 2024-11-04 23:29:28 +01:00
B. Petersen 56a0607a5e update RELEASE.md to changed store ui 2024-11-01 14:43:56 +01:00
B. Petersen 9c08617f7d bump version to 1.48.3 2024-10-31 21:35:26 +01:00
B. Petersen 812dc8d738 update CHANGELOG for 1.48.3 2024-10-31 21:35:26 +01:00
B. Petersen d8b278a1f5 update translations 2024-10-31 21:06:00 +01:00
bjoern 4368a4f63e update translations (#3402) 2024-10-31 18:07:55 +01:00
adb 72ae90ae93 change realtime preference position (#3398)
* change realtime preference position

* tweak realtime explaination

* Update src/main/res/values/strings.xml

Co-authored-by: bjoern <r10s@b44t.com>

* tweak realtime strings

* show warning if realtime is disabled and apps need it

* update realtime warning message

* Update src/main/res/values/strings.xml

Co-authored-by: Hocuri <hocuri@gmx.de>

* Update src/main/res/values/strings.xml

Co-authored-by: Hocuri <hocuri@gmx.de>

* update strings.xml

* update strings.xml

* revert dialog

---------

Co-authored-by: bjoern <r10s@b44t.com>
Co-authored-by: Hocuri <hocuri@gmx.de>
2024-10-31 18:00:20 +01:00
B. Petersen c36ce7a8ab update deltachat-core-rust to 'chore(release): prepare for 1.148.6' of 'v1.148.6' 2024-10-31 17:53:33 +01:00
adbenitez 404097fe30 keep old style for "show full message" 2024-10-30 18:22:21 +01:00
adbenitez 07d5c719d4 improve mst_action_button 2024-10-29 20:47:33 +01:00
adb 13a43473cc improve logging: always log events for accId=0 (#3391)
improve logging: always log events for all accounts including previously ignored accId=0
2024-10-29 17:45:33 +01:00
B. Petersen 5a84198829 bump version to 1.48.2 2024-10-29 00:43:48 +01:00
B. Petersen 1f443cd87c update CHANGELOG for 1.48.2 2024-10-29 00:43:48 +01:00
B. Petersen 4901b92ee2 update local help 2024-10-28 23:41:18 +01:00
B. Petersen cd48e15071 update translations 2024-10-28 23:41:18 +01:00
B. Petersen 2d4b570f48 update deltachat-core-rust to 'chore(release): prepare for 1.148.5' of 'v1.148.5' 2024-10-28 23:41:18 +01:00
B. Petersen 3f0b36bd4b 'message info' just shows the file name
from the view of the user, this is just the 'file name'.
calling it 'original file name' there is maybe correct internally,
as we add a random number for $reasons.
however, some users were alarmed about what the heck is transferred here.
2024-10-27 15:28:11 +01:00
B. Petersen b342fb9c30 update translations 2024-10-27 11:51:00 +01:00
adbenitez 0974655366 update build.gradle 2024-10-27 06:58:30 +01:00
adbenitez 9675c419ac add some margin at the bottom of the new "show full message" buttons 2024-10-27 06:57:25 +01:00
247 changed files with 2332 additions and 5081 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r23c
ndk-version: r27
- name: Compile core
env:
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r23c
ndk-version: r27
- name: Compile core
env:
+39 -3
View File
@@ -1,6 +1,34 @@
# ArcaneChat Android Changelog
## v1.48.1
## v1.49.0
2024-11
* New emoji picker with support for more emojis
* Webxdc apps can now trigger notifications
* Webxdc apps can now deep-link to internal sections when you click their info-messages in chat
* Add "Show in Chat" to the menu of opened Webxdc apps
* Reverse order of messages in the notification group
* Notify reactions to own messages
* Improve the button to start Webxdc apps
* Make account deletion confirmation dialog faster
* Rename "Back up Chats to External Storage" to "Export Backup"
* Improve compatibility with classic email clients in the outgoing messages
* Removed internal font scaling setting in favor of the better system settings
* Use privacy-preserving webxdc addresses
* Use Rustls for connections with strict TLS
* fix: Trim whitespace from scanned QR codes.
* fix quotes: Line-before-quote may be up to 120 character long instead of 80
* fix: Prevent accidental wrong-password-notifications
* fix: Remove footers from "Show Full Message..."
* fix: Only add "member added/removed" messages if they actually do that
* fix: Update state of message when fully downloading it
* fix: send message: Do not fail if the message does not exist anymore
* fix: Do not percent-encode dot when passing to autoconfig server (so, fix handling of some servers)
* fix displaynames not being updated when intially scanned by a QR code
* several bug fixes
* update to core 1.151.2
## v1.48.3
2024-10
* new Proxy settings screen available at "Advanced / Proxy"
@@ -10,6 +38,8 @@
* scan Proxies' QR code and use them
* make Proxy URLs inside Delta Chat tappable
* open Delta Chat when tapping Proxy URLs in other apps
* support for realtime webxdc apps moved out of experimental and enabled by default
* realtime webxdc apps can be disabled at "Settings / Advanced"
* "New Contact / Link" button to view, share or copy the invite line
* "New Contact / Scan" button to easier access the scanner functionality
* open "New Contact" scan/show activities directly, do not try to be too smart and open the last active tab
@@ -44,8 +74,10 @@
* assign messages to ad-hoc group with matching name and members
* use stricter TLS checks for HTTPS downloads (images in HTML mails, Autoconfig)
* improve logging for failed QR code scans, AEAP, Autocrypt, notification permissions and sending errors
* improve logging of multi account setup (log account ID)
* show more context for the "Cannot establish guaranteed..." info message
* show original file name in "Message Info"
* show file name in "Message Info"
* show root SMTP connection failure in connectivity view
* fix: Sort received outgoing message down if it's fresher than all non fresh messages
* fix: avoid app being killed when processing a PUSH notification
* fix crash when refreshing avatar
@@ -65,8 +97,12 @@
* fix: normalize proxy URLs
* fix connections getting stuck in "Updating..." sometimes
* fix scanning "add second device" QR code from scanner above chatlist
* fix warning about wrong password
* fix app getting stale when receiving a PUSH notifications takes longer
* fix app getting stale on network changes
* fix: skip IDLE if we got unsolicited FETCH
* update translations and local help
* update to core 1.148.4
* update to core 1.148.6
## v1.46.14
+3 -3
View File
@@ -34,10 +34,10 @@ ENV PATH ${PATH}:${ANDROID_SDK_ROOT}/cmdline-tools/bin
# Install NDK manually. Other SDK parts are installed automatically by gradle.
#
# If you change the NDK version here, also change it in `flake.nix`.
# NDK version r23c LTS aka 23.2.8568313
RUN sdkmanager --sdk_root=${ANDROID_SDK_ROOT} 'ndk;23.2.8568313'
# NDK version r27 LTS aka 27.0.11902837
RUN sdkmanager --sdk_root=${ANDROID_SDK_ROOT} 'ndk;27.0.11902837'
ENV ANDROID_NDK_ROOT ${ANDROID_SDK_ROOT}/ndk/23.2.8568313
ENV ANDROID_NDK_ROOT ${ANDROID_SDK_ROOT}/ndk/27.0.11902837
ENV PATH ${PATH}:${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain none
+13 -8
View File
@@ -48,7 +48,8 @@ the following steps are done in a PR called `prep-VERSION` (no leading "v"):
6. build APKs:
a) generate debug APK at "Build / Build Bundle(s)/APK / Build APK(s)"
b) generate release APK at "Build / Generate Signed Bundle or APK",
select "APK", add keys, flavor `gplayRelease`
select "APK", add keys, flavor `gplayRelease`.
this APK will go to the stores and is located at `gplay/release`
## Push Test Releases
@@ -79,16 +80,21 @@ only afterwards, push the APK to stores. **consider a blog post.**
on <https://play.google.com/apps/publish/>:
9. a) open "Delta Chat/Release/Production"
then "Create new release" and upload APK from above
9. a) open "Delta Chat / Test and release / Production"
then "Create new release" and upload APK from above
b) fill out "Release details/Release notes" (500 chars), add the line
"These features will roll out over the coming days. Thanks for using Delta Chat!";
release name should be default ("123 (1.2.3)")
c) click "Next", set "Rollout Percentage" to 1% (later 2%, 5%, 10%, 20%, 50%, 100%),
click "Save"
c) click "Next", set "Rollout Percentage" to 50%, click "Save"
d) Go to "Publishing Overview", "Managed publishing" is usually off;
click "Send change for review", confirm
2 days later, change "Rollout Percentage" to 99%. Two more days later to 100%.
Rollout is anyways slower in practise, however,
only as long as we do not enter 100%, we can retract the version
(Once we reach 100%, we have to submit a new version for approval.
During these up to 4 days, sometimes longer, we cannot do anything on existing rollout)
## Tag for F-Droid and create Github release
@@ -111,9 +117,8 @@ on <https://developer.amazon.com/dashboard>:
12. a) for "Delta Chat", select "Add upcoming version" on the left
b) at "Step 1 / Existing file(s)" hit "Replace", upload the APK from above
c) on the "Step 1" page, add "Release notes" from CHANGELOG.md, hit "Next"
d) on "Step 2" page: "Does your app collect or transfer user data to third parties?" -> No, then "Next"
e) on "Step 3" page: "Next"
f) on "Step 4" page: "Submit app"
d) on "Step 2" and "Step 3" pages, hit "Next"
e) on "Step 4" page: "Submit app"
## Release on Huawei AppGallery
+4 -4
View File
@@ -29,12 +29,12 @@ android {
// > Task :stripFatDebugDebugSymbols
// Unable to strip the following libraries, packaging them as they are: libanimation-decoder-gif.so, libnative-utils.so.
// See <https://issuetracker.google.com/issues/237187538> for details.
ndkVersion "23.2.8568313"
ndkVersion "27.0.12077973"
useLibrary 'org.apache.http.legacy'
defaultConfig {
versionCode 30000693
versionName "1.48.1"
versionCode 30000698
versionName "1.49.0"
applicationId "chat.delta.lite"
multiDexEnabled true
@@ -206,7 +206,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.6.2'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
implementation 'androidx.work:work-runtime:2.9.1'
implementation 'androidx.emoji2:emoji2-emojipicker:1.4.0'
implementation 'androidx.emoji2:emoji2-emojipicker:1.5.0'
implementation 'com.google.guava:guava:31.1-android'
implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1' // plays video and audio
implementation 'com.google.android.exoplayer:exoplayer-ui:2.19.1'
Generated
+21 -55
View File
@@ -7,11 +7,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1710633978,
"narHash": "sha256-yemnwSvW7cdWtXGpivFA5jDO35rGPs6fqxlQ4l6ODXs=",
"lastModified": 1720988215,
"narHash": "sha256-nQ0Zx0vAWJo0IOGNFjCOdIkDSgOpMa//GalR8tbTl3A=",
"owner": "tadfisher",
"repo": "android-nixpkgs",
"rev": "e91fb3d8517538e5ad9b422c9a4f604b56008a9e",
"rev": "5a052c62cdb51b210bc0717177d5bd014cba3df1",
"type": "github"
},
"original": {
@@ -29,11 +29,11 @@
]
},
"locked": {
"lastModified": 1708939976,
"narHash": "sha256-O5+nFozxz2Vubpdl1YZtPrilcIXPcRAjqNdNE8oCRoA=",
"lastModified": 1717408969,
"narHash": "sha256-Q0OEFqe35fZbbRPPRdrjTUUChKVhhWXz3T9ZSKmaoVY=",
"owner": "numtide",
"repo": "devshell",
"rev": "5ddecd67edbd568ebe0a55905273e56cc82aabe3",
"rev": "1ebbe68d57457c8cae98145410b164b5477761f4",
"type": "github"
},
"original": {
@@ -65,11 +65,11 @@
"systems": "systems_2"
},
"locked": {
"lastModified": 1709126324,
"narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "d465f4819400de7c8d874d50b982301f28a84605",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
@@ -96,31 +96,13 @@
"type": "github"
}
},
"flake-utils_4": {
"inputs": {
"systems": "systems_4"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1709237383,
"narHash": "sha256-cy6ArO4k5qTx+l5o+0mL9f5fa86tYUX3ozE1S+Txlds=",
"lastModified": 1720768451,
"narHash": "sha256-EYekUHJE2gxeo2pM/zM9Wlqw1Uw2XTJXOSAO79ksc4Y=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1536926ef5621b09bba54035ae2bb6d806d72ac8",
"rev": "7e7c39ea35c5cdd002cd4588b03a3fb9ece6fad9",
"type": "github"
},
"original": {
@@ -132,11 +114,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1710889954,
"narHash": "sha256-Pr6F5Pmd7JnNEMHHmspZ0qVqIBVxyZ13ik1pJtm2QXk=",
"lastModified": 1723221148,
"narHash": "sha256-7pjpeQlZUNQ4eeVntytU3jkw9dFK3k1Htgk2iuXjaD8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "7872526e9c5332274ea5932a0c3270d6e4724f3b",
"rev": "154bcb95ad51bc257c2ce4043a725de6ca700ef6",
"type": "github"
},
"original": {
@@ -148,11 +130,11 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1706487304,
"narHash": "sha256-LE8lVX28MV2jWJsidW13D2qrHU/RUUONendL2Q/WlJg=",
"lastModified": 1718428119,
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "90f456026d284c22b3e3497be980b2e47d0b28ac",
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
"type": "github"
},
"original": {
@@ -172,15 +154,14 @@
},
"rust-overlay": {
"inputs": {
"flake-utils": "flake-utils_4",
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1711073443,
"narHash": "sha256-PpNb4xq7U5Q/DdX40qe7CijUsqhVVM3VZrhN0+c6Lcw=",
"lastModified": 1723429325,
"narHash": "sha256-4x/32xTCd+xCwFoI/kKSiCr5LQA2ZlyTRYXKEni5HR8=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "eec55ba9fcde6be4c63942827247e42afef7fafe",
"rev": "65e3dc0fe079fe8df087cd38f1fe6836a0373aad",
"type": "github"
},
"original": {
@@ -233,21 +214,6 @@
"repo": "default",
"type": "github"
}
},
"systems_4": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
+2 -2
View File
@@ -20,7 +20,7 @@
ndk-bundle
platform-tools
platforms-android-34
ndk-23-2-8568313
ndk-27-0-11902837
]);
rust-version = pkgs.lib.removeSuffix "\n"
(builtins.readFile ./scripts/rust-toolchain);
@@ -28,7 +28,7 @@
devShells.default = pkgs.mkShell {
ANDROID_SDK_ROOT = "${android-sdk}/share/android-sdk";
ANDROID_NDK_ROOT =
"${android-sdk}/share/android-sdk/ndk/23.2.8568313";
"${android-sdk}/share/android-sdk/ndk/27.0.11902837";
buildInputs = [
android-sdk
pkgs.openjdk17
+11 -55
View File
@@ -7,57 +7,6 @@
#include "deltachat-core-rust/deltachat-ffi/deltachat.h"
#if __ANDROID_API__ == 16
unsigned long getauxval(unsigned long type) {
return 0;
}
#include <sys/socket.h>
#include <unistd.h>
int sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
int flags)
{
if (flags != 0) {
// Not supported by the fallback.
return -1;
}
if (vlen == 0) {
return 0;
}
ssize_t n = sendmsg(sockfd, &msgvec->msg_hdr, flags);
if (n == -1) {
return -1;
}
(*msgvec).msg_len = n;
return 1;
}
int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
int flags, struct timespec *timeout)
{
if (flags != 0) {
// Not supported by the fallback.
return -1;
}
if (vlen == 0) {
return 0;
}
int n = recvmsg(sockfd, &msgvec->msg_hdr, flags);
if (n == -1) {
return -1;
}
(*msgvec).msg_len = n;
return 1;
}
#endif
static dc_msg_t* get_dc_msg(JNIEnv *env, jobject obj);
@@ -780,12 +729,10 @@ JNIEXPORT jint Java_com_b44t_messenger_DcContext_sendVideochatInvitation(JNIEnv
}
JNIEXPORT jboolean Java_com_b44t_messenger_DcContext_sendWebxdcStatusUpdate(JNIEnv *env, jobject obj, jint msg_id, jstring payload, jstring descr)
JNIEXPORT jboolean Java_com_b44t_messenger_DcContext_sendWebxdcStatusUpdate(JNIEnv *env, jobject obj, jint msg_id, jstring payload)
{
CHAR_REF(payload);
CHAR_REF(descr);
jboolean ret = dc_send_webxdc_status_update(get_dc_context(env, obj), msg_id, payloadPtr, descrPtr) != 0;
CHAR_UNREF(descr);
jboolean ret = dc_send_webxdc_status_update(get_dc_context(env, obj), msg_id, payloadPtr, NULL) != 0;
CHAR_UNREF(payload);
return ret;
}
@@ -1626,6 +1573,15 @@ JNIEXPORT jstring Java_com_b44t_messenger_DcMsg_getWebxdcInfoJson(JNIEnv *env, j
}
JNIEXPORT jstring Java_com_b44t_messenger_DcMsg_getWebxdcHref(JNIEnv *env, jobject obj)
{
char* temp = dc_msg_get_webxdc_href(get_dc_msg(env, obj));
jstring ret = JSTRING_NEW(temp);
dc_str_unref(temp);
return ret;
}
JNIEXPORT jboolean Java_com_b44t_messenger_DcMsg_isForwarded(JNIEnv *env, jobject obj)
{
return dc_msg_is_forwarded(get_dc_msg(env, obj))!=0;
+1 -1
View File
@@ -1 +1 @@
../deltachat-pages/tools/create-local-help.py ../deltachat-pages/_site src/main/assets/help
../deltachat-pages/tools/create-local-help.py ../deltachat-pages/result src/main/assets/help
+4 -4
View File
@@ -77,9 +77,9 @@ if test -z "$NDK_HOST_TAG"; then
fi
TOOLCHAIN="$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$NDK_HOST_TAG"
export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="$TOOLCHAIN/bin/armv7a-linux-androideabi16-clang"
export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="$TOOLCHAIN/bin/armv7a-linux-androideabi21-clang"
export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$TOOLCHAIN/bin/aarch64-linux-android21-clang"
export CARGO_TARGET_I686_LINUX_ANDROID_LINKER="$TOOLCHAIN/bin/i686-linux-android16-clang"
export CARGO_TARGET_I686_LINUX_ANDROID_LINKER="$TOOLCHAIN/bin/i686-linux-android21-clang"
export CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="$TOOLCHAIN/bin/x86_64-linux-android21-clang"
export RUSTUP_TOOLCHAIN=$(cat "$(dirname "$0")/rust-toolchain")
@@ -125,7 +125,7 @@ unset CPATH
if test -z $1 || test $1 = armeabi-v7a; then
echo "-- cross compiling to armv7-linux-androideabi (arm) --"
TARGET_CC="$TOOLCHAIN/bin/armv7a-linux-androideabi16-clang" \
TARGET_CC="$TOOLCHAIN/bin/armv7a-linux-androideabi21-clang" \
TARGET_AR="$TOOLCHAIN/bin/llvm-ar" \
TARGET_RANLIB="$TOOLCHAIN/bin/llvm-ranlib" \
cargo build $RELEASEFLAG --target armv7-linux-androideabi -p deltachat_ffi --features jsonrpc
@@ -143,7 +143,7 @@ fi
if test -z $1 || test $1 = x86; then
echo "-- cross compiling to i686-linux-android (x86) --"
TARGET_CC="$TOOLCHAIN/bin/i686-linux-android16-clang" \
TARGET_CC="$TOOLCHAIN/bin/i686-linux-android21-clang" \
TARGET_AR="$TOOLCHAIN/bin/llvm-ar" \
TARGET_RANLIB="$TOOLCHAIN/bin/llvm-ranlib" \
cargo build $RELEASEFLAG --target i686-linux-android -p deltachat_ffi --features jsonrpc
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
VERSION=$1
@@ -28,11 +28,6 @@ public class FcmReceiveService extends FirebaseMessagingService {
private static volatile String prefixedToken;
public static void register(Context context) {
if (Build.VERSION.SDK_INT < 19) {
Log.w(TAG, "FCM not available on SDK < 19");
triedRegistering = true;
return;
}
if (FcmReceiveService.prefixedToken != null) {
Log.i(TAG, "FCM already registered");
+4
View File
@@ -66,6 +66,10 @@
android:resource="@xml/automotive_app_desc" />
-->
<!-- Opt out of metrics collection: https://developer.android.com/develop/ui/views/layout/webapps/managing-webview#metrics -->
<meta-data android:name="android.webkit.WebView.MetricsOptOut"
android:value="true" />
<activity android:name=".ShareActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:excludeFromRecents="true"
+38 -2
View File
@@ -99,6 +99,7 @@
<li><a href="#lze-delta-chat-používat-s-protonmail--tutanota--criptext">Lze Delta Chat používat s Protonmail / Tutanota / Criptext?</a></li>
<li><a href="#remove-account">How can I delete my account?</a></li>
<li><a href="#mám-zájem-o-technické-podrobnosti-kde-najdu-víc">Mám zájem o technické podrobnosti. Kde najdu víc?</a></li>
<li><a href="#where-can-my-friends-find-delta-chat">Where can my friends find Delta Chat?</a></li>
<li><a href="#jak-je-financován-vývoj-delta-chatu">Jak je financován vývoj Delta Chatu?</a></li>
</ul>
</li>
@@ -297,8 +298,7 @@ Archived chats remain accessible above the chat list or via search.</p>
</li>
</ul>
<p>To archive or pin a chat, long tap (Android), use the chats menu (Android/Desktop) or swipe to the left (iOS);
to mute a chat, use the chats menu (Android/Desktop) or the chats profile (iOS).</p>
<p>To use the functions, long tap or right click a chat in the chat list.</p>
<h3 id="what-does-the-green-dot-mean">
@@ -1309,6 +1309,12 @@ One device is not needed for the other to work.</p>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
<li>
<p>On <strong>iOS</strong>, make sure “System Settings / Apps / Delta Chat / <strong>Local Network</strong>” access is granted</p>
</li>
<li>
<p>On <strong>macOS</strong>, enable “System Settings / Privacy &amp; Security / <strong>Local Network</strong> / Delta Chat”</p>
</li>
<li>
<p>Your system might have a “personal firewall”,
which is known to cause problems (especially on Windows).
@@ -1425,6 +1431,7 @@ you only open attachments from senders you trust, and not from spammers.</li>
<ul>
<li>In general, anyone can share webxdc apps with each
other without restrictions.</li>
<li>From <a href="https://webxdc.org/apps/">webxdc.org/apps</a>.</li>
<li>You can <a href="https://delta.chat/en/2023-08-11-xstore">send hi to xstore@testrun.org</a>
to see an experimental webxdc appstore.
All of the apps are open source and for free.</li>
@@ -1874,6 +1881,35 @@ Otherwise you might receive undecryptable messages from those group chats.</p>
<li>Dobrý začátek je <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">Standards used in Delta Chat</a>.</li>
</ul>
<h3 id="where-can-my-friends-find-delta-chat">
Where can my friends find Delta Chat? <a href="#where-can-my-friends-find-delta-chat" class="anchor"></a>
</h3>
<p>Delta Chat is available for all major and some minor platforms:</p>
<ul>
<li>
<p>The <strong>official website</strong>, <a href="https://delta.chat/download">https://delta.chat/download</a> shows all options in detail</p>
</li>
<li>
<p>If unavailable, use the <strong>mirror</strong> at <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Open one of the following <strong>app stores and search for “Delta Chat”:</strong>
Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, iOS and macOS App Store, Microsoft Store</p>
</li>
<li>
<p>Check the <strong>package manager</strong> of your Linux distributions</p>
</li>
<li>
<p><strong>Android APKs</strong> are also available on <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a></p>
</li>
</ul>
<h3 id="jak-je-financován-vývoj-delta-chatu">
+37 -1
View File
@@ -99,6 +99,7 @@
<li><a href="#ist-delta-chat-kompatibel-mit-protonmail--tutanota--criptext">Ist Delta Chat kompatibel mit Protonmail / Tutanota / Criptext?</a></li>
<li><a href="#remove-account">Wie kann ich mein Konto löschen?</a></li>
<li><a href="#ich-bin-an-technischen-details-interessiert-gibt-es-hierzu-weitere-infos">Ich bin an technischen Details interessiert. Gibt es hierzu weitere Infos?</a></li>
<li><a href="#wo-können-meine-freunde-delta-chat-finden">Wo können meine Freunde Delta Chat finden?</a></li>
<li><a href="#wie-wird-delta-chat-finanziert">Wie wird Delta Chat finanziert?</a></li>
</ul>
</li>
@@ -286,7 +287,7 @@ unter dem Text Ihrer Nachrichten.</p>
</li>
</ul>
<p>Zum Archivieren oder Anheften, tippen Sie lange auf den Chat (Android), verwenden Sie das Chatmenü (Android/Desktop) oder wischen Sie nach links (iOS); zum Stummschalten, verwenden Sie das Chatmenü (Android/Desktop) oder das Chatprofil (iOS).</p>
<p>Um die Funktionen zu nutzen, lang auf einen Chat in der Chatliste tippen oder den Chat mit der rechten Maustaste anklicken.</p>
<h3 id="was-bedeutet-der-grüne-punkt">
@@ -1215,6 +1216,12 @@ Es wurden keine kritischen Probleme gefunden, aber zwei Probleme mit hohem Schwe
und sicherstellen, dass <strong>Privates Netzwerk</strong> als “Netzwerkprofiltyp” ausgewählt ist.
(nach der Übertragung kann wieder der ursprüngliche Wert verwendet werden)</p>
</li>
<li>
<p>Auf <strong>iOS</strong>, sicherstellen, dass „Systemeinstellungen / Apps / Delta Chat / <strong>Lokales Netzwerk</strong>“ eingeschaltet ist</p>
</li>
<li>
<p>Auf <strong>macOS,</strong> „Systemeinstellungen / Datenschutz &amp; Sicherheit / <strong>Lokales Netzwerk</strong> / Delta Chat“ aktivieren</p>
</li>
<li>
<p>Ihr System verfügt möglicherweise über eine “Personal Firewall”, diese sind dafür bekannt, Probleme zu verursachen (insbesondere bei Windows). <strong>Deaktivieren Sie die Personal Firewall</strong> für Delta Chat auf beiden Seiten und versuchen Sie es erneut</p>
</li>
@@ -1305,6 +1312,7 @@ Verwenden Sie nach Möglichkeit ein Nicht-Gast-Netzwerk. Wenn Sie Zugriff auf de
<ul>
<li>Grundsätzlich kann jeder Webxdc Apps mit anderen ohne Einschränkungen teilen.</li>
<li>Von <a href="https://webxdc.org/apps/">webxdc.org/apps</a>.</li>
<li>Sie können <a href="https://delta.chat/en/2023-08-11-xstore">hallo an xstore@testrun.org senden</a>
um einen experimentellen Webxdc Appstore zu sehen.
Alle Apps dort sind Open Source und umsonst.</li>
@@ -1680,6 +1688,34 @@ Andernfalls könnten Sie unverschlüsselte Nachrichten aus diesen Gruppenchats e
<li>Siehe hierzu <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">in Delta Chat genutzte Standards</a>.</li>
</ul>
<h3 id="wo-können-meine-freunde-delta-chat-finden">
Wo können meine Freunde Delta Chat finden? <a href="#wo-können-meine-freunde-delta-chat-finden" class="anchor"></a>
</h3>
<p>Delta Chat ist für alle großen und einige kleinere Plattformen verfügbar:</p>
<ul>
<li>
<p>Die <strong>offizielle Website</strong>, <a href="https://delta.chat/download">https://delta.chat/download</a> zeigt alle Optionen im Detail</p>
</li>
<li>
<p>Falls nicht verfügbar, verwenden Sie den <strong>Mirror</strong> auf <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Öffnen Sie einen der folgenden <strong>App-Stores und suchen Sie nach „Delta Chat“:</strong> Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, iOS und macOS App Store, Microsoft Store</p>
</li>
<li>
<p>Im <strong>Paketmanager</strong> Ihrer Linux-Distribution nachschauen</p>
</li>
<li>
<p><strong>Android APKs</strong> sind auch auf <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a> verfügbar.</p>
</li>
</ul>
<h3 id="wie-wird-delta-chat-finanziert">
+38 -2
View File
@@ -99,6 +99,7 @@
<li><a href="#is-delta-chat-compatible-with-protonmail--tutanota--criptext">Is Delta Chat compatible with Protonmail / Tutanota / Criptext?</a></li>
<li><a href="#remove-account">How can I delete my account?</a></li>
<li><a href="#im-interested-in-the-technical-details-can-you-tell-me-more">Im interested in the technical details. Can you tell me more?</a></li>
<li><a href="#where-can-my-friends-find-delta-chat">Where can my friends find Delta Chat?</a></li>
<li><a href="#how-are-delta-chat-developments-funded">How are Delta Chat developments funded?</a></li>
</ul>
</li>
@@ -301,8 +302,7 @@ Archived chats remain accessible above the chat list or via search.</p>
</li>
</ul>
<p>To archive or pin a chat, long tap (Android), use the chats menu (Android/Desktop) or swipe to the left (iOS);
to mute a chat, use the chats menu (Android/Desktop) or the chats profile (iOS).</p>
<p>To use the functions, long tap or right click a chat in the chat list.</p>
<h3 id="what-does-the-green-dot-mean">
@@ -1315,6 +1315,12 @@ One device is not needed for the other to work.</p>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
<li>
<p>On <strong>iOS</strong>, make sure “System Settings / Apps / Delta Chat / <strong>Local Network</strong>” access is granted</p>
</li>
<li>
<p>On <strong>macOS</strong>, enable “System Settings / Privacy &amp; Security / <strong>Local Network</strong> / Delta Chat”</p>
</li>
<li>
<p>Your system might have a “personal firewall”,
which is known to cause problems (especially on Windows).
@@ -1432,6 +1438,7 @@ you only open attachments from senders you trust, and not from spammers.</li>
<ul>
<li>In general, anyone can share webxdc apps with each
other without restrictions.</li>
<li>From <a href="https://webxdc.org/apps/">webxdc.org/apps</a>.</li>
<li>You can <a href="https://delta.chat/en/2023-08-11-xstore">send hi to xstore@testrun.org</a>
to see an experimental webxdc appstore.
All of the apps are open source and for free.</li>
@@ -1883,6 +1890,35 @@ Otherwise you might receive undecryptable messages from those group chats.</p>
<li>See <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">Standards used in Delta Chat</a>.</li>
</ul>
<h3 id="where-can-my-friends-find-delta-chat">
Where can my friends find Delta Chat? <a href="#where-can-my-friends-find-delta-chat" class="anchor"></a>
</h3>
<p>Delta Chat is available for all major and some minor platforms:</p>
<ul>
<li>
<p>The <strong>official website</strong>, <a href="https://delta.chat/download">https://delta.chat/download</a> shows all options in detail</p>
</li>
<li>
<p>If unavailable, use the <strong>mirror</strong> at <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Open one of the following <strong>app stores and search for “Delta Chat”:</strong>
Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, iOS and macOS App Store, Microsoft Store</p>
</li>
<li>
<p>Check the <strong>package manager</strong> of your Linux distributions</p>
</li>
<li>
<p><strong>Android APKs</strong> are also available on <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a></p>
</li>
</ul>
<h3 id="how-are-delta-chat-developments-funded">
+37
View File
@@ -99,6 +99,7 @@
<li><a href="#delta-chat-es-compatible-con-protonmail--tutanota--criptext">¿Delta Chat es compatible con Protonmail / Tutanota / Criptext?</a></li>
<li><a href="#remove-account">¿Cómo puedo eliminar mi cuenta?</a></li>
<li><a href="#estoy-interesado-en-los-detalles-técnicos-pueden-decirme-más">Estoy interesado en los detalles técnicos. ¿Pueden decirme más?</a></li>
<li><a href="#where-can-my-friends-find-delta-chat">Where can my friends find Delta Chat?</a></li>
<li><a href="#cómo-se-financia-el-desarrollo-de-delta-chat">¿Cómo se financia el desarrollo de Delta Chat?</a></li>
</ul>
</li>
@@ -1277,6 +1278,12 @@ No es necesario un dispositivo para que el otro funcione.</p>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
<li>
<p>On <strong>iOS</strong>, make sure “System Settings / Apps / Delta Chat / <strong>Local Network</strong>” access is granted</p>
</li>
<li>
<p>On <strong>macOS</strong>, enable “System Settings / Privacy &amp; Security / <strong>Local Network</strong> / Delta Chat”</p>
</li>
<li>
<p>Your system might have a “personal firewall”,
which is known to cause problems (especially on Windows).
@@ -1394,6 +1401,7 @@ solo abre archivos adjuntos de remitentes en los que confía y no de spammers.</
<ul>
<li>In general, anyone can share webxdc apps with each
other without restrictions.</li>
<li>From <a href="https://webxdc.org/apps/">webxdc.org/apps</a>.</li>
<li>You can <a href="https://delta.chat/en/2023-08-11-xstore">send hi to xstore@testrun.org</a>
to see an experimental webxdc appstore.
All of the apps are open source and for free.</li>
@@ -1827,6 +1835,35 @@ Otherwise you might receive undecryptable messages from those group chats.</p>
<li>Visita la página <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">Estándares usados en Delta Chat</a>.</li>
</ul>
<h3 id="where-can-my-friends-find-delta-chat">
Where can my friends find Delta Chat? <a href="#where-can-my-friends-find-delta-chat" class="anchor"></a>
</h3>
<p>Delta Chat is available for all major and some minor platforms:</p>
<ul>
<li>
<p>The <strong>official website</strong>, <a href="https://delta.chat/download">https://delta.chat/download</a> shows all options in detail</p>
</li>
<li>
<p>If unavailable, use the <strong>mirror</strong> at <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Open one of the following <strong>app stores and search for “Delta Chat”:</strong>
Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, iOS and macOS App Store, Microsoft Store</p>
</li>
<li>
<p>Check the <strong>package manager</strong> of your Linux distributions</p>
</li>
<li>
<p><strong>Android APKs</strong> are also available on <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a></p>
</li>
</ul>
<h3 id="cómo-se-financia-el-desarrollo-de-delta-chat">
+37
View File
@@ -99,6 +99,7 @@
<li><a href="#delta-chat-est-il-compatible-avec-protonmail--tutanota--criptext-">Delta Chat est-il compatible avec Protonmail / Tutanota / Criptext ?</a></li>
<li><a href="#remove-account">Comment supprimer mon compte ?</a></li>
<li><a href="#les-détails-techniques-mintéressent-pouvez-vous-men-dire-plus-">Les détails techniques mintéressent. Pouvez-vous men dire plus ?</a></li>
<li><a href="#where-can-my-friends-find-delta-chat">Where can my friends find Delta Chat?</a></li>
<li><a href="#comment-est-financé-le-développement-de-delta-chat-">Comment est financé le développement de Delta Chat ?</a></li>
</ul>
</li>
@@ -1262,6 +1263,12 @@ Lun na pas besoin de lautre pour pouvoir fonctionner.</p>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
<li>
<p>On <strong>iOS</strong>, make sure “System Settings / Apps / Delta Chat / <strong>Local Network</strong>” access is granted</p>
</li>
<li>
<p>On <strong>macOS</strong>, enable “System Settings / Privacy &amp; Security / <strong>Local Network</strong> / Delta Chat”</p>
</li>
<li>
<p>Il se peut que votre système dispose dun “pare-feu personnalisé”,
source bien connue de dysfonctionnements (en particulier sur Windows).
@@ -1375,6 +1382,7 @@ you only open attachments from senders you trust, and not from spammers.</li>
<ul>
<li>In general, anyone can share webxdc apps with each
other without restrictions.</li>
<li>From <a href="https://webxdc.org/apps/">webxdc.org/apps</a>.</li>
<li>You can <a href="https://delta.chat/en/2023-08-11-xstore">send hi to xstore@testrun.org</a>
to see an experimental webxdc appstore.
All of the apps are open source and for free.</li>
@@ -1789,6 +1797,35 @@ Otherwise you might receive undecryptable messages from those group chats.</p>
<li>Consultez les <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">standards utilisés dans Delta Chat</a>.</li>
</ul>
<h3 id="where-can-my-friends-find-delta-chat">
Where can my friends find Delta Chat? <a href="#where-can-my-friends-find-delta-chat" class="anchor"></a>
</h3>
<p>Delta Chat is available for all major and some minor platforms:</p>
<ul>
<li>
<p>The <strong>official website</strong>, <a href="https://delta.chat/download">https://delta.chat/download</a> shows all options in detail</p>
</li>
<li>
<p>If unavailable, use the <strong>mirror</strong> at <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Open one of the following <strong>app stores and search for “Delta Chat”:</strong>
Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, iOS and macOS App Store, Microsoft Store</p>
</li>
<li>
<p>Check the <strong>package manager</strong> of your Linux distributions</p>
</li>
<li>
<p><strong>Android APKs</strong> are also available on <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a></p>
</li>
</ul>
<h3 id="comment-est-financé-le-développement-de-delta-chat-">
+38 -2
View File
@@ -99,6 +99,7 @@
<li><a href="#is-delta-chat-compatible-with-protonmail--tutanota--criptext">Is Delta Chat compatible with Protonmail / Tutanota / Criptext?</a></li>
<li><a href="#remove-account">How can I delete my account?</a></li>
<li><a href="#im-interested-in-the-technical-details-can-you-tell-me-more">Im interested in the technical details. Can you tell me more?</a></li>
<li><a href="#where-can-my-friends-find-delta-chat">Where can my friends find Delta Chat?</a></li>
<li><a href="#how-are-delta-chat-developments-funded">How are Delta Chat developments funded?</a></li>
</ul>
</li>
@@ -301,8 +302,7 @@ Archived chats remain accessible above the chat list or via search.</p>
</li>
</ul>
<p>To archive or pin a chat, long tap (Android), use the chats menu (Android/Desktop) or swipe to the left (iOS);
to mute a chat, use the chats menu (Android/Desktop) or the chats profile (iOS).</p>
<p>To use the functions, long tap or right click a chat in the chat list.</p>
<h3 id="what-does-the-green-dot-mean">
@@ -1315,6 +1315,12 @@ One device is not needed for the other to work.</p>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
<li>
<p>On <strong>iOS</strong>, make sure “System Settings / Apps / Delta Chat / <strong>Local Network</strong>” access is granted</p>
</li>
<li>
<p>On <strong>macOS</strong>, enable “System Settings / Privacy &amp; Security / <strong>Local Network</strong> / Delta Chat”</p>
</li>
<li>
<p>Your system might have a “personal firewall”,
which is known to cause problems (especially on Windows).
@@ -1432,6 +1438,7 @@ you only open attachments from senders you trust, and not from spammers.</li>
<ul>
<li>In general, anyone can share webxdc apps with each
other without restrictions.</li>
<li>From <a href="https://webxdc.org/apps/">webxdc.org/apps</a>.</li>
<li>You can <a href="https://delta.chat/en/2023-08-11-xstore">send hi to xstore@testrun.org</a>
to see an experimental webxdc appstore.
All of the apps are open source and for free.</li>
@@ -1883,6 +1890,35 @@ Otherwise you might receive undecryptable messages from those group chats.</p>
<li>See <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">Standards used in Delta Chat</a>.</li>
</ul>
<h3 id="where-can-my-friends-find-delta-chat">
Where can my friends find Delta Chat? <a href="#where-can-my-friends-find-delta-chat" class="anchor"></a>
</h3>
<p>Delta Chat is available for all major and some minor platforms:</p>
<ul>
<li>
<p>The <strong>official website</strong>, <a href="https://delta.chat/download">https://delta.chat/download</a> shows all options in detail</p>
</li>
<li>
<p>If unavailable, use the <strong>mirror</strong> at <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Open one of the following <strong>app stores and search for “Delta Chat”:</strong>
Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, iOS and macOS App Store, Microsoft Store</p>
</li>
<li>
<p>Check the <strong>package manager</strong> of your Linux distributions</p>
</li>
<li>
<p><strong>Android APKs</strong> are also available on <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a></p>
</li>
</ul>
<h3 id="how-are-delta-chat-developments-funded">
+36
View File
@@ -99,6 +99,7 @@
<li><a href="#delta-chat-è-compatibile-con-protonmail--tutanota--criptext">Delta Chat è compatibile con Protonmail / Tutanota / Criptext?</a></li>
<li><a href="#remove-account">Come posso eliminare il mio profilo?</a></li>
<li><a href="#sono-interessato-ai-dettagli-tecnici-mi-puoi-dire-di-più">Sono interessato ai dettagli tecnici. Mi puoi dire di più?</a></li>
<li><a href="#dove-possono-trovare-delta-chat-i-miei-amici">Dove possono trovare Delta Chat i miei amici?</a></li>
<li><a href="#come-viene-finanziato-lo-sviluppo-di-delta-chat">Come viene finanziato lo sviluppo di Delta Chat?</a></li>
</ul>
</li>
@@ -1302,6 +1303,12 @@ Un dispositivo non è necessario perché laltro funzioni.</p>
e assicurati che <strong>Rete Privata</strong> sia selezionata come “Tipo di profilo di rete”
(dopo il trasferimento è possibile ripristinare il valore originale)</p>
</li>
<li>
<p>Su <strong>iOS</strong>, assicurati che laccesso a “Impostazioni di Sistema / App / Delta Chat / <strong>Rete locale</strong>” sia concesso</p>
</li>
<li>
<p>Su <strong>macOS</strong>, abilita “Impostazioni di Sistema / Privacy &amp; Sicurezza / <strong>Rete locale</strong> / Delta Chat”</p>
</li>
<li>
<p>Il sistema potrebbe avere un “personal firewall”,
che è noto per causare problemi (soprattutto su Windows).
@@ -1845,6 +1852,35 @@ Altrimenti potresti ricevere messaggi non decifrabili da quelle chat di gruppo.<
<li>Vedi <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">Standard usati in Delta Chat</a>.</li>
</ul>
<h3 id="dove-possono-trovare-delta-chat-i-miei-amici">
Dove possono trovare Delta Chat i miei amici? <a href="#dove-possono-trovare-delta-chat-i-miei-amici" class="anchor"></a>
</h3>
<p>Delta Chat è disponibile per tutte le piattaforme principali e alcune minori:</p>
<ul>
<li>
<p>Il <strong>sito ufficiale</strong>, <a href="https://delta.chat/download">https://delta.chat/download</a> mostra tutte le opzioni in dettaglio</p>
</li>
<li>
<p>Se non disponibile, utilizzare <strong>mirror</strong> su <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Apri uno dei seguenti <strong>app store e cerca “Delta Chat”:</strong>
Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, iOS e macOS App Store, Microsoft Store</p>
</li>
<li>
<p>Controlla il <strong>gestore pacchetti</strong> delle tue distribuzioni Linux</p>
</li>
<li>
<p><strong>APK Android</strong> sono disponibili anche su <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a></p>
</li>
</ul>
<h3 id="come-viene-finanziato-lo-sviluppo-di-delta-chat">
+37
View File
@@ -99,6 +99,7 @@
<li><a href="#is-delta-chat-compatibel-met-protonmailtutanotacriptext">Is Delta Chat compatibel met Protonmail/Tutanota/Criptext?</a></li>
<li><a href="#remove-account">Hoe kan ik mijn account verwijderen?</a></li>
<li><a href="#ik-wil-graag-meer-weten-over-de-gebruikte-technieken-waar-kan-ik-meer-informatie-vinden">Ik wil graag meer weten over de gebruikte technieken. Waar kan ik meer informatie vinden?</a></li>
<li><a href="#where-can-my-friends-find-delta-chat">Where can my friends find Delta Chat?</a></li>
<li><a href="#hoe-wordt-de-ontwikkeling-van-delta-chat-gefinancierd">Hoe wordt de ontwikkeling van Delta Chat gefinancierd?</a></li>
</ul>
</li>
@@ -1306,6 +1307,12 @@ op beide apparaten</strong>. Hierdoor hoef je niet het ene apparaat bij de hand
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
<li>
<p>On <strong>iOS</strong>, make sure “System Settings / Apps / Delta Chat / <strong>Local Network</strong>” access is granted</p>
</li>
<li>
<p>On <strong>macOS</strong>, enable “System Settings / Privacy &amp; Security / <strong>Local Network</strong> / Delta Chat”</p>
</li>
<li>
<p>Wellicht is een firewall actief op je apparaat,
welke problemen kan veroorzaken (met name op Windows).
@@ -1423,6 +1430,7 @@ you only open attachments from senders you trust, and not from spammers.</li>
<ul>
<li>In general, anyone can share webxdc apps with each
other without restrictions.</li>
<li>From <a href="https://webxdc.org/apps/">webxdc.org/apps</a>.</li>
<li>You can <a href="https://delta.chat/en/2023-08-11-xstore">send hi to xstore@testrun.org</a>
to see an experimental webxdc appstore.
All of the apps are open source and for free.</li>
@@ -1868,6 +1876,35 @@ Otherwise you might receive undecryptable messages from those group chats.</p>
<li>Bekijk de pagina <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">Door Delta Chat gebruikte standaarden</a>.</li>
</ul>
<h3 id="where-can-my-friends-find-delta-chat">
Where can my friends find Delta Chat? <a href="#where-can-my-friends-find-delta-chat" class="anchor"></a>
</h3>
<p>Delta Chat is available for all major and some minor platforms:</p>
<ul>
<li>
<p>The <strong>official website</strong>, <a href="https://delta.chat/download">https://delta.chat/download</a> shows all options in detail</p>
</li>
<li>
<p>If unavailable, use the <strong>mirror</strong> at <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Open one of the following <strong>app stores and search for “Delta Chat”:</strong>
Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, iOS and macOS App Store, Microsoft Store</p>
</li>
<li>
<p>Check the <strong>package manager</strong> of your Linux distributions</p>
</li>
<li>
<p><strong>Android APKs</strong> are also available on <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a></p>
</li>
</ul>
<h3 id="hoe-wordt-de-ontwikkeling-van-delta-chat-gefinancierd">
+37 -2
View File
@@ -99,6 +99,7 @@
<li><a href="#czy-delta-chat-jest-kompatybilny-z-protonmail--tutanota--criptext">Czy Delta Chat jest kompatybilny z Protonmail / Tutanota / Criptext?</a></li>
<li><a href="#remove-account">Jak mogę usunąć swoje konto?</a></li>
<li><a href="#interesują-mnie-szczegóły-techniczne-możesz-powiedzieć-mi-coś-więcej">Interesują mnie szczegóły techniczne. Możesz powiedzieć mi coś więcej?</a></li>
<li><a href="#gdzie-moi-znajomi-mogą-znaleźć-delta-chat">Gdzie moi znajomi mogą znaleźć Delta Chat?</a></li>
<li><a href="#w-jaki-sposób-finansowany-jest-rozwój-delta-chat">W jaki sposób finansowany jest rozwój Delta Chat?</a></li>
</ul>
</li>
@@ -265,8 +266,7 @@
</li>
</ul>
<p>Aby zarchiwizować lub przypiąć czat, dotknij i przytrzymaj (Android), yj menu czatu (Android/Desktop) lub przesuń palcem w lewo (iOS);
aby wyciszyć czat, użyj menu czatu (Android/Desktop) lub profilu czatu (iOS).</p>
<p>Aby skorzystać z tych funkcji, przytrzymaj ej lub kliknij prawym przyciskiem myszy czat na liście czatów.</p>
<h3 id="co-oznacza-zielona-kropka">
@@ -941,6 +941,12 @@ W przypadku innych programów można znaleźć rozwiązanie online.</p>
<p>Na <strong>Windowsie</strong>, przejdź do <strong>Panel sterowania / Sieć i internet</strong> i upewnij się, że <strong>Sieć prywatna</strong> jest wybrana jako “Typ profilu sieci”
(po przeniesieniu możesz wrócić do pierwotnej wartości)</p>
</li>
<li>
<p>W systemie <strong>iOS</strong> upewnij się, że jest przydzielony dostęp do opcji „Ustawienia » Aplikacje » Delta Chat » <strong>Sieć lokalna</strong></p>
</li>
<li>
<p>W systemie <strong>macOS</strong> włącz „Ustawienia systemowe » Prywatność i bezpieczeństwo » <strong>Sieć lokalna</strong> » Delta Chat”</p>
</li>
<li>
<p>Twój system może mieć „zaporę ogniową”, o której wiadomo, że powoduje problemy (szczególnie w systemie Windows).
<strong>Wyłącz zaporę</strong> dla Delta Chat po obu stronach i spróbuj ponownie</p>
@@ -1031,6 +1037,7 @@ W przypadku innych programów można znaleźć rozwiązanie online.</p>
<ul>
<li>Ogólnie rzecz biorąc, każdy może udostępniać sobie aplikacje webxdc bez ograniczeń.</li>
<li>Od <a href="https://webxdc.org/apps/">webxdc.org/apps</a>.</li>
<li>Możesz <a href="https://delta.chat/en/2023-08-11-xstore">wysłać „hi” na adres xstore@testrun.org</a>, aby zobaczyć eksperymentalny sklep z aplikacjami webxdc. Wszystkie aplikacje są otwarto źródłowe i są bezpłatne.</li>
<li>Wiele osób pisze własne, aplikacje webxdc i publikuje je na <a href="https://support.delta.chat/c/webxdc/20">forum Delta Chat</a>.</li>
</ul>
@@ -1356,6 +1363,34 @@ jeśli chcesz sprawdzić, czy Twoje poświadczenia są przetwarzane w bezpieczny
<li>Zobacz <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">Standardy używane w Delta Chat</a>.</li>
</ul>
<h3 id="gdzie-moi-znajomi-mogą-znaleźć-delta-chat">
Gdzie moi znajomi mogą znaleźć Delta Chat? <a href="#gdzie-moi-znajomi-mogą-znaleźć-delta-chat" class="anchor"></a>
</h3>
<p>Delta Chat jest dostępny na wszystkich głównych i niektórych mniejszych platformach:</p>
<ul>
<li>
<p>Oficjalna <strong>strona internetowa</strong> <a href="https://delta.chat/download">https://delta.chat/download</a> pokazuje wszystkie opcje szczegółowo</p>
</li>
<li>
<p>Jeśli nie jest dostępna, użyj <strong>kopii strony</strong> pod adresem <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Otwórz jeden z następujących <strong>sklepów z aplikacjami i wyszukaj „Delta Chat”</strong>: Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, iOS i macOS App Store, Microsoft Store</p>
</li>
<li>
<p>Sprawdź <strong>menedżera pakietów</strong> swoich dystrybucji Linuksa</p>
</li>
<li>
<p><strong>Aplikacje na Androida</strong> są również dostępne na <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a></p>
</li>
</ul>
<h3 id="w-jaki-sposób-finansowany-jest-rozwój-delta-chat">
+38 -2
View File
@@ -99,6 +99,7 @@
<li><a href="#o-delta-chat-é-compatível-com-protonmail--tutanota--criptext">O Delta Chat é compatível com Protonmail / Tutanota / Criptext?</a></li>
<li><a href="#remove-account">How can I delete my account?</a></li>
<li><a href="#estou-interessado-nos-detalhes-técnicos-pode-me-dizer-mais">Estou interessado nos detalhes técnicos. Pode me dizer mais?</a></li>
<li><a href="#where-can-my-friends-find-delta-chat">Where can my friends find Delta Chat?</a></li>
<li><a href="#como-são-os-desenvolvimentos-do-delta-chat-financiados">Como são os desenvolvimentos do Delta Chat financiados?</a></li>
</ul>
</li>
@@ -297,8 +298,7 @@ Archived chats remain accessible above the chat list or via search.</p>
</li>
</ul>
<p>To archive or pin a chat, long tap (Android), use the chats menu (Android/Desktop) or swipe to the left (iOS);
to mute a chat, use the chats menu (Android/Desktop) or the chats profile (iOS).</p>
<p>To use the functions, long tap or right click a chat in the chat list.</p>
<h3 id="what-does-the-green-dot-mean">
@@ -1309,6 +1309,12 @@ One device is not needed for the other to work.</p>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
<li>
<p>On <strong>iOS</strong>, make sure “System Settings / Apps / Delta Chat / <strong>Local Network</strong>” access is granted</p>
</li>
<li>
<p>On <strong>macOS</strong>, enable “System Settings / Privacy &amp; Security / <strong>Local Network</strong> / Delta Chat”</p>
</li>
<li>
<p>Your system might have a “personal firewall”,
which is known to cause problems (especially on Windows).
@@ -1426,6 +1432,7 @@ you only open attachments from senders you trust, and not from spammers.</li>
<ul>
<li>In general, anyone can share webxdc apps with each
other without restrictions.</li>
<li>From <a href="https://webxdc.org/apps/">webxdc.org/apps</a>.</li>
<li>You can <a href="https://delta.chat/en/2023-08-11-xstore">send hi to xstore@testrun.org</a>
to see an experimental webxdc appstore.
All of the apps are open source and for free.</li>
@@ -1869,6 +1876,35 @@ Otherwise you might receive undecryptable messages from those group chats.</p>
<li>Veja <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">As normas usadas no Delta Chat</a>.</li>
</ul>
<h3 id="where-can-my-friends-find-delta-chat">
Where can my friends find Delta Chat? <a href="#where-can-my-friends-find-delta-chat" class="anchor"></a>
</h3>
<p>Delta Chat is available for all major and some minor platforms:</p>
<ul>
<li>
<p>The <strong>official website</strong>, <a href="https://delta.chat/download">https://delta.chat/download</a> shows all options in detail</p>
</li>
<li>
<p>If unavailable, use the <strong>mirror</strong> at <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Open one of the following <strong>app stores and search for “Delta Chat”:</strong>
Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, iOS and macOS App Store, Microsoft Store</p>
</li>
<li>
<p>Check the <strong>package manager</strong> of your Linux distributions</p>
</li>
<li>
<p><strong>Android APKs</strong> are also available on <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a></p>
</li>
</ul>
<h3 id="como-são-os-desenvolvimentos-do-delta-chat-financiados">
+66 -31
View File
@@ -87,7 +87,7 @@
<li><a href="#разное">Разное</a>
<ul>
<li><a href="#какие-разрешения-нужны-delta-chat">Какие разрешения нужны Delta Chat?</a></li>
<li><a href="#работает-ли-delta-chat-работу-с-моим-провайдером-электронной-почты">Работает ли Delta Chat работу с <em>моим</em> провайдером электронной почты?</a></li>
<li><a href="#работает-ли-delta-chat-с-моим-провайдером-электронной-почты">Работает ли Delta Chat с <em>моим</em> провайдером электронной почты?</a></li>
<li><a href="#я-хочу-управлять-своим-собственным-почтовым-сервером-для-delta-chat-что-вы-посоветуете">Я хочу управлять своим собственным почтовым сервером для Delta Chat. Что вы посоветуете?</a></li>
<li><a href="#почему-я-должен-вводить-свой-пароль-от-электронной-почты-в-delta-chat-это-безопасно">Почему я должен вводить свой пароль от электронной почты в Delta Chat? Это безопасно?</a></li>
<li><a href="#какие-сообщения-отображаются-в-delta-chat">Какие сообщения отображаются в Delta Chat?</a></li>
@@ -99,6 +99,7 @@
<li><a href="#совместим-ли-delta-chat-с-protonmail--tutanota--criptext">Совместим ли Delta Chat с Protonmail / Tutanota / Criptext?</a></li>
<li><a href="#remove-account">Как я могу удалить свой аккаунт?</a></li>
<li><a href="#меня-интересуют-технические-детали-можете-рассказать-больше">Меня интересуют технические детали. Можете рассказать больше?</a></li>
<li><a href="#где-мои-друзья-могут-найти-delta-chat">Где мои друзья могут найти Delta Chat?</a></li>
<li><a href="#как-финансируются-разработки-delta-chat">Как финансируются разработки Delta Chat?</a></li>
</ul>
</li>
@@ -217,7 +218,7 @@
</h3>
<p>Профиль - это <strong>имя, изображение</strong> и некоторая дополнительная информация для шифрования сообщений.
<p>Профиль это <strong>имя, изображение</strong> и некоторая дополнительная информация для шифрования сообщений.
Профиль существует только на вашем устройстве(ах)
и использует серверы chatmail или обычные серверы электронной почты для передачи сообщений.</p>
@@ -301,8 +302,7 @@
</li>
</ul>
<p>Чтобы архивировать или закрепить чат, нажмите на него долгим нажатием (Android), воспользуйтесь меню чата (Android/Desktop) или проведите пальцем влево (iOS);
чтобы отключить уведомления, воспользуйтесь меню чата (Android/Desktop) или профиль чата (iOS).</p>
<p>Чтобы использовать функции, нажмите долгим нажатием или щелкните правой кнопкой мыши по чату в списке чатов.</p>
<h3 id="что-означает-зеленая-точка">
@@ -350,7 +350,7 @@
приложение Delta Chat каждого участника чата позаботится
об удалении сообщений
по истечении выбранного периода времени.
Отсчет времени начинается
Отсчет времени начинается,
когда получатель впервые видит сообщение в Delta Chat.
Сообщения удаляются
как в каждой учетной записи электронной почты на сервере,
@@ -429,7 +429,7 @@
Если позже вы снова захотите присоединиться к группе, попросите другого участника группы добавить вас.</p>
</li>
<li>
<p>Или, вместо этого, вы можете “отключить уведомления” для группы - это означает, что вы будете получать все сообщения и сможете их писать, но больше не будете получать уведомления о новых сообщениях.</p>
<p>Или, вместо этого, вы можете “отключить уведомления” для группы это означает, что вы будете получать все сообщения и сможете их писать, но больше не будете получать уведомления о новых сообщениях.</p>
</li>
</ul>
@@ -474,7 +474,7 @@
По истечении этого срока все сообщения, полученные Delta Chat, будут удалены с сервера.</p>
</li>
<li>
<p>Обратите внимание, что если вы используете Delta Chat на нескольких устройствах,
<p>Обратите внимание, что, если вы используете Delta Chat на нескольких устройствах,
необходимо оставлять сообщения на сервере с достаточным временным интервалом,
чтобы другое устройство(а) тоже смогло их загрузить.</p>
</li>
@@ -575,8 +575,8 @@ Push-уведомления недоступны.</p>
</li>
<li>
<p>Принудительное соединение в фоновом режиме: это запасной вариант
если предыдущие варианты недоступны или или не обеспечивают “мгновенную доставку”.
Включение этого параметра вызывает постоянное уведомление на вашем телефоне
если предыдущие варианты недоступны или не обеспечивают “мгновенную доставку”.
Включение этого параметра вызывает постоянное уведомление на вашем телефоне,
которое иногда может быть “сжато” на последних телефонах Android.</p>
</li>
</ul>
@@ -642,7 +642,7 @@ Push-уведомления недоступны.</p>
</h3>
<p>Delta Chat - это бесплатный децентрализованный мессенджер с открытым исходным кодом и возможностью выбора сервера,
<p>Delta Chat это бесплатный децентрализованный мессенджер с открытым исходным кодом и возможностью выбора сервера,
но мы хотим, чтобы пользователи гарантированно получали “мгновенную доставку” сообщений,
такую же, что и в приложениях WhatsApp, Signal или Telegram,
не задавая вопросов, которые больше подходят для опытных пользователей или разработчиков.</p>
@@ -671,7 +671,7 @@ Push-уведомления недоступны.</p>
<p><a href="https://autocrypt.org">Autocrypt</a> используется для автоматической
установки сквозного шифрования при работе с контактами и групповыми чатами.
Autocrypt использует ограниченное и <a href="#openpgp-secure">безопасное подмножество стандарта OpenPGP</a>.
Сообщения, зашифрованные сквозным шифрованием помечаются замком
Сообщения, зашифрованные сквозным шифрованием, помечаются замком
<img style="vertical-align:middle; width:1.2em; margin:1px" src="../lock-icon.png" alt="padlock" />.</p>
<p><a href="https://securejoin.delta.chat/en/latest/new.html">Протоколы Secure-Join</a>
@@ -689,7 +689,7 @@ Autocrypt использует ограниченное и <a href="#openpgp-sec
</h3>
<p>Все сообщения зашифрованые сквозным шифрованием имеют значок замка:</p>
<p>Все сообщения, зашифрованные сквозным шифрованием, имеют значок замка:</p>
<p><img style="width:160px; margin:1px" src="../lock-screenshot.png" alt="Значок замка в пузырьке" /></p>
@@ -779,7 +779,7 @@ Autocrypt использует ограниченное и <a href="#openpgp-sec
В профиле контакта можно несколько раз нажать на текст “Представлен …” несколько раз
пока не дойдёте до того, с кем вы непосредственно провели <a href="#howtoe2ee">QR-сканирование</a>.</p>
<p>Обратите внимание, что в профиле контакта вы можете увидеть и присоеденённых знакомых
<p>Обратите внимание, что в профиле контакта вы можете увидеть и присоединённых знакомых,
но в заголовке профиля нет зелёной галочки.
Обычно это означает, что контакт <a href="#nocryptanymore">“отправил сообщение с другого устройства”</a>.</p>
@@ -797,10 +797,10 @@ Autocrypt использует ограниченное и <a href="#openpgp-sec
</h3>
<p>Ваш чат с контактом утратил гарантированное сквозное шифрование.
Зелёная галочка была удалёна для этого чата и контакта, и когда вы увидели предупреждение.
Зелёная галочка была удалена для этого чата и контакта, и когда вы увидели предупреждение.
<strong>Если вы обнаружили внезапное прекращение гарантированного сквозного шифрования
для этого контакта, не принимайте предупреждение!</strong>
Вместо этого свяжитесь со своим контактом по второму каналу
Вместо этого свяжитесь со своим контактом по второму каналу,
например, видеозвонок, другой мессенджер или телефонный звонок,
чтобы узнать, что произошло.</p>
@@ -935,7 +935,7 @@ Delta Chat вместо этого использует реализацию Ope
и возвращает <a href="https://docs.rs/pgp/latest/pgp/errors/enum.Error.html#variant.MdcError">ошибку</a>
если код обнаружения модификаций не совпадает.</p>
<p>Delta Chat также никогда не был уязвим к атаке “Direct Exfiltration” EFAIL
<p>Delta Chat также никогда не был уязвим к атаке “Direct Exfiltration” EFAIL,
потому что он расшифровывает только <code class="language-plaintext highlighter-rouge">многочастные/зашифрованные</code> сообщения,
которые содержат ровно одну зашифрованную и подписанную часть,
как определено спецификацией Autocrypt Level 1.</p>
@@ -1172,16 +1172,16 @@ Look for something like <strong>Start Autocrypt Setup Transfer</strong> in the s
</h3>
<p>Наиболее вероятная причина - это то, что ваш ключ зашифрован и/или имеет пароль.
<p>Наиболее вероятная причина это то, что ваш ключ зашифрован и/или имеет пароль.
Delta Chat не поддерживает такие ключи.
Вы можете снять шифрование пароля и пароль, а затем повторить попытку импорта.</p>
<p>Другая распространенная ошибка - неправильное расширение файла.
Используйте формат ASCII armored и расширение файла <code class="language-plaintext highlighter-rouge">.asc</code>.</p>
<p>Delta Chat поддерживает расспространённые форматы закрытых ключей OpenPGP, однако
<p>Delta Chat поддерживает распространённые форматы закрытых ключей OpenPGP, однако
маловероятно, что мы будем поддерживать все закрытые ключи из любых источников. Это
не является основной целью Delta Chat. На самом деле, подавляющее большинство пользователей 
не является основной целью Delta Chat. На самом деле, подавляющее большинство пользователей
Delta Chat не будут иметь никакого ключа, прежде чем они начнут использовать его.
Однако мы стремимся поддерживать закрытые ключи из как можно большего числа источников.</p>
@@ -1305,13 +1305,19 @@ Chat <a href="https://github.com/rpgp/rpgp">PGP</a> и
<ul>
<li>
<p>Перепроверьте, что оба устройства находятся в <strong>в одной Wi-Fi или локальной сети</strong>.</p>
<p>Перепроверьте, что оба устройства находятся <strong>в одной Wi-Fi или локальной сети</strong>.</p>
</li>
<li>
<p>В <strong>Windows</strong> перейдите в <strong>Панель управления / Сеть и Интернет</strong>
и убедитесь, что в качестве “Типа сетевого профиля” выбрана <strong>Частная сеть</strong>.
(после передачи, вы можете изменить обратно на исходное значение)</p>
</li>
<li>
<p>На <strong>iOS</strong>, убедитесь, что предоставлен доступ “Настройки системы / Приложения / Delta Chat / <strong>Локальная сеть</strong></p>
</li>
<li>
<p>На <strong>macOS</strong>, включите “Системные настройки / Конфиденциальность и безопасность / <strong>Локальная сеть</strong> / Delta Chat”</p>
</li>
<li>
<p>В вашей системе может быть установлен “персональный брандмауэр”,
который может вызвать проблемы (особенно на Windows).
@@ -1407,12 +1413,12 @@ PIN-код разблокировки экрана, графический кл
</h3>
<ul>
<li>Приложения webxdc webxdc не могут отправлять данные в Интернет или скачивать что-либо.</li>
<li>Приложения webxdc не могут отправлять данные в Интернет или скачивать что-либо.</li>
<li>Приложение webxdc может обмениваться данными только внутри чата Delta Chat, с его
копиями на устройствах ваших собеседников по чату.
В остальном, оно полностью
изолировано от Интернета.</li>
<li>Конфиденциальность, которую обеспечивает приложение webxdc - это конфиденциальность вашего чата - пока
<li>Конфиденциальность, которую обеспечивает приложение webxdc это конфиденциальность вашего чата - пока
вы доверяете людям, с которыми вы общаетесь, вы можете доверять приложению webxdc.</li>
<li>Это также означает, что открытие приложений webxdc в чатах с участниками, которым вы
не доверяете, тоже самое, что и с вложениями электронной почты, когда вы открываете
@@ -1430,11 +1436,11 @@ PIN-код разблокировки экрана, графический кл
<ul>
<li>В общем, каждый может поделиться приложениями webxdc
друг с другом без ограничений.</li>
<li>Из <a href="https://webxdc.org/apps/">webxdc.org/apps</a>.</li>
<li>Вы можете <a href="https://delta.chat/en/2023-08-11-xstore">отправить hi на xstore@testrun.org</a>
чтобы увидеть экспериментальный магазин приложений webxdc.
Все приложения с открытым исходным кодом и бесплатны.</li>
<li>Многие люди пишут свои собственные приложения webxdc и публикуют их на <a href="https://support.delta.chat/c/webxdc/20">форуме
Delta Chat</a>.</li>
<li>Многие люди создают свои собственные приложения webxdc и публикуют их на <a href="https://support.delta.chat/c/webxdc/20">форуме Delta Chat</a>.</li>
</ul>
<h3 id="как-я-могу-создать-свои-собственные-приложения-webxdc">
@@ -1446,7 +1452,7 @@ Delta Chat</a>.</li>
</h3>
<ul>
<li>Приложения webxdc - это просто архивы zip, содержащие код html, css и javascript.</li>
<li>Приложения webxdc это просто архивы zip, содержащие код html, css и javascript.</li>
<li>Вы можете расширить <a href="https://github.com/webxdc/hello">пример приложения Hello World</a>
для начала.</li>
<li>Все остальное, что вам нужно знать, написано в
@@ -1572,7 +1578,7 @@ Delta Chat</a>.</li>
<ol>
<li>
<p>Измените свой адрес в разделе “Настройки → Дополнительные параметры → Пароль и аккаунт” и
введите пароль вашей новой электронной почты (и если необходимо, настройки сервера).
введите пароль вашей новой электронной почты (и, если необходимо, настройки сервера).
Вы получите информационное сообщение о том, что переходите на новый адрес.
Дополнительное уведомление также появится в вашем чате “Сообщения устройства”.</p>
</li>
@@ -1656,10 +1662,10 @@ Delta Chat</a>.</li>
</li>
</ul>
<h3 id="работает-ли-delta-chat-работу-с-моим-провайдером-электронной-почты">
<h3 id="работает-ли-delta-chat-с-моим-провайдером-электронной-почты">
Работает ли Delta Chat работу с <em>моим</em> провайдером электронной почты? <a href="#работает-ли-delta-chat-работу-с-моим-провайдером-электронной-почты" class="anchor"></a>
Работает ли Delta Chat с <em>моим</em> провайдером электронной почты? <a href="#работает-ли-delta-chat-с-моим-провайдером-электронной-почты" class="anchor"></a>
</h3>
@@ -1791,7 +1797,7 @@ Delta Chat необходим пароль, чтобы использовать
</h3>
<p>Единственная причина, по которой вы можете захотеть наблюдать за папкой “Отправленные”, - это если вы используете другую
<p>Единственная причина, по которой вы можете захотеть наблюдать за папкой “Отправленные”, это если вы используете другую
почтовую программу (например, Thunderbird) вместе с приложением Delta Chat, и хотите, чтобы ваш почтовый клиент
мог участвовать в разговорах в чате.</p>
@@ -1879,6 +1885,35 @@ Chat.</li>
<li>Смотрите <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">Стандарты, используемые в Delta Chat</a>.</li>
</ul>
<h3 id="где-мои-друзья-могут-найти-delta-chat">
Где мои друзья могут найти Delta Chat? <a href="#где-мои-друзья-могут-найти-delta-chat" class="anchor"></a>
</h3>
<p>Delta Chat доступен на всех популярных и некоторых менее известных платформах:</p>
<ul>
<li>
<p>На <strong>официальной странице загрузки</strong>, <a href="https://delta.chat/download">https://delta.chat/download</a> можно найти подробную информацию о всех вариантах</p>
</li>
<li>
<p>Если основной сайт недоступен, используйте <strong>зеркало</strong> <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Откройте один из следующих <strong>онлайн-магазинов приложений и введите запрос “Delta Chat”:</strong>
Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, App Store для iOS и macOS, Microsoft Store</p>
</li>
<li>
<p>Проверьте <strong>менеджер пакетов</strong> вашего дистрибутива Linux</p>
</li>
<li>
<p>Файлы <strong>APK для Android</strong> доступны на <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a></p>
</li>
</ul>
<h3 id="как-финансируются-разработки-delta-chat">
@@ -1895,7 +1930,7 @@ Chat.</li>
основанную на Свободном и Открытом исходном коде.</p>
<p>В частности, разработка Delta Chat финансировалась из следующих источников,
перечисленны в хронологическом порядке:</p>
перечислены в хронологическом порядке:</p>
<ul>
<li>
@@ -1910,7 +1945,7 @@ Chat.</li>
и выпустили первую бета-версию приложения для настольных систем, а также провели
исследования в области UX в контексте прав человека,
см. наш заключительный отчет <a href="https://delta.chat/en/2019-07-19-uxreport">Needfinding and UX report</a>.
Второй грант, полученый в 2019/2020 году (~$300тыс.), помог нам
Второй грант, полученный в 2019/2020 году (~$300 тыс.), помог нам
выпустить версии Delta/iOS, перевести наш основной код на Rust и
предоставить новые функции для всех платформ.</p>
</li>
+38 -2
View File
@@ -99,6 +99,7 @@
<li><a href="#je-delta-chat-kompatibilný-s-protonmail--tutanota--criptext">Je Delta Chat kompatibilný s Protonmail / Tutanota / Criptext?</a></li>
<li><a href="#remove-account">How can I delete my account?</a></li>
<li><a href="#zaujímajú-ma-technické-detaily-môžete-mi-povedať-viac">Zaujímajú ma technické detaily. Môžete mi povedať viac?</a></li>
<li><a href="#where-can-my-friends-find-delta-chat">Where can my friends find Delta Chat?</a></li>
<li><a href="#ako-sa-financuje-vývoj-delta-chat">Ako sa financuje vývoj Delta Chat?</a></li>
</ul>
</li>
@@ -301,8 +302,7 @@ Archived chats remain accessible above the chat list or via search.</p>
</li>
</ul>
<p>To archive or pin a chat, long tap (Android), use the chats menu (Android/Desktop) or swipe to the left (iOS);
to mute a chat, use the chats menu (Android/Desktop) or the chats profile (iOS).</p>
<p>To use the functions, long tap or right click a chat in the chat list.</p>
<h3 id="what-does-the-green-dot-mean">
@@ -1315,6 +1315,12 @@ One device is not needed for the other to work.</p>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
<li>
<p>On <strong>iOS</strong>, make sure “System Settings / Apps / Delta Chat / <strong>Local Network</strong>” access is granted</p>
</li>
<li>
<p>On <strong>macOS</strong>, enable “System Settings / Privacy &amp; Security / <strong>Local Network</strong> / Delta Chat”</p>
</li>
<li>
<p>Your system might have a “personal firewall”,
which is known to cause problems (especially on Windows).
@@ -1432,6 +1438,7 @@ you only open attachments from senders you trust, and not from spammers.</li>
<ul>
<li>In general, anyone can share webxdc apps with each
other without restrictions.</li>
<li>From <a href="https://webxdc.org/apps/">webxdc.org/apps</a>.</li>
<li>You can <a href="https://delta.chat/en/2023-08-11-xstore">send hi to xstore@testrun.org</a>
to see an experimental webxdc appstore.
All of the apps are open source and for free.</li>
@@ -1879,6 +1886,35 @@ Otherwise you might receive undecryptable messages from those group chats.</p>
<li>Pozrite si <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">Štandardy používané v Delta Chate</a>.</li>
</ul>
<h3 id="where-can-my-friends-find-delta-chat">
Where can my friends find Delta Chat? <a href="#where-can-my-friends-find-delta-chat" class="anchor"></a>
</h3>
<p>Delta Chat is available for all major and some minor platforms:</p>
<ul>
<li>
<p>The <strong>official website</strong>, <a href="https://delta.chat/download">https://delta.chat/download</a> shows all options in detail</p>
</li>
<li>
<p>If unavailable, use the <strong>mirror</strong> at <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Open one of the following <strong>app stores and search for “Delta Chat”:</strong>
Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, iOS and macOS App Store, Microsoft Store</p>
</li>
<li>
<p>Check the <strong>package manager</strong> of your Linux distributions</p>
</li>
<li>
<p><strong>Android APKs</strong> are also available on <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a></p>
</li>
</ul>
<h3 id="ako-sa-financuje-vývoj-delta-chat">
+36
View File
@@ -99,6 +99,7 @@
<li><a href="#a-është-i-përputhshëm-delta-chat-i-me-protonmail-in--tutanota-n--criptext-in">A është i përputhshëm Delta Chat-i me Protonmail-in / Tutanota-n / Criptext-in?</a></li>
<li><a href="#remove-account">Si mund ta fshij llogarinë time?</a></li>
<li><a href="#më-interesojnë-hollësitë-teknike-mund-të-më-tregoni-diçka-më-tepër">Më interesojnë hollësitë teknike. Mund të më tregoni diçka më tepër?</a></li>
<li><a href="#where-can-my-friends-find-delta-chat">Where can my friends find Delta Chat?</a></li>
<li><a href="#si-financohet-zhvillimi-i-delta-chat-it">Si financohet zhvillimi i Delta Chat-it?</a></li>
</ul>
</li>
@@ -1315,6 +1316,12 @@ Njëra pajisja ska nevojë për tjetrën që të funksionojë.</p>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
<li>
<p>On <strong>iOS</strong>, make sure “System Settings / Apps / Delta Chat / <strong>Local Network</strong>” access is granted</p>
</li>
<li>
<p>On <strong>macOS</strong>, enable “System Settings / Privacy &amp; Security / <strong>Local Network</strong> / Delta Chat”</p>
</li>
<li>
<p>Sistemi juaj mund të ketë një “firewall personal”,
që dihet se shkakton probleme (veçanërisht në Windows).
@@ -1890,6 +1897,35 @@ Otherwise you might receive undecryptable messages from those group chats.</p>
<li>Shihni <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">Standarde të përdorur në Delta Chat</a>.</li>
</ul>
<h3 id="where-can-my-friends-find-delta-chat">
Where can my friends find Delta Chat? <a href="#where-can-my-friends-find-delta-chat" class="anchor"></a>
</h3>
<p>Delta Chat is available for all major and some minor platforms:</p>
<ul>
<li>
<p>The <strong>official website</strong>, <a href="https://delta.chat/download">https://delta.chat/download</a> shows all options in detail</p>
</li>
<li>
<p>If unavailable, use the <strong>mirror</strong> at <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Open one of the following <strong>app stores and search for “Delta Chat”:</strong>
Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, iOS and macOS App Store, Microsoft Store</p>
</li>
<li>
<p>Check the <strong>package manager</strong> of your Linux distributions</p>
</li>
<li>
<p><strong>Android APKs</strong> are also available on <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a></p>
</li>
</ul>
<h3 id="si-financohet-zhvillimi-i-delta-chat-it">
+41 -4
View File
@@ -99,6 +99,7 @@
<li><a href="#чи-сумісний-delta-chat-із-protonmail--tutanota--criptext">Чи сумісний Delta Chat із Protonmail / Tutanota / Criptext?</a></li>
<li><a href="#remove-account">Як мені видалити свій обліковий запис?</a></li>
<li><a href="#мене-цікавлять-технічні-деталі-можете-розповісти-більше">Мене цікавлять технічні деталі. Можете розповісти більше?</a></li>
<li><a href="#де-мої-друзі-можуть-знайти-delta-chat">Де мої друзі можуть знайти Delta Chat?</a></li>
<li><a href="#як-фінансується-розробка-delta-chat">Як фінансується розробка Delta Chat?</a></li>
</ul>
</li>
@@ -267,7 +268,7 @@
</li>
</ul>
<p>Щоб заархівувати або закріпити чат, довго торкніться (Android), скористайтеся меню чату (Android/комп’ютер) або проведіть пальцем ліворуч (iOS); щоб вимкнути звук чату, скористайтеся меню чату (Android/комп’ютер) або профілем чату (iOS).</p>
<p>Щоб скористатися функціями, утримуйте натиснутою клавішу або клацніть правою кнопкою миші на чаті у списку чатів.</p>
<h3 id="що-означає-зелена-точка">
@@ -944,6 +945,12 @@ Look for something like <strong>Start Autocrypt Setup Transfer</strong> in the s
<li>
<p>У <strong>Windows</strong> перейдіть до <strong>Панель керування / Мережа та Інтернет</strong> і переконайтеся, що <strong>Приватна мережа</strong> вибрано як “Тип мережевого профілю” (після перенесення ви можете повернути початкове значення)</p>
</li>
<li>
<p>На <strong>iOS</strong> переконайтеся, що доступ до “Системні налаштування / Програми / Delta Chat / <strong>Локальна мережа</strong>” дозволено</p>
</li>
<li>
<p>У <strong>macOS</strong> увімкніть “Системні налаштування / Конфіденційність і безпека / <strong>Локальна мережа</strong> / Delta Chat”</p>
</li>
<li>
<p>Ваша система може мати “персональний брандмауер”, який, як відомо, викликає проблеми (особливо у Windows). <strong>Вимкніть персональний брандмауер</strong> для Delta Chat на обох кінцях і повторіть спробу</p>
</li>
@@ -1032,9 +1039,11 @@ Look for something like <strong>Start Autocrypt Setup Transfer</strong> in the s
</h3>
<ul>
<li>Загалом, будь-хто може ділитися додатками webxdc один з одним один з одним без обмежень.</li>
<li>Ви можете <a href="https://delta.chat/en/2023-08-11-xstore">надіслати “hi” на xstore@testrun.org</a> щоб побачити експериментальний магазин додатків webxdc. Всі програми мають відкритий вихідний код і є безкоштовними.</li>
<li>Багато людей пишуть власні програми для webxdc і публікують їх на <a href="https://support.delta.chat/c/webxdc/20">форумі Delta Chat</a>.</li>
<li>Загалом, будь-хто може ділитися додатками webxdc один з одним без обмежень.</li>
<li>З <a href="https://webxdc.org/apps/">webxdc.org/apps</a>.</li>
<li>Ви можете <a href="https://delta.chat/en/2023-08-11-xstore">надіслати “привіт” на xstore@testrun.org</a> щоб побачити експериментальний магазин додатків webxdc.
Всі програми мають відкритий вихідний код і є безкоштовними.</li>
<li>Багато людей пишуть власні програми webxdc і публікують їх на <a href="https://support.delta.chat/c/webxdc/20">форумі Delta Chat</a>.</li>
</ul>
<h3 id="як-я-можу-створювати-власні-програми-webxdc">
@@ -1374,6 +1383,34 @@ Look for something like <strong>Start Autocrypt Setup Transfer</strong> in the s
<li>Дивіться <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">Стандарти, що використовуються у Delta Chat</a>.</li>
</ul>
<h3 id="де-мої-друзі-можуть-знайти-delta-chat">
Де мої друзі можуть знайти Delta Chat? <a href="#де-мої-друзі-можуть-знайти-delta-chat" class="anchor"></a>
</h3>
<p>Delta Chat доступний для всіх основних і деяких другорядних платформ:</p>
<ul>
<li>
<p>На <strong>офіційному сайті</strong>, <a href="https://delta.chat/download">https://delta.chat/download</a> детально описані всі варіанти</p>
</li>
<li>
<p>Якщо вона недоступна, скористайтеся <strong>дзеркалом</strong> за адресою <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Відкрийте один з наступних <strong>магазинів додатків і введіть у пошук “Delta Chat”:</strong> Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, iOS та macOS App Store, Microsoft Store</p>
</li>
<li>
<p>Перевірте <strong>менеджер пакунків</strong> вашого дистрибутива Linux</p>
</li>
<li>
<p><strong>APK для Android</strong> також доступні на <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a></p>
</li>
</ul>
<h3 id="як-фінансується-розробка-delta-chat">
+87 -62
View File
@@ -27,9 +27,9 @@
</li>
<li><a href="#instant-delivery">Instant message delivery and Push Notifications</a>
<ul>
<li><a href="#what-are-push-notifications-how-can-i-get-instant-message-delivery">What are Push Notifications? How can I get instant message delivery?</a></li>
<li><a href="#are-push-notifications-enabled-on-ios-devices-is-there-an-alternative">Are Push Notifications enabled on iOS devices? Is there an alternative?</a></li>
<li><a href="#android-push">Are Push notifications enabled / needed on Android devices?</a></li>
<li><a href="#什么是推送通知如何获得即时消息传递">什么是推送通知?如何获得即时消息传递?</a></li>
<li><a href="#ios-设备上是否启用了推送通知我还有其他的选择吗">iOS 设备上是否启用了推送通知?我还有其他的选择吗?</a></li>
<li><a href="#android-设备上是否启用需要推送通知android-push">Android 设备上是否启用/需要推送通知?{#android-push}</a></li>
<li><a href="#privacy-notifications">How private are Delta Chat Push Notifications?</a></li>
<li><a href="#why-does-delta-chat-integrate-with-centralized-proprietary-applegoogle-push-services">Why does Delta Chat integrate with centralized proprietary Apple/Google push services?</a></li>
</ul>
@@ -44,7 +44,7 @@
<li><a href="#are-attachments-pictures-files-audio-etc-end-to-end-encrypted">Are attachments (pictures, files, audio etc.) end-to-end encrypted?</a></li>
<li><a href="#openpgp-secure">Is OpenPGP secure?</a></li>
<li><a href="#openpgp-alternatives">Did you consider using alternatives to OpenPGP for end-to-end -encryption?</a></li>
<li><a href="#is-delta-chat-vulnerable-to-efail">Is Delta Chat vulnerable to EFAIL?</a></li>
<li><a href="#delta-chat-是否容易受到-efail-攻击">Delta Chat 是否容易受到 EFAIL 攻击?</a></li>
<li><a href="#tls">Is a message exposed in cleartext if end-to-end encryption is not available?</a></li>
<li><a href="#message-metadata">How does Delta Chat protect metadata in messages?</a></li>
<li><a href="#device-seizure">How to protect metadata and contacts when a device is seized?</a></li>
@@ -53,7 +53,7 @@
<li><a href="#why-do-i-see-unencrypted-messages">Why do I see unencrypted messages?</a></li>
<li><a href="#how-can-i-get-an-end-to-end-encrypted-chat-with-a-delta-chat-contact-who-sometimes-uses-webmail-or-another-non-autocrypt-e-mail-app">How can i get an end-to-end encrypted chat with a Delta Chat contact who sometimes uses webmail or another non-Autocrypt e-mail app?</a></li>
<li><a href="#how-can-i-ensure-message-end-to-end-encryption-and-deletion">How can I ensure message end-to-end encryption and deletion?</a></li>
<li><a href="#pfs">Does Delta Chat support Perfect Forward Secrecy?</a></li>
<li><a href="#pfs">Delta Chat 是否支持完美前向保密?</a></li>
<li><a href="#is-end-to-end-encryption-of-delta-chat-as-safe-as-signal">Is end-to-end encryption of Delta Chat as safe as Signal?</a></li>
<li><a href="#importkey">我可以重复使用现有的私钥吗?</a></li>
<li><a href="#我无法将现有的-pgp-密钥导入-delta-chat">我无法将现有的 PGP 密钥导入 Delta Chat。</a></li>
@@ -99,6 +99,7 @@
<li><a href="#delta-chat-与-protonmail--tutanota--criptext-兼容吗">Delta Chat 与 Protonmail / Tutanota / Criptext 兼容吗?</a></li>
<li><a href="#remove-account">如何删除我的账户?</a></li>
<li><a href="#我对技术细节很感兴趣能告诉我更多吗">我对技术细节很感兴趣。能告诉我更多吗?</a></li>
<li><a href="#where-can-my-friends-find-delta-chat">Where can my friends find Delta Chat?</a></li>
<li><a href="#delta-chat-的开发是如何被资助的">Delta Chat 的开发是如何被资助的?</a></li>
</ul>
</li>
@@ -337,10 +338,8 @@ The messages are deleted
both in each email account on the server,
and in the app itself.</p>
<p>Note that you can rely on disappearing messages
only as long as you trust your chat partners;
malicious chat partners can take photos,
or otherwise save, copy or forward messages before deletion.</p>
<p>请注意,只有当您信任您的聊天伙伴时,您才可以依赖“消息定时销毁”;
不怀好意的人可能会拍照,或者在删除之前以其他方式保存、复制或转发消息。</p>
<p>Apart from that,
if one chat partner uninstalls Delta Chat,
@@ -473,64 +472,59 @@ Delta Chat 收到的所有电子邮件都将在此期限后从服务器中删除
</h2>
<h3 id="what-are-push-notifications-how-can-i-get-instant-message-delivery">
<h3 id="什么是推送通知如何获得即时消息传递">
What are Push Notifications? How can I get instant message delivery? <a href="#what-are-push-notifications-how-can-i-get-instant-message-delivery" class="anchor"></a>
什么是推送通知?如何获得即时消息传递? <a href="#什么是推送通知如何获得即时消息传递" class="anchor"></a>
</h3>
<p>Push Notifications are sent by Apple and Google “Push services” to a users device
so that an inactive Delta Chat app can fetch messages in the background
and show notifications on a users phone if needed.</p>
<p>推送通知由 Apple Google 的“推送服务”发送到用户的设备,以便非活动状态的 Delta Chat 应用可以在后台获取消息,并在需要时在用户的手机上显示通知。</p>
<p>Push Notifications work with all <a href="https://delta.chat/chatmail">chatmail</a> servers on</p>
<p>推送通知适用于以下所有 <a href="https://delta.chat/chatmail">chatmail</a> 服务器:</p>
<ul>
<li>
<p>iOS devices, by integrating with Apple Push services.</p>
<p>iOS 设备通过与 Apple Push 服务集成。</p>
</li>
<li>
<p>Android devices, by integrating with the Google FCM Push service,
including on devices that use <a href="https://microg.org">microG</a>
instead of proprietary Google code on the phone.</p>
<p>Android 设备,通过与 Google FCM Push 服务集成,
包括使用 <a href="https://microg.org">microG</a>
而不是手机上专有 Google 代码的设备。</p>
</li>
</ul>
<p>As of May 2024, classic e-mail servers do not support Push Notifications
for Delta Chat users.</p>
<p>自 2024 年 5 月起,传统电子邮件服务器不再支持 Delta Chat 用户的推送通知。</p>
<h3 id="are-push-notifications-enabled-on-ios-devices-is-there-an-alternative">
<h3 id="ios-设备上是否启用了推送通知我还有其他的选择吗">
Are Push Notifications enabled on iOS devices? Is there an alternative? <a href="#are-push-notifications-enabled-on-ios-devices-is-there-an-alternative" class="anchor"></a>
iOS 设备上是否启用了推送通知?我还有其他的选择吗? <a href="#ios-设备上是否启用了推送通知我还有其他的选择吗" class="anchor"></a>
</h3>
<p>Yes, Delta Chat automatically uses Push Notifications for <a href="https://delta.chat/chatmail">chatmail</a> profiles.
And no, there is no alternative on Apples phones to achieve instant message delivery
because Apple devices do not allow Delta Chat to fetch data in the background.
Push notifications are automatically activated for iOS users because
<a href="#privacy-notifications">Delta Chats privacy-preserving Push Notification system</a>
does not expose data to Apple that it doesnt already have.</p>
<p>是的,Delta Chat 会自动使用推送通知来接收 <a href="https://delta.chat/chatmail">chatmail</a> 个人资料。
而且,Apple 手机上没有其他方式可以实现即时消息传递
因为 Apple 设备不允许 Delta Chat 在后台获取数据。
推送通知会自动为 iOS 用户激活,因为
<a href="#privacy-notifications">Delta Chat 的隐私保护推送通知系统</a>
不会向 Apple 泄露其尚未拥有的数据。</p>
<h3 id="android-push">
<h3 id="android-设备上是否启用需要推送通知android-push">
Are Push notifications enabled / needed on Android devices? <a href="#android-push" class="anchor"></a>
Android 设备上是否启用/需要推送通知?{#android-push} <a href="#android-设备上是否启用需要推送通知android-push" class="anchor"></a>
</h3>
<p>If a “Push Service” is available, Delta Chat enables Push Notifications
to achieve instant message delivery for all chatmail users.
If you are using a classic e-mail provider instead of <a href="https://delta.chat/chatmail">chatmail</a> servers,
Push Notifications are not available.</p>
<p>如果有“推送服务”可用,Delta Chat 将启用推送通知,
以实现所有聊天邮件用户的即时消息传递。
如果您使用的是传统电子邮件提供商而不是 <a href="https://delta.chat/chatmail">chatmail</a> 服务器,则无法使用推送通知。</p>
<p>In the Delta Chat “Notifications” settings for “Instant delivery”
you can change the following settings effecting all chat profiles:</p>
<p> Delta Chat“通知”的“推送通知”设置中,您可以更改以下影响所有聊天配置文件的设置:</p>
<ul>
<li>
@@ -710,17 +704,13 @@ both sides will eventually see a (group or direct) chat with a green checkmark
next to the title.</p>
</li>
<li>
<p>If one of the devices is offline, the green checkmarks will only
appear later when the device is internet-connected again
and the Secure-Join network protocol completed.</p>
<p>如果其中一个设备处于离线状态,则只有当设备再次连接互联网且安全加入网络协议完成时,绿色复选标记才会出现。</p>
</li>
</ul>
<p>Congratulations!
You now will automatically use guaranteed end-to-end encryption
with this contact and both of you can add each other to green-checkmarked groups
<img style="vertical-align:middle; width:1.5em; margin:1px" src="../green-checkmark.png" alt="green checkmark" />,
thereby automatically spreading guaranteed end-to-end encryption among its members.</p>
<p>恭喜!
从现在开始您将会自动使用安全的端到端加密与此联系人联系,并且双方都可以将对方添加到绿色复选标记的群组
<img style="vertical-align:middle; width:1.5em; margin:1px" src="../green-checkmark.png" alt="green checkmark" />,从而自动在其成员之间传播有保障的端到端加密。</p>
<h3 id="e2eeguarantee">
@@ -891,19 +881,18 @@ only one part of “user outcomes”,
see also our answers to <a href="#device-seizure">device-seizure</a>
and <a href="#message-metadata">message-metadata</a> questions.</p>
<h3 id="is-delta-chat-vulnerable-to-efail">
<h3 id="delta-chat-是否容易受到-efail-攻击">
Is Delta Chat vulnerable to EFAIL? <a href="#is-delta-chat-vulnerable-to-efail" class="anchor"></a>
Delta Chat 是否容易受到 EFAIL 攻击? <a href="#delta-chat-是否容易受到-efail-攻击" class="anchor"></a>
</h3>
<p>No, <a href="https://delta.chat/en/2018-05-15-delta-chat-not-vulnerable-to-efail">Delta Chat never was vulnerable to EFAIL</a>
because its OpenPGP implementation <a href="https://github.com/rpgp/rpgp">rPGP</a>
uses Modification Detection Code when encrypting messages
and returns <a href="https://docs.rs/pgp/latest/pgp/errors/enum.Error.html#variant.MdcError">an error</a>
if the Modification Detection Code is incorrect.</p>
<p>不,<a href="https://delta.chat/en/2018-05-15-delta-chat-not-vulnerable-to-efail">Delta Chat 从未受到任何 EFAIL 攻击</a>
因为所使用的 OpenPGP 实现了 <a href="https://github.com/rpgp/rpgp">rPGP</a>
在加密消息时“修改检测代码”
并且如果“修改检测代码”不正确则返回 <a href="https://docs.rs/pgp/latest/pgp/errors/enum.Error.html#variant.MdcError">错误</a></p>
<p>Delta Chat also never was vulnerable to the “Direct Exfiltration” EFAIL attack
because it only decrypts <code class="language-plaintext highlighter-rouge">multipart/encrypted</code> messages
@@ -1072,21 +1061,21 @@ you can also turn on <a href="#delold">“delete messages from server automatica
<h3 id="pfs">
Does Delta Chat support Perfect Forward Secrecy? <a href="#pfs" class="anchor"></a>
Delta Chat 是否支持完美前向保密? <a href="#pfs" class="anchor"></a>
</h3>
<p>No, Delta Chat doesnt support Perfect Forward Secrecy (PFS).
This means that if your Delta Chat private decryption key is leaked,
and someone has collected your prior in-transit messages,
they will be able to decrypt and read them using the leaked decryption key.</p>
<p>不,Delta Chat 不支持完美前向保密 (PFS)
这意味着,如果您的 Delta Chat 私人解密密钥泄露,
并且有人收集了您之前传输的消息,
他们将能够使用泄露的解密密钥解密并阅读这些消息。</p>
<p>Note, however, that if anyone obtains to your decryption keys,
they will typically also be able to obtain your messages,
irrespective if Perfect Forward Secrecy is in place or not.
The typical real-world situation for leaked decryption keys is device seizure
which we discuss in our answer <a href="#device-seizure">on metadata and device seizure</a>.</p>
<p>但请注意,如果有人获得您的解密密钥,
他们通常也能够获得您的消息,
无论是否实施了完全前向保密。
泄露解密密钥的典型现实情况是设备扣押,
我们在<a href="#device-seizure">关于元数据和设备扣押</a>的回答中讨论了这一点。</p>
<p>It is possible that Delta Chat evolves to support Perfect Forward Secrecy,
because OpenPGP is just a container for encrypted messages
@@ -1272,6 +1261,12 @@ One device is not needed for the other to work.</p>
and make sure, <strong>Private Network</strong> is selected as “Network profile type”
(after transfer, you can change back to the original value)</p>
</li>
<li>
<p>On <strong>iOS</strong>, make sure “System Settings / Apps / Delta Chat / <strong>Local Network</strong>” access is granted</p>
</li>
<li>
<p>On <strong>macOS</strong>, enable “System Settings / Privacy &amp; Security / <strong>Local Network</strong> / Delta Chat”</p>
</li>
<li>
<p>Your system might have a “personal firewall”,
which is known to cause problems (especially on Windows).
@@ -1384,6 +1379,7 @@ you only open attachments from senders you trust, and not from spammers.</li>
<ul>
<li>In general, anyone can share webxdc apps with each
other without restrictions.</li>
<li>From <a href="https://webxdc.org/apps/">webxdc.org/apps</a>.</li>
<li>You can <a href="https://delta.chat/en/2023-08-11-xstore">send hi to xstore@testrun.org</a>
to see an experimental webxdc appstore.
All of the apps are open source and for free.</li>
@@ -1794,6 +1790,35 @@ Otherwise you might receive undecryptable messages from those group chats.</p>
<li>请参阅 <a href="https://github.com/deltachat/deltachat-core-rust/blob/master/standards.md#standards-used-in-delta-chat">Delta Chat 中使用的标准</a></li>
</ul>
<h3 id="where-can-my-friends-find-delta-chat">
Where can my friends find Delta Chat? <a href="#where-can-my-friends-find-delta-chat" class="anchor"></a>
</h3>
<p>Delta Chat is available for all major and some minor platforms:</p>
<ul>
<li>
<p>The <strong>official website</strong>, <a href="https://delta.chat/download">https://delta.chat/download</a> shows all options in detail</p>
</li>
<li>
<p>If unavailable, use the <strong>mirror</strong> at <a href="https://deltachat.github.io/deltachat-pages">https://deltachat.github.io/deltachat-pages</a></p>
</li>
<li>
<p>Open one of the following <strong>app stores and search for “Delta Chat”:</strong>
Google Play Store, F-Droid, Huawei App Gallery, Amazon App Store, iOS and macOS App Store, Microsoft Store</p>
</li>
<li>
<p>Check the <strong>package manager</strong> of your Linux distributions</p>
</li>
<li>
<p><strong>Android APKs</strong> are also available on <a href="https://github.com/deltachat/deltachat-android/releases">https://github.com/deltachat/deltachat-android/releases</a></p>
</li>
</ul>
<h3 id="delta-chat-的开发是如何被资助的">
@@ -2,16 +2,14 @@ package com.b44t.messenger;
public class DcContext {
public final static int DC_PREF_DEFAULT_MDNS_ENABLED = 1;
public final static int DC_PREF_DEFAULT_TRIM_ENABLED = 0;
public final static int DC_PREF_DEFAULT_TRIM_LENGTH = 500;
public final static int DC_EVENT_INFO = 100;
public final static int DC_EVENT_WARNING = 300;
public final static int DC_EVENT_ERROR = 400;
public final static int DC_EVENT_ERROR_SELF_NOT_IN_GROUP = 410;
public final static int DC_EVENT_MSGS_CHANGED = 2000;
public final static int DC_EVENT_REACTIONS_CHANGED = 2001;
public final static int DC_EVENT_INCOMING_REACTION = 2002;
public final static int DC_EVENT_INCOMING_WEBXDC_NOTIFY = 2003;
public final static int DC_EVENT_INCOMING_MSG = 2005;
public final static int DC_EVENT_MSGS_NOTICED = 2008;
public final static int DC_EVENT_MSG_DELIVERED = 2010;
@@ -82,16 +80,13 @@ public class DcContext {
public final static int DC_MEDIA_QUALITY_BALANCED = 0;
public final static int DC_MEDIA_QUALITY_WORSE = 1;
public final static int DC_DECISION_START_CHAT = 0;
public final static int DC_DECISION_BLOCK = 1;
public final static int DC_DECISION_NOT_NOW = 2;
public final static int DC_CONNECTIVITY_NOT_CONNECTED = 1000;
public final static int DC_CONNECTIVITY_CONNECTING = 2000;
public final static int DC_CONNECTIVITY_WORKING = 3000;
public final static int DC_CONNECTIVITY_CONNECTED = 4000;
private static final String CONFIG_ACCOUNT_ENABLED = "ui.enabled";
private static final String CONFIG_MENTION_NOTIF_ENABLED = "ui.notify_mentions";
// when using DcAccounts, use DcAccounts.addAccount() instead
public DcContext(String osName, String dbfile) {
@@ -204,7 +199,7 @@ public class DcContext {
public native int sendMsg (int chat_id, DcMsg msg);
public native int sendTextMsg (int chat_id, String text);
public native int sendVideochatInvitation(int chat_id);
public native boolean sendWebxdcStatusUpdate(int msg_id, String payload, String descr);
public native boolean sendWebxdcStatusUpdate(int msg_id, String payload);
public native String getWebxdcStatusUpdates(int msg_id, int last_known_serial);
public native void setWebxdcIntegration (String file);
public native int initWebxdcIntegration(int chat_id);
@@ -232,22 +227,12 @@ public class DcContext {
}
}
public String getNameNAddr() {
String displayname = getConfig("displayname");
String addr = getConfig("addr");
String ret = "";
public boolean isMentionsEnabled() {
return !"0".equals(getConfig(CONFIG_MENTION_NOTIF_ENABLED));
}
if (!displayname.isEmpty() && !addr.isEmpty()) {
ret = String.format("%s (%s)", displayname, addr);
} else if (!addr.isEmpty()) {
ret = addr;
}
if (ret.isEmpty() || isConfigured() == 0) {
ret += " (not configured)";
}
return ret.trim();
public void setMentionsEnabled(boolean enabled) {
setConfigInt(CONFIG_MENTION_NOTIF_ENABLED, enabled? 1 : 0);
}
public String getName() {
+3 -2
View File
@@ -148,6 +148,7 @@ public class DcMsg {
return new JSONObject();
}
}
public native String getWebxdcHref ();
public native boolean isForwarded ();
public native boolean isInfo ();
public native boolean isSetupMessage ();
@@ -170,10 +171,10 @@ public class DcMsg {
public native boolean isOutgoing();
public native int getSenderColor();
public String getSenderName(DcContact dcContact, boolean markOverride) {
public String getSenderName(DcContact dcContact) {
String overrideName = getOverrideSenderName();
if (overrideName != null) {
return (markOverride ? "~" : "") + overrideName;
return "~" + overrideName;
} else {
return dcContact.getDisplayName();
}
@@ -27,7 +27,6 @@ import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcEventEmitter;
import com.b44t.messenger.rpc.Rpc;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.connect.AccountManager;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
@@ -37,7 +36,6 @@ import org.thoughtcrime.securesms.connect.KeepAliveService;
import org.thoughtcrime.securesms.connect.NetworkStateReceiver;
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
import org.thoughtcrime.securesms.crypto.PRNGFixes;
import org.thoughtcrime.securesms.geolocation.DcLocationManager;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.notifications.FcmReceiveService;
@@ -107,11 +105,6 @@ public class ApplicationContext extends MultiDexApplication {
Log.i("DeltaChat", "++++++++++++++++++ ApplicationContext.onCreate() ++++++++++++++++++");
// The first call to `getInstance` takes about 100ms-300ms, so, do it on a background thread
Thread t = new Thread(() -> EmojiProvider.getInstance(this), "InitEmojiProviderThread");
t.setPriority(Thread.MIN_PRIORITY);
t.start();
System.loadLibrary("native-utils");
dcAccounts = new DcAccounts(new File(getFilesDir(), "accounts").getAbsolutePath());
@@ -209,7 +202,6 @@ public class ApplicationContext extends MultiDexApplication {
KeepAliveService.maybeStartSelf(this);
initializeRandomNumberFix();
initializeLogging();
initializeJobManager();
InChatSounds.getInstance(this);
@@ -258,10 +250,6 @@ public class ApplicationContext extends MultiDexApplication {
return jobManager;
}
private void initializeRandomNumberFix() {
PRNGFixes.apply();
}
private void initializeLogging() {
SignalProtocolLoggerProvider.setProvider(new AndroidSignalProtocolLogger());
}
@@ -156,10 +156,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
this.findPreference(PREFERENCE_CATEGORY_HELP)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_HELP));
if (VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
tintIcons(getActivity());
}
DcHelper.getEventCenter(getActivity()).addObserver(DcContext.DC_EVENT_CONNECTIVITY_CHANGED, this);
}
@@ -33,9 +33,6 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
onPreCreate();
if (BaseActivity.isMenuWorkaroundRequired()) {
forceOverflowMenu();
}
super.onCreate(savedInstanceState);
}
@@ -46,20 +43,6 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
dynamicTheme.onResume(this);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return (keyCode == KeyEvent.KEYCODE_MENU && BaseActivity.isMenuWorkaroundRequired()) || super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU && BaseActivity.isMenuWorkaroundRequired()) {
openOptionsMenu();
return true;
}
return super.onKeyUp(keyCode, event);
}
private void initializeScreenshotSecurity() {
if (Prefs.isScreenSecurityEnabled(this)) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
@@ -1,28 +0,0 @@
package org.thoughtcrime.securesms;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import android.view.KeyEvent;
public abstract class BaseActivity extends FragmentActivity {
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return (keyCode == KeyEvent.KEYCODE_MENU && isMenuWorkaroundRequired()) || super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU && isMenuWorkaroundRequired()) {
openOptionsMenu();
return true;
}
return super.onKeyUp(keyCode, event);
}
public static boolean isMenuWorkaroundRequired() {
return VERSION.SDK_INT < VERSION_CODES.KITKAT && ("LGE".equalsIgnoreCase(Build.MANUFACTURER) || "E6710".equalsIgnoreCase(Build.DEVICE));
}
}
@@ -364,9 +364,7 @@ public abstract class BaseConversationListFragment extends Fragment implements A
mode.setTitle("1");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
requireActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
}
requireActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
return true;
}
@@ -395,11 +393,9 @@ public abstract class BaseConversationListFragment extends Fragment implements A
actionMode = null;
getListAdapter().initializeBatchMode(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
TypedArray color = requireActivity().getTheme().obtainStyledAttributes(new int[] {android.R.attr.statusBarColor});
requireActivity().getWindow().setStatusBarColor(color.getColor(0, Color.BLACK));
color.recycle();
}
TypedArray color = requireActivity().getTheme().obtainStyledAttributes(new int[]{android.R.attr.statusBarColor});
requireActivity().getWindow().setStatusBarColor(color.getColor(0, Color.BLACK));
color.recycle();
Context context = getContext();
if (context != null) {
@@ -136,9 +136,7 @@ public class ContactSelectionListFragment extends Fragment
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.contact_list, menu);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
}
getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
setCorrectMenuVisibility(menu);
actionMode.setTitle("1");
return true;
@@ -170,11 +168,9 @@ public class ContactSelectionListFragment extends Fragment
ContactSelectionListFragment.this.actionMode = null;
getContactSelectionListAdapter().resetActionModeSelection();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
TypedArray color = getActivity().getTheme().obtainStyledAttributes(new int[] {android.R.attr.statusBarColor});
getActivity().getWindow().setStatusBarColor(color.getColor(0, Color.BLACK));
color.recycle();
}
TypedArray color = getActivity().getTheme().obtainStyledAttributes(new int[]{android.R.attr.statusBarColor});
getActivity().getWindow().setStatusBarColor(color.getColor(0, Color.BLACK));
color.recycle();
}
};
@@ -89,8 +89,6 @@ import org.thoughtcrime.securesms.components.InputPanel;
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener;
import org.thoughtcrime.securesms.components.ScaleStableImageView;
import org.thoughtcrime.securesms.components.SendButton;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.connect.AccountManager;
import org.thoughtcrime.securesms.connect.DcEventCenter;
@@ -710,7 +708,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
DcMsg draft = dcContext.getDraft(chatId);
final String sharedText = RelayUtil.getSharedText(this);
if (draft == null) {
if (!draft.isOk()) {
if (TextUtils.isEmpty(sharedText)) {
composeText.setText("");
future.set(false);
@@ -931,12 +929,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
case AttachmentTypeSelector.TAKE_PHOTO:
attachmentManager.capturePhoto(this, TAKE_PHOTO); break;
case AttachmentTypeSelector.RECORD_VIDEO:
if(VideoRecoder.canRecode()) {
attachmentManager.captureVideo(this, RECORD_VIDEO);
}
else {
Toast.makeText(this, "This device does not support video-compression (requires Android 4.4 KitKat)", Toast.LENGTH_LONG).show();
}
attachmentManager.captureVideo(this, RECORD_VIDEO);
break;
}
}
@@ -1238,7 +1231,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void onEmojiToggle() {
if (!emojiDrawerStub.resolved()) {
initializeMediaKeyboardProviders(emojiDrawerStub.get(), false);
inputPanel.setMediaKeyboard(emojiDrawerStub.get());
}
@@ -1277,13 +1269,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
dcContext.sendMsg(chatId, msg);
}
private void initializeMediaKeyboardProviders(@NonNull MediaKeyboard mediaKeyboard, boolean stickersAvailable) {
boolean isSystemEmojiPreferred = Prefs.isSystemEmojiPreferred(this);
if (!isSystemEmojiPreferred) {
mediaKeyboard.setProviders(0, new EmojiKeyboardProvider(this, inputPanel));
}
}
// Listeners
private class AttachmentTypeListener implements AttachmentTypeSelector.AttachmentClickedListener {
@@ -24,7 +24,6 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
@@ -442,7 +441,7 @@ public class ConversationFragment extends MessageSelectorFragment
if (msg.getFromId() != prevMsg.getFromId() && !singleMsg) {
DcContact contact = dcContext.getContact(msg.getFromId());
result.append(msg.getSenderName(contact, false)).append(":\n");
result.append(msg.getSenderName(contact)).append(":\n");
}
if (msg.getType() == DcMsg.DC_MSG_TEXT || (singleMsg && !msg.getText().isEmpty())) {
result.append(msg.getText());
@@ -810,7 +809,7 @@ public class ConversationFragment extends MessageSelectorFragment
DozeReminder.dozeReminderTapped(getContext());
}
else if(messageRecord.getInfoType() == DcMsg.DC_INFO_WEBXDC_INFO_MESSAGE) {
scrollMaybeSmoothToMsgId(messageRecord.getParent().getId());
WebxdcActivity.openWebxdcActivity(getContext(), messageRecord.getParent(), messageRecord.getWebxdcHref());
}
else {
String self_mail = dcContext.getConfig("configured_mail_user");
@@ -911,11 +910,9 @@ public class ConversationFragment extends MessageSelectorFragment
mode.setTitle("1");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getActivity().getWindow();
statusBarColor = window.getStatusBarColor();
window.setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
}
Window window = getActivity().getWindow();
statusBarColor = window.getStatusBarColor();
window.setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
setCorrectMenuVisibility(menu);
ConversationAdaptiveActionsToolbar.adjustMenuActions(menu, 10, requireActivity().getWindow().getDecorView().getMeasuredWidth());
@@ -932,9 +929,7 @@ public class ConversationFragment extends MessageSelectorFragment
((ConversationAdapter)list.getAdapter()).clearSelection();
list.getAdapter().notifyDataSetChanged();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getActivity().getWindow().setStatusBarColor(statusBarColor);
}
getActivity().getWindow().setStatusBarColor(statusBarColor);
actionMode = null;
hideAddReactionView();
@@ -22,12 +22,10 @@ import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.os.Build;
import android.text.Spannable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
@@ -56,7 +54,6 @@ import org.thoughtcrime.securesms.components.DocumentView;
import org.thoughtcrime.securesms.components.QuoteView;
import org.thoughtcrime.securesms.components.VcardView;
import org.thoughtcrime.securesms.components.WebxdcView;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.mms.AudioSlide;
import org.thoughtcrime.securesms.mms.DocumentSlide;
@@ -69,10 +66,10 @@ import org.thoughtcrime.securesms.mms.StickerSlide;
import org.thoughtcrime.securesms.mms.VcardSlide;
import org.thoughtcrime.securesms.reactions.ReactionsConversationView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Linkifier;
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
import org.thoughtcrime.securesms.util.MarkdownUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.Stub;
@@ -112,6 +109,7 @@ public class ConversationItem extends BaseConversationItem
protected ViewGroup contactPhotoHolder;
private ViewGroup container;
private Button msgActionButton;
private Button showFullButton;
private @NonNull Stub<ConversationItemThumbnail> mediaThumbnailStub;
private @NonNull Stub<AudioView> audioViewStub;
@@ -158,6 +156,7 @@ public class ConversationItem extends BaseConversationItem
this.container = findViewById(R.id.container);
this.replyView = findViewById(R.id.reply_icon);
this.msgActionButton = findViewById(R.id.msg_action_button);
this.showFullButton = findViewById(R.id.show_full_button);
setOnClickListener(new ClickListener(null));
@@ -385,7 +384,6 @@ public class ConversationItem extends BaseConversationItem
private void setBodyText(DcMsg messageRecord) {
bodyText.setClickable(false);
bodyText.setFocusable(false);
bodyText.setTextSize(TypedValue.COMPLEX_UNIT_SP, Prefs.getMessageBodyTextSize(context));
String text = messageRecord.getText();
@@ -399,7 +397,7 @@ public class ConversationItem extends BaseConversationItem
else {
Spannable spannable = (Spannable) MarkdownUtil.toMarkdown(context, text);
if (batchSelected.isEmpty()) {
spannable = EmojiTextView.linkify(spannable);
spannable = Linkifier.linkify(spannable);
}
bodyText.setText(spannable);
bodyText.setVisibility(View.VISIBLE);
@@ -407,6 +405,7 @@ public class ConversationItem extends BaseConversationItem
int downloadState = messageRecord.getDownloadState();
if (downloadState == DcMsg.DC_DOWNLOAD_AVAILABLE || downloadState == DcMsg.DC_DOWNLOAD_FAILURE || downloadState == DcMsg.DC_DOWNLOAD_IN_PROGRESS) {
showFullButton.setVisibility(View.GONE);
msgActionButton.setVisibility(View.VISIBLE);
if (downloadState==DcMsg.DC_DOWNLOAD_IN_PROGRESS) {
msgActionButton.setEnabled(false);
@@ -427,6 +426,7 @@ public class ConversationItem extends BaseConversationItem
}
});
} else if (messageRecord.getType() == DcMsg.DC_MSG_WEBXDC) {
showFullButton.setVisibility(View.GONE);
msgActionButton.setVisibility(View.VISIBLE);
msgActionButton.setEnabled(true);
msgActionButton.setText(webxdcViewStub.get().isCommunity()? R.string.join: R.string.start_app);
@@ -439,10 +439,11 @@ public class ConversationItem extends BaseConversationItem
});
}
else if (messageRecord.hasHtml()) {
msgActionButton.setVisibility(View.VISIBLE);
msgActionButton.setEnabled(true);
msgActionButton.setText(R.string.show_full_message);
msgActionButton.setOnClickListener(view -> {
msgActionButton.setVisibility(View.GONE);
showFullButton.setVisibility(View.VISIBLE);
showFullButton.setEnabled(true);
showFullButton.setText(R.string.show_full_message);
showFullButton.setOnClickListener(view -> {
if (eventListener != null && batchSelected.isEmpty()) {
eventListener.onShowFullClicked(messageRecord);
} else {
@@ -451,6 +452,7 @@ public class ConversationItem extends BaseConversationItem
});
} else {
msgActionButton.setVisibility(View.GONE);
showFullButton.setVisibility(View.GONE);
}
}
@@ -492,9 +494,7 @@ public class ConversationItem extends BaseConversationItem
audioViewStub.get().setAudio(new AudioSlide(context, messageRecord), duration);
audioViewStub.get().setOnClickListener(passthroughClickListener);
audioViewStub.get().setOnLongClickListener(passthroughClickListener);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
audioViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
audioViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -512,9 +512,7 @@ public class ConversationItem extends BaseConversationItem
documentViewStub.get().setDocument(new DocumentSlide(context, messageRecord));
documentViewStub.get().setDocumentClickListener(new ThumbnailClickListener());
documentViewStub.get().setOnLongClickListener(passthroughClickListener);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
documentViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
documentViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -531,9 +529,7 @@ public class ConversationItem extends BaseConversationItem
webxdcViewStub.get().setWebxdc(messageRecord, context.getString(R.string.webxdc_app));
webxdcViewStub.get().setWebxdcClickListener(new ThumbnailClickListener());
webxdcViewStub.get().setOnLongClickListener(passthroughClickListener);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webxdcViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
webxdcViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -551,9 +547,7 @@ public class ConversationItem extends BaseConversationItem
vcardViewStub.get().setVcardClickListener(new ThumbnailClickListener());
vcardViewStub.get().setOnLongClickListener(passthroughClickListener);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
vcardViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
vcardViewStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -589,9 +583,7 @@ public class ConversationItem extends BaseConversationItem
mediaThumbnailStub.get().setOnLongClickListener(passthroughClickListener);
mediaThumbnailStub.get().setOnClickListener(passthroughClickListener);
mediaThumbnailStub.get().showShade(TextUtils.isEmpty(messageRecord.getText()));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mediaThumbnailStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
mediaThumbnailStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
setThumbnailOutlineCorners(messageRecord, showSender);
@@ -614,9 +606,7 @@ public class ConversationItem extends BaseConversationItem
stickerStub.get().setThumbnailClickListener(new StickerClickListener());
stickerStub.get().setOnLongClickListener(passthroughClickListener);
stickerStub.get().setOnClickListener(passthroughClickListener);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
stickerStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
stickerStub.get().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -677,7 +667,7 @@ public class ConversationItem extends BaseConversationItem
contactPhoto.setVisibility(View.GONE);
} else {
int color = messageRecord.getSenderColor();
Recipient recipient = new Recipient(context, dcContact, messageRecord.getSenderName(dcContact, !dcContext.isCommunity()), color);
Recipient recipient = new Recipient(context, dcContact, messageRecord.getSenderName(dcContact), color);
contactPhoto.setAvatar(glideRequests, recipient, true);
contactPhoto.setVisibility(View.VISIBLE);
}
@@ -802,14 +792,14 @@ public class ConversationItem extends BaseConversationItem
if (messageRecord.isForwarded()) {
if (showSender && dcContact !=null) {
this.groupSender.setText(context.getString(R.string.forwarded_by, messageRecord.getSenderName(dcContact, false)));
this.groupSender.setText(context.getString(R.string.forwarded_by, messageRecord.getSenderName(dcContact)));
} else {
this.groupSender.setText(context.getString(R.string.forwarded_message));
}
this.groupSender.setTextColor(context.getResources().getColor(R.color.unknown_sender));
}
else if (showSender && dcContact !=null) {
this.groupSender.setText(messageRecord.getSenderName(dcContact, true));
this.groupSender.setText(messageRecord.getSenderName(dcContact));
int color = messageRecord.getSenderColor();
this.groupSender.setTextColor(Util.rgbToArgbColor(color!=0? color : dcContact.getColor()));
}
@@ -174,9 +174,7 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
vibrate(viewHolder.itemView.getContext());
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
recyclerView.cancelPendingInputEvents();
}
recyclerView.cancelPendingInputEvents();
}
private static void resetProgress(RecyclerView.ViewHolder viewHolder) {
@@ -199,11 +197,7 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
}
private static float getSignFromDirection(@NonNull View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -1f : 1f;
} else {
return 1f;
}
return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -1f : 1f;
}
private static boolean sameSign(float dX, float sign) {
@@ -564,7 +564,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
private void shareInvite() {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
String inviteURL = Util.QrDataToInviteURL(DcHelper.getContext(this).getSecurejoinQr(0));
String inviteURL = DcHelper.getContext(this).getSecurejoinQr(0);
intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.invite_friends_text, inviteURL));
startActivity(Intent.createChooser(intent, getString(R.string.chat_share_with_title)));
}
@@ -29,7 +29,6 @@ public class ConversationListItemInboxZero extends LinearLayout implements Binda
super(context, attrs, defStyleAttr);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public ConversationListItemInboxZero(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@@ -5,7 +5,6 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
@@ -35,8 +34,6 @@ import com.soundcloud.android.crop.Crop;
import org.thoughtcrime.securesms.components.AvatarSelector;
import org.thoughtcrime.securesms.components.InputAwareLayout;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.mms.AttachmentManager;
@@ -54,7 +51,7 @@ import java.security.SecureRandom;
@SuppressLint("StaticFieldLeak")
public class CreateProfileActivity extends BaseActionBarActivity implements EmojiKeyboardProvider.EmojiEventListener {
public class CreateProfileActivity extends BaseActionBarActivity {
private static final String TAG = CreateProfileActivity.class.getSimpleName();
@@ -66,7 +63,6 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Emoj
private ImageView avatar;
private EditText name;
private EditText overridenName;
private MediaKeyboard emojiDrawer;
private EditText statusView;
private boolean fromWelcome;
@@ -91,7 +87,6 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Emoj
attachmentManager = new AttachmentManager(this, () -> {});
avatarChanged = false;
initializeResources();
initializeEmojiInput();
initializeProfileName();
initializeProfileAvatar();
initializeStatusText();
@@ -130,15 +125,6 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Emoj
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (container.getCurrentInput() == emojiDrawer) {
container.hideAttachedInput(true);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
@@ -206,7 +192,6 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Emoj
this.avatar = ViewUtil.findById(this, R.id.avatar);
this.name = ViewUtil.findById(this, R.id.name_text);
this.overridenName = ViewUtil.findById(this, R.id.overriden_name);
this.emojiDrawer = ViewUtil.findById(this, R.id.emoji_drawer);
this.container = ViewUtil.findById(this, R.id.container);
this.statusView = ViewUtil.findById(this, R.id.status_text);
@@ -261,33 +246,6 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Emoj
);
}
@Override
public void onEmojiSelected(String emoji) {
final int start = name.getSelectionStart();
final int end = name.getSelectionEnd();
name.getText().replace(Math.min(start, end), Math.max(start, end), emoji);
name.setSelection(start + emoji.length());
}
@Override
public void onKeyEvent(KeyEvent keyEvent) {
name.dispatchKeyEvent(keyEvent);
}
private void initializeMediaKeyboardProviders(@NonNull MediaKeyboard mediaKeyboard) {
boolean isSystemEmojiPreferred = Prefs.isSystemEmojiPreferred(this);
if (!isSystemEmojiPreferred) {
mediaKeyboard.setProviders(0, new EmojiKeyboardProvider(this, this));
}
}
private void initializeEmojiInput() {
initializeMediaKeyboardProviders(emojiDrawer);
this.name.setOnClickListener(v -> container.showSoftkey(name));
}
private void initializeStatusText() {
String status = DcHelper.get(this, DcHelper.CONFIG_SELF_STATUS);
statusView.setText(status);
@@ -246,16 +246,22 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
protected void onPause() {
super.onPause();
final String displayName = name.getText().toString();
DcHelper.set(this, DcHelper.CONFIG_DISPLAY_NAME, TextUtils.isEmpty(displayName)? null : displayName);
// Save display name and avatar in the unconfigured profile.
// If the currently selected profile is configured, then this means that rollbackAccountCreation()
// was called (see handleOnBackPressed() above), i.e. the newly created profile was removed already
// and we can't save the display name & avatar.
if (DcHelper.getContext(this).isConfigured() == 0) {
final String displayName = name.getText().toString();
DcHelper.set(this, DcHelper.CONFIG_DISPLAY_NAME, TextUtils.isEmpty(displayName) ? null : displayName);
if (avatarChanged) {
try {
AvatarHelper.setSelfAvatar(InstantOnboardingActivity.this, avatarBmp);
Prefs.setProfileAvatarId(InstantOnboardingActivity.this, new SecureRandom().nextInt());
avatarChanged = false;
} catch (IOException e) {
Log.e(TAG, "Failed to save avatar", e);
if (avatarChanged) {
try {
AvatarHelper.setSelfAvatar(InstantOnboardingActivity.this, avatarBmp);
Prefs.setProfileAvatarId(InstantOnboardingActivity.this, new SecureRandom().nextInt());
avatarChanged = false;
} catch (IOException e) {
Log.e(TAG, "Failed to save avatar", e);
}
}
}
}
@@ -197,12 +197,11 @@ public class LogViewFragment extends Fragment {
asMegs(info.maxMemory()));
}
@TargetApi(VERSION_CODES.KITKAT)
public static String getMemoryClass(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
String lowMem = "";
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT && activityManager.isLowRamDevice()) {
if (activityManager.isLowRamDevice()) {
lowMem = ", low-mem device";
}
return activityManager.getMemoryClass() + lowMem;
@@ -252,10 +251,8 @@ public class LogViewFragment extends Fragment {
Locale locale = Util.getLocale();
builder.append("lang=").append(locale.toString()).append("\n");
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
boolean isRtl = Util.getLayoutDirection(context) == View.LAYOUT_DIRECTION_RTL;
builder.append("rtl=").append(isRtl).append("\n");
}
boolean isRtl = Util.getLayoutDirection(context) == View.LAYOUT_DIRECTION_RTL;
builder.append("rtl=").append(isRtl).append("\n");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
boolean notifPermGranted = PermissionChecker.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PermissionChecker.PERMISSION_GRANTED;
@@ -229,11 +229,9 @@ public class ProfileDocumentsFragment
mode.getMenuInflater().inflate(R.menu.profile_context, menu);
mode.setTitle("1");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getActivity().getWindow();
originalStatusBarColor = window.getStatusBarColor();
window.setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
}
Window window = getActivity().getWindow();
originalStatusBarColor = window.getStatusBarColor();
window.setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
setCorrectMenuVisibility(menu);
return true;
}
@@ -283,9 +281,7 @@ public class ProfileDocumentsFragment
actionMode = null;
getListAdapter().clearSelection();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getActivity().getWindow().setStatusBarColor(originalStatusBarColor);
}
getActivity().getWindow().setStatusBarColor(originalStatusBarColor);
}
}
}
@@ -219,11 +219,9 @@ public class ProfileGalleryFragment
mode.getMenuInflater().inflate(R.menu.profile_context, menu);
mode.setTitle("1");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getActivity().getWindow();
originalStatusBarColor = window.getStatusBarColor();
window.setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
}
Window window = getActivity().getWindow();
originalStatusBarColor = window.getStatusBarColor();
window.setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
setCorrectMenuVisibility(menu);
return true;
}
@@ -269,9 +267,7 @@ public class ProfileGalleryFragment
actionMode = null;
getListAdapter().clearSelection();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getActivity().getWindow().setStatusBarColor(originalStatusBarColor);
}
getActivity().getWindow().setStatusBarColor(originalStatusBarColor);
}
}
}
@@ -268,11 +268,9 @@ public class ProfileSettingsFragment extends Fragment
menu.findItem(R.id.menu_select_all).setVisible(false);
mode.setTitle("1");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getActivity().getWindow();
originalStatusBarColor = window.getStatusBarColor();
window.setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
}
Window window = getActivity().getWindow();
originalStatusBarColor = window.getStatusBarColor();
window.setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
return true;
}
@@ -314,9 +312,7 @@ public class ProfileSettingsFragment extends Fragment
public void onDestroyActionMode(ActionMode mode) {
actionMode = null;
adapter.clearSelection();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getActivity().getWindow().setStatusBarColor(originalStatusBarColor);
}
getActivity().getWindow().setStatusBarColor(originalStatusBarColor);
}
}
@@ -6,12 +6,14 @@ import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import androidx.appcompat.widget.AppCompatTextView;
import org.thoughtcrime.securesms.util.Linkifier;
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
public class ProfileStatusItem extends LinearLayout {
private EmojiTextView statusTextView;
private AppCompatTextView statusTextView;
private final PassthroughClickListener passthroughClickListener = new PassthroughClickListener();
public ProfileStatusItem(Context context) {
@@ -32,7 +34,7 @@ public class ProfileStatusItem extends LinearLayout {
}
public void set(String status) {
statusTextView.setText(EmojiTextView.linkify(new SpannableString(status)));
statusTextView.setText(Linkifier.linkify(new SpannableString(status)));
}
private class PassthroughClickListener implements View.OnLongClickListener, View.OnClickListener {
@@ -31,7 +31,6 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
@@ -59,6 +58,7 @@ import org.thoughtcrime.securesms.proxy.ProxySettingsActivity;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.IntentUtils;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.ProgressDialog;
import java.lang.ref.WeakReference;
@@ -185,7 +185,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements DcEve
expandAdvanced = expandAdvanced || !TextUtils.isEmpty(strVal);
intVal = DcHelper.getInt(this, CONFIG_MAIL_SECURITY);
imapSecurity.setSelection(intVal);
imapSecurity.setSelection(ViewUtil.checkBounds(intVal, imapSecurity));
expandAdvanced = expandAdvanced || intVal != 0;
TextInputEditText smtpLoginInput = findViewById(R.id.smtp_login_text);
@@ -207,7 +207,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements DcEve
expandAdvanced = expandAdvanced || !TextUtils.isEmpty(strVal);
intVal = DcHelper.getInt(this, CONFIG_SEND_SECURITY);
smtpSecurity.setSelection(intVal);
smtpSecurity.setSelection(ViewUtil.checkBounds(intVal, smtpSecurity));
expandAdvanced = expandAdvanced || intVal != 0;
int serverFlags = DcHelper.getInt(this, CONFIG_SERVER_FLAGS);
@@ -222,12 +222,15 @@ public class RegistrationActivity extends BaseActionBarActivity implements DcEve
}
// /remove gmail oauth2
}
authMethod.setSelection(sel);
authMethod.setSelection(ViewUtil.checkBounds(sel, authMethod));
expandAdvanced = expandAdvanced || sel != 0;
int certCheckFlags = DcHelper.getInt(this, "imap_certificate_checks");
certCheck.setSelection(certCheckFlags);
expandAdvanced = expandAdvanced || certCheckFlags != 0;
int imapCertificateChecks = DcHelper.getInt(this, "imap_certificate_checks");
if (imapCertificateChecks == 3) {
imapCertificateChecks = 2; // 3 is a deprecated alias for 2
}
certCheck.setSelection(ViewUtil.checkBounds(imapCertificateChecks, certCheck));
expandAdvanced = expandAdvanced || imapCertificateChecks != 0;
} else if (getIntent() != null && getIntent().getBundleExtra(ACCOUNT_DATA) != null) {
// Companion app might have sent account data
Bundle b = getIntent().getBundleExtra(ACCOUNT_DATA);
@@ -40,6 +40,7 @@ import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcMsg;
import com.b44t.messenger.rpc.Rpc;
import com.b44t.messenger.rpc.RpcException;
import com.google.common.base.Charsets;
import org.json.JSONObject;
import org.thoughtcrime.securesms.connect.AccountManager;
@@ -54,6 +55,8 @@ import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -63,6 +66,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
private static final String EXTRA_ACCOUNT_ID = "accountId";
private static final String EXTRA_APP_MSG_ID = "appMessageId";
private static final String EXTRA_HIDE_ACTION_BAR = "hideActionBar";
private static final String EXTRA_HREF = "href";
private static final int REQUEST_CODE_FILE_PICKER = 51426;
private ValueCallback<Uri[]> filePathCallback;
@@ -71,6 +75,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
private DcMsg dcAppMsg;
private String baseURL;
private String sourceCodeUrl = "";
private String selfAddr = "";
private boolean internetAccess = false;
private boolean hideActionBar = false;
@@ -92,33 +97,34 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
return;
}
}
openWebxdcActivity(context, msgId, true);
openWebxdcActivity(context, msgId, true, "");
}
public static void openWebxdcActivity(Context context, DcMsg instance) {
openWebxdcActivity(context, instance.getId(), false);
openWebxdcActivity(context, instance, "");
}
public static void openWebxdcActivity(Context context, int msgId, boolean hideActionBar) {
public static void openWebxdcActivity(Context context, DcMsg instance, String href) {
openWebxdcActivity(context, instance.getId(), false, href);
}
public static void openWebxdcActivity(Context context, int msgId, boolean hideActionBar, String href) {
if (!Util.isClickedRecently()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (Prefs.isDeveloperModeEnabled(context)) {
WebView.setWebContentsDebuggingEnabled(true);
}
context.startActivity(getWebxdcIntent(context, msgId, hideActionBar));
} else {
Toast.makeText(context, "At least Android 5.0 (Lollipop) required for Webxdc.", Toast.LENGTH_LONG).show();
if (Prefs.isDeveloperModeEnabled(context)) {
WebView.setWebContentsDebuggingEnabled(true);
}
context.startActivity(getWebxdcIntent(context, msgId, hideActionBar, href));
}
}
private static Intent getWebxdcIntent(Context context, int msgId, boolean hideActionBar) {
private static Intent getWebxdcIntent(Context context, int msgId, boolean hideActionBar, String href) {
DcContext dcContext = DcHelper.getContext(context);
Intent intent = new Intent(context, WebxdcActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(EXTRA_ACCOUNT_ID, dcContext.getAccountId());
intent.putExtra(EXTRA_APP_MSG_ID, msgId);
intent.putExtra(EXTRA_HIDE_ACTION_BAR, hideActionBar);
intent.putExtra(EXTRA_HREF, href);
return intent;
}
@@ -129,7 +135,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcContext.getMsg(msgId).getChatId())
.setAction(Intent.ACTION_VIEW);
final Intent webxdcIntent = getWebxdcIntent(context, msgId, false);
final Intent webxdcIntent = getWebxdcIntent(context, msgId, false, "");
return TaskStackBuilder.create(context)
.addNextIntentWithParentStack(chatIntent)
@@ -197,6 +203,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
// this is needed here because if the app is opened while already in landscape mode, onConfigurationChanged() is not triggered
setScreenMode(getResources().getConfiguration());
}
selfAddr = JsonUtils.optString(info, "self_addr");
toggleFakeProxy(!internetAccess);
@@ -213,7 +220,15 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
webView.setNetworkAvailable(internetAccess); // this does not block network but sets `window.navigator.isOnline` in js land
webView.addJavascriptInterface(new InternalJSApi(), "InternalJSApi");
webView.loadUrl(this.baseURL + "/index.html");
String href = b.getString(EXTRA_HREF, "");
String encodedHref = "";
try {
encodedHref = URLEncoder.encode(href, Charsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
webView.loadUrl(this.baseURL + "/webxdc_bootstrap324567869.html?i=1&href=" + encodedHref);
Util.runOnAnyBackgroundThread(() -> {
final DcChat chat = dcContext.getChat(dcAppMsg.getChatId());
@@ -223,6 +238,18 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
});
}
@Override
public void onResume() {
super.onResume();
DcHelper.getNotificationCenter(this).updateVisibleWebxdc(dcContext.getAccountId(), dcAppMsg.getId());
}
@Override
protected void onPause() {
super.onPause();
DcHelper.getNotificationCenter(this).clearVisibleWebxdc();
}
@Override
protected void onDestroy() {
DcHelper.getEventCenter(this.getApplicationContext()).removeObservers(this);
@@ -249,6 +276,9 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
case R.id.source_code:
openUrlInBrowser(this, sourceCodeUrl);
return true;
case R.id.show_in_chat:
showInChat();
return true;
}
return false;
}
@@ -334,7 +364,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
res = new WebResourceResponse("text/plain", "UTF-8", targetStream);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !internetAccess) {
if (!internetAccess) {
Map<String, String> headers = new HashMap<>();
headers.put("Content-Security-Policy",
"default-src 'self'; "
@@ -353,9 +383,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
}
private void callJavaScriptFunction(String func) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript("window." + func + ";", null);
}
webView.evaluateJavascript("document.getElementById('frame').contentWindow." + func + ";", null);
}
@Override
@@ -394,6 +422,13 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
}
}
private void showInChat() {
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcAppMsg.getChatId());
intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, DcMsg.getMessagePosition(dcAppMsg, dcContext));
startActivity(intent);
}
public static void addToHomeScreen(Activity activity, int msgId) {
Context context = activity.getApplicationContext();
try {
@@ -475,7 +510,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
int flags = Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE;
return Base64.encodeToString(dcContext.getCommunityUser().getBytes(), flags);
}
return dcContext.getConfig("configured_addr");
return WebxdcActivity.this.selfAddr;
}
/** @noinspection unused*/
@@ -493,9 +528,9 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
/** @noinspection unused*/
@JavascriptInterface
public boolean sendStatusUpdate(String payload, String descr) {
public boolean sendStatusUpdate(String payload) {
Log.i(TAG, "sendStatusUpdate");
if (!WebxdcActivity.this.dcContext.sendWebxdcStatusUpdate(WebxdcActivity.this.dcAppMsg.getId(), payload, descr)) {
if (!WebxdcActivity.this.dcContext.sendWebxdcStatusUpdate(WebxdcActivity.this.dcAppMsg.getId(), payload)) {
DcChat dcChat = WebxdcActivity.this.dcContext.getChat(WebxdcActivity.this.dcAppMsg.getChatId());
Toast.makeText(WebxdcActivity.this,
dcChat.isContactRequest() ?
@@ -166,7 +166,7 @@ public class WelcomeActivity extends BaseActionBarActivity implements DcEventCen
.withPermanentDenialDialog(getString(R.string.perm_explain_access_to_storage_denied))
.onAllGranted(() -> {
File imexDir = DcHelper.getImexDir();
if (Build.VERSION.SDK_INT >= 30) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
AttachmentManager.selectMediaType(this, "application/x-tar", null, PICK_BACKUP, StorageUtil.getDownloadUri());
} else {
final String backupFile = dcContext.imexHasBackup(imexDir.getAbsolutePath());
@@ -189,11 +189,16 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv
avatar.setAvatar(GlideApp.with(activity), recipient, false);
nameView.setText(name);
addrView.setText(contact.getAddr());
try {
sizeView.setText(Util.getPrettyFileSize(rpc.getAccountFileSize(accountId)));
} catch (RpcException e) {
e.printStackTrace();
}
Util.runOnAnyBackgroundThread(() -> {
try {
final int sizeBytes = rpc.getAccountFileSize(accountId);
Util.runOnMain(() -> {
sizeView.setText(Util.getPrettyFileSize(sizeBytes));
});
} catch (RpcException e) {
e.printStackTrace();
}
});
description.setText(activity.getString(R.string.delete_account_explain_with_name, name));
AlertDialog dialog = new AlertDialog.Builder(activity)
@@ -130,33 +130,23 @@ public class AttachmentTypeSelector extends PopupWindow {
public void onGlobalLayout() {
getContentView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
animateWindowInCircular(anchor, getContentView());
} else {
animateWindowInTranslate(getContentView());
}
animateWindowInCircular(anchor, getContentView());
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//animateButtonIn(cameraButton, ANIMATION_DURATION / 2);
animateButtonIn(videoButton, ANIMATION_DURATION / 2);
animateButtonIn(imageButton, ANIMATION_DURATION / 3);
animateButtonIn(contactButton, ANIMATION_DURATION / 3);
animateButtonIn(locationButton, ANIMATION_DURATION / 4);
animateButtonIn(documentButton, ANIMATION_DURATION / 4);
animateButtonIn(videoChatButton, 0);
animateButtonIn(closeButton, 0);
}
//animateButtonIn(cameraButton, ANIMATION_DURATION / 2);
animateButtonIn(videoButton, ANIMATION_DURATION / 2);
animateButtonIn(imageButton, ANIMATION_DURATION / 3);
animateButtonIn(contactButton, ANIMATION_DURATION / 3);
animateButtonIn(locationButton, ANIMATION_DURATION / 4);
animateButtonIn(documentButton, ANIMATION_DURATION / 4);
animateButtonIn(videoChatButton, 0);
animateButtonIn(closeButton, 0);
}
@Override
public void dismiss() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
animateWindowOutCircular(currentAnchor, getContentView());
} else {
animateWindowOutTranslate(getContentView());
}
animateWindowOutCircular(currentAnchor, getContentView());
}
public void setListener(@Nullable AttachmentClickedListener listener) {
@@ -186,7 +176,6 @@ public class AttachmentTypeSelector extends PopupWindow {
button.startAnimation(animation);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void animateWindowInCircular(@Nullable View anchor, @NonNull View contentView) {
Pair<Integer, Integer> coordinates = getClickOrigin(anchor, contentView);
Animator animator = ViewAnimationUtils.createCircularReveal(contentView,
@@ -205,7 +194,6 @@ public class AttachmentTypeSelector extends PopupWindow {
getContentView().startAnimation(animation);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void animateWindowOutCircular(@Nullable View anchor, @NonNull View contentView) {
Pair<Integer, Integer> coordinates = getClickOrigin(anchor, contentView);
Animator animator = ViewAnimationUtils.createCircularReveal(getContentView(),
@@ -71,12 +71,10 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener
this.pauseButton.setOnClickListener(new PauseClickedListener());
this.seekBar.setOnSeekBarChangeListener(new SeekBarModifiedListener());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
this.playButton.setImageDrawable(context.getDrawable(R.drawable.play_icon));
this.pauseButton.setImageDrawable(context.getDrawable(R.drawable.pause_icon));
this.playButton.setBackground(context.getDrawable(R.drawable.ic_circle_fill_white_48dp));
this.pauseButton.setBackground(context.getDrawable(R.drawable.ic_circle_fill_white_48dp));
}
this.playButton.setImageDrawable(context.getDrawable(R.drawable.play_icon));
this.pauseButton.setImageDrawable(context.getDrawable(R.drawable.pause_icon));
this.playButton.setBackground(context.getDrawable(R.drawable.ic_circle_fill_white_48dp));
this.pauseButton.setBackground(context.getDrawable(R.drawable.ic_circle_fill_white_48dp));
setTint(getContext().getResources().getColor(R.color.audio_icon));
}
@@ -192,13 +190,8 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener
}
public void setTint(int foregroundTint) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
this.playButton.setBackgroundTintList(ColorStateList.valueOf(foregroundTint));
this.pauseButton.setBackgroundTintList(ColorStateList.valueOf(foregroundTint));
} else {
this.playButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
this.pauseButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
}
this.playButton.setBackgroundTintList(ColorStateList.valueOf(foregroundTint));
this.pauseButton.setBackgroundTintList(ColorStateList.valueOf(foregroundTint));
this.seekBar.getProgressDrawable().setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
@@ -220,25 +213,20 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener
private void togglePlayToPause() {
controlToggle.displayQuick(pauseButton);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AnimatedVectorDrawable playToPauseDrawable = (AnimatedVectorDrawable)getContext().getDrawable(R.drawable.play_to_pause_animation);
pauseButton.setImageDrawable(playToPauseDrawable);
playToPauseDrawable.start();
}
AnimatedVectorDrawable playToPauseDrawable = (AnimatedVectorDrawable) getContext().getDrawable(R.drawable.play_to_pause_animation);
pauseButton.setImageDrawable(playToPauseDrawable);
playToPauseDrawable.start();
}
private void togglePauseToPlay() {
controlToggle.displayQuick(playButton);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AnimatedVectorDrawable pauseToPlayDrawable = (AnimatedVectorDrawable)getContext().getDrawable(R.drawable.pause_to_play_animation);
playButton.setImageDrawable(pauseToPlayDrawable);
pauseToPlayDrawable.start();
}
AnimatedVectorDrawable pauseToPlayDrawable = (AnimatedVectorDrawable) getContext().getDrawable(R.drawable.pause_to_play_animation);
playButton.setImageDrawable(pauseToPlayDrawable);
pauseToPlayDrawable.start();
}
private class PlayClickedListener implements View.OnClickListener {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onClick(View v) {
try {
@@ -279,7 +267,6 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener
}
private class PauseClickedListener implements View.OnClickListener {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onClick(View v) {
Log.w(TAG, "pausebutton onClick");
@@ -7,6 +7,7 @@ import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.core.view.inputmethod.EditorInfoCompat;
import androidx.core.view.inputmethod.InputConnectionCompat;
import androidx.core.view.inputmethod.InputContentInfoCompat;
@@ -25,10 +26,9 @@ import android.view.inputmethod.InputConnection;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.components.emoji.EmojiEditText;
import org.thoughtcrime.securesms.util.Prefs;
public class ComposeText extends EmojiEditText {
public class ComposeText extends AppCompatEditText {
private CharSequence hint;
private SpannableString subHint;
@@ -122,7 +122,6 @@ public class ComposeText extends EmojiEditText {
editorInfo.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION;
}
if (Build.VERSION.SDK_INT < 21) return inputConnection;
if (mediaListener == null) return inputConnection;
if (inputConnection == null) return null;
@@ -120,15 +120,10 @@ public class ConversationItemThumbnail extends FrameLayout {
@SuppressWarnings("SuspiciousNameCombination")
@Override
protected void dispatchDraw(Canvas canvas) {
if (cornerMask.isLegacy()) {
cornerMask.mask(canvas);
}
super.dispatchDraw(canvas);
if (!cornerMask.isLegacy()) {
cornerMask.mask(canvas);
}
cornerMask.mask(canvas);
final float halfStrokeWidth = outlinePaint.getStrokeWidth() / 2;
@@ -7,7 +7,7 @@ import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.os.Build;
import androidx.annotation.NonNull;
import android.view.View;
@@ -20,11 +20,7 @@ public class CornerMask {
private final RectF bounds = new RectF();
public CornerMask(@NonNull View view) {
if (isLegacy()) {
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
} else {
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
clearPaint.setColor(Color.BLACK);
clearPaint.setStyle(Paint.Style.FILL);
@@ -44,19 +40,10 @@ public class CornerMask {
// Note: There's a bug in the P beta where most PorterDuff modes aren't working. But CLEAR does.
// So we find and inverse path and use Mode.CLEAR for versions that support Path.op().
// See issue https://issuetracker.google.com/issues/111394085.
if (!isLegacy()) {
outline.reset();
outline.addRect(bounds, Path.Direction.CW);
outline.op(corners, Path.Op.DIFFERENCE);
canvas.drawPath(outline, clearPaint);
} else {
corners.addRoundRect(bounds, radii, Path.Direction.CW);
canvas.clipPath(corners);
}
}
public boolean isLegacy() {
return Build.VERSION.SDK_INT < 19;
outline.reset();
outline.addRect(bounds, Path.Direction.CW);
outline.op(corners, Path.Op.DIFFERENCE);
canvas.drawPath(outline, clearPaint);
}
public void setRadius(int radius) {
@@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.graphics.Typeface;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.core.view.ViewCompat;
import android.text.Spannable;
import android.text.SpannableString;
@@ -14,12 +16,11 @@ import android.text.style.TypefaceSpan;
import android.util.AttributeSet;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ResUtil;
import org.thoughtcrime.securesms.util.spans.CenterAlignedRelativeSizeSpan;
public class FromTextView extends EmojiTextView {
public class FromTextView extends AppCompatTextView {
private static final String TAG = FromTextView.class.getSimpleName();
@@ -9,7 +9,6 @@ import android.os.Build;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
@@ -29,14 +28,12 @@ import com.b44t.messenger.util.concurrent.SettableFuture;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.QuoteModel;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
@@ -46,9 +43,9 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class InputPanel extends ConstraintLayout
implements MicrophoneRecorderView.Listener,
KeyboardAwareLinearLayout.OnKeyboardShownListener,
EmojiKeyboardProvider.EmojiEventListener
implements MicrophoneRecorderView.Listener,
KeyboardAwareLinearLayout.OnKeyboardShownListener,
MediaKeyboard.MediaKeyboardListener
{
private static final String TAG = InputPanel.class.getSimpleName();
@@ -56,7 +53,7 @@ public class InputPanel extends ConstraintLayout
private static final int FADE_TIME = 150;
private QuoteView quoteView;
private EmojiToggle mediaKeyboard;
private EmojiToggle emojiToggle;
private ComposeText composeText;
private View quickCameraToggle;
private View quickAudioToggle;
@@ -70,7 +67,6 @@ public class InputPanel extends ConstraintLayout
private ValueAnimator quoteAnimator;
private @Nullable Listener listener;
private boolean emojiVisible;
public InputPanel(Context context) {
super(context);
@@ -92,7 +88,7 @@ public class InputPanel extends ConstraintLayout
View quoteDismiss = findViewById(R.id.quote_dismiss);
this.quoteView = findViewById(R.id.quote_view);
this.mediaKeyboard = findViewById(R.id.emoji_toggle);
this.emojiToggle = findViewById(R.id.emoji_toggle);
this.composeText = findViewById(R.id.embedded_text_editor);
this.quickCameraToggle = findViewById(R.id.quick_camera_toggle);
this.quickAudioToggle = findViewById(R.id.quick_audio_toggle);
@@ -106,21 +102,13 @@ public class InputPanel extends ConstraintLayout
this.recordLockCancel.setOnClickListener(v -> microphoneRecorderView.cancelAction());
if (Prefs.isSystemEmojiPreferred(getContext())) {
mediaKeyboard.setVisibility(View.GONE);
emojiVisible = false;
} else {
mediaKeyboard.setVisibility(View.VISIBLE);
emojiVisible = true;
}
quoteDismiss.setOnClickListener(v -> clearQuote());
}
public void setListener(final @NonNull Listener listener) {
this.listener = listener;
mediaKeyboard.setOnClickListener(v -> listener.onEmojiToggle());
emojiToggle.setOnClickListener(v -> listener.onEmojiToggle());
}
public void setMediaListener(@NonNull MediaListener listener) {
@@ -207,7 +195,7 @@ public class InputPanel extends ConstraintLayout
}
public void setMediaKeyboard(@NonNull MediaKeyboard mediaKeyboard) {
this.mediaKeyboard.attach(mediaKeyboard);
mediaKeyboard.setKeyboardListener(this);
}
@Override
@@ -221,7 +209,7 @@ public class InputPanel extends ConstraintLayout
recordTime.display();
slideToCancel.display();
if (emojiVisible) ViewUtil.fadeOut(mediaKeyboard, FADE_TIME, View.INVISIBLE);
ViewUtil.fadeOut(emojiToggle, FADE_TIME, View.INVISIBLE);
ViewUtil.fadeOut(composeText, FADE_TIME, View.INVISIBLE);
ViewUtil.fadeOut(quickCameraToggle, FADE_TIME, View.INVISIBLE);
ViewUtil.fadeOut(quickAudioToggle, FADE_TIME, View.INVISIBLE);
@@ -276,7 +264,7 @@ public class InputPanel extends ConstraintLayout
public void setEnabled(boolean enabled) {
composeText.setEnabled(enabled);
mediaKeyboard.setEnabled(enabled);
emojiToggle.setEnabled(enabled);
quickAudioToggle.setEnabled(enabled);
quickCameraToggle.setEnabled(enabled);
}
@@ -290,7 +278,7 @@ public class InputPanel extends ConstraintLayout
future.addListener(new AssertedSuccessListener<Void>() {
@Override
public void onSuccess(Void result) {
if (emojiVisible) ViewUtil.fadeIn(mediaKeyboard, FADE_TIME);
ViewUtil.fadeIn(emojiToggle, FADE_TIME);
ViewUtil.fadeIn(composeText, FADE_TIME);
ViewUtil.fadeIn(quickCameraToggle, FADE_TIME);
ViewUtil.fadeIn(quickAudioToggle, FADE_TIME);
@@ -304,17 +292,7 @@ public class InputPanel extends ConstraintLayout
@Override
public void onKeyboardShown() {
mediaKeyboard.setToMedia();
}
@Override
public void onKeyEvent(KeyEvent keyEvent) {
composeText.dispatchKeyEvent(keyEvent);
}
@Override
public void onEmojiSelected(String emoji) {
composeText.insertEmoji(emoji);
emojiToggle.setToMedia();
}
public boolean isRecordingInLockedMode() {
@@ -325,6 +303,25 @@ public class InputPanel extends ConstraintLayout
microphoneRecorderView.unlockAction();
}
@Override
public void onShown() {
emojiToggle.setToIme();
}
@Override
public void onHidden() {
emojiToggle.setToMedia();
}
@Override
public void onEmojiPicked(String emoji) {
final int start = composeText.getSelectionStart();
final int end = composeText.getSelectionEnd();
composeText.getText().replace(Math.min(start, end), Math.max(start, end), emoji);
composeText.setSelection(start + emoji.length());
}
public interface Listener {
void onRecorderStarted();
void onRecorderLocked();
@@ -104,7 +104,7 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
}
private void updateKeyboardState() {
if (viewInset == 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) viewInset = getViewInset();
if (viewInset == 0) viewInset = getViewInset();
getWindowVisibleDisplayFrame(rect);
@@ -148,7 +148,6 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private int getViewInset() {
try {
Field attachInfoField = View.class.getDeclaredField("mAttachInfo");
@@ -219,11 +218,7 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
return Surface.ROTATION_0;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
return ServiceUtil.getWindowManager(getContext()).getDefaultDisplay().getRotation();
}
if (Build.VERSION.SDK_INT >= 30) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
getContext().getDisplay().getRealMetrics(displayMetrics);
} else {
ServiceUtil.getWindowManager(getContext()).getDefaultDisplay().getRealMetrics(displayMetrics);
@@ -40,7 +40,6 @@ public class MediaView extends FrameLayout {
initialize();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public MediaView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize();
@@ -4,7 +4,6 @@ package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.net.Uri;
import android.os.Build;
import android.text.Spannable;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -17,7 +16,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.annimon.stream.Stream;
import com.b44t.messenger.DcContact;
@@ -80,7 +78,6 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
initialize(attrs);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public QuoteView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize(attrs);
@@ -158,7 +155,7 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
if (contact == null) {
authorView.setText(getContext().getString(R.string.forwarded_message));
} else {
authorView.setText(getContext().getString(R.string.forwarded_by, quotedMsg.getSenderName(contact, false)));
authorView.setText(getContext().getString(R.string.forwarded_by, quotedMsg.getSenderName(contact)));
}
authorView.setTextColor(getForwardedColor());
quoteBarView.setBackgroundColor(getForwardedColor());
@@ -169,7 +166,7 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
quoteBarView.setBackgroundColor(getForwardedColor());
} else {
authorView.setVisibility(VISIBLE);
authorView.setText(quotedMsg.getSenderName(contact, true));
authorView.setText(quotedMsg.getSenderName(contact));
if (hasSticker) {
authorView.setTextColor(getResources().getColor(R.color.core_dark_05));
quoteBarView.setBackgroundColor(getResources().getColor(R.color.core_dark_05));
@@ -29,7 +29,6 @@ public class RepeatableImageKey extends ImageButton {
init();
}
@TargetApi(VERSION_CODES.LOLLIPOP)
public RepeatableImageKey(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes)
{
@@ -130,15 +130,11 @@ public class SearchToolbar extends LinearLayout {
searchItem.expandActionView();
if (Build.VERSION.SDK_INT >= 21) {
Animator animator = ViewAnimationUtils.createCircularReveal(this, (int)x, (int)y, 0, getWidth());
animator.setDuration(400);
Animator animator = ViewAnimationUtils.createCircularReveal(this, (int) x, (int) y, 0, getWidth());
animator.setDuration(400);
setVisibility(View.VISIBLE);
animator.start();
} else {
setVisibility(View.VISIBLE);
}
setVisibility(View.VISIBLE);
animator.start();
}
}
@@ -153,19 +149,15 @@ public class SearchToolbar extends LinearLayout {
if (listener != null) listener.onSearchClosed();
if (Build.VERSION.SDK_INT >= 21) {
Animator animator = ViewAnimationUtils.createCircularReveal(this, (int)x, (int)y, getWidth(), 0);
animator.setDuration(400);
animator.addListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
setVisibility(View.INVISIBLE);
}
});
animator.start();
} else {
setVisibility(View.INVISIBLE);
}
Animator animator = ViewAnimationUtils.createCircularReveal(this, (int) x, (int) y, getWidth(), 0);
animator.setDuration(400);
animator.addListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
setVisibility(View.INVISIBLE);
}
});
animator.start();
}
}
@@ -18,7 +18,6 @@ public class SwitchPreferenceCompat extends CheckBoxPreference {
setLayoutRes();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SwitchPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setLayoutRes();
@@ -1,12 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.Callback;
import android.text.style.ImageSpan;
public class AnimatingImageSpan extends ImageSpan {
public AnimatingImageSpan(Drawable drawable, Callback callback) {
super(drawable, ALIGN_BOTTOM);
drawable.setCallback(callback);
}
}
@@ -1,60 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ResUtil;
public class AsciiEmojiView extends View {
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private String emoji;
public AsciiEmojiView(Context context) {
super(context);
}
public AsciiEmojiView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public void setEmoji(String emoji) {
this.emoji = emoji;
}
@Override
protected void onDraw(Canvas canvas) {
if (TextUtils.isEmpty(emoji)) {
return;
}
float targetFontSize = 0.75f * getHeight() - getPaddingTop() - getPaddingBottom();
paint.setTextSize(targetFontSize);
paint.setColor(ResUtil.getColor(getContext(), R.attr.emoji_text_color));
paint.setTextAlign(Paint.Align.CENTER);
int xPos = (getWidth() / 2);
int yPos = (int) ((getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2));
float overflow = paint.measureText(emoji) / (getWidth() - getPaddingLeft() - getPaddingRight());
if (overflow > 1f) {
paint.setTextSize(targetFontSize / overflow);
yPos = (int) ((getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2));
}
canvas.drawText(emoji, xPos, yPos, paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//noinspection SuspiciousNameCombination
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
}
File diff suppressed because one or more lines are too long
@@ -1,55 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.LinkedList;
import java.util.List;
public class CompositeEmojiPageModel implements EmojiPageModel {
@AttrRes private final int iconAttr;
@NonNull private final EmojiPageModel[] models;
public CompositeEmojiPageModel(@AttrRes int iconAttr, @NonNull EmojiPageModel... models) {
this.iconAttr = iconAttr;
this.models = models;
}
public int getIconAttr() {
return iconAttr;
}
@Override
public @NonNull List<String> getEmoji() {
List<String> emojis = new LinkedList<>();
for (EmojiPageModel model : models) {
emojis.addAll(model.getEmoji());
}
return emojis;
}
@Override
public @NonNull List<Emoji> getDisplayEmoji() {
List<Emoji> emojis = new LinkedList<>();
for (EmojiPageModel model : models) {
emojis.addAll(model.getDisplayEmoji());
}
return emojis;
}
@Override
public boolean hasSpriteMap() {
return false;
}
@Override
public @Nullable String getSprite() {
return null;
}
@Override
public boolean isDynamic() {
return false;
}
}
@@ -1,21 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import java.util.Arrays;
import java.util.List;
public class Emoji {
private final List<String> variations;
public Emoji(String... variations) {
this.variations = Arrays.asList(variations);
}
public String getValue() {
return variations.get(0);
}
public List<String> getVariations() {
return variations;
}
}
@@ -1,62 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatEditText;
import android.text.InputFilter;
import android.util.AttributeSet;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
import org.thoughtcrime.securesms.util.Prefs;
public class EmojiEditText extends AppCompatEditText {
private static final String TAG = EmojiEditText.class.getSimpleName();
public EmojiEditText(Context context) {
this(context, null);
}
public EmojiEditText(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.editTextStyle);
}
public EmojiEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (!Prefs.isSystemEmojiPreferred(getContext())) {
setFilters(appendEmojiFilter(this.getFilters()));
}
}
public void insertEmoji(String emoji) {
final int start = getSelectionStart();
final int end = getSelectionEnd();
getText().replace(Math.min(start, end), Math.max(start, end), emoji);
setSelection(start + emoji.length());
}
@Override
public void invalidateDrawable(@NonNull Drawable drawable) {
if (drawable instanceof EmojiDrawable) invalidate();
else super.invalidateDrawable(drawable);
}
private InputFilter[] appendEmojiFilter(@Nullable InputFilter[] originalFilters) {
InputFilter[] result;
if (originalFilters != null) {
result = new InputFilter[originalFilters.length + 1];
System.arraycopy(originalFilters, 0, result, 1, originalFilters.length);
} else {
result = new InputFilter[1];
}
result[0] = new EmojiFilter(this);
return result;
}
}
@@ -1,30 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.text.InputFilter;
import android.text.Spannable;
import android.text.Spanned;
import android.text.TextUtils;
import android.widget.TextView;
public class EmojiFilter implements InputFilter {
private final TextView view;
public EmojiFilter(TextView view) {
this.view = view;
}
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend)
{
char[] v = new char[end - start];
TextUtils.getChars(source, start, end, v, 0);
Spannable emojified = EmojiProvider.getInstance(view.getContext()).emojify(new String(v), view);
if (source instanceof Spanned && emojified != null) {
TextUtils.copySpansFrom((Spanned) source, start, end, null, emojified, 0);
}
return emojified;
}
}
@@ -1,20 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatImageView;
public class EmojiImageView extends AppCompatImageView {
public EmojiImageView(Context context) {
super(context);
}
public EmojiImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setImageEmoji(CharSequence emoji) {
setImageDrawable(EmojiProvider.getInstance(getContext()).getEmojiDrawable(emoji));
}
}
@@ -1,166 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.util.ResUtil;
import org.thoughtcrime.securesms.util.ThemeUtil;
import java.util.LinkedList;
import java.util.List;
/**
* A provider to select emoji in the {@link org.thoughtcrime.securesms.components.emoji.MediaKeyboard}.
*/
public class EmojiKeyboardProvider implements MediaKeyboardProvider,
MediaKeyboardProvider.TabIconProvider,
MediaKeyboardProvider.BackspaceObserver,
VariationSelectorListener
{
private static final KeyEvent DELETE_KEY_EVENT = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
private static final String RECENT_STORAGE_KEY = "pref_recent_emoji2";
private final Context context;
private final List<EmojiPageModel> models;
private final RecentEmojiPageModel recentModel;
private final EmojiPagerAdapter emojiPagerAdapter;
private final EmojiEventListener emojiEventListener;
private Controller controller;
public EmojiKeyboardProvider(@NonNull Context context, @Nullable EmojiEventListener emojiEventListener) {
this.context = context;
this.emojiEventListener = emojiEventListener;
this.models = new LinkedList<>();
this.recentModel = new RecentEmojiPageModel(context, RECENT_STORAGE_KEY);
this.emojiPagerAdapter = new EmojiPagerAdapter(context, models, new EmojiEventListener() {
@Override
public void onEmojiSelected(String emoji) {
recentModel.onCodePointSelected(emoji);
if (emojiEventListener != null) {
emojiEventListener.onEmojiSelected(emoji);
}
}
@Override
public void onKeyEvent(KeyEvent keyEvent) {
if (emojiEventListener != null) {
emojiEventListener.onKeyEvent(keyEvent);
}
}
}, this);
models.add(recentModel);
models.addAll(EmojiPages.DISPLAY_PAGES);
}
@Override
public void requestPresentation(@NonNull Presenter presenter, boolean isSoloProvider) {
presenter.present(this, emojiPagerAdapter, this, this, null, null, recentModel.getEmoji().size() > 0 ? 0 : 1);
}
@Override
public void setController(@Nullable Controller controller) {
this.controller = controller;
}
@Override
public int getProviderIconView(boolean selected) {
if (selected) {
return ThemeUtil.isDarkTheme(context) ? R.layout.emoji_keyboard_icon_dark_selected : R.layout.emoji_keyboard_icon_light_selected;
} else {
return ThemeUtil.isDarkTheme(context) ? R.layout.emoji_keyboard_icon_dark : R.layout.emoji_keyboard_icon_light;
}
}
@Override
public void loadCategoryTabIcon(@NonNull GlideRequests glideRequests, @NonNull ImageView imageView, int index) {
Drawable drawable = ResUtil.getDrawable(context, models.get(index).getIconAttr());
imageView.setImageDrawable(drawable);
}
@Override
public void onBackspaceClicked() {
if (emojiEventListener != null) {
emojiEventListener.onKeyEvent(DELETE_KEY_EVENT);
}
}
@Override
public void onVariationSelectorStateChanged(boolean open) {
if (controller != null) {
controller.setViewPagerEnabled(!open);
}
}
@Override
public boolean equals(@Nullable Object obj) {
return obj instanceof EmojiKeyboardProvider;
}
private static class EmojiPagerAdapter extends PagerAdapter {
private final Context context;
private final List<EmojiPageModel> pages;
private final EmojiEventListener emojiSelectionListener;
private final VariationSelectorListener variationSelectorListener;
public EmojiPagerAdapter(@NonNull Context context,
@NonNull List<EmojiPageModel> pages,
@NonNull EmojiEventListener emojiSelectionListener,
@NonNull VariationSelectorListener variationSelectorListener)
{
super();
this.context = context;
this.pages = pages;
this.emojiSelectionListener = emojiSelectionListener;
this.variationSelectorListener = variationSelectorListener;
}
@Override
public int getCount() {
return pages.size();
}
@Override
public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
EmojiPageView page = new EmojiPageView(context, emojiSelectionListener, variationSelectorListener, true);
page.setModel(pages.get(position));
container.addView(page);
return page;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
EmojiPageView current = (EmojiPageView) object;
current.onSelected();
super.setPrimaryItem(container, position, object);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
public interface EmojiEventListener {
void onEmojiSelected(String emoji);
void onKeyEvent(KeyEvent keyEvent);
}
}
@@ -1,12 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import java.util.List;
public interface EmojiPageModel {
int getIconAttr();
List<String> getEmoji();
List<Emoji> getDisplayEmoji();
boolean hasSpriteMap();
String getSprite();
boolean isDynamic();
}
@@ -1,104 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider.EmojiEventListener;
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener;
public class EmojiPageView extends FrameLayout implements VariationSelectorListener {
private static final String TAG = EmojiPageView.class.getSimpleName();
private EmojiPageModel model;
private final EmojiPageViewGridAdapter adapter;
private final RecyclerView recyclerView;
private final GridLayoutManager layoutManager;
private final RecyclerView.OnItemTouchListener scrollDisabler;
private final VariationSelectorListener variationSelectorListener;
private final EmojiVariationSelectorPopup popup;
public EmojiPageView(@NonNull Context context,
@NonNull EmojiEventListener emojiSelectionListener,
@NonNull VariationSelectorListener variationSelectorListener,
boolean allowVariations)
{
super(context);
final View view = LayoutInflater.from(getContext()).inflate(R.layout.emoji_grid_layout, this, true);
this.variationSelectorListener = variationSelectorListener;
recyclerView = view.findViewById(R.id.emoji);
layoutManager = new GridLayoutManager(context, 8);
scrollDisabler = new ScrollDisabler();
popup = new EmojiVariationSelectorPopup(context, emojiSelectionListener);
adapter = new EmojiPageViewGridAdapter(EmojiProvider.getInstance(context),
popup,
emojiSelectionListener,
this,
allowVariations);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
}
public void onSelected() {
if (model.isDynamic() && adapter != null) {
adapter.notifyDataSetChanged();
}
}
public void setModel(EmojiPageModel model) {
this.model = model;
adapter.setEmoji(model.getDisplayEmoji());
}
@Override
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
if (visibility != VISIBLE) {
popup.dismiss();
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
int idealWidth = getContext().getResources().getDimensionPixelOffset(R.dimen.emoji_drawer_item_width);
layoutManager.setSpanCount(Math.max(w / idealWidth, 1));
}
@Override
public void onVariationSelectorStateChanged(boolean open) {
if (open) {
recyclerView.addOnItemTouchListener(scrollDisabler);
} else {
post(() -> recyclerView.removeOnItemTouchListener(scrollDisabler));
}
if (variationSelectorListener != null) {
variationSelectorListener.onVariationSelectorStateChanged(open);
}
}
public void setRecyclerNestedScrollingEnabled(boolean enabled) {
recyclerView.setNestedScrollingEnabled(enabled);
}
private static class ScrollDisabler implements RecyclerView.OnItemTouchListener {
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
return true;
}
@Override
public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) { }
@Override
public void onRequestDisallowInterceptTouchEvent(boolean b) { }
}
}
@@ -1,120 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.PopupWindow;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider.EmojiEventListener;
import java.util.ArrayList;
import java.util.List;
public class EmojiPageViewGridAdapter extends RecyclerView.Adapter<EmojiPageViewGridAdapter.EmojiViewHolder> implements PopupWindow.OnDismissListener {
private final List<Emoji> emojiList;
private final EmojiProvider emojiProvider;
private final EmojiVariationSelectorPopup popup;
private final VariationSelectorListener variationSelectorListener;
private final EmojiEventListener emojiEventListener;
private final boolean allowVariations;
public EmojiPageViewGridAdapter(@NonNull EmojiProvider emojiProvider,
@NonNull EmojiVariationSelectorPopup popup,
@NonNull EmojiEventListener emojiEventListener,
@NonNull VariationSelectorListener variationSelectorListener,
boolean allowVariations)
{
this.emojiList = new ArrayList<>();
this.emojiProvider = emojiProvider;
this.popup = popup;
this.emojiEventListener = emojiEventListener;
this.variationSelectorListener = variationSelectorListener;
this.allowVariations = allowVariations;
popup.setOnDismissListener(this);
}
@NonNull
@Override
public EmojiViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return new EmojiViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.emoji_display_item, viewGroup, false));
}
@Override
public void onBindViewHolder(@NonNull EmojiViewHolder viewHolder, int i) {
Emoji emoji = emojiList.get(i);
Drawable drawable = emojiProvider.getEmojiDrawable(emoji.getValue());
if (drawable != null) {
viewHolder.textView.setVisibility(View.GONE);
viewHolder.imageView.setVisibility(View.VISIBLE);
viewHolder.imageView.setContentDescription(emoji.getValue());
viewHolder.imageView.setImageDrawable(drawable);
} else {
viewHolder.textView.setVisibility(View.VISIBLE);
viewHolder.imageView.setVisibility(View.GONE);
viewHolder.textView.setEmoji(emoji.getValue());
}
viewHolder.itemView.setOnClickListener(v -> {
emojiEventListener.onEmojiSelected(emoji.getValue());
});
if (allowVariations && emoji.getVariations().size() > 1) {
viewHolder.itemView.setOnLongClickListener(v -> {
popup.dismiss();
popup.setVariations(emoji.getVariations());
popup.showAsDropDown(viewHolder.itemView, 0, -(2 * viewHolder.itemView.getHeight()));
variationSelectorListener.onVariationSelectorStateChanged(true);
return true;
});
viewHolder.hintCorner.setVisibility(View.VISIBLE);
} else {
viewHolder.itemView.setOnLongClickListener(null);
viewHolder.hintCorner.setVisibility(View.GONE);
}
}
@Override
public int getItemCount() {
return emojiList.size();
}
public void setEmoji(@NonNull List<Emoji> emojiList) {
this.emojiList.clear();
this.emojiList.addAll(emojiList);
notifyDataSetChanged();
}
@Override
public void onDismiss() {
variationSelectorListener.onVariationSelectorStateChanged(false);
}
static class EmojiViewHolder extends RecyclerView.ViewHolder {
private final ImageView imageView;
private final AsciiEmojiView textView;
private final ImageView hintCorner;
public EmojiViewHolder(@NonNull View itemView) {
super(itemView);
this.imageView = itemView.findViewById(R.id.emoji_image);
this.textView = itemView.findViewById(R.id.emoji_text);
this.hintCorner = itemView.findViewById(R.id.emoji_variation_hint);
}
}
public interface VariationSelectorListener {
void onVariationSelectorStateChanged(boolean open);
}
}
File diff suppressed because one or more lines are too long
@@ -1,258 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.util.Log;
import android.widget.TextView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiPageBitmap;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiTree;
import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.Pair;
import org.thoughtcrime.securesms.util.StringUtil;
import org.thoughtcrime.securesms.util.Util;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.regex.Pattern;
public class EmojiProvider {
private static final String TAG = EmojiProvider.class.getSimpleName();
private static volatile EmojiProvider instance = null;
private static final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
private static final Pattern EMOJI_PATTERN = Pattern.compile("^(?:(?:[\u00a9\u00ae\u203c\u2049\u2122\u2139\u2194-\u2199\u21a9-\u21aa\u231a-\u231b\u2328\u23cf\u23e9-\u23f3\u23f8-\u23fa\u24c2\u25aa-\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614-\u2615\u2618\u261d\u2620\u2622-\u2623\u2626\u262a\u262e-\u262f\u2638-\u263a\u2648-\u2653\u2660\u2663\u2665-\u2666\u2668\u267b\u267f\u2692-\u2694\u2696-\u2697\u2699\u269b-\u269c\u26a0-\u26a1\u26aa-\u26ab\u26b0-\u26b1\u26bd-\u26be\u26c4-\u26c5\u26c8\u26ce-\u26cf\u26d1\u26d3-\u26d4\u26e9-\u26ea\u26f0-\u26f5\u26f7-\u26fa\u26fd\u2702\u2705\u2708-\u270d\u270f\u2712\u2714\u2716\u271d\u2721\u2728\u2733-\u2734\u2744\u2747\u274c\u274e\u2753-\u2755\u2757\u2763-\u2764\u2795-\u2797\u27a1\u27b0\u27bf\u2934-\u2935\u2b05-\u2b07\u2b1b-\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299\ud83c\udc04\ud83c\udccf\ud83c\udd70-\ud83c\udd71\ud83c\udd7e-\ud83c\udd7f\ud83c\udd8e\ud83c\udd91-\ud83c\udd9a\ud83c\ude01-\ud83c\ude02\ud83c\ude1a\ud83c\ude2f\ud83c\ude32-\ud83c\ude3a\ud83c\ude50-\ud83c\ude51\u200d\ud83c\udf00-\ud83d\uddff\ud83d\ude00-\ud83d\ude4f\ud83d\ude80-\ud83d\udeff\ud83e\udd00-\ud83e\uddff\udb40\udc20-\udb40\udc7f]|\u200d[\u2640\u2642]|[\ud83c\udde6-\ud83c\uddff]{2}|.[\u20e0\u20e3\ufe0f]+)+)+$");
private final EmojiTree emojiTree = new EmojiTree();
private static final int EMOJI_RAW_HEIGHT = 64;
private static final int EMOJI_RAW_WIDTH = 64;
private static final int EMOJI_VERT_PAD = 0;
private static final int EMOJI_PER_ROW = 16;
private final float decodeScale;
private final float verticalPad;
public static EmojiProvider getInstance(Context context) {
if (instance == null) {
synchronized (EmojiProvider.class) {
if (instance == null) {
instance = new EmojiProvider(context);
}
}
}
return instance;
}
private EmojiProvider(Context context) {
this.decodeScale = Math.min(1f, context.getResources().getDimension(R.dimen.emoji_drawer_size) / EMOJI_RAW_HEIGHT);
this.verticalPad = EMOJI_VERT_PAD * this.decodeScale;
for (EmojiPageModel page : EmojiPages.DATA_PAGES) {
if (page.hasSpriteMap()) {
EmojiPageBitmap pageBitmap = new EmojiPageBitmap(context, page, decodeScale);
List<String> emojis = page.getEmoji();
for (int i = 0; i < emojis.size(); i++) {
emojiTree.add(emojis.get(i), new EmojiDrawInfo(pageBitmap, i));
}
}
}
for (Pair<String,String> obsolete : EmojiPages.OBSOLETE) {
emojiTree.add(obsolete.first(), emojiTree.getEmoji(obsolete.second(), 0, obsolete.second().length()));
}
}
@Nullable EmojiParser.CandidateList getCandidates(@Nullable CharSequence text) {
if (text == null) return null;
return new EmojiParser(emojiTree).findCandidates(text);
}
@Nullable Spannable emojify(@Nullable CharSequence text, @NonNull TextView tv) {
return emojify(getCandidates(text), text, tv, false);
}
@Nullable Spannable emojify(@Nullable EmojiParser.CandidateList matches,
@Nullable CharSequence text,
@NonNull TextView tv,
boolean background) {
if (matches == null || text == null) return null;
SpannableStringBuilder builder = new SpannableStringBuilder(text);
for (EmojiParser.Candidate candidate : matches) {
Drawable drawable = getEmojiDrawable(candidate.getDrawInfo(), background);
if (drawable != null) {
builder.setSpan(new EmojiSpan(drawable, tv), candidate.getStartIndex(), candidate.getEndIndex(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
return builder;
}
@Nullable Drawable getEmojiDrawable(CharSequence emoji) {
EmojiDrawInfo drawInfo = emojiTree.getEmoji(emoji, 0, emoji.length());
return getEmojiDrawable(drawInfo, false);
}
public boolean isEmoji(CharSequence emoji) {
return emojiTree.getEmoji(emoji, 0, emoji.length()) != null;
}
/**
* True if the text is likely a single, valid emoji. Otherwise false.
*
* We do a two-tier check: first using our own knowledge of emojis (which could be incomplete),
* followed by a more wide check for all of the valid emoji unicode ranges (which could lead to
* some false positives). YMMV.
*/
public boolean maybeEmoji(CharSequence emoji) {
if (Util.isEmpty(emoji)) {
return false;
}
if (StringUtil.getGraphemeCount(emoji) != 1) {
return false;
}
return isEmoji(emoji) || EMOJI_PATTERN.matcher(emoji).matches();
}
public @Nullable Bitmap getEmojiBitmap(CharSequence emoji, float scale, boolean background) {
EmojiDrawInfo drawInfo = emojiTree.getEmoji(emoji, 0, emoji.length());
EmojiDrawable drawable = ((EmojiDrawable) getEmojiDrawable(drawInfo, background));
if (drawable != null) {
return drawable.getEmojiBitmap(scale);
}
return null;
}
protected @Nullable Drawable getEmojiDrawable(@Nullable EmojiDrawInfo drawInfo, boolean background) {
if (drawInfo == null) {
return null;
}
final EmojiDrawable drawable = new EmojiDrawable(drawInfo, decodeScale);
if (background) {
try {
drawable.setBitmap(drawInfo.getPage().loadPage(), background);
} catch (IOException e) {
e.printStackTrace();
}
} else {
drawInfo.getPage().get().addListener(new FutureTaskListener<Bitmap>() {
@Override public void onSuccess(final Bitmap result) {
Util.runOnMain(() -> drawable.setBitmap(result));
}
@Override public void onFailure(ExecutionException error) {
Log.w(TAG, error);
}
});
}
return drawable;
}
class EmojiDrawable extends Drawable {
private final EmojiDrawInfo info;
private Bitmap bmp;
private final float intrinsicWidth;
private final float intrinsicHeight;
@Override
public int getIntrinsicWidth() {
return (int)intrinsicWidth;
}
@Override
public int getIntrinsicHeight() {
return (int)intrinsicHeight;
}
EmojiDrawable(EmojiDrawInfo info, float decodeScale) {
this.info = info;
this.intrinsicWidth = EMOJI_RAW_WIDTH * decodeScale;
this.intrinsicHeight = EMOJI_RAW_HEIGHT * decodeScale;
}
private Bitmap getEmojiBitmap(float scale) {
Bitmap singleEmoji = Bitmap.createBitmap((int) (intrinsicWidth * scale), (int) (intrinsicHeight*scale), Bitmap.Config.ARGB_8888);
final int row = info.getIndex() / EMOJI_PER_ROW;
final int rowIndex = info.getIndex() % EMOJI_PER_ROW;
Rect desRect = new Rect(0, 0, (int) intrinsicWidth, (int) intrinsicWidth);
Rect srcRect = new Rect((int)(rowIndex * intrinsicWidth),
(int)(row * intrinsicHeight + row * verticalPad)+1,
(int)(((rowIndex + 1) * intrinsicWidth)-1),
(int)((row + 1) * intrinsicHeight + row * verticalPad)-1);
Canvas canvas = new Canvas(singleEmoji);
canvas.scale(scale, scale);
canvas.drawBitmap(bmp, srcRect, desRect, paint);
return singleEmoji;
}
@Override
public void draw(@NonNull Canvas canvas) {
if (bmp == null) {
return;
}
final int row = info.getIndex() / EMOJI_PER_ROW;
final int row_index = info.getIndex() % EMOJI_PER_ROW;
canvas.drawBitmap(bmp,
new Rect((int)(row_index * intrinsicWidth),
(int)(row * intrinsicHeight + row * verticalPad)+1,
(int)(((row_index + 1) * intrinsicWidth)-1),
(int)((row + 1) * intrinsicHeight + row * verticalPad)-1),
getBounds(),
paint);
}
public void setBitmap(Bitmap bitmap) {
setBitmap(bitmap, false);
}
public void setBitmap(Bitmap bitmap, boolean background) {
if (!background) {
Util.assertMainThread();
}
if (bmp == null || !bmp.sameAs(bitmap)) {
bmp = bitmap;
invalidateSelf();
}
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void setAlpha(int alpha) { }
@Override
public void setColorFilter(ColorFilter cf) { }
}
}
@@ -1,48 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import android.widget.TextView;
import org.thoughtcrime.securesms.R;
public class EmojiSpan extends AnimatingImageSpan {
private final float SHIFT_FACTOR = 1.5f;
private final int size;
private final FontMetricsInt fm;
public EmojiSpan(@NonNull Drawable drawable, @NonNull TextView tv) {
super(drawable, tv);
fm = tv.getPaint().getFontMetricsInt();
size = fm != null ? Math.abs(fm.descent) + Math.abs(fm.ascent)
: tv.getResources().getDimensionPixelSize(R.dimen.conversation_item_body_text_size);
getDrawable().setBounds(0, 0, size, size);
}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) {
if (fm != null && this.fm != null) {
fm.ascent = this.fm.ascent;
fm.descent = this.fm.descent;
fm.top = this.fm.top;
fm.bottom = this.fm.bottom;
fm.leading = this.fm.leading;
return size;
} else {
return super.getSize(paint, text, start, end, fm);
}
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
int height = bottom - top;
int centeringMargin = (height - size) / 2;
int adjustedMargin = (int) (centeringMargin * SHIFT_FACTOR);
super.draw(canvas, text, start, end, x, top, y, bottom - adjustedMargin, paint);
}
}
@@ -1,5 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
public final class EmojiStrings {
public static final String BUST_IN_SILHOUETTE = "\uD83D\uDC64";
}
@@ -1,249 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.util.AttributeSet;
import android.util.TypedValue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.core.widget.TextViewCompat;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
import org.thoughtcrime.securesms.util.LongClickCopySpan;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.guava.Optional;
import java.util.regex.Pattern;
public class EmojiTextView extends AppCompatTextView {
private final boolean scaleEmojis;
private final boolean forceCustom;
private final boolean createInBackground;
private static final char ELLIPSIS = '…';
private CharSequence previousText;
private BufferType previousBufferType;
private float originalFontSize;
private boolean useSystemEmoji;
private boolean sizeChangeInProgress;
private final int maxLength;
private CharSequence overflowText;
private CharSequence previousOverflowText;
public EmojiTextView(Context context) {
this(context, null);
}
public EmojiTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public EmojiTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EmojiTextView, 0, 0);
scaleEmojis = a.getBoolean(R.styleable.EmojiTextView_scaleEmojis, false);
maxLength = a.getInteger(R.styleable.EmojiTextView_emoji_maxLength, -1);
forceCustom = a.getBoolean(R.styleable.EmojiTextView_emoji_forceCustom, false);
createInBackground = a.getBoolean(R.styleable.EmojiTextView_createInBackground, false);
a.recycle();
a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.textSize});
originalFontSize = a.getDimensionPixelSize(0, 0);
a.recycle();
}
@Override public void setText(@Nullable CharSequence text, BufferType type) {
EmojiProvider provider = EmojiProvider.getInstance(getContext());
EmojiParser.CandidateList candidates = provider.getCandidates(text);
if (scaleEmojis && candidates != null && candidates.allEmojis) {
int emojis = candidates.size();
float scale = 1.0f;
if (emojis <= 8) scale += 0.25f;
if (emojis <= 6) scale += 0.25f;
if (emojis <= 4) scale += 0.25f;
if (emojis <= 2) scale += 0.25f;
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, originalFontSize * scale);
} else if (scaleEmojis) {
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, originalFontSize);
}
if (unchanged(text, overflowText, type)) {
return;
}
previousText = text;
previousOverflowText = overflowText;
previousBufferType = type;
useSystemEmoji = useSystemEmoji();
if (useSystemEmoji || candidates == null || candidates.size() == 0) {
super.setText(new SpannableStringBuilder(Optional.fromNullable(text).or("")).append(Optional.fromNullable(overflowText).or("")), BufferType.NORMAL);
if (getEllipsize() == TextUtils.TruncateAt.END && maxLength > 0) {
ellipsizeAnyTextForMaxLength();
}
} else {
CharSequence emojified = provider.emojify(candidates, text, this, createInBackground);
super.setText(new SpannableStringBuilder(emojified).append(Optional.fromNullable(overflowText).or("")), BufferType.SPANNABLE);
// Android fails to ellipsize spannable strings. (https://issuetracker.google.com/issues/36991688)
// We ellipsize them ourselves by manually truncating the appropriate section.
if (getEllipsize() == TextUtils.TruncateAt.END) {
if (maxLength > 0) {
ellipsizeAnyTextForMaxLength();
} else {
ellipsizeEmojiTextForMaxLines();
}
}
}
}
public void setOverflowText(@Nullable CharSequence overflowText) {
this.overflowText = overflowText;
setText(previousText, BufferType.SPANNABLE);
}
private void ellipsizeAnyTextForMaxLength() {
if (maxLength > 0 && getText().length() > maxLength + 1) {
SpannableStringBuilder newContent = new SpannableStringBuilder();
newContent.append(getText().subSequence(0, maxLength)).append(ELLIPSIS).append(Optional.fromNullable(overflowText).or(""));
EmojiParser.CandidateList newCandidates = EmojiProvider.getInstance(getContext()).getCandidates(newContent);
if (useSystemEmoji || newCandidates == null || newCandidates.size() == 0) {
super.setText(newContent, BufferType.NORMAL);
} else {
CharSequence emojified = EmojiProvider.getInstance(getContext()).emojify(newCandidates, newContent, this, createInBackground);
super.setText(emojified, BufferType.SPANNABLE);
}
}
}
private void ellipsizeEmojiTextForMaxLines() {
post(() -> {
if (getLayout() == null) {
ellipsizeEmojiTextForMaxLines();
return;
}
int maxLines = TextViewCompat.getMaxLines(EmojiTextView.this);
if (maxLines <= 0 && maxLength < 0) {
return;
}
int lineCount = getLineCount();
if (lineCount > maxLines) {
int overflowStart = getLayout().getLineStart(maxLines - 1);
CharSequence overflow = getText().subSequence(overflowStart, getText().length());
CharSequence ellipsized = TextUtils.ellipsize(overflow, getPaint(), getWidth(), TextUtils.TruncateAt.END);
SpannableStringBuilder newContent = new SpannableStringBuilder();
newContent.append(getText().subSequence(0, overflowStart))
.append(ellipsized.subSequence(0, ellipsized.length()))
.append(Optional.fromNullable(overflowText).or(""));
EmojiParser.CandidateList newCandidates = EmojiProvider.getInstance(getContext()).getCandidates(newContent);
CharSequence emojified = EmojiProvider.getInstance(getContext()).emojify(newCandidates, newContent, this, createInBackground);
super.setText(emojified, BufferType.SPANNABLE);
}
});
}
private boolean unchanged(CharSequence text, CharSequence overflowText, BufferType bufferType) {
return Util.equals(previousText, text) &&
Util.equals(previousOverflowText, overflowText) &&
Util.equals(previousBufferType, bufferType) &&
useSystemEmoji == useSystemEmoji() &&
!sizeChangeInProgress;
}
private boolean useSystemEmoji() {
return !forceCustom && Prefs.isSystemEmojiPreferred(getContext());
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (!sizeChangeInProgress) {
sizeChangeInProgress = true;
setText(previousText, previousBufferType);
sizeChangeInProgress = false;
}
}
@Override
public void invalidateDrawable(@NonNull Drawable drawable) {
if (drawable instanceof EmojiDrawable) invalidate();
else super.invalidateDrawable(drawable);
}
@Override
public void setTextSize(float size) {
setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
}
@Override
public void setTextSize(int unit, float size) {
this.originalFontSize = TypedValue.applyDimension(unit, size, getResources().getDisplayMetrics());
super.setTextSize(unit, size);
}
// tools for linkify
private static final Pattern CMD_PATTERN = Pattern.compile("(?<=^|\\s)/[a-zA-Z][a-zA-Z@\\d_/.-]{0,254}");
private static final Pattern CUSTOM_PATTERN = Pattern.compile("(?<=^|\\s)(OPENPGP4FPR|openpgp4fpr|mumble):[^ \\n]+");
private static final Pattern PROXY_PATTERN = Pattern.compile("(?<=^|\\s)(SOCKS5|socks5|ss|SS):[^ \\n]+");
private static void replaceURLSpan(Spannable messageBody) {
URLSpan[] urlSpans = messageBody.getSpans(0, messageBody.length(), URLSpan.class);
for (URLSpan urlSpan : urlSpans) {
int start = messageBody.getSpanStart(urlSpan);
int end = messageBody.getSpanEnd(urlSpan);
// LongClickCopySpan must not be derived from URLSpan, otherwise links will be removed on the next addLinks() call
messageBody.setSpan(new LongClickCopySpan(urlSpan.getURL()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
public static Spannable linkify(Spannable messageBody) {
// linkify commands such as `/echo` -
// do this first to avoid `/xkcd_123456` to be treated partly as a phone number
Linkify.addLinks(messageBody, CMD_PATTERN, "cmd:", null, null);
EmojiTextView.replaceURLSpan(messageBody); // replace URLSpan so that it is not removed on the next addLinks() call
Linkify.addLinks(messageBody, CUSTOM_PATTERN, null, null, null);
EmojiTextView.replaceURLSpan(messageBody);
if (Linkify.addLinks(messageBody, PROXY_PATTERN, null, null, null)) {
EmojiTextView.replaceURLSpan(messageBody); // replace URLSpan so that it is not removed on the next addLinks() call
}
// linkyfiy urls etc., this removes all existing URLSpan
if (Linkify.addLinks(messageBody, Linkify.EMAIL_ADDRESSES|Linkify.WEB_URLS|Linkify.PHONE_NUMBERS)) {
EmojiTextView.replaceURLSpan(messageBody);
}
return messageBody;
}
}
@@ -2,14 +2,14 @@ package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageButton;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatImageButton;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ResUtil;
public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.MediaKeyboardListener {
public class EmojiToggle extends AppCompatImageButton {
private Drawable emojiToggle;
// private Drawable stickerToggle;
@@ -50,10 +50,6 @@ public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.M
setToMedia();
}
public void attach(MediaKeyboard drawer) {
drawer.setKeyboardListener(this);
}
public void setStickerMode(boolean stickerMode) {
this.mediaToggle = /*stickerMode ? stickerToggle :*/ emojiToggle;
@@ -66,20 +62,4 @@ public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.M
//return this.mediaToggle == stickerToggle;
return false;
}
@Override public void onShown() {
setToIme();
}
@Override public void onHidden() {
setToMedia();
}
@Override
public void onKeyboardProviderChanged(@NonNull MediaKeyboardProvider provider) {
setStickerMode(false);
//setStickerMode(provider instanceof StickerKeyboardProvider);
/*TextSecurePreferences.setMediaKeyboardMode(getContext(), (provider instanceof StickerKeyboardProvider) ? TextSecurePreferences.MediaKeyboardMode.STICKER
: TextSecurePreferences.MediaKeyboardMode.EMOJI);*/
}
}
@@ -1,51 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.os.Build;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.PopupWindow;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider.EmojiEventListener;
import java.util.List;
public class EmojiVariationSelectorPopup extends PopupWindow {
private final Context context;
private final ViewGroup list;
private final EmojiEventListener listener;
public EmojiVariationSelectorPopup(@NonNull Context context, @NonNull EmojiEventListener listener) {
super(LayoutInflater.from(context).inflate(R.layout.emoji_variation_selector, null),
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
this.context = context;
this.listener = listener;
this.list = (ViewGroup) getContentView().findViewById(R.id.emoji_variation_container);
setBackgroundDrawable(null);
setOutsideTouchable(true);
if (Build.VERSION.SDK_INT >= 21) {
setElevation(20);
}
}
public void setVariations(List<String> variations) {
list.removeAllViews();
for (String variation : variations) {
ImageView imageView = (ImageView) LayoutInflater.from(context).inflate(R.layout.emoji_variation_selector_item, list, false);
imageView.setImageDrawable(EmojiProvider.getInstance(context).getEmojiDrawable(variation));
imageView.setOnClickListener(v -> {
listener.onEmojiSelected(variation);
dismiss();
});
list.addView(imageView);
}
}
}
@@ -1,74 +1,33 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import androidx.core.util.Consumer;
import androidx.emoji2.emojipicker.EmojiPickerView;
import androidx.emoji2.emojipicker.EmojiViewItem;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
import org.thoughtcrime.securesms.components.RepeatableImageKey;
import org.thoughtcrime.securesms.mms.GlideApp;
import java.util.Arrays;
public class MediaKeyboard extends FrameLayout implements InputView,
MediaKeyboardProvider.Presenter,
MediaKeyboardProvider.Controller,
MediaKeyboardBottomTabAdapter.EventListener
{
public class MediaKeyboard extends FrameLayout implements InputView, Consumer<EmojiViewItem> {
private static final String TAG = MediaKeyboard.class.getSimpleName();
private RecyclerView categoryTabs;
private ViewPager categoryPager;
private ViewGroup providerTabs;
private RepeatableImageKey backspaceButton;
private RepeatableImageKey backspaceButtonBackup;
private View searchButton;
private View addButton;
@Nullable private MediaKeyboardListener keyboardListener;
private MediaKeyboardProvider[] providers;
private int providerIndex;
private EmojiPickerView emojiPicker;
private final boolean tabsAtBottom;
private MediaKeyboardBottomTabAdapter categoryTabAdapter;
public MediaKeyboard(Context context) {
this(context, null);
public MediaKeyboard(@NonNull Context context) {
super(context);
}
public MediaKeyboard(Context context, AttributeSet attrs) {
public MediaKeyboard(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MediaKeyboard, 0, 0);
try {
tabsAtBottom = typedArray.getInt(R.styleable.MediaKeyboard_tabs_gravity, 0) == 0;
} finally {
typedArray.recycle();
}
}
public void setProviders(int startIndex, MediaKeyboardProvider... providers) {
if (!Arrays.equals(this.providers, providers)) {
this.providers = providers;
this.providerIndex = startIndex;
requestPresent(providers, providerIndex);
}
}
public void setKeyboardListener(@Nullable MediaKeyboardListener listener) {
@@ -82,8 +41,6 @@ public class MediaKeyboard extends FrameLayout implements InputView,
@Override
public void show(int height, boolean immediate) {
if (this.categoryPager == null) initView();
ViewGroup.LayoutParams params = getLayoutParams();
params.height = height;
Log.i(TAG, "showing emoji drawer with height " + params.height);
@@ -93,12 +50,12 @@ public class MediaKeyboard extends FrameLayout implements InputView,
}
public void show() {
if (this.categoryPager == null) initView();
if (emojiPicker == null) {
emojiPicker = findViewById(R.id.emoji_picker);
emojiPicker.setOnEmojiPickedListener(this);
};
setVisibility(VISIBLE);
if (keyboardListener != null) keyboardListener.onShown();
requestPresent(providers, providerIndex);
}
@Override
@@ -109,188 +66,13 @@ public class MediaKeyboard extends FrameLayout implements InputView,
}
@Override
public void present(@NonNull MediaKeyboardProvider provider,
@NonNull PagerAdapter pagerAdapter,
@NonNull MediaKeyboardProvider.TabIconProvider tabIconProvider,
@Nullable MediaKeyboardProvider.BackspaceObserver backspaceObserver,
@Nullable MediaKeyboardProvider.AddObserver addObserver,
@Nullable MediaKeyboardProvider.SearchObserver searchObserver,
int startingIndex)
{
if (categoryPager == null) return;
if (!provider.equals(providers[providerIndex])) return;
if (keyboardListener != null) keyboardListener.onKeyboardProviderChanged(provider);
boolean isSolo = providers.length == 1;
presentProviderStrip(isSolo);
presentCategoryPager(pagerAdapter, tabIconProvider, startingIndex);
presentProviderTabs(providers, providerIndex);
presentSearchButton(searchObserver);
presentBackspaceButton(backspaceObserver, isSolo);
presentAddButton(addObserver);
}
@Override
public int getCurrentPosition() {
return categoryPager != null ? categoryPager.getCurrentItem() : 0;
}
@Override
public void requestDismissal() {
hide(true);
providerIndex = 0;
if (keyboardListener != null) keyboardListener.onKeyboardProviderChanged(providers[providerIndex]);
}
@Override
public boolean isVisible() {
return getVisibility() == View.VISIBLE;
}
@Override
public void onTabSelected(int index) {
if (categoryPager != null) {
categoryPager.setCurrentItem(index);
categoryTabs.smoothScrollToPosition(index);
}
}
@Override
public void setViewPagerEnabled(boolean enabled) {
if (categoryPager != null) {
categoryPager.setEnabled(enabled);
}
}
private void initView() {
final View view = LayoutInflater.from(getContext()).inflate(R.layout.media_keyboard, this, true);
RecyclerView categoryTabsTop = view.findViewById(R.id.media_keyboard_tabs_top);
RecyclerView categoryTabsBottom = view.findViewById(R.id.media_keyboard_tabs);
this.categoryTabs = tabsAtBottom ? categoryTabsBottom : categoryTabsTop;
this.categoryPager = view.findViewById(R.id.media_keyboard_pager);
this.providerTabs = view.findViewById(R.id.media_keyboard_provider_tabs);
this.backspaceButton = view.findViewById(R.id.media_keyboard_backspace);
this.backspaceButtonBackup = view.findViewById(R.id.media_keyboard_backspace_backup);
this.searchButton = view.findViewById(R.id.media_keyboard_search);
this.addButton = view.findViewById(R.id.media_keyboard_add);
this.categoryTabAdapter = new MediaKeyboardBottomTabAdapter(GlideApp.with(this), this, tabsAtBottom);
categoryTabs.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
categoryTabs.setAdapter(categoryTabAdapter);
categoryTabs.setVisibility(VISIBLE);
}
private void requestPresent(@NonNull MediaKeyboardProvider[] providers, int newIndex) {
providers[providerIndex].setController(null);
providerIndex = newIndex;
providers[providerIndex].setController(this);
providers[providerIndex].requestPresentation(this, providers.length == 1);
}
private void presentCategoryPager(@NonNull PagerAdapter pagerAdapter,
@NonNull MediaKeyboardProvider.TabIconProvider iconProvider,
int startingIndex) {
if (categoryPager.getAdapter() != pagerAdapter) {
categoryPager.setAdapter(pagerAdapter);
}
categoryPager.setCurrentItem(startingIndex);
categoryPager.clearOnPageChangeListeners();
categoryPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {
}
@Override
public void onPageSelected(int i) {
categoryTabAdapter.setActivePosition(i);
categoryTabs.smoothScrollToPosition(i);
}
@Override
public void onPageScrollStateChanged(int i) {
}
});
categoryTabAdapter.setTabIconProvider(iconProvider, pagerAdapter.getCount());
categoryTabAdapter.setActivePosition(startingIndex);
}
private void presentProviderTabs(@NonNull MediaKeyboardProvider[] providers, int selected) {
providerTabs.removeAllViews();
LayoutInflater inflater = LayoutInflater.from(getContext());
for (int i = 0; i < providers.length; i++) {
MediaKeyboardProvider provider = providers[i];
View view = inflater.inflate(provider.getProviderIconView(i == selected), providerTabs, false);
view.setTag(provider);
final int index = i;
view.setOnClickListener(v -> {
requestPresent(providers, index);
});
providerTabs.addView(view);
}
}
private void presentBackspaceButton(@Nullable MediaKeyboardProvider.BackspaceObserver backspaceObserver,
boolean useBackupPosition)
{
if (backspaceObserver != null) {
if (useBackupPosition) {
backspaceButton.setVisibility(INVISIBLE);
backspaceButton.setOnKeyEventListener(null);
backspaceButtonBackup.setVisibility(VISIBLE);
backspaceButtonBackup.setOnKeyEventListener(backspaceObserver::onBackspaceClicked);
} else {
backspaceButton.setVisibility(VISIBLE);
backspaceButton.setOnKeyEventListener(backspaceObserver::onBackspaceClicked);
backspaceButtonBackup.setVisibility(GONE);
backspaceButtonBackup.setOnKeyEventListener(null);
}
} else {
backspaceButton.setVisibility(INVISIBLE);
backspaceButton.setOnKeyEventListener(null);
backspaceButtonBackup.setVisibility(GONE);
backspaceButton.setOnKeyEventListener(null);
}
}
private void presentAddButton(@Nullable MediaKeyboardProvider.AddObserver addObserver) {
if (addObserver != null) {
addButton.setVisibility(VISIBLE);
addButton.setOnClickListener(v -> addObserver.onAddClicked());
} else {
addButton.setVisibility(GONE);
addButton.setOnClickListener(null);
}
}
private void presentSearchButton(@Nullable MediaKeyboardProvider.SearchObserver searchObserver) {
searchButton.setVisibility(searchObserver != null ? VISIBLE : INVISIBLE);
}
private void presentProviderStrip(boolean isSolo) {
int visibility = isSolo ? View.GONE : View.VISIBLE;
searchButton.setVisibility(visibility);
backspaceButton.setVisibility(visibility);
providerTabs.setVisibility(visibility);
public void accept(EmojiViewItem emojiViewItem) {
if (keyboardListener != null) keyboardListener.onEmojiPicked(emojiViewItem.getEmoji());
}
public interface MediaKeyboardListener {
void onShown();
void onHidden();
void onKeyboardProviderChanged(@NonNull MediaKeyboardProvider provider);
void onEmojiPicked(String emoji);
}
}
@@ -1,103 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboardProvider.TabIconProvider;
import org.thoughtcrime.securesms.mms.GlideRequests;
public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter<MediaKeyboardBottomTabAdapter.MediaKeyboardBottomTabViewHolder> {
private final GlideRequests glideRequests;
private final EventListener eventListener;
private final boolean highlightTop;
private TabIconProvider tabIconProvider;
private int activePosition;
private int count;
public MediaKeyboardBottomTabAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener, boolean highlightTop) {
this.glideRequests = glideRequests;
this.eventListener = eventListener;
this.highlightTop = highlightTop;
}
@Override
public @NonNull MediaKeyboardBottomTabViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return new MediaKeyboardBottomTabViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.media_keyboard_bottom_tab_item, viewGroup, false),
highlightTop);
}
@Override
public void onBindViewHolder(@NonNull MediaKeyboardBottomTabViewHolder viewHolder, int i) {
viewHolder.bind(glideRequests, eventListener, tabIconProvider, i, i == activePosition);
}
@Override
public void onViewRecycled(@NonNull MediaKeyboardBottomTabViewHolder holder) {
holder.recycle();
}
@Override
public int getItemCount() {
return count;
}
public void setTabIconProvider(@NonNull TabIconProvider iconProvider, int count) {
this.tabIconProvider = iconProvider;
this.count = count;
notifyDataSetChanged();
}
public void setActivePosition(int position) {
this.activePosition = position;
notifyDataSetChanged();
}
static class MediaKeyboardBottomTabViewHolder extends RecyclerView.ViewHolder {
private final ImageView image;
private final View indicator;
public MediaKeyboardBottomTabViewHolder(@NonNull View itemView, boolean highlightTop) {
super(itemView);
View indicatorTop = itemView.findViewById(R.id.media_keyboard_top_tab_indicator);
View indicatorBottom = itemView.findViewById(R.id.media_keyboard_bottom_tab_indicator);
this.image = itemView.findViewById(R.id.media_keyboard_bottom_tab_image);
this.indicator = highlightTop ? indicatorTop : indicatorBottom;
this.indicator.setVisibility(View.VISIBLE);
}
void bind(@NonNull GlideRequests glideRequests,
@NonNull EventListener eventListener,
@NonNull TabIconProvider tabIconProvider,
int index,
boolean selected)
{
tabIconProvider.loadCategoryTabIcon(glideRequests, image, index);
image.setAlpha(selected ? 1 : 0.5f);
image.setSelected(selected);
indicator.setVisibility(selected ? View.VISIBLE : View.INVISIBLE);
itemView.setOnClickListener(v -> eventListener.onTabSelected(index));
}
void recycle() {
itemView.setOnClickListener(null);
}
}
interface EventListener {
void onTabSelected(int index);
}
}
@@ -1,51 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import android.widget.ImageView;
import org.thoughtcrime.securesms.mms.GlideRequests;
public interface MediaKeyboardProvider {
@LayoutRes int getProviderIconView(boolean selected);
/** @return True if the click was handled with provider-specific logic, otherwise false */
void requestPresentation(@NonNull Presenter presenter, boolean isSoloProvider);
void setController(@Nullable Controller controller);
interface BackspaceObserver {
void onBackspaceClicked();
}
interface AddObserver {
void onAddClicked();
}
interface SearchObserver {
void onSearchOpened();
void onSearchClosed();
void onSearchChanged(@NonNull String query);
}
interface Controller {
void setViewPagerEnabled(boolean enabled);
}
interface Presenter {
void present(@NonNull MediaKeyboardProvider provider,
@NonNull PagerAdapter pagerAdapter,
@NonNull TabIconProvider iconProvider,
@Nullable BackspaceObserver backspaceObserver,
@Nullable AddObserver addObserver,
@Nullable SearchObserver searchObserver,
int startingIndex);
int getCurrentPosition();
void requestDismissal();
boolean isVisible();
}
interface TabIconProvider {
void loadCategoryTabIcon(@NonNull GlideRequests glideRequests, @NonNull ImageView imageView, int index);
}
}
@@ -1,105 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.util.Log;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.JsonUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
public class RecentEmojiPageModel implements EmojiPageModel {
private static final String TAG = RecentEmojiPageModel.class.getSimpleName();
private static final int EMOJI_LRU_SIZE = 50;
private final SharedPreferences prefs;
private final String preferenceName;
private final LinkedHashSet<String> recentlyUsed;
public RecentEmojiPageModel(Context context, @NonNull String preferenceName) {
this.prefs = PreferenceManager.getDefaultSharedPreferences(context);
this.preferenceName = preferenceName;
this.recentlyUsed = getPersistedCache();
}
private LinkedHashSet<String> getPersistedCache() {
String serialized = prefs.getString(preferenceName, "[]");
try {
CollectionType collectionType = TypeFactory.defaultInstance()
.constructCollectionType(LinkedHashSet.class, String.class);
return JsonUtils.getMapper().readValue(serialized, collectionType);
} catch (IOException e) {
Log.w(TAG, e);
return new LinkedHashSet<>();
}
}
@Override public int getIconAttr() {
return R.attr.emoji_category_recent;
}
@Override public List<String> getEmoji() {
List<String> emoji = new ArrayList<>(recentlyUsed);
Collections.reverse(emoji);
return emoji;
}
@Override public List<Emoji> getDisplayEmoji() {
return Stream.of(getEmoji()).map(Emoji::new).toList();
}
@Override public boolean hasSpriteMap() {
return false;
}
@Override public String getSprite() {
return null;
}
@Override public boolean isDynamic() {
return true;
}
public void onCodePointSelected(String emoji) {
recentlyUsed.remove(emoji);
recentlyUsed.add(emoji);
if (recentlyUsed.size() > EMOJI_LRU_SIZE) {
Iterator<String> iterator = recentlyUsed.iterator();
iterator.next();
iterator.remove();
}
final LinkedHashSet<String> latestRecentlyUsed = new LinkedHashSet<>(recentlyUsed);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
String serialized = JsonUtils.toJson(latestRecentlyUsed);
prefs.edit()
.putString(preferenceName, serialized)
.apply();
} catch (IOException e) {
Log.w(TAG, e);
}
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
@@ -1,66 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class StaticEmojiPageModel implements EmojiPageModel {
@AttrRes private final int iconAttr;
@NonNull private final List<Emoji> emoji;
@Nullable private final String sprite;
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull String[] strings, @Nullable String sprite) {
List<Emoji> emoji = new ArrayList<>(strings.length);
for (String s : strings) {
emoji.add(new Emoji(s));
}
this.iconAttr = iconAttr;
this.emoji = emoji;
this.sprite = sprite;
}
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull Emoji[] emoji, @Nullable String sprite) {
this.iconAttr = iconAttr;
this.emoji = Arrays.asList(emoji);
this.sprite = sprite;
}
public int getIconAttr() {
return iconAttr;
}
@Override
public @NonNull List<String> getEmoji() {
List<String> emojis = new LinkedList<>();
for (Emoji e : emoji) {
emojis.addAll(e.getVariations());
}
return emojis;
}
@Override
public @NonNull List<Emoji> getDisplayEmoji() {
return emoji;
}
@Override
public boolean hasSpriteMap() {
return sprite != null;
}
@Override
public @Nullable String getSprite() {
return sprite;
}
@Override
public boolean isDynamic() {
return false;
}
}
@@ -1,31 +0,0 @@
package org.thoughtcrime.securesms.components.emoji.parsing;
import androidx.annotation.NonNull;
public class EmojiDrawInfo {
private final EmojiPageBitmap page;
private final int index;
public EmojiDrawInfo(final @NonNull EmojiPageBitmap page, final int index) {
this.page = page;
this.index = index;
}
public @NonNull EmojiPageBitmap getPage() {
return page;
}
public int getIndex() {
return index;
}
@Override
public @NonNull String toString() {
return "DrawInfo{" +
"page=" + page +
", index=" + index +
'}';
}
}
@@ -1,106 +0,0 @@
package org.thoughtcrime.securesms.components.emoji.parsing;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.thoughtcrime.securesms.util.Util;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.util.concurrent.Callable;
public class EmojiPageBitmap {
private static final String TAG = EmojiPageBitmap.class.getSimpleName();
private final Context context;
private final EmojiPageModel model;
private final float decodeScale;
private SoftReference<Bitmap> bitmapReference;
private ListenableFutureTask<Bitmap> task;
public EmojiPageBitmap(@NonNull Context context, @NonNull EmojiPageModel model, float decodeScale) {
this.context = context.getApplicationContext();
this.model = model;
this.decodeScale = decodeScale;
}
@SuppressLint("StaticFieldLeak")
public ListenableFutureTask<Bitmap> get() {
Util.assertMainThread();
if (bitmapReference != null && bitmapReference.get() != null) {
return new ListenableFutureTask<>(bitmapReference.get());
} else if (task != null) {
return task;
} else {
Callable<Bitmap> callable = () -> {
try {
Log.i(TAG, "loading page " + model.getSprite());
return loadPage();
} catch (IOException ioe) {
Log.w(TAG, ioe);
}
return null;
};
task = new ListenableFutureTask<>(callable);
new AsyncTask<Void, Void, Void>() {
@Override protected Void doInBackground(Void... params) {
task.run();
return null;
}
@Override protected void onPostExecute(Void aVoid) {
task = null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
return task;
}
public Bitmap loadPage() throws IOException {
if (bitmapReference != null && bitmapReference.get() != null) return bitmapReference.get();
float scale = decodeScale;
AssetManager assetManager = context.getAssets();
InputStream assetStream = assetManager.open(model.getSprite());
BitmapFactory.Options options = new BitmapFactory.Options();
if (Util.isLowMemory(context)) {
Log.i(TAG, "Low memory detected. Changing sample size.");
options.inSampleSize = 2;
scale = decodeScale * 2;
}
Stopwatch stopwatch = new Stopwatch(model.getSprite());
Bitmap bitmap = BitmapFactory.decodeStream(assetStream, null, options);
stopwatch.split("decode");
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, (int)(bitmap.getWidth() * scale), (int)(bitmap.getHeight() * scale), true);
stopwatch.split("scale");
stopwatch.stop(TAG);
bitmapReference = new SoftReference<>(scaledBitmap);
Log.i(TAG, "onPageLoaded(" + model.getSprite() + ") originalByteCount: " + bitmap.getByteCount()
+ " scaledByteCount: " + scaledBitmap.getByteCount()
+ " scaledSize: " + scaledBitmap.getWidth() + "x" + scaledBitmap.getHeight());
return scaledBitmap;
}
@Override
public @NonNull String toString() {
return model.getSprite();
}
}
@@ -1,136 +0,0 @@
/**
* Copyright (c) 2014-present Vincent DURMONT vdurmont@gmail.com
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.thoughtcrime.securesms.components.emoji.parsing;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* Based in part on code from emoji-java
*/
public class EmojiParser {
private final EmojiTree emojiTree;
public EmojiParser(EmojiTree emojiTree) {
this.emojiTree = emojiTree;
}
public @NonNull CandidateList findCandidates(@Nullable CharSequence text) {
List<Candidate> results = new LinkedList<>();
if (text == null) {
return new CandidateList(results, false);
}
boolean allEmojis = text.length() > 0;
for (int i = 0; i < text.length(); i++) {
int emojiEnd = getEmojiEndPos(text, i);
if (emojiEnd != -1) {
EmojiDrawInfo drawInfo = emojiTree.getEmoji(text, i, emojiEnd);
if (emojiEnd + 2 <= text.length()) {
if (Fitzpatrick.fitzpatrickFromUnicode(text, emojiEnd) != null) {
emojiEnd += 2;
}
}
results.add(new Candidate(i, emojiEnd, drawInfo));
i = emojiEnd - 1;
} else if (text.charAt(i) != ' '){
allEmojis = false;
}
}
allEmojis &= !results.isEmpty();
return new CandidateList(results, allEmojis);
}
private int getEmojiEndPos(CharSequence text, int startPos) {
int best = -1;
for (int j = startPos + 1; j <= text.length(); j++) {
EmojiTree.Matches status = emojiTree.isEmoji(text, startPos, j);
if (status.exactMatch()) {
best = j;
} else if (status.impossibleMatch()) {
return best;
}
}
return best;
}
public static class Candidate {
private final int startIndex;
private final int endIndex;
private final EmojiDrawInfo drawInfo;
Candidate(int startIndex, int endIndex, EmojiDrawInfo drawInfo) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.drawInfo = drawInfo;
}
public EmojiDrawInfo getDrawInfo() {
return drawInfo;
}
public int getEndIndex() {
return endIndex;
}
public int getStartIndex() {
return startIndex;
}
}
public static class CandidateList implements Iterable<Candidate> {
public final List<EmojiParser.Candidate> list;
public final boolean allEmojis;
public CandidateList(List<EmojiParser.Candidate> candidates, boolean allEmojis) {
this.list = candidates;
this.allEmojis = allEmojis;
}
public int size() {
return list.size();
}
@Override
public @NonNull Iterator<Candidate> iterator() {
return list.iterator();
}
}
}
@@ -1,138 +0,0 @@
/**
* Copyright (c) 2014-present Vincent DURMONT vdurmont@gmail.com
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.thoughtcrime.securesms.components.emoji.parsing;
import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
/**
* Based in part on code from emoji-java
*/
public class EmojiTree {
private final EmojiTreeNode root = new EmojiTreeNode();
private static final char TERMINATOR = '\ufe0f';
public void add(String emojiEncoding, EmojiDrawInfo emoji) {
EmojiTreeNode tree = root;
for (char c: emojiEncoding.toCharArray()) {
if (!tree.hasChild(c)) {
tree.addChild(c);
}
tree = tree.getChild(c);
}
tree.setEmoji(emoji);
}
public Matches isEmoji(CharSequence sequence, int startPosition, int endPosition) {
if (sequence == null) {
return Matches.POSSIBLY;
}
EmojiTreeNode tree = root;
for (int i=startPosition; i<endPosition; i++) {
char character = sequence.charAt(i);
if (!tree.hasChild(character)) {
return Matches.IMPOSSIBLE;
}
tree = tree.getChild(character);
}
if (tree.isEndOfEmoji()) {
return Matches.EXACTLY;
} else if (sequence.charAt(endPosition-1) != TERMINATOR && tree.hasChild(TERMINATOR) && tree.getChild(TERMINATOR).isEndOfEmoji()) {
return Matches.EXACTLY;
} else {
return Matches.POSSIBLY;
}
}
public @Nullable EmojiDrawInfo getEmoji(CharSequence unicode, int startPosition, int endPostiion) {
EmojiTreeNode tree = root;
for (int i=startPosition; i<endPostiion; i++) {
char character = unicode.charAt(i);
if (!tree.hasChild(character)) {
return null;
}
tree = tree.getChild(character);
}
if (tree.getEmoji() != null) return tree.getEmoji();
else if (unicode.charAt(endPostiion-1) != TERMINATOR && tree.hasChild(TERMINATOR)) return tree.getChild(TERMINATOR).getEmoji();
else return null;
}
private static class EmojiTreeNode {
private final Map<Character, EmojiTreeNode> children = new HashMap<>();
private EmojiDrawInfo emoji;
public void setEmoji(EmojiDrawInfo emoji) {
this.emoji = emoji;
}
public @Nullable EmojiDrawInfo getEmoji() {
return emoji;
}
boolean hasChild(char child) {
return children.containsKey(child);
}
void addChild(char child) {
children.put(child, new EmojiTreeNode());
}
EmojiTreeNode getChild(char child) {
return children.get(child);
}
boolean isEndOfEmoji() {
return emoji != null;
}
}
public enum Matches {
EXACTLY, POSSIBLY, IMPOSSIBLE;
public boolean exactMatch() {
return this == EXACTLY;
}
public boolean impossibleMatch() {
return this == IMPOSSIBLE;
}
}
}
@@ -1,64 +0,0 @@
package org.thoughtcrime.securesms.components.emoji.parsing;
public enum Fitzpatrick {
/**
* Fitzpatrick modifier of type 1/2 (pale white/white)
*/
TYPE_1_2("\uD83C\uDFFB"),
/**
* Fitzpatrick modifier of type 3 (cream white)
*/
TYPE_3("\uD83C\uDFFC"),
/**
* Fitzpatrick modifier of type 4 (moderate brown)
*/
TYPE_4("\uD83C\uDFFD"),
/**
* Fitzpatrick modifier of type 5 (dark brown)
*/
TYPE_5("\uD83C\uDFFE"),
/**
* Fitzpatrick modifier of type 6 (black)
*/
TYPE_6("\uD83C\uDFFF");
/**
* The unicode representation of the Fitzpatrick modifier
*/
public final String unicode;
Fitzpatrick(String unicode) {
this.unicode = unicode;
}
public static Fitzpatrick fitzpatrickFromUnicode(CharSequence unicode, int index) {
for (Fitzpatrick v : values()) {
boolean match = true;
for (int i=0;i<v.unicode.toCharArray().length;i++) {
if (v.unicode.toCharArray()[i] != unicode.charAt(index + i)) {
match = false;
}
}
if (match) return v;
}
return null;
}
public static Fitzpatrick fitzpatrickFromType(String type) {
try {
return Fitzpatrick.valueOf(type.toUpperCase());
} catch (IllegalArgumentException e) {
return null;
}
}
}
@@ -171,7 +171,15 @@ public class DcEventCenter {
switch (id) {
case DcContext.DC_EVENT_INCOMING_MSG:
DcHelper.getNotificationCenter(context).addNotification(accountId, event.getData1Int(), event.getData2Int());
DcHelper.getNotificationCenter(context).notifyMessage(accountId, event.getData1Int(), event.getData2Int());
break;
case DcContext.DC_EVENT_INCOMING_REACTION:
DcHelper.getNotificationCenter(context).notifyReaction(accountId, event.getData1Int(), event.getData2Int(), event.getData2Str());
break;
case DcContext.DC_EVENT_INCOMING_WEBXDC_NOTIFY:
DcHelper.getNotificationCenter(context).notifyWebxdc(accountId, event.getData1Int(), event.getData2Int(), event.getData2Str());
break;
case DcContext.DC_EVENT_MSGS_NOTICED:
@@ -187,19 +195,26 @@ public class DcEventCenter {
return 0;
}
final String logPrefix = "[accId="+accountId + "] ";
switch (id) {
case DcContext.DC_EVENT_INFO:
Log.i("DeltaChat", logPrefix + event.getData2Str());
break;
case DcContext.DC_EVENT_WARNING:
Log.w("DeltaChat", logPrefix + event.getData2Str());
break;
case DcContext.DC_EVENT_ERROR:
Log.e("DeltaChat", logPrefix + event.getData2Str());
break;
}
if (accountId != context.dcContext.getAccountId()) {
return 0;
}
switch (id) {
case DcContext.DC_EVENT_INFO:
Log.i("DeltaChat", event.getData2Str());
break;
case DcContext.DC_EVENT_WARNING:
Log.w("DeltaChat", event.getData2Str());
break;
case DcContext.DC_EVENT_ERROR:
handleError(id, event.getData2Str());
break;
@@ -263,9 +263,7 @@ public class DcHelper {
}
public static File getImexDir() {
// DIRECTORY_DOCUMENTS is only available since KitKat;
// as we also support Ice Cream Sandwich and Jellybean (2017: 11% in total), this is no option.
// Moreover, DIRECTORY_DOWNLOADS seems to be easier accessible by the user,
// DIRECTORY_DOCUMENTS could be used but DIRECTORY_DOWNLOADS seems to be easier accessible by the user,
// eg. "Download Managers" are nearly always installed.
// CAVE: do not use DownloadManager to add the file as it is deleted on uninstall then ...
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
@@ -1,345 +0,0 @@
/*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will Google be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, as long as the origin is not misrepresented.
*/
package org.thoughtcrime.securesms.crypto;
import android.os.Build;
import android.os.Process;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.security.Security;
/**
* This class is taken directly from the Android blog post announcing this bug:
* http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html
*
* Since I still don't know exactly what the source of this bug was, I'm using
* this class verbatim under the assumption that the Android team knows what
* they're doing. Although, at this point, that is perhaps a foolish assumption.
*/
/**
* Fixes for the output of the default PRNG having low entropy.
*
* The fixes need to be applied via {@link #apply()} before any use of Java
* Cryptography Architecture primitives. A good place to invoke them is in the
* application's {@code onCreate}.
*/
public final class PRNGFixes {
private static final int VERSION_CODE_JELLY_BEAN = 16;
private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
getBuildFingerprintAndDeviceSerial();
/** Hidden constructor to prevent instantiation. */
private PRNGFixes() {}
/**
* Applies all fixes.
*
* @throws SecurityException if a fix is needed but could not be applied.
*/
public static void apply() {
applyOpenSSLFix();
installLinuxPRNGSecureRandom();
}
/**
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
* fix is not needed.
*
* @throws SecurityException if the fix is needed but could not be applied.
*/
private static void applyOpenSSLFix() throws SecurityException {
if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
|| (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
// No need to apply the fix
return;
}
try {
// Mix in the device- and invocation-specific seed.
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class)
.invoke(null, generateSeed());
// Mix output of Linux PRNG into OpenSSL's PRNG
int bytesRead = (Integer) Class.forName(
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_load_file", String.class, long.class)
.invoke(null, "/dev/urandom", 1024);
if (bytesRead != 1024) {
throw new IOException(
"Unexpected number of bytes read from Linux PRNG: "
+ bytesRead);
}
} catch (Exception e) {
throw new SecurityException("Failed to seed OpenSSL PRNG", e);
}
}
/**
* Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
* default. Does nothing if the implementation is already the default or if
* there is not need to install the implementation.
*
* @throws SecurityException if the fix is needed but could not be applied.
*/
private static void installLinuxPRNGSecureRandom()
throws SecurityException {
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
// No need to apply the fix
return;
}
// Install a Linux PRNG-based SecureRandom implementation as the
// default, if not yet installed.
Provider[] secureRandomProviders =
Security.getProviders("SecureRandom.SHA1PRNG");
if ((secureRandomProviders == null)
|| (secureRandomProviders.length < 1)
|| (!LinuxPRNGSecureRandomProvider.class.equals(
secureRandomProviders[0].getClass()))) {
Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
}
// Assert that new SecureRandom() and
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
// by the Linux PRNG-based SecureRandom implementation.
SecureRandom rng1 = new SecureRandom();
if (!LinuxPRNGSecureRandomProvider.class.equals(
rng1.getProvider().getClass())) {
throw new SecurityException(
"new SecureRandom() backed by wrong Provider: "
+ rng1.getProvider().getClass());
}
SecureRandom rng2;
try {
rng2 = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new SecurityException("SHA1PRNG not available", e);
}
if (!LinuxPRNGSecureRandomProvider.class.equals(
rng2.getProvider().getClass())) {
throw new SecurityException(
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
+ " Provider: " + rng2.getProvider().getClass());
}
}
/**
* {@code Provider} of {@code SecureRandom} engines which pass through
* all requests to the Linux PRNG.
*/
private static class LinuxPRNGSecureRandomProvider extends Provider {
public LinuxPRNGSecureRandomProvider() {
super("LinuxPRNG",
1.0,
"A Linux-specific random number provider that uses"
+ " /dev/urandom");
// Although /dev/urandom is not a SHA-1 PRNG, some apps
// explicitly request a SHA1PRNG SecureRandom and we thus need to
// prevent them from getting the default implementation whose output
// may have low entropy.
put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
}
}
/**
* {@link SecureRandomSpi} which passes all requests to the Linux PRNG
* ({@code /dev/urandom}).
*/
public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
/*
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
* are passed through to the Linux PRNG (/dev/urandom). Instances of
* this class seed themselves by mixing in the current time, PID, UID,
* build fingerprint, and hardware serial number (where available) into
* Linux PRNG.
*
* Concurrency: Read requests to the underlying Linux PRNG are
* serialized (on sLock) to ensure that multiple threads do not get
* duplicated PRNG output.
*/
private static final File URANDOM_FILE = new File("/dev/urandom");
private static final Object sLock = new Object();
/**
* Input stream for reading from Linux PRNG or {@code null} if not yet
* opened.
*
* @GuardedBy("sLock")
*/
private static DataInputStream sUrandomIn;
/**
* Output stream for writing to Linux PRNG or {@code null} if not yet
* opened.
*
* @GuardedBy("sLock")
*/
private static OutputStream sUrandomOut;
/**
* Whether this engine instance has been seeded. This is needed because
* each instance needs to seed itself if the client does not explicitly
* seed it.
*/
private boolean mSeeded;
@Override
protected void engineSetSeed(byte[] bytes) {
try {
OutputStream out;
synchronized (sLock) {
out = getUrandomOutputStream();
}
out.write(bytes);
out.flush();
} catch (IOException e) {
// On a small fraction of devices /dev/urandom is not writable.
// Log and ignore.
Log.w(PRNGFixes.class.getSimpleName(),
"Failed to mix seed into " + URANDOM_FILE);
} finally {
mSeeded = true;
}
}
@Override
protected void engineNextBytes(byte[] bytes) {
if (!mSeeded) {
// Mix in the device- and invocation-specific seed.
engineSetSeed(generateSeed());
}
try {
DataInputStream in;
synchronized (sLock) {
in = getUrandomInputStream();
}
synchronized (in) {
in.readFully(bytes);
}
} catch (IOException e) {
throw new SecurityException(
"Failed to read from " + URANDOM_FILE, e);
}
}
@Override
protected byte[] engineGenerateSeed(int size) {
byte[] seed = new byte[size];
engineNextBytes(seed);
return seed;
}
private DataInputStream getUrandomInputStream() {
synchronized (sLock) {
if (sUrandomIn == null) {
// NOTE: Consider inserting a BufferedInputStream between
// DataInputStream and FileInputStream if you need higher
// PRNG output performance and can live with future PRNG
// output being pulled into this process prematurely.
try {
sUrandomIn = new DataInputStream(
new FileInputStream(URANDOM_FILE));
} catch (IOException e) {
throw new SecurityException("Failed to open "
+ URANDOM_FILE + " for reading", e);
}
}
return sUrandomIn;
}
}
private OutputStream getUrandomOutputStream() throws IOException {
synchronized (sLock) {
if (sUrandomOut == null) {
sUrandomOut = new FileOutputStream(URANDOM_FILE);
}
return sUrandomOut;
}
}
}
/**
* Generates a device- and invocation-specific seed to be mixed into the
* Linux PRNG.
*/
private static byte[] generateSeed() {
try {
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
DataOutputStream seedBufferOut =
new DataOutputStream(seedBuffer);
seedBufferOut.writeLong(System.currentTimeMillis());
seedBufferOut.writeLong(System.nanoTime());
seedBufferOut.writeInt(Process.myPid());
seedBufferOut.writeInt(Process.myUid());
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
seedBufferOut.close();
return seedBuffer.toByteArray();
} catch (IOException e) {
throw new SecurityException("Failed to generate seed", e);
}
}
/**
* Gets the hardware serial number of this device.
*
* @return serial number or {@code null} if not available.
*/
private static String getDeviceSerialNumber() {
// We're using the Reflection API because Build.SERIAL is only available
// since API Level 9 (Gingerbread, Android 2.3).
try {
return (String) Build.class.getField("SERIAL").get(null);
} catch (Exception ignored) {
return null;
}
}
private static byte[] getBuildFingerprintAndDeviceSerial() {
StringBuilder result = new StringBuilder();
String fingerprint = Build.FINGERPRINT;
if (fingerprint != null) {
result.append(fingerprint);
}
String serial = getDeviceSerialNumber();
if (serial != null) {
result.append(serial);
}
try {
return result.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported");
}
}
}

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