mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 05:26:42 +03:00
Compare commits
20 Commits
py-1.66.0
...
chatlist_c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e35c23f29 | ||
|
|
7f97768c56 | ||
|
|
ecd548a7aa | ||
|
|
62efb0795b | ||
|
|
53f042ee08 | ||
|
|
6ce97bd0cd | ||
|
|
c29149e74c | ||
|
|
487f7593ce | ||
|
|
6b3b33d2a0 | ||
|
|
2d70ccc2bf | ||
|
|
e90fc9504a | ||
|
|
5108314c03 | ||
|
|
f2b86a1c0f | ||
|
|
9583d41446 | ||
|
|
e594064f93 | ||
|
|
7c52fd95ec | ||
|
|
416bf3a829 | ||
|
|
cc78347293 | ||
|
|
27e4ae992a | ||
|
|
eb610c27bf |
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,5 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
### API Changes
|
||||
- add `DC_EVENT_CHAT_LIST_CHANGED` event
|
||||
|
||||
### Changes
|
||||
- add chatlist changed event
|
||||
|
||||
## 1.68.0
|
||||
|
||||
### Fixes
|
||||
- fix chat assignment when forwarding #2843
|
||||
- fix layout issues with the generated QR code svg #2842
|
||||
|
||||
|
||||
## 1.67.0
|
||||
|
||||
### API changes
|
||||
- `dc_get_securejoin_qr_svg(chat_id)` added #2815
|
||||
- added stock-strings `DC_STR_SETUP_CONTACT_QR_DESC` and `DC_STR_SECURE_JOIN_GROUP_QR_DESC`
|
||||
|
||||
|
||||
## 1.66.0
|
||||
|
||||
### API changes
|
||||
|
||||
102
Cargo.lock
generated
102
Cargo.lock
generated
@@ -114,9 +114,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.47"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38d9ff5d688f1c13395289f67db01d4826b46dd694e7580accdc3e8430f2d98e"
|
||||
checksum = "62e1f47f7dc0422027a4e370dd4548d4d66b26782e513e98dca1e689e058a80e"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
@@ -706,7 +706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"textwrap",
|
||||
"textwrap 0.11.0",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
@@ -1056,7 +1056,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.66.0"
|
||||
version = "1.68.0"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
@@ -1098,6 +1098,7 @@ dependencies = [
|
||||
"pgp",
|
||||
"pretty_env_logger",
|
||||
"proptest",
|
||||
"qrcodegen",
|
||||
"quick-xml",
|
||||
"r2d2",
|
||||
"r2d2_sqlite",
|
||||
@@ -1116,7 +1117,9 @@ dependencies = [
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"surf",
|
||||
"tagger",
|
||||
"tempfile",
|
||||
"textwrap 0.14.2",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"url",
|
||||
@@ -1133,7 +1136,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.66.0"
|
||||
version = "1.68.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
@@ -1548,9 +1551,9 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.17"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca"
|
||||
checksum = "8cd0210d8c325c245ff06fd95a3b13689a1a276ac8cfa8e8720cb840bfb84b9e"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -1563,9 +1566,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.17"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
|
||||
checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -1573,15 +1576,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.17"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
|
||||
checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.17"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c"
|
||||
checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@@ -1590,9 +1593,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.17"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
|
||||
checksum = "e481354db6b5c353246ccf6a728b0c5511d752c08da7260546fc0933869daa11"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
@@ -1611,12 +1614,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.17"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb"
|
||||
checksum = "a89f17b21645bc4ed773c69af9c9a0effd4a3f1a3876eadd453469f8854e7fdd"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -1624,23 +1625,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.17"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11"
|
||||
checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.17"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
|
||||
checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.17"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
|
||||
checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
@@ -1650,8 +1650,6 @@ dependencies = [
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"proc-macro-hack",
|
||||
"proc-macro-nested",
|
||||
"slab",
|
||||
]
|
||||
|
||||
@@ -2069,9 +2067,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.107"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
|
||||
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@@ -2737,12 +2735,6 @@ version = "0.5.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-nested"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.32"
|
||||
@@ -2769,6 +2761,12 @@ dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qrcodegen"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "135e6754eed8ca897dd70584d895e72e36860b3e163b6bcedce48571cbaef343"
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
@@ -3392,6 +3390,12 @@ version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.19"
|
||||
@@ -3593,6 +3597,12 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tagger"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c933e626196d509b053f49573e35ad237c0ff6a36c23c1aa61ffeab49251d24f"
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
@@ -3631,6 +3641,17 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
||||
dependencies = [
|
||||
"smawk",
|
||||
"unicode-linebreak",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
@@ -3828,6 +3849,15 @@ version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-linebreak"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.19"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.66.0"
|
||||
version = "1.68.0"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
@@ -73,6 +73,9 @@ url = "2"
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
fast-socks5 = "0.4"
|
||||
humansize = "1"
|
||||
qrcodegen = "1.7.0"
|
||||
tagger = "3.2.1"
|
||||
textwrap = "0.14.2"
|
||||
|
||||
[dev-dependencies]
|
||||
ansi_term = "0.12.0"
|
||||
|
||||
10
assets/qrcode_logo_footer.svg
Normal file
10
assets/qrcode_logo_footer.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-weight:bold;font-size:24.4118px;line-height:1.25;font-family:sans-serif;fill:#aaaaaa;fill-opacity:1;stroke:none;stroke-width:0.915439"
|
||||
x="42.325161"
|
||||
y="23.32255"
|
||||
id="text72398">get.delta.chat</text>
|
||||
<path
|
||||
id="path84310"
|
||||
style="opacity:0.25;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.915439"
|
||||
d="M 17.13769,0.00129321 C 7.6753075,0.11650893 0,7.8915283 0,17.362467 c 0,9.47094 7.6753075,17.059745 17.13769,16.944599 8.99669,-0.03598 6.880074,-5.025654 16.824785,-0.405885 -5.447648,-8.510047 0.184241,-9.642482 0.311117,-16.955289 0,-9.4709395 -7.673512,-17.0597453 -17.135895,-16.94459879 z M 17.0769,4.9986797 c 1.84214,0 3.447355,0.253959 4.815003,0.7616693 1.381603,0.5076411 2.072253,1.207862 2.072253,2.0990711 0,0.4286855 -0.167495,0.7836052 -0.50242,1.0656242 -0.334921,0.2819844 -0.724544,0.4237724 -1.171121,0.4237724 -0.641952,0 -1.396532,-0.3909376 -2.261778,-1.169353 C 19.14963,7.3898036 18.402555,6.83791 17.788507,6.5220182 17.188416,6.1950547 16.484552,6.0321266 15.675129,6.0321266 c -1.032717,0 -1.883352,0.1854523 -2.553215,0.5578447 -0.655913,0.372254 -0.98517,0.8460916 -0.98517,1.4214436 0,0.5414792 0.272815,1.0495355 0.817093,1.5233385 0.544275,0.4738026 1.946291,1.3367446 4.207097,2.5889976 2.414319,1.342419 4.117377,2.390985 5.108232,3.146807 1.004795,0.755857 1.821505,1.675853 2.449514,2.758846 0.628002,1.082993 0.942253,2.227607 0.942253,3.434674 0,2.120834 -0.929555,3.993314 -2.785656,5.617786 -1.84214,1.613228 -3.99694,2.41915 -6.467082,2.41915 -2.246845,0 -4.145607,-0.647976 -5.694677,-1.945312 -1.5490699,-1.297336 -2.3225722,-3.028063 -2.3225722,-5.194049 0,-2.087031 0.8506345,-3.83094 2.5532182,-5.229825 1.716541,-1.398884 3.824203,-2.245599 6.322256,-2.538897 -0.697774,-0.631749 -1.668763,-1.387225 -2.910816,-2.267155 -1.367648,-0.970199 -2.287914,-1.73045 -2.762402,-2.283243 -0.474491,-0.5640381 -0.711618,-1.1795944 -0.711618,-1.8451814 0,-0.9927581 0.572093,-1.7710351 1.716451,-2.3351077 1.144362,-0.5753173 2.636724,-0.8635642 4.478865,-0.8635642 z m 1.110327,10.3738083 c -4.005262,0.5302 -6.007576,2.75279 -6.007576,6.667322 0,2.01932 0.49495,3.587291 1.485805,4.704157 1.004806,1.116832 2.169696,1.675299 3.495479,1.675299 1.381602,0 2.520072,-0.535632 3.413229,-1.60738 0.893168,-1.082959 1.339187,-2.545264 1.339187,-4.384079 0,-2.662348 -1.242022,-5.013441 -3.726124,-7.055319 z" />
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.66.0"
|
||||
version = "1.68.0"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -2243,6 +2243,20 @@ dc_lot_t* dc_check_qr (dc_context_t* context, const char*
|
||||
char* dc_get_securejoin_qr (dc_context_t* context, uint32_t chat_id);
|
||||
|
||||
|
||||
/**
|
||||
* Get QR code image from the QR code text generated by dc_get_securejoin_qr().
|
||||
* See dc_get_securejoin_qr() for details about the contained QR code.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @param chat_id group-chat-id for secure-join or 0 for setup-contact,
|
||||
* see dc_get_securejoin_qr() for details.
|
||||
* @return SVG-Image with the QR code.
|
||||
* On errors, an empty string is returned.
|
||||
* The returned string must be released using dc_str_unref() after usage.
|
||||
*/
|
||||
char* dc_get_securejoin_qr_svg (dc_context_t* context, uint32_t chat_id);
|
||||
|
||||
/**
|
||||
* Continue a Setup-Contact or Verified-Group-Invite protocol
|
||||
* started on another device with dc_get_securejoin_qr().
|
||||
@@ -5287,6 +5301,14 @@ void dc_event_unref(dc_event_t* event);
|
||||
*/
|
||||
#define DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED 2021
|
||||
|
||||
/**
|
||||
* Chatlist changed and ui should reload it (reordering, entry added or removed)
|
||||
*
|
||||
* This event is fired when the ordering or contents of the chatlist change,
|
||||
* for changes in individual chat list items listen to `IncomingMsg`, `MsgsNoticed`, `MsgDelivered`, `MsgFailed`, `MsgRead` and `ChatModified`
|
||||
*/
|
||||
#define DC_EVENT_CHAT_LIST_CHANGED 2025
|
||||
|
||||
|
||||
/**
|
||||
* Contact(s) created, renamed, verified, blocked or deleted.
|
||||
@@ -6077,6 +6099,20 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// `%1$s` will be replaced by the name of the inviter.
|
||||
#define DC_STR_SECURE_JOIN_REPLIES 118
|
||||
|
||||
/// "Scan to chat with %1$s"
|
||||
///
|
||||
/// Subtitle for verification qrcode svg image generated by the core.
|
||||
///
|
||||
/// `%1$s` will be replaced by name and address of the inviter.
|
||||
#define DC_STR_SETUP_CONTACT_QR_DESC 119
|
||||
|
||||
/// "Scan to join %1$s"
|
||||
///
|
||||
/// Subtitle for group join qrcode svg image generated by the core.
|
||||
///
|
||||
/// `%1$s` will be replaced with the group name.
|
||||
#define DC_STR_SECURE_JOIN_GROUP_QR_DESC 120
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
@@ -25,6 +25,7 @@ use std::time::{Duration, SystemTime};
|
||||
use anyhow::Context as _;
|
||||
use async_std::sync::RwLock;
|
||||
use async_std::task::{block_on, spawn};
|
||||
use deltachat::qr_code_generator::get_securejoin_qr_svg;
|
||||
use num_traits::{FromPrimitive, ToPrimitive};
|
||||
|
||||
use deltachat::chat::{ChatId, ChatVisibility, MuteDuration, ProtectionStatus};
|
||||
@@ -439,6 +440,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
|
||||
| EventType::Error(_)
|
||||
| EventType::ConnectivityChanged
|
||||
| EventType::SelfavatarChanged
|
||||
| EventType::ChatListChanged
|
||||
| EventType::ErrorSelfNotInGroup(_) => 0,
|
||||
EventType::MsgsChanged { chat_id, .. }
|
||||
| EventType::IncomingMsg { chat_id, .. }
|
||||
@@ -490,7 +492,8 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
|
||||
| EventType::MsgsNoticed(_)
|
||||
| EventType::ConnectivityChanged
|
||||
| EventType::SelfavatarChanged
|
||||
| EventType::ChatModified(_) => 0,
|
||||
| EventType::ChatModified(_)
|
||||
| EventType::ChatListChanged => 0,
|
||||
EventType::MsgsChanged { msg_id, .. }
|
||||
| EventType::IncomingMsg { msg_id, .. }
|
||||
| EventType::MsgDelivered { msg_id, .. }
|
||||
@@ -533,6 +536,7 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
|
||||
| EventType::MsgFailed { .. }
|
||||
| EventType::MsgRead { .. }
|
||||
| EventType::ChatModified(_)
|
||||
| EventType::ChatListChanged
|
||||
| EventType::ContactsChanged(_)
|
||||
| EventType::LocationChanged(_)
|
||||
| EventType::ImexProgress(_)
|
||||
@@ -2075,6 +2079,27 @@ pub unsafe extern "C" fn dc_get_securejoin_qr(
|
||||
.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_get_securejoin_qr_svg(
|
||||
context: *mut dc_context_t,
|
||||
chat_id: u32,
|
||||
) -> *mut libc::c_char {
|
||||
if context.is_null() {
|
||||
eprintln!("ignoring careless call to generate_verification_qr()");
|
||||
return "".strdup();
|
||||
}
|
||||
let ctx = &*context;
|
||||
let chat_id = if chat_id == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(ChatId::new(chat_id))
|
||||
};
|
||||
|
||||
block_on(get_securejoin_qr_svg(ctx, chat_id))
|
||||
.unwrap_or_else(|_| "".to_string())
|
||||
.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_join_securejoin(
|
||||
context: *mut dc_context_t,
|
||||
|
||||
33
draft/aeap-mvp.rst
Normal file
33
draft/aeap-mvp.rst
Normal file
@@ -0,0 +1,33 @@
|
||||
AEAP MVP
|
||||
========
|
||||
|
||||
Changes to the UIs
|
||||
------------------
|
||||
|
||||
- The secondary self addresses (see below) are shown in the UI, but not editable.
|
||||
|
||||
- When the user changed the email address in the configure screen, show a dialog to the user, either directly explaining things or with a link to the FAQ (see "Other" below)
|
||||
|
||||
Changes in the core
|
||||
-------------------
|
||||
|
||||
- We have one primary self address and any number of secondary self addresses. `is_self_addr()` checks all of them.
|
||||
|
||||
- If the user does a reconfigure and changes the email address, the previous address is added as a secondary self address.
|
||||
|
||||
- don't forget to deduplicate secondary self addresses in case the user switches back and forth between addresses).
|
||||
|
||||
- The key stays the same.
|
||||
|
||||
- No changes for 1:1 chats, there simply is a new one
|
||||
|
||||
- When we send a message to a group, and the primary address is not a member of a group, but a secondary address is:
|
||||
|
||||
Add Chat-Group-Member-Removed=<old address> and Chat-Group-Member-Added=<new address> headers to this message
|
||||
|
||||
- On the receiving side, make sure that we accept this (even in verified groups) if the message is signed and the key stayed the same
|
||||
|
||||
Other
|
||||
-----
|
||||
|
||||
- The user is responsible that messages to the old address arrive at the new address, for example by configuring the old provider to forward all emails to the new one.
|
||||
@@ -96,6 +96,7 @@ async fn reset_tables(context: &Context, bits: i32) {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
}
|
||||
|
||||
async fn poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<()> {
|
||||
@@ -167,6 +168,7 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -423,6 +425,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
listblocked\n\
|
||||
======================================Misc.==\n\
|
||||
getqr [<chat-id>]\n\
|
||||
getqrsvg [<chat-id>]\n\
|
||||
getbadqr\n\
|
||||
checkqr <qr-content>\n\
|
||||
joinqr <qr-content>\n\
|
||||
@@ -754,7 +757,12 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
"groupname" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
ensure!(!arg1.is_empty(), "Argument <name> missing.");
|
||||
chat::set_chat_name(&context, sel_chat.as_ref().unwrap().get_id(), arg1).await?;
|
||||
chat::set_chat_name(
|
||||
&context,
|
||||
sel_chat.as_ref().unwrap().get_id(),
|
||||
&format!("{} {}", arg1, arg2).trim(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Chat name set");
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ use rustyline::{
|
||||
|
||||
mod cmdline;
|
||||
use self::cmdline::*;
|
||||
use deltachat::qr_code_generator::get_securejoin_qr_svg;
|
||||
use std::fs;
|
||||
|
||||
/// Event Handler
|
||||
fn receive_event(event: EventType) {
|
||||
@@ -112,6 +114,12 @@ fn receive_event(event: EventType) {
|
||||
yellow.paint(format!("Received CHAT_MODIFIED({})", chat))
|
||||
);
|
||||
}
|
||||
EventType::ChatListChanged(chat) => {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint("Received CHAT_LIST_CHANGED")
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
info!("Received {:?}", event);
|
||||
}
|
||||
@@ -224,8 +232,9 @@ const CONTACT_COMMANDS: [&str; 9] = [
|
||||
"unblock",
|
||||
"listblocked",
|
||||
];
|
||||
const MISC_COMMANDS: [&str; 11] = [
|
||||
const MISC_COMMANDS: [&str; 12] = [
|
||||
"getqr",
|
||||
"getqrsvg",
|
||||
"getbadqr",
|
||||
"checkqr",
|
||||
"joinqr",
|
||||
@@ -427,6 +436,20 @@ async fn handle_cmd(
|
||||
io::stderr().write_all(&output.stderr).unwrap();
|
||||
}
|
||||
}
|
||||
"getqrsvg" => {
|
||||
ctx.start_io().await;
|
||||
let group = arg1.parse::<u32>().ok().map(|id| ChatId::new(id));
|
||||
let file = dirs::home_dir().unwrap_or_default().join("qr.svg");
|
||||
match get_securejoin_qr_svg(&ctx, group).await {
|
||||
Ok(svg) => {
|
||||
fs::write(&file, svg)?;
|
||||
println!("QR code svg written to: {:#?}", file);
|
||||
}
|
||||
Err(err) => {
|
||||
bail!("Failed to get QR code svg: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
"joinqr" => {
|
||||
ctx.start_io().await;
|
||||
if !arg0.is_empty() {
|
||||
|
||||
104
src/chat.rs
104
src/chat.rs
@@ -211,6 +211,7 @@ impl ChatId {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
Ok(chat_id)
|
||||
}
|
||||
|
||||
@@ -301,10 +302,12 @@ impl ChatId {
|
||||
Chattype::Group => {
|
||||
info!(context, "Can't block groups yet, deleting the chat");
|
||||
self.delete(context).await?;
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
}
|
||||
Chattype::Mailinglist => {
|
||||
if self.set_blocked(context, Blocked::Yes).await? {
|
||||
context.emit_event(EventType::ChatModified(self));
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -345,6 +348,7 @@ impl ChatId {
|
||||
|
||||
if self.set_blocked(context, Blocked::Not).await? {
|
||||
context.emit_event(EventType::ChatModified(self));
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -489,6 +493,7 @@ impl ChatId {
|
||||
msg_id: MsgId::new(0),
|
||||
chat_id: ChatId::new(0),
|
||||
});
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -546,6 +551,7 @@ impl ChatId {
|
||||
msg_id: MsgId::new(0),
|
||||
chat_id: ChatId::new(0),
|
||||
});
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
|
||||
job::kill_action(context, Action::Housekeeping).await?;
|
||||
let j = job::Job::new(Action::Housekeeping, 0, Params::new(), 10);
|
||||
@@ -1992,7 +1998,8 @@ pub async fn get_chat_msgs(
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
msg_id: MsgId::new(0),
|
||||
chat_id: ChatId::new(0),
|
||||
})
|
||||
});
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2292,6 +2299,7 @@ pub async fn create_group_chat(
|
||||
msg_id: MsgId::new(0),
|
||||
chat_id: ChatId::new(0),
|
||||
});
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
|
||||
if protect == ProtectionStatus::Protected {
|
||||
// this part is to stay compatible to verified groups,
|
||||
@@ -2349,6 +2357,7 @@ pub async fn create_broadcast_list(context: &Context) -> Result<ChatId> {
|
||||
msg_id: MsgId::new(0),
|
||||
chat_id: ChatId::new(0),
|
||||
});
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
Ok(chat_id)
|
||||
}
|
||||
|
||||
@@ -2846,6 +2855,7 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId)
|
||||
msg.param.remove(Param::ForcePlaintext);
|
||||
msg.param.remove(Param::Cmd);
|
||||
msg.param.remove(Param::OverrideSenderDisplayname);
|
||||
msg.in_reply_to = None;
|
||||
|
||||
// do not leak data as group names; a default subject is generated by mimfactory
|
||||
msg.subject = "".to_string();
|
||||
@@ -4291,6 +4301,98 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_forward_quote() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
let alice_chat = alice.create_chat(&bob).await;
|
||||
let bob_chat = bob.create_chat(&alice).await;
|
||||
|
||||
// Alice sends a message to Bob.
|
||||
let sent_msg = alice.send_text(alice_chat.id, "Hi Bob").await;
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
let received_msg = bob.get_last_msg().await;
|
||||
|
||||
// Bob quotes received message and sends a reply to Alice.
|
||||
let mut reply = Message::new(Viewtype::Text);
|
||||
reply.set_text(Some("Reply".to_owned()));
|
||||
reply.set_quote(&bob, &received_msg).await?;
|
||||
let sent_reply = bob.send_msg(bob_chat.id, &mut reply).await;
|
||||
alice.recv_msg(&sent_reply).await;
|
||||
let received_reply = alice.get_last_msg().await;
|
||||
|
||||
// Alice forwards a reply.
|
||||
forward_msgs(&alice, &[received_reply.id], alice_chat.get_id()).await?;
|
||||
let forwarded_msg = alice.pop_sent_msg().await;
|
||||
bob.recv_msg(&forwarded_msg).await;
|
||||
|
||||
let alice_forwarded_msg = alice.get_last_msg().await;
|
||||
assert!(alice_forwarded_msg.quoted_message(&alice).await?.is_none());
|
||||
assert_eq!(
|
||||
alice_forwarded_msg.quoted_text(),
|
||||
Some("Hi Bob".to_string())
|
||||
);
|
||||
|
||||
let bob_forwarded_msg = bob.get_last_msg().await;
|
||||
assert!(bob_forwarded_msg.quoted_message(&bob).await?.is_none());
|
||||
assert_eq!(bob_forwarded_msg.quoted_text(), Some("Hi Bob".to_string()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_forward_group() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
|
||||
let alice_chat = alice.create_chat(&bob).await;
|
||||
let bob_chat = bob.create_chat(&alice).await;
|
||||
|
||||
// Alice creates a group with Bob.
|
||||
let alice_group_chat_id =
|
||||
create_group_chat(&alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
let bob_id = Contact::create(&alice, "Bob", "bob@example.net").await?;
|
||||
let claire_id = Contact::create(&alice, "Claire", "claire@example.net").await?;
|
||||
add_contact_to_chat(&alice, alice_group_chat_id, bob_id).await?;
|
||||
add_contact_to_chat(&alice, alice_group_chat_id, claire_id).await?;
|
||||
let sent_group_msg = alice
|
||||
.send_text(alice_group_chat_id, "Hi Bob and Claire")
|
||||
.await;
|
||||
bob.recv_msg(&sent_group_msg).await;
|
||||
let bob_group_chat_id = bob.get_last_msg().await.chat_id;
|
||||
|
||||
// Alice deletes a message on her device.
|
||||
// This is needed to make assignment of further messages received in this group
|
||||
// based on `References:` header harder.
|
||||
// Previously this exposed a bug, so this is a regression test.
|
||||
message::delete_msgs(&alice, &[sent_group_msg.sender_msg_id]).await?;
|
||||
|
||||
// Alice sends a message to Bob.
|
||||
let sent_msg = alice.send_text(alice_chat.id, "Hi Bob").await;
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
let received_msg = bob.get_last_msg().await;
|
||||
assert_eq!(received_msg.get_text(), Some("Hi Bob".to_string()));
|
||||
assert_eq!(received_msg.chat_id, bob_chat.id);
|
||||
|
||||
// Alice sends another message to Bob, this has first message as a parent.
|
||||
let sent_msg = alice.send_text(alice_chat.id, "Hello Bob").await;
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
let received_msg = bob.get_last_msg().await;
|
||||
assert_eq!(received_msg.get_text(), Some("Hello Bob".to_string()));
|
||||
assert_eq!(received_msg.chat_id, bob_chat.id);
|
||||
|
||||
// Bob forwards message to a group chat with Alice.
|
||||
forward_msgs(&bob, &[received_msg.id], bob_group_chat_id).await?;
|
||||
let forwarded_msg = bob.pop_sent_msg().await;
|
||||
alice.recv_msg(&forwarded_msg).await;
|
||||
|
||||
let received_forwarded_msg = alice.get_last_msg_in(alice_group_chat_id).await;
|
||||
assert!(received_forwarded_msg.is_forwarded());
|
||||
assert_eq!(received_forwarded_msg.chat_id, alice_group_chat_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_only_minimal_data_are_forwarded() -> Result<()> {
|
||||
// send a message from Alice to a group with Bob
|
||||
|
||||
@@ -35,6 +35,10 @@ pub(crate) fn str_to_color(s: &str) -> u32 {
|
||||
rgb_to_u32(hsluv_to_rgb((str_to_angle(s), 100.0, 50.0)))
|
||||
}
|
||||
|
||||
pub fn color_int_to_hex_string(color: u32) -> String {
|
||||
format!("{:#08x}", color).replace("0x", "#")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -314,6 +314,7 @@ impl Context {
|
||||
msg_id: MsgId::new(0),
|
||||
chat_id: ChatId::new(0),
|
||||
});
|
||||
self.emit_event(EventType::ChatListChanged);
|
||||
ret
|
||||
}
|
||||
Config::Displayname => {
|
||||
|
||||
@@ -252,12 +252,16 @@ impl Contact {
|
||||
|
||||
/// Block the given contact.
|
||||
pub async fn block(context: &Context, id: u32) -> Result<()> {
|
||||
set_block_contact(context, id, true).await
|
||||
set_block_contact(context, id, true).await?;
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unblock the given contact.
|
||||
pub async fn unblock(context: &Context, id: u32) -> Result<()> {
|
||||
set_block_contact(context, id, false).await
|
||||
set_block_contact(context, id, false).await?;
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a single contact as a result of an _explicit_ user action.
|
||||
|
||||
@@ -2113,6 +2113,8 @@ fn set_better_msg(mime_parser: &mut MimeMessage, better_msg: impl AsRef<str>) {
|
||||
/// Returns the last message referenced from `References` header if it is in the database.
|
||||
///
|
||||
/// For Delta Chat messages it is the last message in the chat of the sender.
|
||||
///
|
||||
/// Note that the returned message may be trashed.
|
||||
async fn get_previous_message(
|
||||
context: &Context,
|
||||
mime_parser: &MimeMessage,
|
||||
@@ -2128,6 +2130,8 @@ async fn get_previous_message(
|
||||
}
|
||||
|
||||
/// Given a list of Message-IDs, returns the latest message found in the database.
|
||||
///
|
||||
/// Only messages that are not in the trash chat are considered.
|
||||
async fn get_rfc724_mid_in_list(context: &Context, mid_list: &str) -> Result<Option<Message>> {
|
||||
if mid_list.is_empty() {
|
||||
return Ok(None);
|
||||
@@ -2135,7 +2139,10 @@ async fn get_rfc724_mid_in_list(context: &Context, mid_list: &str) -> Result<Opt
|
||||
|
||||
for id in parse_message_ids(mid_list).iter().rev() {
|
||||
if let Some((_, _, msg_id)) = rfc724_mid_exists(context, id).await? {
|
||||
return Ok(Some(Message::load_from_db(context, msg_id).await?));
|
||||
let msg = Message::load_from_db(context, msg_id).await?;
|
||||
if msg.chat_id != DC_CHAT_ID_TRASH {
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4741,4 +4748,66 @@ Second thread."#;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_get_parent_message() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
|
||||
let mime = br#"Subject: First
|
||||
Message-ID: first@example.net
|
||||
To: Alice <alice@example.com>
|
||||
From: Bob <bob@example.net>
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
First."#;
|
||||
dc_receive_imf(&t, mime, "INBOX", 1, false).await?;
|
||||
let first = t.get_last_msg().await;
|
||||
let mime = br#"Subject: Second
|
||||
Message-ID: second@example.net
|
||||
To: Alice <alice@example.com>
|
||||
From: Bob <bob@example.net>
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
First."#;
|
||||
dc_receive_imf(&t, mime, "INBOX", 2, false).await?;
|
||||
let second = t.get_last_msg().await;
|
||||
let mime = br#"Subject: Third
|
||||
Message-ID: third@example.net
|
||||
To: Alice <alice@example.com>
|
||||
From: Bob <bob@example.net>
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
First."#;
|
||||
dc_receive_imf(&t, mime, "INBOX", 3, false).await?;
|
||||
let third = t.get_last_msg().await;
|
||||
|
||||
let mime = br#"Subject: Message with references.
|
||||
Message-ID: second@example.net
|
||||
To: Alice <alice@example.com>
|
||||
From: Bob <bob@example.net>
|
||||
In-Reply-To: <third@example.net>
|
||||
References: <second@example.net> <nonexistent@example.net> <first@example.net>
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
Message with references."#;
|
||||
let mime_parser = MimeMessage::from_bytes(&t, &mime[..]).await?;
|
||||
|
||||
let parent = get_parent_message(&t, &mime_parser).await?.unwrap();
|
||||
assert_eq!(parent.id, first.id);
|
||||
|
||||
message::delete_msgs(&t, &[first.id]).await?;
|
||||
let parent = get_parent_message(&t, &mime_parser).await?.unwrap();
|
||||
assert_eq!(parent.id, second.id);
|
||||
|
||||
message::delete_msgs(&t, &[second.id]).await?;
|
||||
let parent = get_parent_message(&t, &mime_parser).await?.unwrap();
|
||||
assert_eq!(parent.id, third.id);
|
||||
|
||||
message::delete_msgs(&t, &[third.id]).await?;
|
||||
let parent = get_parent_message(&t, &mime_parser).await?;
|
||||
assert!(parent.is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,6 +422,7 @@ pub async fn schedule_ephemeral_task(context: &Context) {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
context1.emit_event(EventType::ChatListChanged);
|
||||
});
|
||||
*context.ephemeral_task.write().await = Some(ephemeral_task);
|
||||
} else {
|
||||
@@ -430,6 +431,7 @@ pub async fn schedule_ephemeral_task(context: &Context) {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +248,13 @@ pub enum EventType {
|
||||
timer: EphemeralTimer,
|
||||
},
|
||||
|
||||
// Chatlist changed and ui should reload it (reordering, entry added or removed)
|
||||
//
|
||||
// This event is fired when the ordering or contents of the chatlist change,
|
||||
// for changes in individual chat list items listen to `IncomingMsg`, `MsgsNoticed`, `MsgDelivered`, `MsgFailed`, `MsgRead` and `ChatModified`
|
||||
#[strum(props(id = "2025"))]
|
||||
ChatListChanged,
|
||||
|
||||
/// Contact(s) created, renamed, blocked or deleted.
|
||||
///
|
||||
/// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
|
||||
|
||||
@@ -77,6 +77,7 @@ pub mod peerstate;
|
||||
pub mod pgp;
|
||||
pub mod provider;
|
||||
pub mod qr;
|
||||
pub mod qr_code_generator;
|
||||
pub mod quota;
|
||||
pub mod securejoin;
|
||||
mod simplify;
|
||||
|
||||
@@ -877,7 +877,7 @@ impl Message {
|
||||
}
|
||||
|
||||
pub async fn quoted_message(&self, context: &Context) -> Result<Option<Message>> {
|
||||
if self.param.get(Param::Quote).is_some() {
|
||||
if self.param.get(Param::Quote).is_some() && !self.is_forwarded() {
|
||||
if let Some(in_reply_to) = &self.in_reply_to {
|
||||
if let Some((_, _, msg_id)) = rfc724_mid_exists(context, in_reply_to).await? {
|
||||
let msg = Message::load_from_db(context, msg_id).await?;
|
||||
@@ -1295,6 +1295,7 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
context.emit_event(EventType::ChatListChanged);
|
||||
job::kill_action(context, Action::Housekeeping).await?;
|
||||
job::add(
|
||||
context,
|
||||
|
||||
269
src/qr_code_generator.rs
Normal file
269
src/qr_code_generator.rs
Normal file
@@ -0,0 +1,269 @@
|
||||
use anyhow::Result;
|
||||
use qrcodegen::{QrCode, QrCodeEcc};
|
||||
|
||||
use crate::{
|
||||
blob::BlobObject,
|
||||
chat::{Chat, ChatId},
|
||||
color::color_int_to_hex_string,
|
||||
config::Config,
|
||||
constants::DC_CONTACT_ID_SELF,
|
||||
contact::Contact,
|
||||
context::Context,
|
||||
securejoin, stock_str,
|
||||
};
|
||||
|
||||
pub async fn get_securejoin_qr_svg(context: &Context, chat_id: Option<ChatId>) -> Result<String> {
|
||||
if let Some(chat_id) = chat_id {
|
||||
generate_join_group_qr_code(context, chat_id).await
|
||||
} else {
|
||||
generate_verification_qr(context).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn generate_join_group_qr_code(context: &Context, chat_id: ChatId) -> Result<String> {
|
||||
let chat = Chat::load_from_db(context, chat_id).await?;
|
||||
|
||||
let avatar = match chat.get_profile_image(context).await? {
|
||||
Some(path) => {
|
||||
let avatar_blob = BlobObject::from_path(context, &path)?;
|
||||
Some(std::fs::read(avatar_blob.to_abs_path())?)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
inner_generate_secure_join_qr_code(
|
||||
&stock_str::secure_join_group_qr_description(context, &chat).await,
|
||||
&securejoin::dc_get_securejoin_qr(context, Some(chat_id)).await?,
|
||||
&color_int_to_hex_string(chat.get_color(context).await?),
|
||||
avatar,
|
||||
chat.get_name().chars().next().unwrap_or('#'),
|
||||
)
|
||||
}
|
||||
|
||||
async fn generate_verification_qr(context: &Context) -> Result<String> {
|
||||
let contact = Contact::get_by_id(context, DC_CONTACT_ID_SELF).await?;
|
||||
|
||||
let avatar = match contact.get_profile_image(context).await? {
|
||||
Some(path) => {
|
||||
let avatar_blob = BlobObject::from_path(context, &path)?;
|
||||
Some(std::fs::read(avatar_blob.to_abs_path())?)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let displayname = match context.get_config(Config::Displayname).await? {
|
||||
Some(name) => name,
|
||||
None => contact.get_addr().to_owned(),
|
||||
};
|
||||
|
||||
inner_generate_secure_join_qr_code(
|
||||
&stock_str::setup_contact_qr_description(context, &displayname, contact.get_addr()).await,
|
||||
&securejoin::dc_get_securejoin_qr(context, None).await?,
|
||||
&color_int_to_hex_string(contact.get_color()),
|
||||
avatar,
|
||||
displayname.chars().next().unwrap_or('#'),
|
||||
)
|
||||
}
|
||||
|
||||
fn inner_generate_secure_join_qr_code(
|
||||
raw_qrcode_description: &str,
|
||||
qrcode_content: &str,
|
||||
color: &str,
|
||||
avatar: Option<Vec<u8>>,
|
||||
avatar_letter: char,
|
||||
) -> Result<String> {
|
||||
let qrcode_description = &escaper::encode_minimal(raw_qrcode_description);
|
||||
// config
|
||||
let width = 515.0;
|
||||
let height = 630.0;
|
||||
let logo_offset = 28.0;
|
||||
let qr_code_size = 400.0;
|
||||
let qr_translate_up = 40.0;
|
||||
let text_y_pos = ((height - qr_code_size) / 2.0) + qr_code_size;
|
||||
let avatar_border_size = 9.0;
|
||||
let card_border_size = 2.0;
|
||||
let card_roundness = 40.0;
|
||||
|
||||
let qr = QrCode::encode_text(qrcode_content, QrCodeEcc::Medium)?;
|
||||
let mut svg = String::with_capacity(28000);
|
||||
let mut w = tagger::new(&mut svg);
|
||||
|
||||
w.elem("svg", |d| {
|
||||
d.attr("xmlns", "http://www.w3.org/2000/svg")
|
||||
.attr("viewBox", format_args!("0 0 {} {}", width, height));
|
||||
})
|
||||
.build(|w| {
|
||||
// White Background apears like a card
|
||||
w.single("rect", |d| {
|
||||
d.attr("x", card_border_size)
|
||||
.attr("y", card_border_size)
|
||||
.attr("rx", card_roundness)
|
||||
.attr("stroke", "#c6c6c6")
|
||||
.attr("stroke-width", card_border_size)
|
||||
.attr("width", width - (card_border_size * 2.0))
|
||||
.attr("height", height - (card_border_size * 2.0))
|
||||
.attr("style", "fill:#f2f2f2");
|
||||
});
|
||||
// Qrcode
|
||||
w.elem("g", |d| {
|
||||
d.attr(
|
||||
"transform",
|
||||
format!(
|
||||
"translate({},{})",
|
||||
(width - qr_code_size) / 2.0,
|
||||
((height - qr_code_size) / 2.0) - qr_translate_up
|
||||
),
|
||||
);
|
||||
// If the qr code should be in the wrong place,
|
||||
// we could also translate and scale the points in the path already,
|
||||
// but that would make the resulting svg way bigger in size and might bring up rounding issues,
|
||||
// so better avoid doing it manually if possible
|
||||
})
|
||||
.build(|w| {
|
||||
w.single("path", |d| {
|
||||
let mut path_data = String::with_capacity(0);
|
||||
let scale = qr_code_size / qr.size() as f32;
|
||||
|
||||
for y in 0..qr.size() {
|
||||
for x in 0..qr.size() {
|
||||
if qr.get_module(x, y) {
|
||||
path_data += &format!("M{},{}h1v1h-1z", x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.attr("style", "fill:#000000")
|
||||
.attr("d", path_data)
|
||||
.attr("transform", format!("scale({})", scale));
|
||||
});
|
||||
});
|
||||
|
||||
// Text
|
||||
const BIG_TEXT_CHARS_PER_LINE: usize = 32;
|
||||
const SMALL_TEXT_CHARS_PER_LINE: usize = 38;
|
||||
let chars_per_line = if qrcode_description.len() > SMALL_TEXT_CHARS_PER_LINE*2 {
|
||||
SMALL_TEXT_CHARS_PER_LINE
|
||||
} else {
|
||||
BIG_TEXT_CHARS_PER_LINE
|
||||
};
|
||||
let lines = textwrap::fill(qrcode_description, chars_per_line);
|
||||
let (text_font_size, text_y_shift) = if lines.split('\n').count() <= 2 {
|
||||
(27.0, 0.0)
|
||||
} else {
|
||||
(19.0, -10.0)
|
||||
};
|
||||
for (count, line) in lines.split('\n').enumerate()
|
||||
{
|
||||
w.elem("text", |d| {
|
||||
d.attr("y", (count as f32 * (text_font_size * 1.2)) + text_y_pos + text_y_shift)
|
||||
.attr("x", width / 2.0)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr(
|
||||
"style",
|
||||
format!(
|
||||
"font-family:sans-serif;\
|
||||
font-weight:bold;\
|
||||
font-size:{}px;\
|
||||
fill:#000000;\
|
||||
stroke:none",
|
||||
text_font_size
|
||||
),
|
||||
);
|
||||
})
|
||||
.build(|w| {
|
||||
w.put_raw(line);
|
||||
});
|
||||
}
|
||||
// contact avatar in middle of qrcode
|
||||
const LOGO_SIZE: f32 = 94.4;
|
||||
const HALF_LOGO_SIZE: f32 = LOGO_SIZE / 2.0;
|
||||
let logo_position_in_qr = (qr_code_size / 2.0) - HALF_LOGO_SIZE;
|
||||
let logo_position_x = ((width - qr_code_size) / 2.0) + logo_position_in_qr;
|
||||
let logo_position_y =
|
||||
((height - qr_code_size) / 2.0) - qr_translate_up + logo_position_in_qr;
|
||||
|
||||
w.single("circle", |d| {
|
||||
d.attr("cx", logo_position_x + HALF_LOGO_SIZE)
|
||||
.attr("cy", logo_position_y + HALF_LOGO_SIZE)
|
||||
.attr("r", HALF_LOGO_SIZE + avatar_border_size)
|
||||
.attr("style", "fill:#f2f2f2");
|
||||
});
|
||||
|
||||
if let Some(img) = avatar {
|
||||
w.elem("defs", |_| {}).build(|w| {
|
||||
w.elem("clipPath", |d| {
|
||||
d.attr("id", "avatar-cut");
|
||||
})
|
||||
.build(|w| {
|
||||
w.single("circle", |d| {
|
||||
d.attr("cx", logo_position_x + HALF_LOGO_SIZE)
|
||||
.attr("cy", logo_position_y + HALF_LOGO_SIZE)
|
||||
.attr("r", HALF_LOGO_SIZE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
w.single("image", |d| {
|
||||
d.attr("x", logo_position_x)
|
||||
.attr("y", logo_position_y)
|
||||
.attr("width", HALF_LOGO_SIZE * 2.0)
|
||||
.attr("height", HALF_LOGO_SIZE * 2.0)
|
||||
.attr("preserveAspectRatio", "none")
|
||||
.attr("clip-path", "url(#avatar-cut)")
|
||||
.attr(
|
||||
"href" /*might need xlink:href instead if it doesn't work on older devices?*/,
|
||||
format!("data:image/jpeg;base64,{}", base64::encode(img)),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
w.single("circle", |d| {
|
||||
d.attr("cx", logo_position_x + HALF_LOGO_SIZE)
|
||||
.attr("cy", logo_position_y + HALF_LOGO_SIZE)
|
||||
.attr("r", HALF_LOGO_SIZE)
|
||||
.attr("style", format!("fill:{}", &color));
|
||||
});
|
||||
|
||||
let avatar_font_size = LOGO_SIZE * 0.65;
|
||||
let font_offset = avatar_font_size * 0.1;
|
||||
w.elem("text", |d| {
|
||||
d.attr("y", logo_position_y + HALF_LOGO_SIZE + font_offset)
|
||||
.attr("x", logo_position_x + HALF_LOGO_SIZE)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("dominant-baseline", "central")
|
||||
.attr("alignment-baseline", "middle")
|
||||
.attr(
|
||||
"style",
|
||||
format!(
|
||||
"font-family:sans-serif;\
|
||||
font-weight:400;\
|
||||
font-size:{}px;\
|
||||
fill:#ffffff;",
|
||||
avatar_font_size
|
||||
),
|
||||
);
|
||||
})
|
||||
.build(|w| {
|
||||
w.put_raw(avatar_letter.to_uppercase());
|
||||
});
|
||||
}
|
||||
|
||||
// Footer logo
|
||||
const FOOTER_HEIGHT: f32 = 35.0;
|
||||
const FOOTER_WIDTH: f32 = 198.0;
|
||||
w.elem("g", |d| {
|
||||
d.attr(
|
||||
"transform",
|
||||
format!(
|
||||
"translate({},{})",
|
||||
(width - FOOTER_WIDTH) / 2.0,
|
||||
height - logo_offset - FOOTER_HEIGHT - text_y_shift
|
||||
),
|
||||
);
|
||||
})
|
||||
.build(|w| {
|
||||
w.put_raw(include_str!("../assets/qrcode_logo_footer.svg"));
|
||||
});
|
||||
});
|
||||
|
||||
Ok(svg)
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use strum::EnumProperty;
|
||||
use strum_macros::EnumProperty;
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::chat::{self, ChatId, ProtectionStatus};
|
||||
use crate::chat::{self, Chat, ChatId, ProtectionStatus};
|
||||
use crate::config::Config;
|
||||
use crate::constants::{Viewtype, DC_CONTACT_ID_SELF};
|
||||
use crate::contact::{Contact, Origin};
|
||||
@@ -330,6 +330,12 @@ pub enum StockMessage {
|
||||
|
||||
#[strum(props(fallback = "%1$s replied, waiting for being added to the group…"))]
|
||||
SecureJoinReplies = 118,
|
||||
|
||||
#[strum(props(fallback = "Scan to chat with %1$s"))]
|
||||
SetupContactQRDescription = 119,
|
||||
|
||||
#[strum(props(fallback = "Scan to join group %1$s"))]
|
||||
SecureJoinGroupQRDescription = 120,
|
||||
}
|
||||
|
||||
impl StockMessage {
|
||||
@@ -614,6 +620,29 @@ pub(crate) async fn secure_join_replies(context: &Context, contact_id: u32) -> S
|
||||
}
|
||||
}
|
||||
|
||||
/// Stock string: `Scan to chat with %1$s`.
|
||||
pub(crate) async fn setup_contact_qr_description(
|
||||
context: &Context,
|
||||
display_name: &str,
|
||||
addr: &str,
|
||||
) -> String {
|
||||
let name = if display_name == addr {
|
||||
addr.to_owned()
|
||||
} else {
|
||||
format!("{} ({})", display_name, addr)
|
||||
};
|
||||
translated(context, StockMessage::SetupContactQRDescription)
|
||||
.await
|
||||
.replace1(name)
|
||||
}
|
||||
|
||||
/// Stock string: `Scan to join %1$s`.
|
||||
pub(crate) async fn secure_join_group_qr_description(context: &Context, chat: &Chat) -> String {
|
||||
translated(context, StockMessage::SecureJoinGroupQRDescription)
|
||||
.await
|
||||
.replace1(chat.get_name())
|
||||
}
|
||||
|
||||
/// Stock string: `%1$s verified.`.
|
||||
pub(crate) async fn contact_verified(context: &Context, contact_addr: impl AsRef<str>) -> String {
|
||||
translated(context, StockMessage::ContactVerified)
|
||||
|
||||
@@ -695,6 +695,10 @@ fn receive_event(event: &Event) {
|
||||
"{}",
|
||||
green.paint(format!("Received CHAT_MODIFIED({})", chat))
|
||||
),
|
||||
EventType::ChatListChanged => format!(
|
||||
"{}",
|
||||
green.paint("Received CHAT_LIST_CHANGED()")
|
||||
),
|
||||
_ => format!("Received {:?}", event),
|
||||
};
|
||||
let context_names = CONTEXT_NAMES.read().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user