Compare commits

..

20 Commits

Author SHA1 Message Date
Simon Laux
3e35c23f29 add chatlist changed event 2021-11-29 17:56:54 +01:00
bjoern
7f97768c56 prepare 1.68 (#2844)
* update changelog for 1.68.0

* bump version to 1.68.0
2021-11-28 12:22:14 +01:00
dependabot[bot]
ecd548a7aa Merge pull request #2829 from deltachat/dependabot/cargo/libc-0.2.108 2021-11-27 19:03:56 +00:00
dependabot[bot]
62efb0795b Merge pull request #2830 from deltachat/dependabot/cargo/anyhow-1.0.48 2021-11-27 19:03:22 +00:00
bjoern
53f042ee08 tweak qr svg (#2842)
* repl: allow groupname arguments with more than one word (came over that when testing qr codes)

* calcualte text-size from the real number of lines

* shift text and watermark apart when text get longer

* make clippy happy
2021-11-27 18:57:46 +01:00
link2xt
6ce97bd0cd Merge fixes for chat assignment when forwarding messages
GitHub PR #2843
2021-11-27 00:00:00 +00:00
link2xt
c29149e74c Add group forwarding test 2021-11-27 00:00:00 +00:00
link2xt
487f7593ce Reset In-Reply-To when forwarding a message 2021-11-27 00:00:00 +00:00
link2xt
6b3b33d2a0 Test forwarded quoted messages 2021-11-27 00:00:00 +00:00
link2xt
2d70ccc2bf Do not return a quoted message for forwarded messages
For forwarded messages, parent message is not a quoted message.
2021-11-27 00:00:00 +00:00
link2xt
e90fc9504a Test get_parent_message 2021-11-27 00:00:00 +00:00
link2xt
5108314c03 Do not return trashed messages from get_rfc724_mid_in_list
This function is used to lookup the chat by `References` and
`In-Reply-To` header, so it does not make sense to return trashed
message when there is another non-trashed message in one of these
headers with a real chat ID.
2021-11-27 00:00:00 +00:00
Hocuri
f2b86a1c0f Update aeap-mvp.rst 2021-11-25 16:18:30 +01:00
Hocuri
9583d41446 Add a draft how an AEAP MVP could look 2021-11-25 16:18:30 +01:00
dependabot[bot]
e594064f93 cargo: bump anyhow from 1.0.47 to 1.0.48
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.47 to 1.0.48.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.47...1.0.48)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-25 14:30:59 +00:00
bjoern
7c52fd95ec prepare 1.67 (#2838)
* update changelog for 1.67.0

* bump version to 1.67.0
2021-11-25 15:29:10 +01:00
Simon Laux
416bf3a829 generate qr code svg (#2815)
* generate qr code svg prototype

* qr code for groups
fix formatting

* - letter avatar in qrcode
- escape xml in userinput (display/groupname)
- fix "Me" display name
- merge import declarations

* remove dot at the end of VerifyContactQRDescription

* if addr == displayname, show only one of them

Especially useful for yggmail accounts without usernames,
because the text would overflow otherwise.

* use real clipPath for rounded avatar

* - center avatar text better (dominant-baseline)
- add "sans-serif" to font fallback for text if arial is missing

* make corner always blue

* add [logo + "get.delta.chat"] footer to qrcode

* Update deltachat-ffi/deltachat.h

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

* Apply suggestions from code review

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

* new card design
- add stockstrings
- update changelog

* make qrcode pixels also #f2f2f2 instead of full white

* rename VERIFY_CONTACT_QR_DESC to SETUP_CONTACT
make footer text a tiny bit darker upon r10s's request

* avoid using  which is a doxygen command

* point out that one will join a group (this is still shorted and was also suggested in recent chats)

* add option to generate qr-code-svg to repl tool

* use same font-family in text and footer

* thinner card border

* remove superfluous <tspan> from footer to make color tweaking easier

* move font-weight to style, ios renderer does not pick it up from attribute; remove default font attributes not used consequently

* make get.delta.chat more visible

* align properly using dominant-baseline=central and alignment-baseline=middle, this makes things nice on all systems but android (before, ios was wrong and all others not 100% aligned as font metrics are ignored) (android needs a subsequent improvement)

Co-authored-by: bjoern <r10s@b44t.com>
2021-11-24 23:23:01 +01:00
dependabot[bot]
cc78347293 Merge pull request #2834 from deltachat/dependabot/cargo/futures-0.3.18 2021-11-23 23:22:46 +00:00
dependabot[bot]
27e4ae992a cargo: bump futures from 0.3.17 to 0.3.18
Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.17 to 0.3.18.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.17...0.3.18)

---
updated-dependencies:
- dependency-name: futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-23 21:15:34 +00:00
dependabot[bot]
eb610c27bf cargo: bump libc from 0.2.107 to 0.2.108
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.107 to 0.2.108.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.107...0.2.108)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-22 21:11:56 +00:00
22 changed files with 730 additions and 47 deletions

View File

@@ -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
View File

@@ -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"

View File

@@ -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"

View 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" />

View File

@@ -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"

View File

@@ -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
/**
* @}
*/

View File

@@ -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
View 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.

View File

@@ -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");
}

View File

@@ -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() {

View File

@@ -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

View File

@@ -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::*;

View File

@@ -314,6 +314,7 @@ impl Context {
msg_id: MsgId::new(0),
chat_id: ChatId::new(0),
});
self.emit_event(EventType::ChatListChanged);
ret
}
Config::Displayname => {

View File

@@ -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.

View File

@@ -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(())
}
}

View File

@@ -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);
}
}
}

View File

@@ -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.

View File

@@ -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;

View File

@@ -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
View 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)
}

View File

@@ -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)

View File

@@ -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();