mirror of
https://github.com/ArcaneChat/android.git
synced 2026-07-03 14:05:24 +02:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b7446c9a93 | |||
| 0c9b5bbaf4 | |||
| 3126544f00 | |||
| ccdad0469d | |||
| 9f3eeb3d0d | |||
| 1355303360 | |||
| 7227f513a5 | |||
| 4933d66000 | |||
| 977b21618c | |||
| 5cecd5bec3 | |||
| d8db39f9c7 | |||
| 736fc44870 | |||
| 537127ec4e | |||
| a5167f3739 | |||
| d481541ad8 | |||
| 7ab19fb8af | |||
| 715a2c0653 | |||
| 350cd0539c | |||
| 9f8fe08c75 | |||
| 5048239563 | |||
| 6e135e06a9 | |||
| d52647b288 | |||
| c0a729978c | |||
| 041c95c6d2 | |||
| 8de0fed18e | |||
| dd75a8e858 | |||
| 2e77db23d5 | |||
| 04a4a02235 | |||
| 2910730bc1 | |||
| 23458a6c64 | |||
| 9679b22072 | |||
| 3fae301ec2 | |||
| d08069f7b0 | |||
| 56a0607a5e | |||
| 9c08617f7d | |||
| 812dc8d738 | |||
| d8b278a1f5 | |||
| 4368a4f63e | |||
| 72ae90ae93 | |||
| c36ce7a8ab | |||
| 404097fe30 | |||
| 07d5c719d4 | |||
| 13a43473cc | |||
| 5a84198829 | |||
| 1f443cd87c | |||
| 4901b92ee2 | |||
| cd48e15071 | |||
| 2d4b570f48 | |||
| 3f0b36bd4b | |||
| b342fb9c30 | |||
| 0974655366 | |||
| 9675c419ac |
@@ -35,7 +35,7 @@ jobs:
|
||||
- uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r23c
|
||||
ndk-version: r27
|
||||
|
||||
- name: Compile core
|
||||
env:
|
||||
|
||||
+11
-3
@@ -1,6 +1,6 @@
|
||||
# ArcaneChat Android Changelog
|
||||
|
||||
## v1.48.1
|
||||
## v1.48.3
|
||||
2024-10
|
||||
|
||||
* new Proxy settings screen available at "Advanced / Proxy"
|
||||
@@ -10,6 +10,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 +46,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 +69,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
@@ -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
@@ -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
@@ -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 30000696
|
||||
versionName "1.48.5"
|
||||
|
||||
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
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
|
||||
+1
-1
Submodule jni/deltachat-core-rust updated: f66fbc2e29...8f8178d5e0
+4
-4
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -217,7 +217,7 @@
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Профиль - это <strong>имя, изображение</strong> и некоторая дополнительная информация для шифрования сообщений.
|
||||
<p>Профиль — это <strong>имя, изображение</strong> и некоторая дополнительная информация для шифрования сообщений.
|
||||
Профиль существует только на вашем устройстве(ах)
|
||||
и использует серверы chatmail или обычные серверы электронной почты для передачи сообщений.</p>
|
||||
|
||||
@@ -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,7 +1305,7 @@ 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>
|
||||
@@ -1407,12 +1407,12 @@ PIN-код разблокировки экрана, графический кл
|
||||
</h3>
|
||||
|
||||
<ul>
|
||||
<li>Приложения webxdc webxdc не могут отправлять данные в Интернет или скачивать что-либо.</li>
|
||||
<li>Приложения webxdc не могут отправлять данные в Интернет или скачивать что-либо.</li>
|
||||
<li>Приложение webxdc может обмениваться данными только внутри чата Delta Chat, с его
|
||||
копиями на устройствах ваших собеседников по чату.
|
||||
В остальном, оно полностью
|
||||
изолировано от Интернета.</li>
|
||||
<li>Конфиденциальность, которую обеспечивает приложение webxdc - это конфиденциальность вашего чата - пока
|
||||
<li>Конфиденциальность, которую обеспечивает приложение webxdc — это конфиденциальность вашего чата - пока
|
||||
вы доверяете людям, с которыми вы общаетесь, вы можете доверять приложению webxdc.</li>
|
||||
<li>Это также означает, что открытие приложений webxdc в чатах с участниками, которым вы
|
||||
не доверяете, тоже самое, что и с вложениями электронной почты, когда вы открываете
|
||||
@@ -1446,7 +1446,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 +1572,7 @@ Delta Chat</a>.</li>
|
||||
<ol>
|
||||
<li>
|
||||
<p>Измените свой адрес в разделе “Настройки → Дополнительные параметры → Пароль и аккаунт” и
|
||||
введите пароль вашей новой электронной почты (и если необходимо, настройки сервера).
|
||||
введите пароль вашей новой электронной почты (и, если необходимо, настройки сервера).
|
||||
Вы получите информационное сообщение о том, что переходите на новый адрес.
|
||||
Дополнительное уведомление также появится в вашем чате “Сообщения устройства”.</p>
|
||||
</li>
|
||||
@@ -1656,10 +1656,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 +1791,7 @@ Delta Chat необходим пароль, чтобы использовать
|
||||
|
||||
</h3>
|
||||
|
||||
<p>Единственная причина, по которой вы можете захотеть наблюдать за папкой “Отправленные”, - это если вы используете другую
|
||||
<p>Единственная причина, по которой вы можете захотеть наблюдать за папкой “Отправленные”, — это если вы используете другую
|
||||
почтовую программу (например, Thunderbird) вместе с приложением Delta Chat, и хотите, чтобы ваш почтовый клиент
|
||||
мог участвовать в разговорах в чате.</p>
|
||||
|
||||
@@ -1895,7 +1895,7 @@ Chat.</li>
|
||||
основанную на Свободном и Открытом исходном коде.</p>
|
||||
|
||||
<p>В частности, разработка Delta Chat финансировалась из следующих источников,
|
||||
перечисленны в хронологическом порядке:</p>
|
||||
перечислены в хронологическом порядке:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
@@ -1910,7 +1910,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>
|
||||
|
||||
@@ -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;
|
||||
@@ -107,11 +106,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());
|
||||
|
||||
@@ -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;
|
||||
@@ -1238,7 +1236,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
public void onEmojiToggle() {
|
||||
if (!emojiDrawerStub.resolved()) {
|
||||
initializeMediaKeyboardProviders(emojiDrawerStub.get(), false);
|
||||
inputPanel.setMediaKeyboard(emojiDrawerStub.get());
|
||||
}
|
||||
|
||||
@@ -1277,13 +1274,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 {
|
||||
|
||||
@@ -56,7 +56,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,6 +68,7 @@ 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;
|
||||
@@ -112,6 +112,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 +159,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));
|
||||
|
||||
@@ -399,7 +401,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 +409,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 +430,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 +443,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 +456,7 @@ public class ConversationItem extends BaseConversationItem
|
||||
});
|
||||
} else {
|
||||
msgActionButton.setVisibility(View.GONE);
|
||||
showFullButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
+10
-5
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+87
File diff suppressed because one or more lines are too long
-55
@@ -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) { }
|
||||
}
|
||||
}
|
||||
-120
@@ -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);*/
|
||||
}
|
||||
}
|
||||
|
||||
-51
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
-103
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
-106
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,19 +187,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;
|
||||
|
||||
@@ -84,7 +84,8 @@ public class BackupReceiverFragment extends Fragment implements DcEventCenter.Dc
|
||||
} else if (permille < 1000) {
|
||||
percent = permille/10;
|
||||
percentMax = 100;
|
||||
statusLineText = getString(R.string.transferring) + String.format(Util.getLocale(), " %d%%", percent);
|
||||
String formattedPercent = percent > 0 ? String.format(Util.getLocale(), " %d%%", percent) : "";
|
||||
statusLineText = getString(R.string.transferring) + formattedPercent;
|
||||
hideSameNetworkHint = true;
|
||||
} else if (permille == 1000) {
|
||||
getTransferActivity().setTransferState(BackupTransferActivity.TransferState.TRANSFER_SUCCESS);
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.emoji2.emojipicker.EmojiPickerView;
|
||||
|
||||
@@ -17,15 +18,14 @@ import com.b44t.messenger.rpc.Reactions;
|
||||
import com.b44t.messenger.rpc.Rpc;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class AddReactionView extends LinearLayout {
|
||||
private EmojiTextView [] defaultReactionViews;
|
||||
private EmojiTextView anyReactionView;
|
||||
private AppCompatTextView[] defaultReactionViews;
|
||||
private AppCompatTextView anyReactionView;
|
||||
private boolean anyReactionClearsReaction;
|
||||
private Context context;
|
||||
private DcContext dcContext;
|
||||
@@ -46,7 +46,7 @@ public class AddReactionView extends LinearLayout {
|
||||
context = getContext();
|
||||
dcContext = DcHelper.getContext(context);
|
||||
rpc = DcHelper.getRpc(getContext());
|
||||
defaultReactionViews = new EmojiTextView[]{
|
||||
defaultReactionViews = new AppCompatTextView[]{
|
||||
findViewById(R.id.reaction_0),
|
||||
findViewById(R.id.reaction_1),
|
||||
findViewById(R.id.reaction_2),
|
||||
@@ -79,7 +79,7 @@ public class AddReactionView extends LinearLayout {
|
||||
|
||||
final String existingReaction = getSelfReaction();
|
||||
boolean existingHilited = false;
|
||||
for (EmojiTextView defaultReactionView : defaultReactionViews) {
|
||||
for (AppCompatTextView defaultReactionView : defaultReactionViews) {
|
||||
if (defaultReactionView.getText().toString().equals(existingReaction)) {
|
||||
defaultReactionView.setBackground(ContextCompat.getDrawable(context, R.drawable.reaction_pill_background_selected));
|
||||
existingHilited = true;
|
||||
@@ -105,7 +105,7 @@ public class AddReactionView extends LinearLayout {
|
||||
} else {
|
||||
x += offset;
|
||||
}
|
||||
ViewUtil.setLeftMargin(this, x);
|
||||
ViewUtil.setLeftMargin(this, Math.max(x, 0));
|
||||
|
||||
int y = Math.max((int)parentView.getY() - offset, offset/2);
|
||||
ViewUtil.setTopMargin(this, y);
|
||||
|
||||
@@ -11,12 +11,12 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.b44t.messenger.rpc.Reaction;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -93,7 +93,7 @@ public class ReactionsConversationView extends LinearLayout {
|
||||
|
||||
private static View buildPill(@NonNull Context context, @NonNull ViewGroup parent, @NonNull Reaction reaction) {
|
||||
View root = LayoutInflater.from(context).inflate(R.layout.reactions_pill, parent, false);
|
||||
EmojiTextView emojiView = root.findViewById(R.id.reactions_pill_emoji);
|
||||
AppCompatTextView emojiView = root.findViewById(R.id.reactions_pill_emoji);
|
||||
TextView countView = root.findViewById(R.id.reactions_pill_count);
|
||||
View spacer = root.findViewById(R.id.reactions_pill_spacer);
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.URLSpan;
|
||||
import android.text.util.Linkify;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/* Utility for text linkify-ing */
|
||||
public class Linkifier {
|
||||
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);
|
||||
replaceURLSpan(messageBody); // replace URLSpan so that it is not removed on the next addLinks() call
|
||||
|
||||
Linkify.addLinks(messageBody, CUSTOM_PATTERN, null, null, null);
|
||||
replaceURLSpan(messageBody);
|
||||
|
||||
if (Linkify.addLinks(messageBody, PROXY_PATTERN, null, null, null)) {
|
||||
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)) {
|
||||
replaceURLSpan(messageBody);
|
||||
}
|
||||
|
||||
return messageBody;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -52,7 +52,6 @@ public class Prefs {
|
||||
public static final String NOTIFICATION_PRIVACY_PREF = "pref_notification_privacy";
|
||||
public static final String NOTIFICATION_PRIORITY_PREF = "pref_notification_priority";
|
||||
|
||||
public static final String SYSTEM_EMOJI_PREF = "pref_system_emoji";
|
||||
public static final String BUILTIN_CAMERA_PREF = "pref_builtin_camera";
|
||||
public static final String DIRECT_CAPTURE_CAMERA_ID = "pref_direct_capture_camera_id";
|
||||
private static final String PROFILE_AVATAR_ID_PREF = "pref_profile_avatar_id";
|
||||
@@ -256,10 +255,6 @@ public class Prefs {
|
||||
setStringPreference(context, BACKGROUND_PREF+accountId, path);
|
||||
}
|
||||
|
||||
public static boolean isSystemEmojiPreferred(Context context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean getAlwaysLoadRemoteContent(Context context) {
|
||||
return getBooleanPreference(context, Prefs.ALWAYS_LOAD_REMOTE_CONTENT,
|
||||
Prefs.ALWAYS_LOAD_REMOTE_CONTENT_DEFAULT);
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -18,7 +19,6 @@ import com.b44t.messenger.DcContext;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -102,7 +102,7 @@ public class SelectedContactsAdapter extends BaseAdapter {
|
||||
}
|
||||
|
||||
AvatarImageView avatar = v.findViewById(R.id.contact_photo_image);
|
||||
EmojiTextView name = v.findViewById(R.id.name);
|
||||
AppCompatTextView name = v.findViewById(R.id.name);
|
||||
TextView phone = v.findViewById(R.id.phone);
|
||||
ImageButton delete = v.findViewById(R.id.delete);
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -38,6 +40,7 @@ import android.view.ViewGroup;
|
||||
import android.view.ViewStub;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.AbsSpinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.b44t.messenger.util.concurrent.ListenableFuture;
|
||||
@@ -46,6 +49,8 @@ import com.b44t.messenger.util.concurrent.SettableFuture;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
|
||||
public class ViewUtil {
|
||||
private final static String TAG = ViewUtil.class.getSimpleName();
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void setBackground(final @NonNull View v, final @Nullable Drawable drawable) {
|
||||
v.setBackground(drawable);
|
||||
@@ -267,4 +272,15 @@ public class ViewUtil {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if a selection is valid for a given Spinner view.
|
||||
// Returns given selection if valid.
|
||||
// Otherwise, to avoid ArrayIndexOutOfBoundsException, 0 is returned, assuming to refer to a good default.
|
||||
public static int checkBounds(int selection, AbsSpinner view) {
|
||||
if (selection < 0 || selection >= view.getCount()) {
|
||||
Log.w(TAG, "index " + selection + " out of bounds of " + view.toString());
|
||||
return 0;
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -55,7 +55,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/addr_or_tag"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/status_line"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -4,4 +4,15 @@
|
||||
android:id="@+id/emoji_drawer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone" />
|
||||
android:visibility="gone">
|
||||
|
||||
<androidx.emoji2.emojipicker.EmojiPickerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/emoji_picker"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?emoji_drawer_background"
|
||||
app:emojiGridColumns="9"/>
|
||||
|
||||
</org.thoughtcrime.securesms.components.emoji.MediaKeyboard>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/no_messages_text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -83,37 +83,37 @@
|
||||
android:gravity="center"
|
||||
android:background="@drawable/reaction_pill_background">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/reaction_0"
|
||||
android:text="👍"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/AddReaction"/>
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/reaction_1"
|
||||
android:text="❤️"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/AddReaction"/>
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/reaction_2"
|
||||
android:text="😂"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/AddReaction"/>
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/reaction_3"
|
||||
android:text="🎉"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/AddReaction"/>
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/reaction_4"
|
||||
android:text="😢"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/AddReaction"/>
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/reaction_any"
|
||||
android:text="⋯"
|
||||
android:textColor="?attr/pref_icon_tint"
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/group_message_sender"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -163,7 +163,7 @@
|
||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<org.thoughtcrime.securesms.components.emoji.AutoScaledEmojiTextView
|
||||
android:id="@+id/conversation_item_body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -175,7 +175,6 @@
|
||||
android:textColor="?conversation_item_incoming_text_primary_color"
|
||||
android:textColorLink="?conversation_item_incoming_text_primary_color"
|
||||
android:importantForAccessibility="no"
|
||||
app:scaleEmojis="true"
|
||||
tools:text="Mango pickle lorem ipsum"/>
|
||||
|
||||
<Button
|
||||
@@ -187,12 +186,31 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||
android:padding="@dimen/message_bubble_showmore_padding"
|
||||
android:minHeight="1dp"
|
||||
android:minWidth="0dp"
|
||||
android:text="@string/show_full_message"
|
||||
android:textAllCaps="false"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/show_full_button"
|
||||
android:visibility="gone"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding"
|
||||
android:paddingTop="@dimen/message_bubble_showmore_padding"
|
||||
android:paddingBottom="@dimen/message_bubble_showmore_padding"
|
||||
android:minHeight="1dp"
|
||||
android:minWidth="0dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:text="@string/show_full_message"
|
||||
android:textAllCaps="false"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.ConversationItemFooter
|
||||
android:id="@+id/conversation_item_footer"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<org.thoughtcrime.securesms.components.emoji.AutoScaledEmojiTextView
|
||||
android:id="@+id/conversation_item_body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -153,7 +153,6 @@
|
||||
android:textColor="?conversation_item_outgoing_text_primary_color"
|
||||
android:textColorLink="?conversation_item_outgoing_text_primary_color"
|
||||
android:importantForAccessibility="no"
|
||||
app:scaleEmojis="true"
|
||||
tools:text="Mango pickle lorem ipsum"/>
|
||||
|
||||
<Button
|
||||
@@ -165,12 +164,31 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||
android:padding="@dimen/message_bubble_showmore_padding"
|
||||
android:minHeight="1dp"
|
||||
android:minWidth="0dp"
|
||||
android:text="@string/show_full_message"
|
||||
android:textAllCaps="false"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/show_full_button"
|
||||
android:visibility="gone"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding"
|
||||
android:paddingTop="@dimen/message_bubble_showmore_padding"
|
||||
android:paddingBottom="@dimen/message_bubble_showmore_padding"
|
||||
android:minHeight="1dp"
|
||||
android:minWidth="0dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:text="@string/show_full_message"
|
||||
android:textAllCaps="false"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.ConversationItemFooter
|
||||
android:id="@+id/conversation_item_footer"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
android:visibility="gone" />
|
||||
|
||||
<!--Links should be clickable, see https://github.com/deltachat/deltachat-android/issues/1546-->
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/conversation_update_body"
|
||||
style="@style/Delta.Text.UpdateHeader"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/conversation_update_body"
|
||||
style="@style/Delta.Text.UpdateHeader"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
/>
|
||||
</RelativeLayout>
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/TextSecure.TitleTextStyle"
|
||||
android:id="@+id/toolbar_title"
|
||||
android:layout_width="0dp"
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
long word and the chat is muted, then the long word is not shown at all
|
||||
(instead of using `…`). That's why we use android:singleLine="true" -->
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/subject"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/title"
|
||||
style="@style/TextSecure.TitleTextStyle"
|
||||
android:layout_width="0dp"
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="2dp"
|
||||
android:background="?selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/emoji_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingEnd="6dp"
|
||||
android:paddingStart="6dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.AsciiEmojiView
|
||||
android:id="@+id/emoji_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingEnd="6dp"
|
||||
android:paddingStart="6dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/emoji_variation_hint"
|
||||
android:layout_width="7dp"
|
||||
android:layout_height="7dp"
|
||||
android:layout_gravity="bottom|right|end"
|
||||
app:srcCompat="@drawable/triangle_bottom_right_corner"
|
||||
android:tint="@color/gray50"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/emoji"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="8dp"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/media_keyboard_provider_icon_margin"
|
||||
android:layout_marginRight="@dimen/media_keyboard_provider_icon_margin"
|
||||
android:padding="@dimen/media_keyboard_provider_icon_padding">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/ic_emoji_filled" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/media_keyboard_provider_icon_margin"
|
||||
android:layout_marginRight="@dimen/media_keyboard_provider_icon_margin"
|
||||
android:padding="@dimen/media_keyboard_provider_icon_padding"
|
||||
android:background="@drawable/media_keyboard_selected_background_dark">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/ic_emoji_filled" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/media_keyboard_provider_icon_margin"
|
||||
android:layout_marginRight="@dimen/media_keyboard_provider_icon_margin"
|
||||
android:padding="@dimen/media_keyboard_provider_icon_padding">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/ic_emoji_outline" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/media_keyboard_provider_icon_margin"
|
||||
android:layout_marginRight="@dimen/media_keyboard_provider_icon_margin"
|
||||
android:padding="@dimen/media_keyboard_provider_icon_padding"
|
||||
android:background="@drawable/media_keyboard_selected_background_light">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/ic_emoji_outline" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<HorizontalScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/emoji_variation_container"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/emoji_variation_selector_background" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ImageView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:padding="6dp">
|
||||
|
||||
</ImageView>
|
||||
@@ -23,7 +23,7 @@
|
||||
android:layout_marginEnd="10dp"
|
||||
android:contentDescription="@string/group_avatar" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/group_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -34,7 +34,7 @@
|
||||
android:inputType="textAutoCorrect"
|
||||
android:hint="@string/group_name">
|
||||
<requestFocus />
|
||||
</org.thoughtcrime.securesms.components.emoji.EmojiEditText>
|
||||
</androidx.appcompat.widget.AppCompatEditText>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/name_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:parentTag="org.thoughtcrime.securesms.components.emoji.MediaKeyboard">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?emoji_drawer_background">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/media_keyboard_search"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginLeft="6dp"
|
||||
android:padding="6dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/media_keyboard_provider_tabs"
|
||||
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_provider_tabs"
|
||||
tools:visibility="gone"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/media_keyboard_provider_tabs"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toStartOf="@id/media_keyboard_backspace"
|
||||
app:layout_constraintStart_toEndOf="@id/media_keyboard_search"
|
||||
app:layout_constraintTop_toBottomOf="@id/media_keyboard_tabs_top" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.RepeatableImageKey
|
||||
android:id="@+id/media_keyboard_backspace"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp"
|
||||
android:padding="6dp"
|
||||
android:src="@drawable/ic_backspace_grey600_24dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/media_keyboard_provider_tabs"
|
||||
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_provider_tabs"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.ControllableViewPager
|
||||
android:id="@+id/media_keyboard_pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintTop_toBottomOf="@id/media_keyboard_provider_tabs"
|
||||
app:layout_constraintBottom_toTopOf="@id/media_keyboard_tabs"/>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="?emoji_tab_strip_background"
|
||||
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_tabs"
|
||||
app:layout_constraintTop_toTopOf="@+id/media_keyboard_tabs" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/media_keyboard_tabs_top"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="40dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toStartOf="@id/media_keyboard_add"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:layout_height="40dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/media_keyboard_tabs"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="40dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/media_keyboard_add"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:layout_height="40dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.RepeatableImageKey
|
||||
android:id="@+id/media_keyboard_backspace_backup"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp"
|
||||
android:padding="6dp"
|
||||
android:src="@drawable/ic_backspace_grey600_24dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/media_keyboard_tabs"
|
||||
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_tabs"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/media_keyboard_add"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_tabs"
|
||||
app:layout_constraintTop_toTopOf="@id/media_keyboard_tabs"
|
||||
app:layout_constraintEnd_toStartOf="@id/media_keyboard_backspace_backup"
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</merge>
|
||||
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:background="?selectableItemBackground"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
<View
|
||||
android:id="@+id/media_keyboard_top_tab_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="?emoji_tab_indicator"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/media_keyboard_bottom_tab_image"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:padding="6dp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/media_keyboard_bottom_tab_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="?emoji_tab_indicator"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/details_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/overriden_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -69,7 +69,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/name_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -87,7 +87,7 @@
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="16dp">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/status_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -108,13 +108,6 @@
|
||||
android:text="@string/pref_who_can_see_profile_explain"
|
||||
android:textColor="@color/gray50" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.MediaKeyboard
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/emoji_drawer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone" />
|
||||
|
||||
</org.thoughtcrime.securesms.components.InputAwareLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginEnd="16dp">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/profile_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
android:focusable="true"
|
||||
android:padding="16dp">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/status_text"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
android:orientation="vertical"
|
||||
android:layout_weight="1">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/quote_author"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -43,7 +43,7 @@
|
||||
tools:visibility="gone"
|
||||
tools:text="Peter Parker" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/quote_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -48,13 +48,14 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/reaction"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textSize="22dp"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textColor="?attr/emoji_text_color"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
||||
@@ -12,11 +12,13 @@
|
||||
android:paddingEnd="7dp"
|
||||
android:gravity="center">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<!-- android:textColor is set to avoid default transparency making emoji transparent -->
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/reactions_pill_emoji"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:textColor="?attr/emoji_text_color"
|
||||
android:textSize="16dp" />
|
||||
|
||||
<Space
|
||||
|
||||
@@ -384,7 +384,6 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:entries="@array/pref_dc_certck_entries"
|
||||
android:entryValues="@array/pref_dc_certck_values"
|
||||
app:layout_constraintEnd_toEndOf="@id/guideline_root_end"
|
||||
app:layout_constraintStart_toStartOf="@id/guideline_root_start"
|
||||
app:layout_constraintTop_toBottomOf="@id/cert_check_label" />
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
android:layout_centerVertical="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/input_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -248,7 +248,7 @@
|
||||
<string name="menu_copy_image_to_clipboard">Bild kopieren</string>
|
||||
<string name="menu_copy_email_to_clipboard">E-Mail kopieren</string>
|
||||
<string name="paste_from_clipboard">Von der Zwischenablage einfügen</string>
|
||||
<string name="ask_copy_unopenable_link_to_clipboard">Der Verweis \"%1$d\" kann nicht im Webbrowser geöffnet werden. Möchten Sie den Link in die Zwischenablage kopieren?</string>
|
||||
<string name="ask_copy_unopenable_link_to_clipboard">Der Link \"%1$d\" kann nicht im Webbrowser geöffnet werden. Möchten Sie den Link in die Zwischenablage kopieren?</string>
|
||||
<string name="menu_forward">Nachricht weiterleiten</string>
|
||||
<string name="menu_reply">Antworten</string>
|
||||
<string name="menu_mute">Stummschalten</string>
|
||||
@@ -447,8 +447,8 @@
|
||||
<string name="send_file_to">Sende \"%1$s\" an…</string>
|
||||
<!-- title shown above a list contacts where one should be selected (eg. when a webxdc attempts to send a message to a chat) -->
|
||||
<string name="send_message_to">Nachricht senden an…</string>
|
||||
<string name="enable_realtime">Webxdc-Echtzeit-Kanäle</string>
|
||||
<string name="enable_realtime_explain">Webxdc-Echtzeit-APIs aktivieren und direkte Verbindungen zwischen Geräten ermöglichen.</string>
|
||||
<string name="enable_realtime">Echtzeit-Apps</string>
|
||||
<string name="enable_realtime_explain">Aktiviert Echtzeitverbindungen für in Chats verwendeten Apps. Wenn aktiviert, können Chat-Partner möglicherweise Ihre IP-Adresse ermitteln, wenn Sie eine App starten.</string>
|
||||
|
||||
<!-- map -->
|
||||
<string name="filter_map_on_time">Standorte im Zeitrahmen anzeigen</string>
|
||||
@@ -622,12 +622,14 @@
|
||||
<string name="proxy_use_proxy">Proxy verwenden</string>
|
||||
<string name="proxy_add">Proxy hinzufügen</string>
|
||||
<string name="proxy_add_explain">Unterstützte Typen: HTTP(S), SOCKS5 und Shadowsocks.</string>
|
||||
<string name="proxy_add_url_hint">Proxy URL hier eingeben</string>
|
||||
<string name="proxy_invalid">Ungültige oder nicht unterstützte Proxy-URL</string>
|
||||
<string name="proxy_add_url_hint">Proxy-Link hier eingeben</string>
|
||||
<string name="proxy_invalid">Ungültiger oder nicht unterstützter Proxy</string>
|
||||
<string name="proxy_list_header">Proxies</string>
|
||||
<string name="proxy_delete">Proxy löschen</string>
|
||||
<string name="proxy_delete_explain">\"%1$s\" löschen?</string>
|
||||
<string name="proxy_use_proxy_confirm">Proxy \"%1$s\" verwenden?</string>
|
||||
<string name="proxy_share_explain">Freunde können das Proxy durch Scannen des QR-Codes verwenden.</string>
|
||||
<string name="proxy_share_link">Link teilen</string>
|
||||
|
||||
<!-- deprecated -->
|
||||
<string name="login_socks5">SOCKS5</string>
|
||||
@@ -653,6 +655,9 @@
|
||||
<string name="accept_invalid_certificates">Ungültige Zertifikate akzeptieren</string>
|
||||
<string name="switch_account">Profil wechseln</string>
|
||||
<string name="add_account">Profil hinzufügen</string>
|
||||
<string name="profile_tag">Private Bezeichnung</string>
|
||||
<string name="profile_tag_hint">Familie, Arbeit, Freunde</string>
|
||||
<string name="profile_tag_explain">Die Bezeichnung ist nur für Sie sichtbar um zwischen Profilen zu unterscheiden.</string>
|
||||
<string name="delete_account">Profil löschen</string>
|
||||
<string name="delete_account_ask">Möchten Sie das Profil und alle Daten wirklich von diesem Gerät löschen?</string>
|
||||
<string name="delete_account_explain_with_name">Alle Daten des Profils \"%s\" werden von diesem Gerät gelöscht, dies schließt Ihre Ende-zu-Ende Autocrypt-Einstellungen, Kontakte, Chats, Nachrichten und Medien mit ein. Diese Aktion kann nicht rückgängig gemacht werden.</string>
|
||||
|
||||
@@ -466,9 +466,6 @@
|
||||
<string name="send_file_to">Enviar \"%1$s\" a…</string>
|
||||
<!-- title shown above a list contacts where one should be selected (eg. when a webxdc attempts to send a message to a chat) -->
|
||||
<string name="send_message_to">Enviar mensaje a…</string>
|
||||
<string name="enable_realtime">Canales Webxdc en tiempo real</string>
|
||||
<string name="enable_realtime_explain">Habilitar la API de tiempo real de Webxdc para poder crear conexiones directas entre dispositivos.</string>
|
||||
|
||||
<!-- map -->
|
||||
<string name="filter_map_on_time">Mostrar ubicaciones en el marco de tiempo</string>
|
||||
<string name="show_location_traces">Mostrar trazas</string>
|
||||
|
||||
@@ -427,9 +427,6 @@
|
||||
<string name="send_file_to">Lähetä \"%1$s\" kohteeseen...</string>
|
||||
<!-- title shown above a list contacts where one should be selected (eg. when a webxdc attempts to send a message to a chat) -->
|
||||
<string name="send_message_to">Lähetä viesti kohteeseen...</string>
|
||||
<string name="enable_realtime">Reaaliaikaiset Webxdc -kanavat</string>
|
||||
<string name="enable_realtime_explain">Ota Webxdc:n reaaliaikaiset ohjelmointirajapinnat käyttöön niin voit luoda suoria yhteyksiä laitteiden välillä.</string>
|
||||
|
||||
<!-- map -->
|
||||
<string name="filter_map_on_time">Näytä sijainnit aikajanalla</string>
|
||||
<string name="show_location_traces">Näytä jäljet</string>
|
||||
|
||||
@@ -443,9 +443,6 @@
|
||||
<string name="send_file_to">Envoyer \"%1$s\" à</string>
|
||||
<!-- title shown above a list contacts where one should be selected (eg. when a webxdc attempts to send a message to a chat) -->
|
||||
<string name="send_message_to">Envoyer un message à</string>
|
||||
<string name="enable_realtime">Canaux Webxdc temps réel</string>
|
||||
<string name="enable_realtime_explain">Enclenchez l\'API temps réel Webxdc pour créer des connexions entre les appareils.</string>
|
||||
|
||||
<!-- map -->
|
||||
<string name="filter_map_on_time">Afficher les positions pour une période</string>
|
||||
<string name="show_location_traces">Afficher les traces</string>
|
||||
@@ -598,8 +595,6 @@
|
||||
<string name="proxy_use_proxy">Utiliser le proxy</string>
|
||||
<string name="proxy_add">Ajouter un proxy</string>
|
||||
<string name="proxy_add_explain">Types de proxy supportés: HTTP(S), SOCKS5 et Shadowsocks.</string>
|
||||
<string name="proxy_add_url_hint">Entrez l\'URL du proxy ici</string>
|
||||
<string name="proxy_invalid">URL invalide ou non supportée</string>
|
||||
<string name="proxy_list_header">Sauver les proxy</string>
|
||||
<string name="proxy_delete">Effacer le proxy</string>
|
||||
<!-- deprecated -->
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
<!-- deprecated, the word "or" to separate blocks in the user interface that are mutually exclusive -->
|
||||
<string name="or_separator">vagy</string>
|
||||
<string name="clear_search">Keresés kiürítése</string>
|
||||
<!-- a noun, used on a button, short for "show link" -->
|
||||
<string name="link">Hivatkozás</string>
|
||||
<!-- "scan" in the meaning of "scan QR code" -->
|
||||
<string name="scan">Beolvasás</string>
|
||||
<string name="yes">Igen</string>
|
||||
<string name="no">Nem</string>
|
||||
<string name="select">Kiválasztás</string>
|
||||
@@ -234,6 +238,7 @@
|
||||
<string name="menu_message_details">Üzenet részletei</string>
|
||||
<string name="menu_copy_to_clipboard">Vágólapra másolás</string>
|
||||
<string name="share_invite_link">Meghívó-hivatkozás megosztása</string>
|
||||
<string name="share_invite_link_explain">Ezen a hivatkozáson keresztül bárki megtekintheti az Ön profilját, és csevegést kezdeményezhet Önnel. Csak olyan emberekkel ossza meg, akikben megbízik.</string>
|
||||
<string name="invite_friends">Ismerősök meghívása</string>
|
||||
<!-- %1$s is replaced by the user's invitation link ("https://i.delta.chat/...") -->
|
||||
<string name="invite_friends_text">Lépjen kapcsolatba velem a Delta Chat-en:\n%1$s</string>
|
||||
@@ -441,9 +446,6 @@
|
||||
<string name="send_file_to">„%1$s” küldése…</string>
|
||||
<!-- title shown above a list contacts where one should be selected (eg. when a webxdc attempts to send a message to a chat) -->
|
||||
<string name="send_message_to">Üzenet küldése…</string>
|
||||
<string name="enable_realtime">Valósidejű Webxdc-csatornák</string>
|
||||
<string name="enable_realtime_explain">A Webxdc valós idejű API-k engedélyezése az eszközök közötti közvetlen kapcsolatok létrehozásához.</string>
|
||||
|
||||
<!-- map -->
|
||||
<string name="filter_map_on_time">Helyszínek megjelenítése időkeretben</string>
|
||||
<string name="show_location_traces">Nyomvonalak megjelenítése</string>
|
||||
@@ -616,12 +618,14 @@
|
||||
<string name="proxy_use_proxy">Proxy használata</string>
|
||||
<string name="proxy_add">Proxy hozzáadása</string>
|
||||
<string name="proxy_add_explain">Támogatott proxytípusok: HTTP(S), SOCKS5 és ShadowSOCKS</string>
|
||||
<string name="proxy_add_url_hint">Adja meg a proxy webcímét</string>
|
||||
<string name="proxy_invalid">Érvénytelen vagy nem támogatott a proxy webcíme</string>
|
||||
<string name="proxy_add_url_hint">Adja meg a proxy hivatkozását</string>
|
||||
<string name="proxy_invalid">Érvénytelen vagy nem támogatott proxy</string>
|
||||
<string name="proxy_list_header">Mentett proxyk</string>
|
||||
<string name="proxy_delete">Proxy törlése</string>
|
||||
<string name="proxy_delete_explain">Biztosan törli a következőt: „%1$s”?</string>
|
||||
<string name="proxy_use_proxy_confirm">Biztosan használni akarja a következő proxyt: „%1$s”?</string>
|
||||
<string name="proxy_share_explain">Az ismerősei a QR-kód beolvasásával hozzáadhatják ezt a proxyt.</string>
|
||||
<string name="proxy_share_link">Hivatkozás megosztása</string>
|
||||
|
||||
<!-- deprecated -->
|
||||
<string name="login_socks5">SOCKS5</string>
|
||||
@@ -647,6 +651,9 @@
|
||||
<string name="accept_invalid_certificates">Érvénytelen tanúsítványok elfogadása</string>
|
||||
<string name="switch_account">Profilváltás</string>
|
||||
<string name="add_account">Profil hozzáadása</string>
|
||||
<string name="profile_tag">Privát címke</string>
|
||||
<string name="profile_tag_hint">Például: „Munka”, „Család”</string>
|
||||
<string name="profile_tag_explain">A címke csak az Ön számára jelenik meg; segít megkülönböztetni a profiljait.</string>
|
||||
<string name="delete_account">Profil törlése</string>
|
||||
<string name="delete_account_ask">Biztosan törölni szeretné a profiladatait?</string>
|
||||
<string name="delete_account_explain_with_name">A(z) „%s” nevű profilhoz tartozó mindet adat ezen az eszközön törölve lesz, beleértve a végpontok közötti titkosítás beállításait, a névjegyeket, a csevegéseket, az üzeneteket és a médiát. Ez a művelet nem vonható vissza. </string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user