[fractal/fractal-next] session: Use crosssigning to verify new sessions
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] session: Use crosssigning to verify new sessions
- Date: Wed, 27 Oct 2021 13:24:26 +0000 (UTC)
commit 3dea9a339860cf5a0a742ab833e8f6bba6f39093
Author: Julian Sparber <julian sparber net>
Date: Fri Oct 8 16:58:52 2021 +0200
session: Use crosssigning to verify new sessions
Cargo.lock | 1 +
Cargo.toml | 1 +
.../icons/scalable/status/other-device.svg | 435 ++++++++++
.../icons/scalable/status/setup-complete.svg | 437 ++++++++++
data/resources/icons/scalable/status/welcome.svg | 903 ++++++++++++++++-----
data/resources/resources.gresource.xml | 4 +
data/resources/style.css | 12 +-
data/resources/ui/pill.ui | 2 +-
data/resources/ui/session-verification.ui | 507 ++++++++++++
data/resources/ui/verification-emoji.ui | 24 +
po/POTFILES.in | 9 +
src/application.rs | 2 +-
src/contrib/mod.rs | 3 +
src/contrib/qr_code.rs | 313 +++++++
src/login.rs | 66 +-
src/main.rs | 1 +
src/meson.build | 6 +
src/session/mod.rs | 170 +++-
src/session/verification/emoji.rs | 58 ++
src/session/verification/identity_verification.rs | 548 +++++++++++++
src/session/verification/mod.rs | 9 +
src/session/verification/session_verification.rs | 424 ++++++++++
src/session/verification/to_device_handler.rs | 95 +++
src/window.rs | 10 +-
24 files changed, 3764 insertions(+), 276 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index 5842ad97..5a29bd53 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -740,6 +740,7 @@ dependencies = [
"matrix-sdk",
"mime",
"once_cell",
+ "qrcode",
"rand 0.8.4",
"secret-service",
"serde_json",
diff --git a/Cargo.toml b/Cargo.toml
index 9ca43780..82fcbe53 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,6 +27,7 @@ html2pango = "0.4"
futures = "0.3"
rand = "0.8"
indexmap = "1.6.2"
+qrcode = "0.12.0"
[dependencies.sourceview]
package = "sourceview5"
diff --git a/data/resources/icons/scalable/status/other-device.svg
b/data/resources/icons/scalable/status/other-device.svg
new file mode 100644
index 00000000..b2596e77
--- /dev/null
+++ b/data/resources/icons/scalable/status/other-device.svg
@@ -0,0 +1,435 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="340"
+ height="200"
+ viewBox="0 0 89.958331 52.916668"
+ version="1.1"
+ id="svg8662"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs
+ id="defs8656">
+ <linearGradient
+ id="linearGradient104655">
+ <stop
+ style="stop-color:#3d3846;stop-opacity:1"
+ offset="0"
+ id="stop104651" />
+ <stop
+ style="stop-color:#5e5c64;stop-opacity:1"
+ offset="1"
+ id="stop104653" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient82129">
+ <stop
+ style="stop-color:#62a0ea;stop-opacity:1;"
+ offset="0"
+ id="stop82125" />
+ <stop
+ style="stop-color:#c061cb;stop-opacity:1"
+ offset="1"
+ id="stop82127" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient70265">
+ <stop
+ style="stop-color:#99c1f1;stop-opacity:1"
+ offset="0"
+ id="stop70261" />
+ <stop
+ style="stop-color:#dc8add;stop-opacity:1"
+ offset="1"
+ id="stop70263" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient56117">
+ <stop
+ style="stop-color:#3d3846;stop-opacity:1"
+ offset="0"
+ id="stop56113" />
+ <stop
+ style="stop-color:#77767b;stop-opacity:1"
+ offset="1"
+ id="stop56115" />
+ </linearGradient>
+ <linearGradient
+ xlink:href="#linearGradient70265"
+ id="linearGradient39055"
+ x1="34.925"
+ y1="35.057291"
+ x2="67.943703"
+ y2="35.057289"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(6.6145842)" />
+ <linearGradient
+ xlink:href="#linearGradient82129"
+ id="linearGradient82131"
+ x1="11.200694"
+ y1="33.249307"
+ x2="32.54375"
+ y2="33.249307"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.5,0,0,1.5,-0.9260416,-15.875)" />
+ <linearGradient
+ xlink:href="#linearGradient82129"
+ id="linearGradient82161"
+ x1="73.554153"
+ y1="31.750002"
+ x2="85.195839"
+ y2="31.750002"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,2.6458333)" />
+ <linearGradient
+ xlink:href="#linearGradient56117"
+ id="linearGradient98041"
+ x1="9.260417"
+ y1="46.302082"
+ x2="9.260417"
+ y2="45.243752"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ xlink:href="#linearGradient104655"
+ id="linearGradient98624"
+ gradientUnits="userSpaceOnUse"
+ x1="9.260417"
+ y1="46.302082"
+ x2="9.260417"
+ y2="45.243752"
+ gradientTransform="matrix(0.38333333,0,0,1,30.021389,0)" />
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath99524">
+ <rect
+
style="fill:#dc8add;fill-opacity:1;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:26.4"
+ id="rect99526"
+ width="35.71875"
+ height="26.458332"
+ x="14.552083"
+ y="17.727087" />
+ </clipPath>
+ </defs>
+ <metadata
+ id="metadata8659">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="g77929"
+ transform="translate(-26.223863,0.26458333)">
+ <rect
+ y="10.054167"
+ x="59.29678"
+ height="7.1434927"
+ width="18.520782"
+ id="rect77923"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#c0bfbc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.00569266;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ rx="1.5874978"
+ ry="1.2245986" />
+ <rect
+ y="8.7312498"
+ x="59.29678"
+ height="7.9372444"
+ width="18.520782"
+ id="rect77925"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#deddda;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0060006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ rx="1.5874978"
+ ry="1.3606703" />
+ </g>
+ <g
+ id="g72761"
+ transform="translate(7.9375002,8.4666665)">
+ <g
+ id="g72759">
+ <rect
+ y="7.1435065"
+ x="43.65625"
+ height="9.2604046"
+ width="19.843752"
+ id="rect72755"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#c0bfbc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.00648149;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ rx="1.5874978"
+ ry="1.5874978" />
+ <rect
+ y="6.6143403"
+ x="43.65625"
+ height="9.2604046"
+ width="19.843752"
+ id="rect72757"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#deddda;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.00648149;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ rx="1.5874978"
+ ry="1.5874978" />
+ </g>
+ </g>
+ <rect
+ y="10.318505"
+ x="59.29678"
+ height="9.2604046"
+ width="18.520782"
+ id="rect72763"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#813d9c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.00648149;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ rx="1.5874978"
+ ry="1.5874978" />
+ <rect
+ y="9.78934"
+ x="59.29678"
+ height="9.2604046"
+ width="18.520782"
+ id="rect72765"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#c061cb;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.00648149;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ rx="1.5874978"
+ ry="1.5874978" />
+ <g
+ transform="matrix(0.19843723,0,0,0.19843723,839.155,59.163211)"
+ id="g72771"
+ style="stroke-width:0.666667">
+ <path
+
style="display:inline;fill:#62a0ea;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
+ d="m -3989.3337,-232.71069 c -4.4319,0 -8,3.56799 -8,8 v 27.89844 c 0,4.43202 3.5681,8 8,8 h 22.9999
l 12.6667,12.66667 v -12.66667 h 48.3335 c 4.432,0 8,-3.56798 8,-8 v -27.89844 c 0,-4.43201 -3.568,-8 -8,-8 z"
+ id="path72767" />
+ <path
+ id="path72769"
+ d="m -3997.3337,-198.81225 v 2 c 0,4.43202 3.5681,8 8,8 h 22.9999 l 12.6667,12.66667 v -2 l
-12.6667,-12.66667 h -22.9999 c -4.4319,0 -8,-3.56798 -8,-8 z m 100.0001,0 c 0,4.43202 -3.568,8 -8,8 h
-48.3335 v 2 h 48.3335 c 4.432,0 8,-3.56798 8,-8 z"
+
style="display:inline;fill:#3584e4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
/>
+ </g>
+ <g
+ transform="matrix(0.21083945,0,0,0.13229149,781.89724,63.69551)"
+ id="g72785"
+ style="stroke-width:0.792118">
+ <rect
+ y="-443.48032"
+ x="-3490"
+ height="70"
+ width="101.50459"
+ id="rect72773"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#1a5fb4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.041718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ rx="8.7004051"
+ ry="12" />
+ <rect
+ y="-447.48032"
+ x="-3490"
+ height="70"
+ width="101.50459"
+ id="rect72775"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#1c71d8;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.041718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ rx="8.7004051"
+ ry="12" />
+ <g
+ id="g72783"
+ style="fill:#99c1f1"
+ transform="translate(8.0037955e-5,0.50000642)">
+ <rect
+ y="-431.98038"
+ x="-3477.4512"
+ height="4.9999967"
+ width="76.406708"
+ id="rect72777"
+
style="fill:#99c1f1;fill-opacity:1;stroke:none;stroke-width:12.5555;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ <rect
+ y="-421.98038"
+ x="-3477.4512"
+ height="4.9999967"
+ width="59.607872"
+ id="rect72779"
+
style="fill:#99c1f1;fill-opacity:1;stroke:none;stroke-width:11.0897;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ <rect
+
style="fill:#99c1f1;fill-opacity:1;stroke:none;stroke-width:11.0897;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect72781"
+ width="37.01963"
+ height="4.9999967"
+ x="-3477.4512"
+ y="-411.98038" />
+ </g>
+ </g>
+ <g
+ transform="matrix(0.19843723,0,0,0.19843723,813.68885,55.062177)"
+ id="g72797"
+ style="stroke-width:0.666667">
+ <path
+ id="path72787"
+ d="m -3913.6666,-217.48035 c -4.4319,0 -8,3.56798 -8,8 v 24 c 0,4.43202 3.5681,8 8,8 h 15.3333 v
12.66667 l 12.6667,-12.66667 h 45.9999 c 4.432,0 8,-3.56798 8,-8 v -24 c 0,-4.43202 -3.568,-8 -8,-8 z"
+
style="display:inline;fill:#dc8add;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
/>
+ <path
+ id="path72789"
+ d="m -3921.6666,-187.48035 v 2 c 0,4.43202 3.5681,8 8,8 h 15.3333 v -2 h -15.3333 c -4.4319,0
-8,-3.56798 -8,-8 z m 89.9999,0 c 0,4.43202 -3.568,8 -8,8 h -45.9999 l -12.6667,12.66667 v 2 l
12.6667,-12.66667 h 45.9999 c 4.432,0 8,-3.56798 8,-8 z"
+
style="display:inline;fill:#c061cb;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
/>
+ <rect
+
style="opacity:1;fill:#c061cb;fill-opacity:1;stroke:none;stroke-width:9.33333;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect72791"
+ width="56.666668"
+ height="3.3333311"
+ x="-3905"
+ y="-207.48033" />
+ <rect
+
style="opacity:1;fill:#c061cb;fill-opacity:1;stroke:none;stroke-width:9.33333;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect72793"
+ width="56.666668"
+ height="3.3333311"
+ x="-3905"
+ y="-200.81364" />
+ <rect
+ y="-194.14696"
+ x="-3905"
+ height="3.3333311"
+ width="33.333332"
+ id="rect72795"
+
style="opacity:1;fill:#c061cb;fill-opacity:1;stroke:none;stroke-width:9.33337;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ </g>
+ <g
+ id="g72817"
+ transform="matrix(0.49999932,0,0,0.49999932,35.483542,3.5716243)">
+ <g
+ id="g72813"
+ transform="matrix(0.26458333,0,0,0.26458333,939.27082,123.15833)">
+ <g
+ style="stroke-width:0.666667"
+ transform="matrix(1.5,0,0,1.5,2465.0001,-75.912199)"
+ id="g72803">
+ <path
+
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
+ d="m -3905.3334,-237.71207 c -4.4319,0 -8,3.84245 -8,8.61537 v 29.43586 c 0,4.77294
3.5681,8.61538 8,8.61538 H -3848 c 4.432,0 8,-3.84244 8,-8.61538 v -29.43586 c 0,-4.77292 -3.568,-8.61537
-8,-8.61537 z"
+ id="path72799" />
+ <path
+ id="path72801"
+ d="m -3913.3334,-201.04546 v 2 c 0,4.43202 3.5681,8 8,8 h 19 l 12.6667,12.66667 v -2 l
-12.6667,-12.66667 h -19 c -4.4319,0 -8,-3.56798 -8,-8 z m 73.3334,0 c 0,4.43202 -3.568,8 -8,8 h -25.6667 v 2
H -3848 c 4.432,0 8,-3.56798 8,-8 z"
+
style="display:inline;fill:#deddda;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
/>
+ </g>
+ <rect
+ y="-407.48035"
+ x="-3385"
+ height="4.9999967"
+ width="70"
+ id="rect72805"
+
style="opacity:1;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ <rect
+ y="-397.48035"
+ x="-3385"
+ height="4.9999967"
+ width="70"
+ id="rect72807"
+
style="opacity:1;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ <rect
+
style="opacity:1;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect72809"
+ width="59.000008"
+ height="4.9999967"
+ x="-3385"
+ y="-387.48035" />
+ <rect
+ y="-417.48035"
+ x="-3385"
+ height="4.9999967"
+ width="70"
+ id="rect72811"
+
style="opacity:1;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ </g>
+ <path
+
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 54.107302,26.458311 v 5.027084 l -5.027097,-5.027084 z"
+ id="path72815" />
+ </g>
+ <g
+ id="g137252"
+ transform="matrix(0.26458333,0,0,0.26208726,931.33332,121.81302)"
+ style="stroke-width:1.00475" />
+ <g
+ id="g99322"
+ clip-path="url(#clipPath99524)">
+ <rect
+
style="fill:#3d3846;stroke-width:3.07951;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="rect4199"
+ width="33.602081"
+ height="26.987755"
+ x="15.081252"
+ y="20.637245"
+ rx="2.9104166" />
+ <rect
+
style="fill:url(#linearGradient82131);fill-opacity:1;stroke-width:3.14283;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="rect47002"
+ width="32.014584"
+ height="25.135418"
+ x="15.875"
+ y="21.431252"
+ rx="2.1166668" />
+ </g>
+ <g
+ id="g9559"
+ transform="matrix(1.5,0,0,1.5,6.6369485,-1.7057333)"
+ style="fill:#ffffff;fill-opacity:1">
+ <g
+ id="g9536"
+ transform="matrix(0.49999955,0,0,0.49999955,5.4165084,8.0651133)"
+ style="fill:#ffffff;fill-opacity:1">
+ <path
+
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00149466px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
+ d="m 17.712178,25.65522 c -0.879455,0 -1.5875,0.762486 -1.5875,1.709612 v 5.444313 c 0,0.94713
0.708045,1.709614 1.5875,1.709614 h 11.112513 c 0.879475,0 1.5875,-0.762484 1.5875,-1.709614 v -5.444313 c
0,-0.947126 -0.708025,-1.709612 -1.5875,-1.709612 z"
+ id="path137290-3" />
+ <path
+
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 26.178854,32.534384 v 5.027084 l -5.027097,-5.027084 z"
+ id="path62781-6" />
+ </g>
+ </g>
+ <path
+
style="fill:none;stroke:url(#linearGradient39055);stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 42.333334,34.395833 h 2.645832 A 2.645834,2.645834 45 0 1 47.625,37.041667 v 1.322917 a
2.6458333,2.6458333 45 0 0 2.645833,2.645833 h 0 a 2.6458337,2.6458337 135 0 0 2.645834,-2.645834 v -9.260418
a 2.645833,2.645833 135 0 1 2.645833,-2.645833 2.645833,2.645833 45 0 1 2.645833,2.645833 V 41.010416 A
2.6458336,2.6458336 45 0 0 60.854167,43.65625 2.6458334,2.6458334 135 0 0 63.5,41.010417 V 31.75 a
2.6458333,2.6458333 135 0 1 2.645833,-2.645833 h 0 a 2.6458337,2.6458337 45 0 1 2.645834,2.645834 v 1.322915
a 2.6458336,2.6458336 45 0 0 2.645834,2.645834 h 2.645832"
+ id="path22180" />
+ <rect
+
style="fill:#3d3846;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="rect37332"
+ width="13.229169"
+ height="26.458334"
+ x="72.760422"
+ y="21.166668"
+ rx="1.8520825"
+ ry="1.8520826" />
+ <rect
+
style="fill:url(#linearGradient82161);fill-opacity:1;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="rect43271"
+ width="11.641685"
+ height="24.870831"
+ x="73.554153"
+ y="21.960421"
+ rx="1.0583326"
+ ry="1.0583326" />
+ <g
+ id="g45487"
+ transform="translate(62.324284,10.923441)"
+ style="fill:#ffffff">
+ <g
+ id="g45485"
+ transform="matrix(0.49999955,0,0,0.49999955,5.4165084,8.0651133)"
+ style="fill:#ffffff">
+ <path
+
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00149466px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
+ d="m 17.712178,25.65522 c -0.879455,0 -1.5875,0.762486 -1.5875,1.709612 v 5.444313 c 0,0.94713
0.708045,1.709614 1.5875,1.709614 h 11.112513 c 0.879475,0 1.5875,-0.762484 1.5875,-1.709614 v -5.444313 c
0,-0.947126 -0.708025,-1.709612 -1.5875,-1.709612 z"
+ id="path45481" />
+ <path
+
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 26.178854,32.534384 v 5.027084 l -5.027097,-5.027084 z"
+ id="path45483" />
+ </g>
+ </g>
+ <path
+ id="rect95344"
+ d="M 1.0583333,44.185417 H 48.683333 a 2.1166662,2.1166662 135.00001 0 1 -2.116666,2.116666 H 3.175 A
2.1166664,2.1166664 44.999991 0 1 1.0583333,44.185417 Z"
+
style="fill:url(#linearGradient98041);fill-opacity:1;stroke-width:3.12455;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:26.4"
/>
+ <path
+ id="path98517"
+
style="fill:#4b4a50;stroke-width:2.11666;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4;fill-opacity:1"
+ d="m 12.594174,44.185417 c 0,0.439737 0.354014,0.79375 0.79375,0.79375 h 6.297072 c 0.439738,0
0.79375,-0.354013 0.79375,-0.79375 z" />
+ <path
+ id="path98618"
+ d="m 30.427083,44.185417 h 18.25625 a 2.1166662,2.1166662 135.00001 0 1 -2.116666,2.116666 H 32.54375 a
2.1166664,2.1166664 44.999991 0 1 -2.116667,-2.116666 z"
+
style="fill:url(#linearGradient98624);fill-opacity:1;stroke-width:1.93453;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:26.4"
/>
+</svg>
diff --git a/data/resources/icons/scalable/status/setup-complete.svg
b/data/resources/icons/scalable/status/setup-complete.svg
new file mode 100644
index 00000000..71338f9e
--- /dev/null
+++ b/data/resources/icons/scalable/status/setup-complete.svg
@@ -0,0 +1,437 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="340"
+ height="200"
+ viewBox="0 0 89.958331 52.916668"
+ version="1.1"
+ id="svg8662"
+ sodipodi:docname="setup-complete.svg"
+ inkscape:version="1.1-rc (52f87abb86, 2021-05-02)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <sodipodi:namedview
+ id="namedview47"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ objecttolerance="10.0"
+ gridtolerance="10.0"
+ guidetolerance="10.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="false"
+ inkscape:zoom="0.32275792"
+ inkscape:cx="-534.45628"
+ inkscape:cy="-774.57432"
+ inkscape:current-layer="svg8662"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-text-baseline="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid1470" />
+ </sodipodi:namedview>
+ <defs
+ id="defs8656">
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient64686">
+ <stop
+ style="stop-color:#3584e4;stop-opacity:1"
+ offset="0"
+ id="stop64682" />
+ <stop
+ style="stop-color:#99c1f1;stop-opacity:1"
+ offset="1"
+ id="stop64684" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient8145">
+ <stop
+ style="stop-color:#33d17a;stop-opacity:1"
+ offset="0"
+ id="stop8141" />
+ <stop
+ style="stop-color:#8ff0a4;stop-opacity:1"
+ offset="1"
+ id="stop8143" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8145"
+ id="linearGradient10957"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.1500001,0,0,1.1500001,4215.3556,-2778.2476)"
+ x1="-3292.5"
+ y1="-438.48035"
+ x2="-3272.5"
+ y2="-438.48035" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient64686"
+ id="linearGradient22523"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.695651,0,0,0.695651,130.55973,-999.02559)"
+ x1="428.98032"
+ y1="-3282.5"
+ x2="451.98032"
+ y2="-3282.5" />
+ </defs>
+ <metadata
+ id="metadata8659">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="g20123"
+ transform="translate(1.3229166,1.3229168)">
+ <rect
+
style="fill:#33d17a;fill-opacity:1;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="rect13252"
+ width="27.781248"
+ height="14.022916"
+ x="6.614583"
+ y="18.520834"
+ rx="3.175"
+ ry="3.175" />
+ <rect
+
style="fill:#8ff0a4;fill-opacity:1;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="rect11780"
+ width="27.781248"
+ height="15.874998"
+ x="6.6145825"
+ y="15.875"
+ rx="3.175"
+ ry="3.175" />
+ </g>
+ <g
+ id="g137252"
+ transform="matrix(0.26458333,0,0,0.26208726,931.33332,121.81302)"
+ style="stroke-width:1.00475" />
+ <g
+ style="stroke-width:0.751809"
+ id="g137258"
+ transform="matrix(0.46810895,0,0,0.26458333,1662.8044,147.5)">
+ <rect
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#c0bfbc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.02834;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect137254"
+ width="65.000107"
+ height="21.999973"
+ x="3425"
+ y="-399.48032"
+ rx="6.782609"
+ ry="12"
+ transform="scale(-1,1)" />
+ <rect
+ transform="scale(-1,1)"
+ ry="12.000001"
+ rx="6.782609"
+ y="-432.48032"
+ x="3425"
+ height="51.999969"
+ width="65.000107"
+ id="rect137256"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#deddda;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.02834;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
/>
+ </g>
+ <g
+ id="g4514"
+ transform="matrix(0.68181824,0,0,1,25.676607,9.2604164)"
+ style="stroke-width:1.21106">
+ <rect
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#26a269;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0130465;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect137266"
+ width="29.104172"
+ height="6.0854096"
+ x="-80.697922"
+ y="23.01874"
+ rx="3.6218512"
+ ry="2.1166666"
+ transform="scale(-1,1)" />
+ <rect
+ transform="scale(-1,1)"
+ ry="2.1166666"
+ rx="3.6218512"
+ y="9.260417"
+ x="-80.697922"
+ height="19.049999"
+ width="29.104172"
+ id="rect137268"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#57e389;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0130465;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
/>
+ </g>
+ <g
+ transform="matrix(0.396875,0,0,0.396875,1599.4062,118.32708)"
+ id="g137264"
+ style="stroke-width:0.666667">
+ <path
+ sodipodi:nodetypes="sssscccsssss"
+
style="display:inline;fill:#62a0ea;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
+ d="m -3912.0003,-233.37879 c -4.4319,0 -8,3.56799 -8,8 v 27.89844 c 0,4.43202 3.5681,8 8,8 h 36.3333
l 12.6667,12.66667 v -12.66667 h 8.3333 c 4.432,0 8,-3.56798 8,-8 v -27.89844 c 0,-4.43201 -3.568,-8 -8,-8 z"
+ id="path137260"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:nodetypes="cssccccsccsccssc"
+ inkscape:connector-curvature="0"
+ id="path137262"
+ d="m -3920.0003,-199.48035 v 2 c 0,4.43202 3.5681,8 8,8 h 36.3333 l 12.6667,12.66667 v -2 l
-12.6667,-12.66667 h -36.3333 c -4.4319,0 -8,-3.56798 -8,-8 z m 73.3333,0 c 0,4.43202 -3.568,8 -8,8 h -8.3333
v 2 h 8.3333 c 4.432,0 8,-3.56798 8,-8 z"
+
style="display:inline;fill:#3584e4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
/>
+ </g>
+ <g
+ transform="matrix(0.42167947,0,0,0.26458333,1495.4739,124.74584)"
+ id="g137288"
+ style="stroke-width:0.792118">
+ <rect
+ y="-447.48032"
+ x="-3490"
+ height="70"
+ width="87.843063"
+ id="rect137286"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#1c71d8;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0388092;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ rx="7.5294156"
+ ry="12" />
+ <g
+ id="g4558"
+ style="fill:#99c1f1"
+ transform="translate(8.0037955e-5,0.50000642)">
+ <rect
+ y="-431.98038"
+ x="-3477.4512"
+ height="4.9999967"
+ width="59.607872"
+ id="rect137296-3"
+
style="fill:#99c1f1;fill-opacity:1;stroke:none;stroke-width:11.0897;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ <rect
+ y="-421.98038"
+ x="-3477.4512"
+ height="4.9999967"
+ width="59.607872"
+ id="rect137298-6"
+
style="fill:#99c1f1;fill-opacity:1;stroke:none;stroke-width:11.0897;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ <rect
+
style="fill:#99c1f1;fill-opacity:1;stroke:none;stroke-width:11.0897;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect137300-7"
+ width="37.01963"
+ height="4.9999967"
+ x="-3477.4512"
+ y="-411.98038" />
+ </g>
+ </g>
+ <g
+ transform="matrix(0.396875,0,0,0.396875,1569.6406,110.125)"
+ id="g137282"
+ style="stroke-width:0.666667">
+ <path
+ inkscape:connector-curvature="0"
+ id="path137272"
+ d="m -3913.6666,-217.48035 c -4.4319,0 -8,3.56798 -8,8 v 24 c 0,4.43202 3.5681,8 8,8 h 15.3333 v
12.66667 l 12.6667,-12.66667 h 45.9999 c 4.432,0 8,-3.56798 8,-8 v -24 c 0,-4.43202 -3.568,-8 -8,-8 z"
+
style="display:inline;fill:#3d3846;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
+ sodipodi:nodetypes="sssscccsssss" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path137274"
+ d="m -3921.6666,-187.48035 v 2 c 0,4.43202 3.5681,8 8,8 h 15.3333 v -2 h -15.3333 c -4.4319,0
-8,-3.56798 -8,-8 z m 89.9999,0 c 0,4.43202 -3.568,8 -8,8 h -45.9999 l -12.6667,12.66667 v 2 l
12.6667,-12.66667 h 45.9999 c 4.432,0 8,-3.56798 8,-8 z"
+
style="display:inline;fill:#241f31;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
+ sodipodi:nodetypes="cssccsccsccccssc" />
+ <rect
+
style="opacity:1;fill:#77767b;fill-opacity:1;stroke:none;stroke-width:9.33333;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect137276"
+ width="56.666668"
+ height="3.3333311"
+ x="-3905"
+ y="-207.48033" />
+ <rect
+
style="opacity:1;fill:#77767b;fill-opacity:1;stroke:none;stroke-width:9.33333;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect137278"
+ width="56.666668"
+ height="3.3333311"
+ x="-3905"
+ y="-200.81364" />
+ <rect
+ y="-194.14696"
+ x="-3905"
+ height="3.3333311"
+ width="33.333332"
+ id="rect137280"
+
style="opacity:1;fill:#77767b;fill-opacity:1;stroke:none;stroke-width:9.33337;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ </g>
+ <g
+ id="g50125"
+ transform="translate(-30.427083,-9.7895847)">
+ <rect
+ ry="3.7041636"
+ rx="3.7041638"
+ y="30.691668"
+ x="51.858337"
+ height="7.4083271"
+ width="17.991665"
+ id="rect48478"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#deddda;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.00997368;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
/>
+ <rect
+ ry="3.7041636"
+ rx="3.7041638"
+ y="29.897917"
+ x="51.858337"
+ height="7.4083271"
+ width="17.991665"
+ id="rect43843"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.00997368;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
/>
+ <circle
+
style="fill:#5e5c64;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="path46775"
+ cx="60.854168"
+ cy="33.602085"
+ r="1.0583339" />
+ <circle
+
style="fill:#5e5c64;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="circle46919"
+ cx="57.679169"
+ cy="33.602085"
+ r="1.0583339" />
+ <circle
+
style="fill:#5e5c64;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="circle46921"
+ cx="64.02916"
+ cy="33.602085"
+ r="1.0583339" />
+ </g>
+ <g
+ id="g63777"
+ transform="translate(2.6458333,4.4979169)">
+ <g
+ id="g137302"
+ transform="matrix(0.26458333,0,0,0.26458333,939.27082,123.15833)">
+ <g
+ style="stroke-width:0.666667"
+ transform="matrix(1.5,0,0,1.5,2465.0001,-75.912199)"
+ id="g137294">
+ <path
+ sodipodi:nodetypes="sssccssss"
+
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
+ d="m -3905.3334,-237.71207 c -4.4319,0 -8,3.84245 -8,8.61537 v 29.43586 c 0,4.77294
3.5681,8.61538 8,8.61538 H -3848 c 4.432,0 8,-3.84244 8,-8.61538 v -29.43586 c 0,-4.77292 -3.568,-8.61537
-8,-8.61537 z"
+ id="path137290"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path137292"
+ d="m -3913.3334,-201.04546 v 2 c 0,4.43202 3.5681,8 8,8 h 19 l 12.6667,12.66667 v -2 l
-12.6667,-12.66667 h -19 c -4.4319,0 -8,-3.56798 -8,-8 z m 73.3334,0 c 0,4.43202 -3.568,8 -8,8 h -25.6667 v 2
H -3848 c 4.432,0 8,-3.56798 8,-8 z"
+
style="display:inline;fill:#deddda;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
+ sodipodi:nodetypes="cssccccsccsccssc" />
+ </g>
+ <rect
+ y="-407.48035"
+ x="-3385"
+ height="4.9999967"
+ width="70"
+ id="rect137296"
+
style="opacity:1;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ <rect
+ y="-397.48035"
+ x="-3385"
+ height="4.9999967"
+ width="70"
+ id="rect137298"
+
style="opacity:1;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ <rect
+
style="opacity:1;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect137300"
+ width="59.000008"
+ height="4.9999967"
+ x="-3385"
+ y="-387.48035" />
+ <rect
+ y="-417.48035"
+ x="-3385"
+ height="4.9999967"
+ width="70"
+ id="rect8784"
+
style="opacity:1;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ </g>
+ <path
+
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 54.107302,26.458311 v 5.027084 l -5.027097,-5.027084 z"
+ id="path62781" />
+ </g>
+ <g
+ id="g4294"
+ transform="matrix(0.52916666,0,0,0.52916666,-2098.675,240.49589)"
+ style="stroke:#000000">
+ <path
+
style="fill:#26a269;stroke:#26a269;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ d="m 4033,-420.48042 h 36 v 14 c 0,15 -6,23 -18,28 -12,-5 -18,-13 -18,-28 z"
+ id="path4286"
+ sodipodi:nodetypes="cccccc" />
+ <path
+
style="fill:#b0f4bf;fill-opacity:1;stroke:#33d17a;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ d="m 4033,-422.48042 h 36 v 14 c 0,15 -6,23 -18,28 -12,-5 -18,-13 -18,-28 z"
+ id="path4288"
+ sodipodi:nodetypes="cccccc" />
+ <path
+ style="color:#000000;fill:#26a269;stroke:none"
+ d="m 4061.9165,-408.5693 -12.9165,9.26071 -7.4112,-3.76328 v 1.50546 l 7.4141,7.41407
12.9141,-12.91407 z"
+ id="path4290"
+ sodipodi:nodetypes="ccccccc" />
+ <path
+
style="fill:none;stroke:#33d17a;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 4043,-404.48042 6,6 11.5,-11.5"
+ id="path4292"
+ sodipodi:nodetypes="ccc" />
+ </g>
+ <g
+ id="g10955"
+ transform="translate(8.7312501,-7.1437498)">
+ <g
+ id="g10953"
+ transform="matrix(0.26458333,0,0,0.26458333,884.23748,129.9052)">
+ <circle
+
style="opacity:1;fill:#26a269;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="circle10949"
+ cx="-3282.5"
+ cy="-437.48032"
+ r="11.500001" />
+ <circle
+ r="11.500001"
+ cy="-3282.5"
+ cx="440.48032"
+ id="circle10951"
+
style="opacity:1;fill:url(#linearGradient10957);fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="rotate(-90)" />
+ </g>
+ </g>
+ <g
+ id="g22521"
+ transform="translate(54.37188,-1.3229148)">
+ <g
+ id="g22519"
+ transform="matrix(0.26458333,0,0,0.26458333,884.23748,129.9052)">
+ <circle
+
style="opacity:1;fill:#1a5fb4;fill-opacity:1;stroke:none;stroke-width:9.73912;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="circle22515"
+ cx="-3282.5"
+ cy="-433.98029"
+ r="7.999989" />
+ <circle
+ r="7.9999871"
+ cy="-3282.5"
+ cx="436.98032"
+ id="circle22517"
+
style="opacity:1;fill:url(#linearGradient22523);fill-opacity:1;stroke:none;stroke-width:9.73911;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="rotate(-90)" />
+ </g>
+ </g>
+</svg>
diff --git a/data/resources/icons/scalable/status/welcome.svg
b/data/resources/icons/scalable/status/welcome.svg
index 915dec91..636f175b 100644
--- a/data/resources/icons/scalable/status/welcome.svg
+++ b/data/resources/icons/scalable/status/welcome.svg
@@ -1,27 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
width="340"
height="200"
viewBox="0 0 89.958331 52.916668"
version="1.1"
- id="svg8662">
+ id="svg8662"
+ sodipodi:docname="welcome-export.svg"
+ inkscape:version="1.1-rc (52f87abb86, 2021-05-02)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <sodipodi:namedview
+ id="namedview47"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ objecttolerance="10.0"
+ gridtolerance="10.0"
+ guidetolerance="10.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="false"
+ inkscape:zoom="0.99583586"
+ inkscape:cx="303.76492"
+ inkscape:cy="15.062723"
+ inkscape:current-layer="svg8662"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-text-baseline="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid1470" />
+ </sodipodi:namedview>
<defs
id="defs8656">
<linearGradient
- xlink:href="#linearGradient8161"
- id="linearGradient8163"
- x1="-3292.5"
- y1="-438.48035"
- x2="-3272.5"
- y2="-438.48035"
- gradientUnits="userSpaceOnUse"
- gradientTransform="translate(3720.9803,-2844.0197)" />
+ inkscape:collect="always"
+ id="linearGradient39832">
+ <stop
+ style="stop-color:#3584e4;stop-opacity:1"
+ offset="0"
+ id="stop39828" />
+ <stop
+ style="stop-color:#c061cb;stop-opacity:1"
+ offset="1"
+ id="stop39830" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient24559">
+ <stop
+ style="stop-color:#ed333b;stop-opacity:1;"
+ offset="0"
+ id="stop24555" />
+ <stop
+ style="stop-color:#f66151;stop-opacity:1"
+ offset="1"
+ id="stop24557" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient12858">
+ <stop
+ style="stop-color:#f66151;stop-opacity:1"
+ offset="0"
+ id="stop12854" />
+ <stop
+ style="stop-color:#f6d32d;stop-opacity:1"
+ offset="1"
+ id="stop12856" />
+ </linearGradient>
<linearGradient
id="linearGradient8161">
<stop
@@ -33,15 +91,6 @@
offset="1"
id="stop8159" />
</linearGradient>
- <linearGradient
- xlink:href="#linearGradient8145"
- id="linearGradient8155"
- x1="-3292.5"
- y1="-438.48035"
- x2="-3272.5"
- y2="-438.48035"
- gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(1.1500001,0,0,1.1500001,4215.3556,-2778.2476)" />
<linearGradient
id="linearGradient8145">
<stop
@@ -53,6 +102,160 @@
offset="1"
id="stop8143" />
</linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient976"
+ id="radialGradient1221"
+ gradientUnits="userSpaceOnUse"
+ cx="64"
+ cy="212"
+ fx="64"
+ fy="212"
+ r="60" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient976">
+ <stop
+ style="stop-color:#f8e45c;stop-opacity:1"
+ offset="0"
+ id="stop972" />
+ <stop
+ style="stop-color:#f5c211;stop-opacity:1"
+ offset="1"
+ id="stop974" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient1117"
+ id="radialGradient1223"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.75,0,54)"
+ cx="36"
+ cy="224"
+ fx="36"
+ fy="224"
+ r="16" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient1117">
+ <stop
+ style="stop-color:#5e5c64;stop-opacity:1"
+ offset="0"
+ id="stop1113" />
+ <stop
+ style="stop-color:#241f31;stop-opacity:1"
+ offset="1"
+ id="stop1115" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient1117"
+ id="radialGradient1225"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.75,40,54)"
+ cx="36"
+ cy="224"
+ fx="36"
+ fy="224"
+ r="16" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient1325">
+ <stop
+ style="stop-color:#f66151;stop-opacity:1"
+ offset="0"
+ id="stop1321" />
+ <stop
+ id="stop1329"
+ offset="0.60000002"
+ style="stop-color:#e6272f;stop-opacity:1;" />
+ <stop
+ style="stop-color:#e01b24;stop-opacity:1"
+ offset="1"
+ id="stop1323" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient976"
+ id="radialGradient1393-0"
+ gradientUnits="userSpaceOnUse"
+ cx="64"
+ cy="212"
+ fx="64"
+ fy="212"
+ r="60"
+ gradientTransform="matrix(0.26458333,0,0,0.26458333,10.972649,-67.464743)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient1325"
+ id="radialGradient1395-6"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.20557447,0,0,0.16449582,14.041109,-16.508601)"
+ cx="52"
+ cy="29.856375"
+ fx="52"
+ fy="29.856375"
+ r="16.084499" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient1325"
+ id="radialGradient1397-9-9"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.20557447,0,0,0.16449582,24.624491,-16.508601)"
+ cx="52"
+ cy="29.856375"
+ fx="52"
+ fy="29.856375"
+ r="16.084499" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient12858"
+ id="linearGradient12860"
+ x1="3467.3748"
+ y1="-422.64157"
+ x2="3467.3748"
+ y2="-383.00339"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8145"
+ id="linearGradient15076"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.1500001,0,0,1.1500001,4215.3556,-2778.2476)"
+ x1="-3292.5"
+ y1="-438.48035"
+ x2="-3272.5"
+ y2="-438.48035" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient39832"
+ id="radialGradient39804"
+ cx="3496.6987"
+ cy="-406.69202"
+ fx="3496.6987"
+ fy="-406.69202"
+ r="30"
+ gradientTransform="matrix(0.61314223,0,0,0.49927324,-2207.2336,232.19657)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8161"
+ id="linearGradient40220"
+ x1="428.48035"
+ y1="-3282.5"
+ x2="448.48035"
+ y2="-3282.5"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.85000059,0,0,0.85000059,64.271801,-492.37307)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient24559"
+ id="linearGradient63749"
+ gradientUnits="userSpaceOnUse"
+ x1="22.671865"
+ y1="9.260417"
+ x2="22.671865"
+ y2="2.9104166" />
</defs>
<metadata
id="metadata8659">
@@ -62,212 +265,466 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
- id="layer1">
+ id="g20123">
+ <rect
+
style="fill:#c01c28;fill-opacity:1;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="rect13252"
+ width="27.781248"
+ height="18.520834"
+ x="6.614583"
+ y="14.022915"
+ rx="3.175"
+ ry="3.175" />
+ <rect
+
style="fill:#f66151;fill-opacity:1;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="rect11780"
+ width="27.781248"
+ height="18.520834"
+ x="6.6145825"
+ y="13.229164"
+ rx="3.175"
+ ry="3.175" />
+ </g>
+ <g
+ transform="matrix(0.42167947,0,0,0.26458333,1495.4739,124.21668)"
+ id="g137288"
+ style="stroke-width:0.792118">
+ <rect
+ y="-447.48032"
+ x="-3490"
+ height="70"
+ width="87.843063"
+ id="rect137286"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#1c71d8;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0388092;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ rx="7.5294156"
+ ry="12" />
+ </g>
+ <g
+ id="g137252"
+ transform="matrix(0.26458333,0,0,0.26208726,931.33332,121.81302)"
+ style="stroke-width:1.00475" />
+ <g
+ style="stroke-width:0.751809"
+ id="g137258"
+ transform="matrix(0.46810895,0,0,0.26458333,1662.8044,147.5)">
+ <rect
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#e5a50a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.02834;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect137254"
+ width="65.000107"
+ height="21.999973"
+ x="3425"
+ y="-399.48032"
+ rx="6.782609"
+ ry="12"
+ transform="scale(-1,1)" />
+ <rect
+ transform="scale(-1,1)"
+ ry="12.000001"
+ rx="6.782609"
+ y="-432.48032"
+ x="3425"
+ height="51.999969"
+ width="65.000107"
+ id="rect137256"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:url(#linearGradient12860);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.02834;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
/>
+ </g>
+ <g
+ transform="matrix(0.396875,0,0,0.396875,1599.4062,118.32708)"
+ id="g137264"
+ style="stroke-width:0.666667">
+ <path
+ sodipodi:nodetypes="sssscccsssss"
+
style="display:inline;fill:#62a0ea;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
+ d="m -3912.0003,-233.37879 c -4.4319,0 -8,3.56799 -8,8 v 27.89844 c 0,4.43202 3.5681,8 8,8 h 36.3333
l 12.6667,12.66667 v -12.66667 h 8.3333 c 4.432,0 8,-3.56798 8,-8 v -27.89844 c 0,-4.43201 -3.568,-8 -8,-8 z"
+ id="path137260"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:nodetypes="cssccccsccsccssc"
+ inkscape:connector-curvature="0"
+ id="path137262"
+ d="m -3920.0003,-199.48035 v 2 c 0,4.43202 3.5681,8 8,8 h 36.3333 l 12.6667,12.66667 v -2 l
-12.6667,-12.66667 h -36.3333 c -4.4319,0 -8,-3.56798 -8,-8 z m 73.3333,0 c 0,4.43202 -3.568,8 -8,8 h -8.3333
v 2 h 8.3333 c 4.432,0 8,-3.56798 8,-8 z"
+
style="display:inline;fill:#3584e4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
/>
+ </g>
+ <rect
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#1c71d8;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0107728;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect137266"
+ width="18.520834"
+ height="6.0854096"
+ x="-78.052101"
+ y="25.664574"
+ rx="2.4694443"
+ ry="2.1166666"
+ transform="scale(-1,1)" />
+ <rect
+ transform="scale(-1,1)"
+ ry="2.1166666"
+ rx="2.4694443"
+ y="15.874991"
+ x="-78.052101"
+ height="15.081258"
+ width="18.520834"
+ id="rect137268"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:url(#radialGradient39804);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0107728;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
/>
+ <g
+ transform="matrix(0.396875,0,0,0.396875,1569.6406,110.125)"
+ id="g137282"
+ style="stroke-width:0.666667">
+ <path
+ inkscape:connector-curvature="0"
+ id="path137272"
+ d="m -3913.6666,-217.48035 c -4.4319,0 -8,3.56798 -8,8 v 24 c 0,4.43202 3.5681,8 8,8 h 15.3333 v
12.66667 l 12.6667,-12.66667 h 45.9999 c 4.432,0 8,-3.56798 8,-8 v -24 c 0,-4.43202 -3.568,-8 -8,-8 z"
+
style="display:inline;fill:#3d3846;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
+ sodipodi:nodetypes="sssscccsssss" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path137274"
+ d="m -3921.6666,-187.48035 v 2 c 0,4.43202 3.5681,8 8,8 h 15.3333 v -2 h -15.3333 c -4.4319,0
-8,-3.56798 -8,-8 z m 89.9999,0 c 0,4.43202 -3.568,8 -8,8 h -45.9999 l -12.6667,12.66667 v 2 l
12.6667,-12.66667 h 45.9999 c 4.432,0 8,-3.56798 8,-8 z"
+
style="display:inline;fill:#241f31;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
+ sodipodi:nodetypes="cssccsccsccccssc" />
+ <rect
+
style="opacity:1;fill:#77767b;fill-opacity:1;stroke:none;stroke-width:9.33333;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect137276"
+ width="56.666668"
+ height="3.3333311"
+ x="-3905"
+ y="-207.48033" />
+ <rect
+
style="opacity:1;fill:#77767b;fill-opacity:1;stroke:none;stroke-width:9.33333;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect137278"
+ width="56.666668"
+ height="3.3333311"
+ x="-3905"
+ y="-200.81364" />
+ <rect
+ y="-194.14696"
+ x="-3905"
+ height="3.3333311"
+ width="33.333332"
+ id="rect137280"
+
style="opacity:1;fill:#77767b;fill-opacity:1;stroke:none;stroke-width:9.33337;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ </g>
+ <g
+ id="g50125"
+ transform="translate(-15.345834,3.4395835)">
+ <rect
+ ry="3.7041636"
+ rx="3.7041638"
+ y="30.691668"
+ x="51.858337"
+ height="7.4083271"
+ width="17.991665"
+ id="rect48478"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#deddda;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.00997368;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
/>
+ <rect
+ ry="3.7041636"
+ rx="3.7041638"
+ y="29.897917"
+ x="51.858337"
+ height="7.4083271"
+ width="17.991665"
+ id="rect43843"
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.00997368;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
/>
+ <circle
+
style="fill:#5e5c64;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="path46775"
+ cx="60.854168"
+ cy="33.602085"
+ r="1.0583339" />
+ <circle
+
style="fill:#5e5c64;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="circle46919"
+ cx="57.679169"
+ cy="33.602085"
+ r="1.0583339" />
+ <circle
+
style="fill:#5e5c64;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="circle46921"
+ cx="64.02916"
+ cy="33.602085"
+ r="1.0583339" />
+ </g>
+ <g
+ id="g63747">
+ <rect
+
style="fill:#c01c28;fill-opacity:1;stroke-width:0.793748;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="rect63739"
+ width="15.875"
+ height="6.3499999"
+ x="17.4625"
+ y="3.7041667"
+ rx="3.175"
+ ry="3.175" />
+ <rect
+
style="fill:url(#linearGradient63749);fill-opacity:1;stroke-width:0.793748;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:26.4"
+ id="rect63741"
+ width="15.875"
+ height="6.3499999"
+ x="17.4625"
+ y="2.9104166"
+ rx="3.175"
+ ry="3.175" />
+ <text
+ xml:space="preserve"
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333px;line-height:1.25;font-family:Cantarell;-inkscape-font-specification:'Cantarell
Bold';text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#ffffff;stroke-width:0.264583"
+ x="25.548162"
+ y="7.6729164"
+ id="text63745"><tspan
+ sodipodi:role="line"
+ id="tspan63743"
+
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:4.23333px;font-family:Cantarell;-inkscape-font-specification:'Cantarell
Ultra-Bold';fill:#ffffff;stroke-width:0.264583"
+ x="25.548162"
+ y="7.6729164">999+</tspan></text>
+ </g>
+ <g
+ id="g137308"
+ transform="matrix(0.26458333,0,0,0.26458333,946.54684,145.64793)">
+ <circle
+
style="opacity:1;fill:#c01c28;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="circle137304"
+ cx="-3282.5"
+ cy="-433.98035"
+ r="8.5000057" />
+ <circle
+ r="8.5000057"
+ cy="-3282.5"
+ cx="436.98035"
+ id="circle137306"
+
style="opacity:1;fill:url(#linearGradient40220);fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="rotate(-90)" />
+ </g>
+ <g
+ id="g27132"
+ transform="translate(-1.8520832,10.583333)">
+ <g
+ id="g7977-9"
+ transform="matrix(0.26458333,0,0,0.26458333,884.23748,129.9052)">
+ <circle
+
style="opacity:1;fill:#26a269;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="circle7973-7"
+ cx="-3282.5"
+ cy="-437.48032"
+ r="11.500001" />
+ <circle
+ r="11.500001"
+ cy="-3282.5"
+ cx="440.48032"
+ id="circle7975-3"
+
style="opacity:1;fill:url(#linearGradient15076);fill-opacity:1.0;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="rotate(-90)" />
+ </g>
+ </g>
+ <g
+ id="g4401"
+ transform="matrix(0.50000001,0,0,0.50000001,8.3343757,26.192195)"
+ style="stroke-width:2">
+ <path
+ id="path2310"
+ style="fill:#1a5fb4;fill-opacity:1;stroke-width:0;stop-color:#000000"
+ d="m 55.850337,32.276064 c -0.557487,0 -1.081579,0.455525 -1.081587,1.058333 v 3.175 h -5.291667 c
-0.538057,0 -1.058333,0.490969 -1.058333,0.981337 l 1.030943,7.3303 c 0.08548,0.687803 0.556554,1.213363
1.085723,1.213363 h 6.35 v 0.01188 h 5.291667 v -6.361885 h -3.175 l -1.965772,-7.408333 z" />
+ <path
+ id="path2098"
+ style="fill:#62a0ea;fill-opacity:1;stroke-width:0;stop-color:#000000"
+ d="m 55.850337,31.217732 c -0.557487,0 -1.081579,0.455525 -1.081587,1.058333 v 3.175 h -5.291667 c
-0.538057,0 -1.058333,0.490969 -1.058333,0.981337 l 1.030943,7.3303 c 0.08548,0.687803 0.556554,1.213363
1.085723,1.213363 h 6.35 v 0.01188 h 5.291667 V 38.62606 h -3.175 l -1.965772,-7.408333 z" />
+ </g>
+ <g
+ style="display:inline;enable-background:new"
+ id="g1219"
+ transform="matrix(0.13229167,0,0,0.13229167,21.431254,-7.9374947)">
+ <circle
+
style="opacity:1;vector-effect:none;fill:url(#radialGradient1221);fill-opacity:1;stroke:none;stroke-width:12.8571;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="circle1189"
+ cx="64"
+ cy="236"
+ r="60" />
+ <rect
+ y="206"
+ x="-108"
+ height="4"
+ width="24"
+ id="rect1191"
+
style="opacity:1;vector-effect:none;fill:#241f31;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="scale(-1,1)"
+ rx="2"
+ ry="2" />
+ <path
+ inkscape:connector-curvature="0"
+
style="opacity:1;vector-effect:none;fill:#e5a50a;fill-opacity:1;stroke:none;stroke-width:12.8571;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 123.82422,231.53906 A 60,60 0 0 1 64,288 60,60 0 0 1 4.17578,232.46094 60,60 0 0 0 4,236 a 60,60
0 0 0 60,60 60,60 0 0 0 60,-60 60,60 0 0 0 -0.17578,-4.46094 z"
+ id="path1193" />
+ <path
+ sodipodi:open="true"
+ sodipodi:end="3.1415927"
+ sodipodi:start="0"
+ sodipodi:ry="7.0068064"
+ sodipodi:rx="7.6309938"
+ sodipodi:cy="236.99103"
+ sodipodi:cx="64"
+ sodipodi:type="arc"
+ id="path1195"
+
style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#3d3846;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:arc-type="arc"
+ d="m 71.630994,236.99103 a 7.6309938,7.0068064 0 0 1 -3.815497,6.06807 7.6309938,7.0068064 0 0 1
-7.630994,0 7.6309938,7.0068064 0 0 1 -3.815497,-6.06807" />
+ <rect
+ ry="8"
+ rx="8"
+ y="206"
+ x="28"
+ height="24"
+ width="32"
+ id="rect1197"
+
style="opacity:1;vector-effect:none;fill:#241f31;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ <path
+
style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#241f31;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path1199"
+ sodipodi:type="arc"
+ sodipodi:cx="64"
+ sodipodi:cy="-216"
+ sodipodi:rx="8"
+ sodipodi:ry="8"
+ sodipodi:start="0"
+ sodipodi:end="3.1415927"
+ sodipodi:open="true"
+ transform="scale(1,-1)"
+ sodipodi:arc-type="arc"
+ d="m 72,-216 a 8,8 0 0 1 -4,6.9282 8,8 0 0 1 -8,0 A 8,8 0 0 1 56,-216" />
+ <rect
+
style="opacity:1;vector-effect:none;fill:#241f31;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect1201"
+ width="32"
+ height="24"
+ x="68"
+ y="206"
+ rx="8"
+ ry="8" />
+ <rect
+
style="opacity:1;vector-effect:none;fill:url(#radialGradient1223);fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect1203"
+ width="32"
+ height="24"
+ x="28"
+ y="204"
+ rx="8"
+ ry="8" />
+ <rect
+ ry="8"
+ rx="8"
+ y="204"
+ x="68"
+ height="24"
+ width="32"
+ id="rect1205"
+
style="opacity:1;vector-effect:none;fill:url(#radialGradient1225);fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ <rect
+ ry="2"
+ rx="2"
+ transform="scale(-1,1)"
+
style="opacity:1;vector-effect:none;fill:#241f31;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect1207"
+ width="24"
+ height="4"
+ x="-44"
+ y="206" />
+ </g>
+ <g
+ id="g63777"
+ transform="translate(-2.6458333)">
<g
- id="g9553"
- transform="translate(-57.074405,-5.2916687)">
+ id="g137302"
+ transform="matrix(0.26458333,0,0,0.26458333,939.27082,123.15833)">
<g
- id="g10404"
- transform="matrix(0.26458333,0,0,0.26458333,-32.869614,144.94489)">
- <g
- style="stroke-width:0.751809"
- id="g8028"
- transform="matrix(1.7692307,0,0,1,6624.5611,29.65709)">
- <rect
-
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#2ec27e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.02834;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
- id="rect8024"
- width="65.000107"
- height="21.999973"
- x="3425"
- y="-399.48032"
- rx="6.782609"
- ry="12"
- transform="scale(-1,1)" />
- <rect
- transform="scale(-1,1)"
- ry="12.000001"
- rx="6.782609"
- y="-432.48032"
- x="3425"
- height="51.999969"
- width="65.000107"
- id="rect8026"
-
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#57e389;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.02834;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
/>
- </g>
- <g
- transform="matrix(1.5,0,0,1.5,6384.9459,-80.602744)"
- id="g4708"
- style="stroke-width:0.666667">
- <path
-
style="display:inline;fill:#1c71d8;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
- d="m -3912.0003,-233.37879 c -4.4319,0 -8,3.56799 -8,8 v 27.89844 c 0,4.43202 3.5681,8 8,8 h
36.3333 l 12.6667,12.66667 v -12.66667 h 8.3333 c 4.432,0 8,-3.56798 8,-8 v -27.89844 c 0,-4.43201 -3.568,-8
-8,-8 z"
- id="path4704" />
- <path
- id="path4706"
- d="m -3920.0003,-199.48035 v 2 c 0,4.43202 3.5681,8 8,8 h 36.3333 l 12.6667,12.66667 v -2 l
-12.6667,-12.66667 h -36.3333 c -4.4319,0 -8,-3.56798 -8,-8 z m 73.3333,0 c 0,4.43202 -3.568,8 -8,8 h -8.3333
v 2 h 8.3333 c 4.432,0 8,-3.56798 8,-8 z"
-
style="display:inline;fill:#1a5fb4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
/>
- </g>
- <g
- id="g5222"
- transform="translate(4089.9459,-0.342944)">
- <rect
-
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#f5c211;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0376957;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
- id="rect5218"
- width="49.99995"
- height="22.999973"
- x="3455"
- y="-420.48032"
- rx="8"
- ry="8"
- transform="scale(-1,1)" />
- <rect
- transform="scale(-1,1)"
- ry="8"
- rx="8"
- y="-457.48032"
- x="3455"
- height="57.000031"
- width="49.99995"
- id="rect5220"
-
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#f8e45c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0376957;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
/>
- </g>
- <g
- transform="matrix(1.5,0,0,1.5,6272.4459,-121.60274)"
- id="g4715"
- style="stroke-width:0.666667">
- <path
- id="path4710"
- d="m -3913.6666,-217.48035 c -4.4319,0 -8,3.56798 -8,8 v 24 c 0,4.43202 3.5681,8 8,8 h 15.3333
v 12.66667 l 12.6667,-12.66667 h 45.9999 c 4.432,0 8,-3.56798 8,-8 v -24 c 0,-4.43202 -3.568,-8 -8,-8 z"
-
style="display:inline;fill:#3d3846;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
/>
- <path
- id="path4712"
- d="m -3921.6666,-187.48035 v 2 c 0,4.43202 3.5681,8 8,8 h 15.3333 v -2 h -15.3333 c -4.4319,0
-8,-3.56798 -8,-8 z m 89.9999,0 c 0,4.43202 -3.568,8 -8,8 h -45.9999 l -12.6667,12.66667 v 2 l
12.6667,-12.66667 h 45.9999 c 4.432,0 8,-3.56798 8,-8 z"
-
style="display:inline;fill:#241f31;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
/>
- <rect
-
style="opacity:1;vector-effect:none;fill:#77767b;fill-opacity:1;stroke:none;stroke-width:9.33333;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
- id="rect7985-1"
- width="56.666668"
- height="3.3333311"
- x="-3905"
- y="-207.48033" />
- <rect
-
style="opacity:1;vector-effect:none;fill:#77767b;fill-opacity:1;stroke:none;stroke-width:9.33333;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
- id="rect7989-4"
- width="56.666668"
- height="3.3333311"
- x="-3905"
- y="-200.81364" />
- <rect
- y="-194.14696"
- x="-3905"
- height="3.3333311"
- width="40"
- id="rect7991-9"
-
style="opacity:1;vector-effect:none;fill:#77767b;fill-opacity:1;stroke:none;stroke-width:9.33333;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
- </g>
- <g
- transform="matrix(1.5937492,0,0,1,5992.1307,-55.342911)"
- id="g5032"
- style="stroke-width:0.792118">
- <rect
-
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#c01c28;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0298595;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
- id="rect5026"
- width="80.000107"
- height="50.000008"
- x="3410"
- y="-447.48035"
- rx="5.0196104"
- ry="8"
- transform="scale(-1,1)" />
- <rect
- transform="scale(-1,1)"
- ry="8"
- rx="5.0196104"
- y="-452.48032"
- x="3410"
- height="51.999996"
- width="80.000107"
- id="rect5028"
-
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#ed333b;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0298595;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
/>
- </g>
- <g
- id="g8139"
- transform="translate(3889.9459,-65.34288)">
- <g
- style="stroke-width:0.666667"
- transform="matrix(1.5,0,0,1.5,2465.0001,-75.912199)"
- id="g4721">
- <path
-
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
- d="m -3905.3334,-234.37879 c -4.4319,0 -8,3.56799 -8,8 v 27.33333 c 0,4.43202 3.5681,8 8,8 h
19 l 12.6667,12.66667 v -12.66667 H -3848 c 4.432,0 8,-3.56798 8,-8 v -27.33333 c 0,-4.43201 -3.568,-8 -8,-8
z"
- id="path4717" />
- <path
- id="path4719"
- d="m -3913.3334,-201.04546 v 2 c 0,4.43202 3.5681,8 8,8 h 19 l 12.6667,12.66667 v -2 l
-12.6667,-12.66667 h -19 c -4.4319,0 -8,-3.56798 -8,-8 z m 73.3334,0 c 0,4.43202 -3.568,8 -8,8 h -25.6667 v 2
H -3848 c 4.432,0 8,-3.56798 8,-8 z"
-
style="display:inline;fill:#deddda;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
/>
- </g>
- <rect
- y="-412.48035"
- x="-3385"
- height="4.9999967"
- width="70"
- id="rect7985"
-
style="opacity:1;vector-effect:none;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
- <rect
- y="-402.48035"
- x="-3385"
- height="4.9999967"
- width="70"
- id="rect7989"
-
style="opacity:1;vector-effect:none;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
- <rect
-
style="opacity:1;vector-effect:none;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
- id="rect7991"
- width="45"
- height="4.9999967"
- x="-3385"
- y="-392.48035" />
- </g>
- <g
- id="g7871"
- transform="translate(3912.4459,32.65712)">
- <circle
-
style="opacity:1;vector-effect:none;fill:#c01c28;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
- id="circle7867"
- cx="-3282.5"
- cy="-435.48035"
- r="10" />
- <circle
- r="10"
- cy="-3282.5"
- cx="438.48035"
- id="circle7869"
-
style="opacity:1;vector-effect:none;fill:url(#linearGradient8163);fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
- transform="rotate(-90)" />
- </g>
- <g
- id="g7977"
- transform="translate(3676.4459,-6.842912)">
- <circle
-
style="opacity:1;vector-effect:none;fill:#26a269;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
- id="circle7973"
- cx="-3282.5"
- cy="-437.48032"
- r="11.500001" />
- <circle
- r="11.500001"
- cy="-3282.5"
- cx="440.48032"
- id="circle7975"
-
style="opacity:1;vector-effect:none;fill:url(#linearGradient8155);fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
- transform="rotate(-90)" />
- </g>
+ style="stroke-width:0.666667"
+ transform="matrix(1.5,0,0,1.5,2465.0001,-75.912199)"
+ id="g137294">
+ <path
+ sodipodi:nodetypes="sssccssss"
+
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
+ d="m -3905.3334,-234.37879 c -4.4319,0 -8,3.56799 -8,8 v 27.33333 c 0,4.43202 3.5681,8 8,8 H
-3848 c 4.432,0 8,-3.56798 8,-8 v -27.33333 c 0,-4.43201 -3.568,-8 -8,-8 z"
+ id="path137290"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path137292"
+ d="m -3913.3334,-201.04546 v 2 c 0,4.43202 3.5681,8 8,8 h 19 l 12.6667,12.66667 v -2 l
-12.6667,-12.66667 h -19 c -4.4319,0 -8,-3.56798 -8,-8 z m 73.3334,0 c 0,4.43202 -3.568,8 -8,8 h -25.6667 v 2
H -3848 c 4.432,0 8,-3.56798 8,-8 z"
+
style="display:inline;fill:#deddda;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.00753214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
+ sodipodi:nodetypes="cssccccsccsccssc" />
</g>
+ <rect
+ y="-412.48035"
+ x="-3385"
+ height="4.9999967"
+ width="70"
+ id="rect137296"
+
style="opacity:1;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ <rect
+ y="-402.48035"
+ x="-3385"
+ height="4.9999967"
+ width="70"
+ id="rect137298"
+
style="opacity:1;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
/>
+ <rect
+
style="opacity:1;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect137300"
+ width="59.000008"
+ height="4.9999967"
+ x="-3385"
+ y="-392.48035" />
</g>
+ <path
+
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 54.107302,26.458311 v 5.027084 l -5.027097,-5.027084 z"
+ id="path62781" />
+ </g>
+ <g
+ id="g1760-2"
+ transform="matrix(0.24999998,0,0,0.24999998,54.891299,38.678132)">
+ <circle
+
style="display:inline;fill:url(#radialGradient1393-0);fill-opacity:1;stroke:none;stroke-width:3.40177;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;enable-background:new"
+ id="circle1367-0"
+ cx="27.905983"
+ cy="-5.023077"
+ r="15.875" />
+ <path
+ inkscape:connector-curvature="0"
+
style="display:inline;fill:#e5a50a;fill-opacity:1;stroke:none;stroke-width:3.40177;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;enable-background:new"
+ d="M 43.734473,-6.2033675 A 15.875,15.875 0 0 1 27.905982,8.735256 15.875,15.875 0 0 1
12.077491,-5.9594534 a 15.875,15.875 0 0 0 -0.04651,0.9363763 15.875,15.875 0 0 0 15.875,15.8750001
15.875,15.875 0 0 0 15.875,-15.8750001 15.875,15.875 0 0 0 -0.04651,-1.1802904 z"
+ id="path1369-2" />
+ <path
+ sodipodi:open="true"
+ sodipodi:end="3.1415927"
+ sodipodi:start="0"
+ sodipodi:ry="1.8538842"
+ sodipodi:rx="2.0190337"
+ sodipodi:cy="-4.7608676"
+ sodipodi:cx="27.905983"
+ sodipodi:type="arc"
+ id="path1371-3"
+
style="display:inline;opacity:1;fill:none;fill-opacity:1;stroke:#3d3846;stroke-width:1.05833;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;enable-background:new"
+ sodipodi:arc-type="arc"
+ d="m 29.925017,-4.7608676 a 2.0190337,1.8538842 0 0 1 -1.009517,1.6055108 2.0190337,1.8538842 0 0 1
-2.019034,0 2.0190337,1.8538842 0 0 1 -1.009517,-1.6055108" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path1373-7"
+ d="m 24.707728,-13.490777 a 2.1168783,2.1168783 0 0 0 -1.459859,0.642337 l -0.620118,0.620117
-0.620117,-0.620117 a 2.1168783,2.1168783 0 0 0 -1.51877,-0.641302 2.1168783,2.1168783 0 0 0
-1.474329,3.6349196 l 2.865975,2.8659745 v -0.00212 a 1.0583333,1.0583333 0 0 0 1.49655,0 l
2.863907,-2.8639082 A 2.1168783,2.1168783 0 0 0 24.707728,-13.49083 Z"
+
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#radialGradient1395-6);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.23333;stro
ke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
/>
+ <path
+ inkscape:connector-curvature="0"
+
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#radialGradient1397-9-9);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.23333;st
roke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 35.291109,-13.490777 a 2.1168783,2.1168783 0 0 0 -1.459859,0.642337 l -0.620118,0.620117
-0.620117,-0.620117 a 2.1168783,2.1168783 0 0 0 -1.51877,-0.641302 2.1168783,2.1168783 0 0 0
-1.474329,3.6349196 l 2.865975,2.8659745 v -0.00212 a 1.0583333,1.0583333 0 0 0 1.49655,0 l
2.863908,-2.8639082 a 2.1168783,2.1168783 0 0 0 -1.53324,-3.6359539 z"
+ id="path1375-3-5" />
+ <path
+ id="path1377-9"
+ d="m 18.412505,-11.77202 a 2.1168783,2.1168783 0 0 0 0.60203,1.9171976 l 2.865975,2.8659745 v
-0.00212 a 1.0583333,1.0583333 0 0 0 1.49655,0 l 2.863907,-2.8639109 a 2.1168783,2.1168783 0 0 0
0.604615,-1.9166782 2.1168783,2.1168783 0 0 1 -0.604615,1.122929 l -2.863907,2.8639074 a 1.0583333,1.0583333
0 0 1 -1.49655,0 v 0.00212 l -2.865975,-2.8659714 a 2.1168783,2.1168783 0 0 1 -0.60203,-1.123448 z"
+
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#c01c28;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.23333;stroke-linecap:round;st
roke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path1379-6-2"
+ d="m 28.995838,-11.77202 a 2.1168783,2.1168783 0 0 0 0.60203,1.9171976 l 2.865975,2.8659745 v
-0.00212 a 1.0583333,1.0583333 0 0 0 1.49655,0 l 2.863908,-2.8639109 a 2.1168783,2.1168783 0 0 0
0.604614,-1.9166782 2.1168783,2.1168783 0 0 1 -0.604614,1.122929 l -2.863908,2.8639074 a 1.0583333,1.0583333
0 0 1 -1.49655,0 v 0.00212 l -2.865975,-2.8659714 a 2.1168783,2.1168783 0 0 1 -0.60203,-1.123448 z"
+
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#c01c28;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.23333;stroke-linecap:round;st
roke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
</g>
</svg>
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 60e0b34e..a2c82cc5 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -40,6 +40,8 @@
<file compressed="true" preprocess="xml-stripblanks"
alias="account-settings-devices-page.ui">ui/account-settings-devices-page.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="components-loading-listbox-row.ui">ui/components-loading-listbox-row.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="room-creation.ui">ui/room-creation.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks"
alias="session-verification.ui">ui/session-verification.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks"
alias="verification-emoji.ui">ui/verification-emoji.ui</file>
<file compressed="true">style.css</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/send-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/status/welcome.svg</file>
@@ -47,5 +49,7 @@
<file preprocess="xml-stripblanks">icons/scalable/status/explore-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/status/devices-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/status/verified-symbolic.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/status/setup-complete.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/status/other-device.svg</file>
</gresource>
</gresources>
diff --git a/data/resources/style.css b/data/resources/style.css
index c2642de1..45a58304 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -45,13 +45,13 @@
padding: 12px 40px;
}
-.pill {
+.inline-pill {
border-radius: 9999px;
background-color: alpha(@theme_text_color, 0.1);
padding-right: 6px;
}
-.app-notification .pill {
+.app-notification .inline-pill {
background-color: alpha(@theme_bg_color, 0.2);
}
@@ -71,6 +71,10 @@
min-width: 250px;
}
+.session-verification .text-button {
+ min-width: 200px;
+}
+
#devnotice {
background-color: lighter(alpha(@warning_color, 0.3));
color: shade(@warning_color, 0.5);
@@ -244,3 +248,7 @@ button.cutout:hover {
button.cutout:active {
background-color: #c4c5c4;
}
+
+.emoji {
+ font-size: 2em;
+}
diff --git a/data/resources/ui/pill.ui b/data/resources/ui/pill.ui
index 0f57182a..08c3d298 100644
--- a/data/resources/ui/pill.ui
+++ b/data/resources/ui/pill.ui
@@ -4,7 +4,7 @@
<property name="focusable">True</property>
<property name="valign">center</property>
<style>
- <class name="pill"/>
+ <class name="inline-pill"/>
</style>
<child>
<object class="GtkBox">
diff --git a/data/resources/ui/session-verification.ui b/data/resources/ui/session-verification.ui
new file mode 100644
index 00000000..5bcb90e9
--- /dev/null
+++ b/data/resources/ui/session-verification.ui
@@ -0,0 +1,507 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="SessionVerification" parent="AdwBin">
+ <style>
+ <class name="session-verification"/>
+ </style>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkHeaderBar">
+ <property name="show-title-buttons">True</property>
+ <style>
+ <class name="flat"/>
+ </style>
+ <child type="start">
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="icon-name">go-previous-symbolic</property>
+ <property name="action-name">verification.previous</property>
+ <style>
+ <class name="circular"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="main_stack">
+ <property name="transition-type">crossfade</property>
+ <property name="vexpand">True</property>
+ <property name="margin-top">24</property>
+ <property name="margin-bottom">24</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">wait-for-device</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Get Another Device</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Accept the verification request from
another session or device.</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkPicture">
+ <property
name="file">resource:///org/gnome/FractalNext/icons/scalable/status/other-device.svg</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">No other devices logged into this
account?</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="label" translatable="yes">Recovery</property>
+ <property name="halign">center</property>
+ <property name="action-name">verification.show-recovery</property>
+ <style>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">bootstrap</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Setup Encryption Identity</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">You need to setup an encryption
identity, since this is the first time you logged into your account.</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="SpinnerButton" id="bootstrap_button">
+ <property name="label" translatable="yes">Setup</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="suggested-action"/>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">qrcode</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Verify Session</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Scan this code from another session
logged into this account</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="TriQRCode" id="qrcode">
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <property name="margin-top">24</property>
+ <property name="margin-bottom">24</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Can't scan QR code?</property>
+ </object>
+ </child>
+ <child>
+ <object class="SpinnerButton" id="start_emoji_btn">
+ <property name="label" translatable="yes">Compare Emoji</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">emoji</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Verify Session</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Check if the same emoji appear in the
same order on the other device</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="emoji_row_1">
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <property name="spacing">30</property>
+ <property name="margin-top">24</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="emoji_row_2">
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <property name="spacing">30</property>
+ <property name="margin-bottom">24</property>
+ </object>
+ </child>
+ <child>
+ <object class="SpinnerButton" id="emoji_not_match_btn">
+ <property name="label" translatable="yes">Do Not Match</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="destructive-action"/>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="SpinnerButton" id="emoji_match_btn">
+ <property name="label" translatable="yes">Match</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="suggested-action"/>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">recovery</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Recovery</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Without another device you need a
recovery passphrase or key to access your messages</property>
+ <property name="wrap">True</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="label" translatable="yes">Recovery Passphrase</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="label" translatable="yes">Recovery Key</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">If you don't have any of these you can
rest your identity, but be aware this makes your old messages inacessible forever.</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="halign">center</property>
+ <property name="label" translatable="yes">Reset Identity</property>
+ <style>
+ <class name="destructive-action"/>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">recovery-passphrase</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Recovery Passphrase</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Your Recovery Passphrase was set up
when you first created this account.</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">If you opted for a Recovery Key
instead go back and choose that option.</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkPasswordEntry">
+ <property name="placeholder-text" translatable="yes">Passphrase</property>
+ <property name="show-peek-icon">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="label" translatable="yes">Next</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">recovery-key</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Recovery Key</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Your Recovery Key was set up when you
first created this account.</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">If you opted for a Recovery Passphrase
instead go back and choose that option.</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTextView">
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox">
+
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="label" translatable="yes">Next</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">completed</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Setup Complete</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkPicture">
+ <property
name="file">resource:///org/gnome/FractalNext/icons/scalable/status/setup-complete.svg</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">This session is ready to send and
receive secure messages</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="get_started_button">
+ <property name="label" translatable="yes">Get Started</property>
+ <property name="action-name">session.show-content</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="suggested-action"/>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
+
diff --git a/data/resources/ui/verification-emoji.ui b/data/resources/ui/verification-emoji.ui
new file mode 100644
index 00000000..42ed5752
--- /dev/null
+++ b/data/resources/ui/verification-emoji.ui
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="VerificationEmoji" parent="AdwBin">
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">6</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="emoji">
+ <style>
+ <class name="emoji"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="emoji_name">
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
+
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 04cb0556..99a2c5fe 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -29,6 +29,7 @@ data/resources/ui/login.ui
data/resources/ui/in-app-notification.ui
data/resources/ui/room-creation.ui
data/resources/ui/session.ui
+data/resources/ui/session-verification.ui
data/resources/ui/shortcuts.ui
data/resources/ui/sidebar-category-row.ui
data/resources/ui/sidebar-entry-row.ui
@@ -38,6 +39,7 @@ data/resources/ui/sidebar.ui
data/resources/ui/spinner-button.ui
data/resources/ui/pill.ui
data/resources/ui/user-entry-row.ui
+data/resources/ui/verification-emoji.ui
data/resources/ui/window.ui
# Rust files
@@ -52,6 +54,8 @@ src/components/in_app_notification.rs
src/components/mod.rs
src/components/spinner_button.rs
src/components/pill.rs
+src/contrib/mod.rs
+src/contrib/qr_code.rs
src/error.rs
src/login.rs
src/main.rs
@@ -94,6 +98,11 @@ src/session/sidebar/mod.rs
src/session/sidebar/room_row.rs
src/session/sidebar/row.rs
src/session/sidebar/selection.rs
+src/session/verification/emoji.rs
+src/session/verification/identity_verification.rs
+src/session/verification/mod.rs
+src/session/verification/session_verification.rs
+src/session/verification/to_device_handler.rs
src/session/user.rs
src/utils.rs
src/window.rs
diff --git a/src/application.rs b/src/application.rs
index 66195760..8374cae4 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -125,7 +125,7 @@ impl Application {
self,
"new-login",
clone!(@weak self as app => move |_, _| {
- app.get_main_window().switch_to_login_page();
+ app.get_main_window().switch_to_login_page(true);
})
);
diff --git a/src/contrib/mod.rs b/src/contrib/mod.rs
new file mode 100644
index 00000000..d5fc7251
--- /dev/null
+++ b/src/contrib/mod.rs
@@ -0,0 +1,3 @@
+mod qr_code;
+
+pub use self::qr_code::{QRCode, QRCodeExt};
diff --git a/src/contrib/qr_code.rs b/src/contrib/qr_code.rs
new file mode 100644
index 00000000..44cbde8c
--- /dev/null
+++ b/src/contrib/qr_code.rs
@@ -0,0 +1,313 @@
+// Taken from https://gitlab.gnome.org/msandova/trinket/-/blob/master/src/qr_code.rs
+// All credit goes to Maximiliano
+use gtk::glib;
+use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+
+use std::convert::TryFrom;
+
+pub(crate) mod imp {
+ use super::*;
+
+ use gtk::{gdk, graphene};
+
+ use once_cell::sync::Lazy;
+ use std::cell::{Cell, RefCell};
+
+ #[derive(Debug, Default)]
+ pub struct QRCode {
+ pub picture: gtk::Picture,
+ pub data: RefCell<QRCodeData>,
+ pub block_size: Cell<u32>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for QRCode {
+ const NAME: &'static str = "TriQRCode";
+ type Type = super::QRCode;
+ type ParentType = gtk::Widget;
+
+ fn new() -> Self {
+ Self {
+ block_size: Cell::new(8),
+ ..Self::default()
+ }
+ }
+ }
+
+ impl ObjectImpl for QRCode {
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+ obj.add_css_class("qrcode");
+ }
+
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![glib::ParamSpec::new_uint(
+ "block-size",
+ "block-size",
+ "block-size",
+ 1,
+ u32::MAX,
+ 8,
+ glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+ )]
+ });
+ PROPERTIES.as_ref()
+ }
+
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "block-size" => obj.block_size().to_value(),
+ _ => unreachable!(),
+ }
+ }
+
+ fn set_property(
+ &self,
+ obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "block-size" => obj.set_block_size(value.get().unwrap()),
+ _ => unreachable!(),
+ }
+ }
+ }
+ impl WidgetImpl for QRCode {
+ fn snapshot(&self, widget: &Self::Type, snapshot: >k::Snapshot) {
+ let square_width = widget.width() as f32 / self.data.borrow().width as f32;
+ let square_height = widget.height() as f32 / self.data.borrow().height as f32;
+
+ self.data
+ .borrow()
+ .items
+ .iter()
+ .enumerate()
+ .for_each(|(y, line)| {
+ line.iter().enumerate().for_each(|(x, is_dark)| {
+ let color = if *is_dark {
+ widget.style_context().color()
+ } else {
+ widget
+ .style_context()
+ .lookup_color("background")
+ .unwrap_or(gdk::RGBA {
+ red: 0.0,
+ blue: 0.0,
+ green: 0.0,
+ alpha: 0.0,
+ })
+ };
+ let position = graphene::Rect::new(
+ (x as f32) * square_width,
+ (y as f32) * square_height,
+ square_width,
+ square_height,
+ );
+
+ snapshot.append_color(&color, &position);
+ });
+ });
+ }
+
+ fn measure(
+ &self,
+ widget: &Self::Type,
+ orientation: gtk::Orientation,
+ for_size: i32,
+ ) -> (i32, i32, i32, i32) {
+ let self_ = imp::QRCode::from_instance(widget);
+
+ let stride = widget.block_size() as i32;
+
+ let minimum = match orientation {
+ gtk::Orientation::Horizontal => self_.data.borrow().width * stride,
+ gtk::Orientation::Vertical => self_.data.borrow().height * stride,
+ _ => unreachable!(),
+ };
+ let natural = std::cmp::max(for_size, minimum);
+ (minimum, natural, -1, -1)
+ }
+ }
+}
+
+glib::wrapper! {
+ /// A widget that display a QR Code.
+ ///
+ /// The QR code of [`QRCode`] is set with the [QRCodeExt::set_bytes()]
+ /// method. It is recommended for a QR Code to have a quiet zone, i.e. a margin of
+ /// four times the value of [`QRCodeExt::block_size()`], in most contexts, widgets
+ /// already count with such a margin.
+ ///
+ /// The code can be themed via css, where a recommended quiet-zone
+ /// can be as a padding:
+ ///
+ /// ```css
+ /// .qrcode {
+ /// color: black;
+ /// background: white;
+ /// padding: 24px; /* 4 ⨉ block-size */
+ /// }
+ /// ```
+ ///
+ /// **Implements**: [QRCodeExt].
+ pub struct QRCode(ObjectSubclass<imp::QRCode>)
+ @extends gtk::Widget;
+}
+
+impl Default for QRCode {
+ fn default() -> Self {
+ glib::Object::new(&[]).unwrap()
+ }
+}
+
+impl QRCode {
+ /// Creates a new [`QRCode`].
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Creates a new [`QRCode`] with a QR code generated from `bytes`.
+ pub fn from_bytes(bytes: &[u8]) -> Self {
+ let qrcode = Self::default();
+ qrcode.set_bytes(bytes);
+
+ qrcode
+ }
+}
+
+pub trait QRCodeExt {
+ /// Sets the displayed code of `self` to a QR code generated from `bytes`.
+ fn set_bytes(&self, bytes: &[u8]);
+
+ /// Gets the block size `self`. This determines the size of the the widget.
+ fn block_size(&self) -> u32;
+
+ /// Sets the block size `self`.
+ fn set_block_size(&self, block_size: u32);
+
+ fn connect_block_size_notify<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId;
+
+ /// Set the `QrCode` to be displayed
+ fn set_qrcode(&self, qrcode: qrcode::QrCode);
+}
+
+impl<W: IsA<QRCode>> QRCodeExt for W {
+ fn set_bytes(&self, bytes: &[u8]) {
+ let this = imp::QRCode::from_instance(self.as_ref());
+
+ let data = QRCodeData::try_from(bytes).unwrap_or_else(|_| {
+ glib::g_warning!(None, "Failed to load QRCode from bytes");
+ Default::default()
+ });
+ this.data.replace(data);
+
+ self.as_ref().queue_draw();
+ self.as_ref().queue_resize();
+ }
+
+ fn set_qrcode(&self, qrcode: qrcode::QrCode) {
+ let this = imp::QRCode::from_instance(self.as_ref());
+
+ this.data.replace(QRCodeData::from(qrcode));
+
+ self.as_ref().queue_draw();
+ self.as_ref().queue_resize();
+ }
+
+ fn block_size(&self) -> u32 {
+ let this = imp::QRCode::from_instance(self.as_ref());
+
+ this.block_size.get()
+ }
+
+ fn set_block_size(&self, block_size: u32) {
+ let this = imp::QRCode::from_instance(self.as_ref());
+
+ this.block_size.set(std::cmp::max(block_size, 1));
+ self.notify("block-size");
+ self.as_ref().queue_draw();
+ self.as_ref().queue_resize();
+ }
+
+ fn connect_block_size_notify<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
+ self.connect_notify_local(Some("block-size"), move |this, _| {
+ f(this);
+ })
+ }
+}
+
+impl Default for QRCodeData {
+ fn default() -> Self {
+ Self::try_from("".as_bytes()).unwrap()
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct QRCodeData {
+ pub width: i32,
+ pub height: i32,
+ pub items: Vec<Vec<bool>>,
+}
+
+impl TryFrom<&[u8]> for QRCodeData {
+ type Error = qrcode::types::QrError;
+
+ fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
+ let code = qrcode::QrCode::new(data)?;
+ let items = code
+ .render::<char>()
+ .quiet_zone(false)
+ .module_dimensions(1, 1)
+ .build()
+ .split('\n')
+ .into_iter()
+ .map(|line| {
+ line.chars()
+ .into_iter()
+ .map(|c| !c.is_whitespace())
+ .collect::<Vec<bool>>()
+ })
+ .collect::<Vec<Vec<bool>>>();
+
+ let height = items.len() as i32;
+ let width = items.len() as i32;
+ let data = Self {
+ width,
+ height,
+ items,
+ };
+
+ Ok(data)
+ }
+}
+
+impl From<qrcode::QrCode> for QRCodeData {
+ fn from(code: qrcode::QrCode) -> Self {
+ let items = code
+ .render::<char>()
+ .quiet_zone(false)
+ .module_dimensions(1, 1)
+ .build()
+ .split('\n')
+ .into_iter()
+ .map(|line| {
+ line.chars()
+ .into_iter()
+ .map(|c| !c.is_whitespace())
+ .collect::<Vec<bool>>()
+ })
+ .collect::<Vec<Vec<bool>>>();
+
+ let height = items.len() as i32;
+ let width = items.len() as i32;
+ Self {
+ width,
+ height,
+ items,
+ }
+ }
+}
diff --git a/src/login.rs b/src/login.rs
index 30f1edb1..2e4e228f 100644
--- a/src/login.rs
+++ b/src/login.rs
@@ -10,6 +10,7 @@ use url::{ParseError, Url};
mod imp {
use super::*;
use glib::subclass::{InitializingObject, Signal};
+ use glib::SignalHandlerId;
use once_cell::sync::Lazy;
use std::cell::RefCell;
@@ -35,6 +36,9 @@ mod imp {
pub password_entry: TemplateChild<gtk::PasswordEntry>,
#[template_child]
pub back_to_session_button: TemplateChild<gtk::Button>,
+ pub prepered_source_id: RefCell<Option<SignalHandlerId>>,
+ pub logged_out_source_id: RefCell<Option<SignalHandlerId>>,
+ pub ready_source_id: RefCell<Option<SignalHandlerId>>,
}
#[glib::object_subclass]
@@ -124,7 +128,6 @@ impl Login {
self.freeze();
let session = Session::new();
-
self.set_handler_for_prepared_session(&session);
session.login_with_password(
@@ -132,16 +135,16 @@ impl Login {
username,
password,
);
-
priv_.current_session.replace(Some(session));
}
- fn clean(&self) {
+ pub fn clean(&self) {
let priv_ = imp::Login::from_instance(self);
priv_.homeserver_entry.set_text("");
priv_.username_entry.set_text("");
priv_.password_entry.set_text("");
self.unfreeze();
+ self.drop_session_reference();
}
fn freeze(&self) {
@@ -180,7 +183,17 @@ impl Login {
fn drop_session_reference(&self) {
let priv_ = imp::Login::from_instance(self);
- priv_.current_session.take();
+ if let Some(session) = priv_.current_session.take() {
+ if let Some(id) = priv_.prepered_source_id.take() {
+ session.disconnect(id);
+ }
+ if let Some(id) = priv_.logged_out_source_id.take() {
+ session.disconnect(id);
+ }
+ if let Some(id) = priv_.ready_source_id.take() {
+ session.disconnect(id);
+ }
+ }
}
pub fn default_widget(&self) -> gtk::Widget {
@@ -194,20 +207,39 @@ impl Login {
}
fn set_handler_for_prepared_session(&self, session: &Session) {
- session.connect_prepared(clone!(@weak self as login => move |session, error| {
- match error {
- Some(e) => {
- login.parent_window().append_error(&e);
+ let priv_ = imp::Login::from_instance(self);
+ priv_
+ .prepered_source_id
+ .replace(Some(session.connect_prepared(
+ clone!(@weak self as login => move |session, error| {
+ match error {
+ Some(e) => {
+ login.parent_window().append_error(&e);
+ login.unfreeze();
+ },
+ None => {
+ debug!("A new session was prepared");
+ login.emit_by_name("new-session", &[&session]).unwrap();
+ }
+ }
+ }),
+ )));
+
+ priv_.ready_source_id.replace(Some(session.connect_ready(
+ clone!(@weak self as login => move |_| {
+ login.clean();
+ }),
+ )));
+
+ priv_
+ .logged_out_source_id
+ .replace(Some(session.connect_logged_out(
+ clone!(@weak self as login => move |_| {
+ login.parent_window().switch_to_login_page(false);
+ login.drop_session_reference();
login.unfreeze();
- },
- None => {
- debug!("A new session was prepared");
- login.emit_by_name("new-session", &[&session]).unwrap();
- login.clean();
- }
- }
- login.drop_session_reference();
- }));
+ }),
+ )));
}
fn parent_window(&self) -> crate::Window {
diff --git a/src/main.rs b/src/main.rs
index d717dc1c..e50ec1c4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,6 +8,7 @@ mod config;
mod prelude;
mod components;
+mod contrib;
mod error;
mod login;
mod matrix_error;
diff --git a/src/meson.build b/src/meson.build
index 2e74b35f..20ba3b0b 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -20,6 +20,7 @@ run_command(
sources = files(
'application.rs',
+ 'contrib/qr_code.rs',
'components/avatar.rs',
'components/auth_dialog.rs',
'components/context_menu_bin.rs',
@@ -83,6 +84,11 @@ sources = files(
'session/sidebar/account_switcher/item.rs',
'session/sidebar/account_switcher/mod.rs',
'session/sidebar/account_switcher/user_entry.rs',
+ 'session/verification/mod.rs',
+ 'session/verification/emoji.rs',
+ 'session/verification/identity_verification.rs',
+ 'session/verification/session_verification.rs',
+ 'session/verification/to_device_handler.rs',
)
custom_target(
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 25a72d24..3e6d01cf 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -7,6 +7,7 @@ mod room_creation;
mod room_list;
mod sidebar;
mod user;
+mod verification;
use self::account_settings::AccountSettings;
pub use self::avatar::Avatar;
@@ -16,6 +17,7 @@ pub use self::room_creation::RoomCreation;
use self::room_list::RoomList;
use self::sidebar::Sidebar;
pub use self::user::{User, UserExt};
+pub use self::verification::{IdentityVerification, SessionVerification, ToDeviceHandler};
use crate::secret;
use crate::secret::StoredSession;
@@ -79,9 +81,11 @@ mod imp {
pub selected_room: RefCell<Option<Room>>,
pub selected_content_type: Cell<ContentType>,
pub is_ready: Cell<bool>,
+ pub logout_on_dispose: Cell<bool>,
pub info: OnceCell<StoredSession>,
pub source_id: RefCell<Option<SourceId>>,
pub sync_tokio_handle: RefCell<Option<JoinHandle<()>>>,
+ pub to_device_handler: OnceCell<ToDeviceHandler>,
}
#[glib::object_subclass]
@@ -98,7 +102,15 @@ mod imp {
});
klass.install_action("session.logout", None, move |session, _, _| {
- session.logout();
+ spawn!(clone!(@weak session => async move {
+ let priv_ = imp::Session::from_instance(&session);
+ priv_.logout_on_dispose.set(false);
+ session.logout().await
+ }));
+ });
+
+ klass.install_action("session.show-content", None, move |session, _, _| {
+ session.show_content();
});
klass.install_action("session.room-creation", None, move |session, _, _| {
@@ -214,13 +226,14 @@ mod imp {
<()>::static_type().into(),
)
.build(),
+ Signal::builder("ready", &[], <()>::static_type().into()).build(),
Signal::builder("logged-out", &[], <()>::static_type().into()).build(),
]
});
SIGNALS.as_ref()
}
- fn dispose(&self, _obj: &Self::Type) {
+ fn dispose(&self, obj: &Self::Type) {
if let Some(source_id) = self.source_id.take() {
let _ = glib::Source::remove(source_id);
}
@@ -228,6 +241,10 @@ mod imp {
if let Some(handle) = self.sync_tokio_handle.take() {
handle.abort();
}
+
+ if self.logout_on_dispose.get() {
+ glib::MainContext::default().block_on(obj.logout());
+ }
}
}
impl WidgetImpl for Session {}
@@ -285,6 +302,8 @@ impl Session {
}
pub fn login_with_password(&self, homeserver: Url, username: String, password: String) {
+ let priv_ = imp::Session::from_instance(self);
+ priv_.logout_on_dispose.set(true);
let mut path = glib::user_data_dir();
path.push(
&Uuid::new_v4()
@@ -361,6 +380,7 @@ impl Session {
.await
.map(|_| (client, session))
});
+
spawn!(
glib::PRIORITY_DEFAULT_IDLE,
clone!(@weak self as obj => async move {
@@ -382,6 +402,11 @@ impl Session {
priv_.user.set(user.clone()).unwrap();
self.notify("user");
+ priv_
+ .to_device_handler
+ .set(ToDeviceHandler::new(self))
+ .unwrap();
+
let handle = spawn_tokio!(async move {
let display_name = client.display_name().await?;
let avatar_url = client.avatar_url().await?;
@@ -407,6 +432,7 @@ impl Session {
priv_.info.set(session).unwrap();
self.room_list().load();
+
self.sync();
None
@@ -465,9 +491,63 @@ impl Session {
}
fn mark_ready(&self) {
- let priv_ = &imp::Session::from_instance(self);
- priv_.stack.set_visible_child(&*priv_.content);
+ let priv_ = imp::Session::from_instance(self);
+ let client = self.client();
+ let user_id = self.user().unwrap().user_id().to_owned();
+
priv_.is_ready.set(true);
+
+ let has_cross_signing_keys = spawn_tokio!(async move {
+ if let Some(cross_signing_status) = client.cross_signing_status().await {
+ cross_signing_status.has_master
+ && cross_signing_status.has_self_signing
+ && cross_signing_status.has_user_signing
+ } else {
+ false
+ }
+ });
+
+ let client = self.client();
+ let need_new_identity = spawn_tokio!(async move {
+ // If there is an error just assume we don't need a new identity since
+ // we will try again during the session verification
+ client
+ .get_user_identity(&user_id)
+ .await
+ .map_or(false, |identity| identity.is_none())
+ });
+
+ spawn!(clone!(@weak self as obj => async move {
+ let priv_ = imp::Session::from_instance(&obj);
+ if !has_cross_signing_keys.await.unwrap() {
+ if need_new_identity.await.unwrap() {
+ let client = obj.client();
+ if spawn_tokio!(async move { client.bootstrap_cross_signing(None).await }).await.is_ok()
{
+ priv_.stack.set_visible_child(&*priv_.content);
+ return;
+ }
+ }
+
+ priv_.logout_on_dispose.set(true);
+
+ let verification = IdentityVerification::new(obj.user().unwrap());
+ let session = SessionVerification::new(&verification, &obj);
+ priv_
+ .to_device_handler
+ .get()
+ .unwrap()
+ .add_request(verification);
+ priv_
+ .stack
+ .add_named(&session, Some("session-verification"));
+ priv_.stack.set_visible_child(&session);
+ session.start_verification();
+
+ return;
+ }
+
+ obj.show_content();
+ }));
}
fn is_ready(&self) -> bool {
@@ -543,13 +623,31 @@ impl Session {
.unwrap()
}
+ pub fn connect_ready<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
+ self.connect_local("ready", true, move |values| {
+ let obj = values[0].get::<Self>().unwrap();
+
+ f(&obj);
+
+ None
+ })
+ .unwrap()
+ }
+
fn handle_sync_response(&self, response: Result<SyncResponse, matrix_sdk::Error>) {
+ let priv_ = imp::Session::from_instance(self);
+
match response {
Ok(response) => {
if !self.is_ready() {
self.mark_ready();
}
self.room_list().handle_response_rooms(response.rooms);
+ priv_
+ .to_device_handler
+ .get()
+ .unwrap()
+ .handle_response_to_device(response.to_device);
}
Err(error) => {
if let matrix_sdk::Error::Http(HttpError::ClientApi(FromHttpResponseError::Http(
@@ -592,36 +690,37 @@ impl Session {
window.show();
}
- pub fn logout(&self) {
- let client = self.client();
+ pub async fn logout(&self) {
+ let priv_ = imp::Session::from_instance(self);
+ self.emit_by_name("logged-out", &[]).unwrap();
+
+ debug!("The session is about to be logout");
+ // First stop the verification in progress
+ if let Some(session_verificiation) = priv_.stack.child_by_name("session-verification") {
+ priv_.stack.remove(&session_verificiation);
+ }
+
+ let client = self.client();
let handle = spawn_tokio!(async move {
let request = logout::Request::new();
client.send(request, None).await
});
- spawn!(
- glib::PRIORITY_DEFAULT_IDLE,
- clone!(@weak self as obj => async move {
- match handle.await.unwrap() {
- Ok(_) => obj.cleanup_session(),
- Err(error) => {
- error!("Couldn’t logout the session {}", error);
- let error = Error::new(
- clone!(@weak obj => @default-return None, move |_| {
- let label = gtk::Label::new(Some(&gettext("Failed to logout the
session.")));
-
- Some(label.upcast())
- }),
- );
-
- if let Some(window) = obj.parent_window() {
- window.append_error(&error);
- }
- }
+ match handle.await.unwrap() {
+ Ok(_) => self.cleanup_session(),
+ Err(error) => {
+ error!("Couldn’t logout the session {}", error);
+ let error = Error::new(move |_| {
+ let label = gtk::Label::new(Some(&gettext("Failed to logout the session.")));
+ Some(label.upcast())
+ });
+
+ if let Some(window) = self.parent_window() {
+ window.append_error(&error);
}
- })
- );
+ }
+ }
}
fn cleanup_session(&self) {
@@ -649,7 +748,22 @@ impl Session {
error!("Failed to remove database after logout: {}", error);
}
- self.emit_by_name("logged-out", &[]).unwrap();
+ debug!("The logged out session was cleaned up");
+ }
+
+ /// Show the content of the session
+ pub fn show_content(&self) {
+ let priv_ = imp::Session::from_instance(self);
+
+ // FIXME: we should actually check if we have now the keys
+ priv_.stack.set_visible_child(&*priv_.content);
+ priv_.logout_on_dispose.set(false);
+
+ if let Some(session_verificiation) = priv_.stack.child_by_name("session-verification") {
+ priv_.stack.remove(&session_verificiation);
+ }
+
+ self.emit_by_name("ready", &[]).unwrap();
}
}
diff --git a/src/session/verification/emoji.rs b/src/session/verification/emoji.rs
new file mode 100644
index 00000000..4505c688
--- /dev/null
+++ b/src/session/verification/emoji.rs
@@ -0,0 +1,58 @@
+use adw::subclass::prelude::*;
+
+use gtk::{glib, prelude::*, subclass::prelude::*, CompositeTemplate};
+
+mod imp {
+ use super::*;
+ use glib::subclass::InitializingObject;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/verification-emoji.ui")]
+ pub struct Emoji {
+ #[template_child]
+ pub emoji: TemplateChild<gtk::Label>,
+ #[template_child]
+ pub emoji_name: TemplateChild<gtk::Label>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for Emoji {
+ const NAME: &'static str = "VerificationEmoji";
+ type Type = super::Emoji;
+ type ParentType = adw::Bin;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &InitializingObject<Self>) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for Emoji {}
+ impl WidgetImpl for Emoji {}
+ impl BinImpl for Emoji {}
+}
+
+glib::wrapper! {
+ /// Preference Window to display and update room details.
+ pub struct Emoji(ObjectSubclass<imp::Emoji>)
+ @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl Emoji {
+ pub fn new(emoji: (&str, &str)) -> Self {
+ let obj: Self = glib::Object::new(&[]).expect("Failed to create Emoji");
+
+ obj.set_emoji(emoji);
+ obj
+ }
+
+ pub fn set_emoji(&self, emoji: (&str, &str)) {
+ let priv_ = imp::Emoji::from_instance(self);
+
+ priv_.emoji.set_text(emoji.0);
+ priv_.emoji_name.set_text(emoji.1);
+ }
+}
diff --git a/src/session/verification/identity_verification.rs
b/src/session/verification/identity_verification.rs
new file mode 100644
index 00000000..a3da69e5
--- /dev/null
+++ b/src/session/verification/identity_verification.rs
@@ -0,0 +1,548 @@
+use crate::session::user::UserExt;
+use crate::session::User;
+use crate::spawn_tokio;
+use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
+use log::error;
+use matrix_sdk::{
+ encryption::{
+ identities::RequestVerificationError,
+ verification::{
+ CancelInfo, QrVerification, SasVerification, Verification as MatrixVerification,
+ VerificationRequest,
+ },
+ },
+ ruma::{
+ api::client::r0::sync::sync_events::ToDevice, events::AnyToDeviceEvent, identifiers::UserId,
+ },
+ Client, Error as MatrixError,
+};
+use qrcode::QrCode;
+use tokio::sync::mpsc;
+
+#[derive(Debug, Eq, PartialEq, Clone, Copy)]
+pub enum State {
+ Request,
+ Ready,
+ Start,
+ Cancel,
+ Accept,
+ Key,
+ Mac,
+ Done,
+}
+
+impl Default for State {
+ fn default() -> Self {
+ Self::Request
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum Verification {
+ SasV1(SasVerification),
+ QrV1(QrVerification),
+ Request(VerificationRequest),
+}
+
+impl Verification {
+ fn cancel_info(&self) -> Option<CancelInfo> {
+ match self {
+ Verification::QrV1(verification) => verification.cancel_info(),
+ Verification::SasV1(verification) => verification.cancel_info(),
+ Verification::Request(verification) => verification.cancel_info(),
+ }
+ }
+}
+
+#[derive(Debug, Eq, PartialEq, Clone, Copy, glib::GEnum)]
+#[repr(u32)]
+#[genum(type_name = "VerificationMode")]
+pub enum Mode {
+ IdentityNotFound,
+ Unavailable,
+ Requested,
+ SasV1,
+ QrV1,
+ Completed,
+ Cancelled,
+}
+
+impl Default for Mode {
+ fn default() -> Self {
+ Self::Unavailable
+ }
+}
+
+#[derive(Debug, Eq, PartialEq, Clone, Copy)]
+pub enum UserAction {
+ Match,
+ NotMatch,
+ Cancel,
+ StartSas,
+}
+
+#[derive(Debug, Eq, PartialEq)]
+pub enum Message {
+ UserAction(UserAction),
+ Sync((String, State)),
+}
+
+mod imp {
+ use super::*;
+ use glib::object::WeakRef;
+ use glib::source::SourceId;
+ use once_cell::{sync::Lazy, unsync::OnceCell};
+ use std::cell::{Cell, RefCell};
+
+ #[derive(Debug, Default)]
+ pub struct IdentityVerification {
+ pub user: OnceCell<WeakRef<User>>,
+ pub mode: Cell<Mode>,
+ pub sync_sender: RefCell<Option<mpsc::Sender<Message>>>,
+ pub main_sender: OnceCell<glib::SyncSender<Verification>>,
+ pub request: RefCell<Option<Verification>>,
+ pub source_id: RefCell<Option<SourceId>>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for IdentityVerification {
+ const NAME: &'static str = "IdentityVerification";
+ type Type = super::IdentityVerification;
+ type ParentType = glib::Object;
+ }
+
+ impl ObjectImpl for IdentityVerification {
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![
+ glib::ParamSpec::new_object(
+ "user",
+ "User",
+ "The user to be verified",
+ User::static_type(),
+ glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+ ),
+ glib::ParamSpec::new_enum(
+ "mode",
+ "Mode",
+ "The verification mode used",
+ Mode::static_type(),
+ Mode::default() as i32,
+ glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
+ ),
+ ]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn set_property(
+ &self,
+ obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "user" => obj.set_user(value.get().unwrap()),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "user" => obj.user().to_value(),
+ "mode" => obj.mode().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+
+ let (main_sender, main_receiver) =
+ glib::MainContext::sync_channel::<Verification>(Default::default(), 100);
+
+ let source_id = main_receiver.attach(
+ None,
+ clone!(@weak obj => @default-return glib::Continue(false), move |verification| {
+ let mode = match verification {
+ Verification::QrV1(_) => Mode::QrV1,
+ Verification::SasV1(_) => Mode::SasV1,
+ Verification::Request(_) => Mode::Requested,
+ };
+ obj.set_request(Some(verification));
+ obj.set_mode(mode);
+
+ glib::Continue(true)
+ }),
+ );
+
+ self.main_sender.set(main_sender).unwrap();
+ self.source_id.replace(Some(source_id));
+ }
+
+ fn dispose(&self, obj: &Self::Type) {
+ obj.cancel();
+ if let Some(source_id) = self.source_id.take() {
+ let _ = glib::Source::remove(source_id);
+ }
+ }
+ }
+}
+
+glib::wrapper! {
+ pub struct IdentityVerification(ObjectSubclass<imp::IdentityVerification>);
+}
+
+impl IdentityVerification {
+ pub fn new(user: &User) -> Self {
+ glib::Object::new(&[("user", user)]).expect("Failed to create IdentityVerification")
+ }
+
+ pub fn user(&self) -> User {
+ let priv_ = imp::IdentityVerification::from_instance(self);
+ priv_.user.get().unwrap().upgrade().unwrap()
+ }
+
+ fn set_user(&self, user: User) {
+ let priv_ = imp::IdentityVerification::from_instance(self);
+ priv_.user.set(user.downgrade()).unwrap()
+ }
+
+ /// Start an interactive identity verification
+ /// Already in progress verifications are cancelled before starting a new one
+ pub async fn start(&self) -> Result<(), RequestVerificationError> {
+ let priv_ = imp::IdentityVerification::from_instance(self);
+ let user = self.user();
+ let client = user.session().client();
+ let user_id = user.user_id().clone();
+ let main_sender = priv_.main_sender.get().unwrap().clone();
+
+ self.set_request(None);
+ // TODO cancel any other request in progress
+
+ let (sync_sender, sync_receiver) = mpsc::channel(100);
+ priv_.sync_sender.replace(Some(sync_sender));
+
+ // TODO add timeout
+
+ let result =
+ spawn_tokio!(async move { start(client, user_id, main_sender, sync_receiver).await })
+ .await
+ .unwrap()?;
+
+ priv_.sync_sender.take();
+
+ self.set_mode(result);
+ Ok(())
+ }
+
+ pub fn emoji_match(&self) {
+ let priv_ = imp::IdentityVerification::from_instance(self);
+
+ if let Some(sync_sender) = &*priv_.sync_sender.borrow() {
+ let result = sync_sender.try_send(Message::UserAction(UserAction::Match));
+
+ if let Err(error) = result {
+ error!("Failed to send message to tokio runtime: {}", error);
+ }
+ }
+ }
+
+ pub fn emoji_not_match(&self) {
+ let priv_ = imp::IdentityVerification::from_instance(self);
+
+ if let Some(sync_sender) = &*priv_.sync_sender.borrow() {
+ let result = sync_sender.try_send(Message::UserAction(UserAction::NotMatch));
+ if let Err(error) = result {
+ error!("Failed to send message to tokio runtime: {}", error);
+ }
+ }
+ }
+
+ pub fn mode(&self) -> Mode {
+ let priv_ = imp::IdentityVerification::from_instance(self);
+ priv_.mode.get()
+ }
+
+ fn set_mode(&self, mode: Mode) {
+ let priv_ = imp::IdentityVerification::from_instance(self);
+
+ if self.mode() == mode {
+ return;
+ }
+
+ priv_.mode.set(mode);
+ self.notify("mode");
+ }
+
+ fn set_request(&self, request: Option<Verification>) {
+ let priv_ = imp::IdentityVerification::from_instance(self);
+
+ priv_.request.replace(request);
+ }
+
+ /// Get the QrCode for this verification request
+ ///
+ /// This is only set once the request reached the `State::Ready`
+ /// and if QrCode verification is possible
+ pub fn qr_code(&self) -> Option<QrCode> {
+ let priv_ = imp::IdentityVerification::from_instance(self);
+
+ match &*priv_.request.borrow() {
+ Some(Verification::QrV1(qr_verification)) => qr_verification.to_qr_code().ok(),
+ _ => None,
+ }
+ }
+
+ /// Get the Emojis for this verification request
+ ///
+ /// This is only set once the request reached the `State::Ready`
+ /// and if a Sas verification was started
+ pub fn emoji(&self) -> Option<[(&'static str, &'static str); 7]> {
+ let priv_ = imp::IdentityVerification::from_instance(self);
+
+ match &*priv_.request.borrow() {
+ Some(Verification::SasV1(qr_verification)) => qr_verification.emoji(),
+ _ => None,
+ }
+ }
+
+ pub fn start_sas(&self) {
+ let priv_ = imp::IdentityVerification::from_instance(self);
+
+ if let Some(sync_sender) = &*priv_.sync_sender.borrow() {
+ let result = sync_sender.try_send(Message::UserAction(UserAction::StartSas));
+
+ if let Err(error) = result {
+ error!("Failed to send message to tokio runtime: {}", error);
+ }
+ }
+ }
+
+ pub fn cancel(&self) {
+ let priv_ = imp::IdentityVerification::from_instance(self);
+ if let Some(sync_sender) = &*priv_.sync_sender.borrow() {
+ let result = sync_sender.try_send(Message::UserAction(UserAction::Cancel));
+ if let Err(error) = result {
+ error!("Failed to send message to tokio runtime: {}", error);
+ }
+ }
+ }
+
+ /// Get information about why the request was cancelled
+ pub fn cancel_info(&self) -> Option<CancelInfo> {
+ let priv_ = imp::IdentityVerification::from_instance(self);
+
+ if let Some(verification) = &*priv_.request.borrow() {
+ verification.cancel_info()
+ } else {
+ None
+ }
+ }
+
+ pub fn handle_response_to_device(&self, to_device: ToDevice) {
+ let priv_ = imp::IdentityVerification::from_instance(self);
+
+ for event in to_device.events.iter().filter_map(|e| e.deserialize().ok()) {
+ let (flow_id, state) = match event {
+ AnyToDeviceEvent::KeyVerificationRequest(e) => {
+ (e.content.transaction_id, State::Request)
+ }
+ AnyToDeviceEvent::KeyVerificationReady(e) => {
+ (e.content.transaction_id, State::Ready)
+ }
+ AnyToDeviceEvent::KeyVerificationStart(e) => {
+ (e.content.transaction_id, State::Start)
+ }
+ AnyToDeviceEvent::KeyVerificationCancel(e) => {
+ (e.content.transaction_id, State::Cancel)
+ }
+ AnyToDeviceEvent::KeyVerificationAccept(e) => {
+ (e.content.transaction_id, State::Accept)
+ }
+ AnyToDeviceEvent::KeyVerificationMac(e) => (e.content.transaction_id, State::Mac),
+ AnyToDeviceEvent::KeyVerificationKey(e) => (e.content.transaction_id, State::Key),
+ AnyToDeviceEvent::KeyVerificationDone(e) => (e.content.transaction_id, State::Done),
+ _ => continue,
+ };
+
+ if let Some(sync_sender) = &*priv_.sync_sender.borrow() {
+ let result = sync_sender.try_send(Message::Sync((flow_id, state)));
+ if let Err(error) = result {
+ error!("Failed to send message to tokio runtime: {}", error);
+ }
+ }
+ }
+ }
+}
+
+async fn start(
+ client: Client,
+ user_id: UserId,
+ main_sender: glib::SyncSender<Verification>,
+ mut sync_receiver: mpsc::Receiver<Message>,
+) -> Result<Mode, RequestVerificationError> {
+ let identity = if let Some(identity) = client
+ .get_user_identity(&user_id)
+ .await
+ .map_err(|error| RequestVerificationError::Sdk(MatrixError::CryptoStoreError(error)))?
+ {
+ identity
+ } else {
+ return Ok(Mode::IdentityNotFound);
+ };
+
+ let request = identity.request_verification().await?;
+ let flow_id = request.flow_id();
+
+ let result = main_sender.send(Verification::Request(request.clone()));
+
+ if let Err(error) = result {
+ error!("Failed to send message to the main context: {}", error);
+ }
+
+ if wait_for_state(flow_id, State::Ready, &mut sync_receiver).await {
+ request.cancel().await?;
+ return Ok(Mode::Cancelled);
+ }
+
+ let qr_verification = request
+ .generate_qr_code()
+ .await
+ .map_err(|error| RequestVerificationError::Sdk(error))?;
+
+ let start_sas = if let Some(qr_verification) = qr_verification {
+ let result = main_sender.send(Verification::QrV1(qr_verification));
+
+ if let Err(error) = result {
+ error!("Failed to send message to the main context: {}", error);
+ }
+
+ let (start_sas, cancel) = loop {
+ match sync_receiver.recv().await.unwrap() {
+ Message::Sync((id, State::Start)) if flow_id == &id => break (false, false),
+ Message::Sync((id, State::Cancel)) if flow_id == &id => break (false, true),
+ Message::UserAction(UserAction::Cancel) => break (false, true),
+ Message::UserAction(UserAction::StartSas) => break (true, false),
+ _ => {}
+ }
+ };
+
+ if cancel {
+ request.cancel().await?;
+ return Ok(Mode::Cancelled);
+ }
+ start_sas
+ } else {
+ true
+ };
+
+ if start_sas {
+ if request
+ .start_sas()
+ .await
+ .map_err(|error| RequestVerificationError::Sdk(error))?
+ .is_some()
+ {
+ let cancel = loop {
+ match sync_receiver.recv().await {
+ Some(Message::Sync((id, State::Start))) if flow_id == &id => break false,
+ Some(Message::Sync((id, State::Accept))) if flow_id == &id => break false,
+ Some(Message::Sync((id, State::Cancel))) if flow_id == &id => break true,
+ Some(Message::UserAction(UserAction::Cancel)) => break true,
+ None => break true,
+ _ => {}
+ }
+ };
+
+ if cancel {
+ request.cancel().await?;
+ return Ok(Mode::Cancelled);
+ }
+ } else {
+ return Ok(Mode::Unavailable);
+ }
+ }
+
+ // Get the verification struct from the sdk, this way we are sure we get the correct type
+ let verification = if let Some(verification) = client.get_verification(&user_id, &flow_id).await
+ {
+ verification
+ } else {
+ return Ok(Mode::Unavailable);
+ };
+
+ match verification {
+ MatrixVerification::QrV1(qr_verification) => {
+ qr_verification.confirm().await?;
+
+ if wait_for_state(flow_id, State::Done, &mut sync_receiver).await {
+ request.cancel().await?;
+ return Ok(Mode::Cancelled);
+ }
+ }
+ MatrixVerification::SasV1(sas_verification) => {
+ sas_verification.accept().await?;
+
+ if wait_for_state(flow_id, State::Key, &mut sync_receiver).await {
+ request.cancel().await?;
+ return Ok(Mode::Cancelled);
+ }
+
+ let result = main_sender.send(Verification::SasV1(sas_verification.clone()));
+
+ if let Err(error) = result {
+ error!("Failed to send message to the main context: {}", error);
+ }
+
+ if wait_for_match_action(flow_id, &mut sync_receiver).await {
+ request.cancel().await?;
+ return Ok(Mode::Cancelled);
+ }
+
+ sas_verification.confirm().await?;
+
+ if wait_for_state(flow_id, State::Done, &mut sync_receiver).await {
+ request.cancel().await?;
+ return Ok(Mode::Cancelled);
+ }
+ }
+ }
+
+ Ok(Mode::Completed)
+}
+
+async fn wait_for_state(
+ flow_id: &str,
+ expected_state: State,
+ sync_receiver: &mut mpsc::Receiver<Message>,
+) -> bool {
+ loop {
+ match sync_receiver.recv().await {
+ Some(Message::Sync((id, State::Cancel))) if flow_id == &id => return true,
+ Some(Message::Sync((id, state))) if flow_id == &id && expected_state == state => break,
+ Some(Message::UserAction(UserAction::Cancel)) => return true,
+ None => return true,
+ _ => {}
+ }
+ }
+
+ false
+}
+
+async fn wait_for_match_action(flow_id: &str, sync_receiver: &mut mpsc::Receiver<Message>) -> bool {
+ loop {
+ match sync_receiver.recv().await {
+ Some(Message::Sync((id, State::Cancel))) if flow_id == &id => return true,
+ Some(Message::UserAction(UserAction::Match)) => break,
+ Some(Message::UserAction(UserAction::NotMatch)) => return true,
+ Some(Message::UserAction(UserAction::Cancel)) => return true,
+ None => return true,
+ _ => {}
+ }
+ }
+
+ false
+}
diff --git a/src/session/verification/mod.rs b/src/session/verification/mod.rs
new file mode 100644
index 00000000..ba2e3074
--- /dev/null
+++ b/src/session/verification/mod.rs
@@ -0,0 +1,9 @@
+mod emoji;
+mod identity_verification;
+mod session_verification;
+mod to_device_handler;
+
+pub use self::emoji::Emoji;
+pub use self::identity_verification::{IdentityVerification, Mode as VerificationMode};
+pub use self::session_verification::SessionVerification;
+pub use self::to_device_handler::ToDeviceHandler;
diff --git a/src/session/verification/session_verification.rs
b/src/session/verification/session_verification.rs
new file mode 100644
index 00000000..88986c62
--- /dev/null
+++ b/src/session/verification/session_verification.rs
@@ -0,0 +1,424 @@
+use adw::subclass::prelude::*;
+use gettextrs::gettext;
+use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
+use log::{debug, error, warn};
+
+use crate::components::{AuthDialog, SpinnerButton};
+use crate::contrib::QRCode;
+use crate::contrib::QRCodeExt;
+use crate::session::verification::{Emoji, IdentityVerification, VerificationMode};
+use crate::session::Session;
+use crate::spawn;
+use crate::Error;
+use crate::Window;
+use matrix_sdk::ruma::events::key::verification::cancel::CancelCode;
+
+mod imp {
+ use super::*;
+ use glib::object::WeakRef;
+ use glib::subclass::InitializingObject;
+ use glib::SignalHandlerId;
+ use once_cell::unsync::OnceCell;
+ use std::cell::RefCell;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/session-verification.ui")]
+ pub struct SessionVerification {
+ pub request: OnceCell<WeakRef<IdentityVerification>>,
+ pub session: OnceCell<WeakRef<Session>>,
+ #[template_child]
+ pub bootstrap_button: TemplateChild<SpinnerButton>,
+ #[template_child]
+ pub qrcode: TemplateChild<QRCode>,
+ #[template_child]
+ pub emoji_row_1: TemplateChild<gtk::Box>,
+ #[template_child]
+ pub emoji_row_2: TemplateChild<gtk::Box>,
+ #[template_child]
+ pub emoji_match_btn: TemplateChild<SpinnerButton>,
+ #[template_child]
+ pub emoji_not_match_btn: TemplateChild<SpinnerButton>,
+ #[template_child]
+ pub start_emoji_btn: TemplateChild<SpinnerButton>,
+ #[template_child]
+ pub main_stack: TemplateChild<gtk::Stack>,
+ pub mode_handler: RefCell<Option<SignalHandlerId>>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for SessionVerification {
+ const NAME: &'static str = "SessionVerification";
+ type Type = super::SessionVerification;
+ type ParentType = adw::Bin;
+
+ fn class_init(klass: &mut Self::Class) {
+ SpinnerButton::static_type();
+ QRCode::static_type();
+ Emoji::static_type();
+ Self::bind_template(klass);
+
+ klass.install_action("verification.show-recovery", None, move |obj, _, _| {
+ obj.show_recovery();
+ });
+
+ klass.install_action("verification.previous", None, move |obj, _, _| {
+ obj.previous();
+ });
+ }
+
+ fn instance_init(obj: &InitializingObject<Self>) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for SessionVerification {
+ fn properties() -> &'static [glib::ParamSpec] {
+ use once_cell::sync::Lazy;
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![
+ glib::ParamSpec::new_object(
+ "request",
+ "Request",
+ "The Object holding the data for the verification",
+ IdentityVerification::static_type(),
+ glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+ ),
+ glib::ParamSpec::new_object(
+ "session",
+ "Session",
+ "The current Session",
+ Session::static_type(),
+ glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+ ),
+ ]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn set_property(
+ &self,
+ obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "request" => obj.set_request(value.get().unwrap()),
+ "session" => obj.set_session(value.get().unwrap()),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "request" => obj.request().to_value(),
+ "session" => obj.session().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+
+ obj.action_set_enabled("verification.show-recovery", false);
+
+ self.emoji_match_btn
+ .connect_clicked(clone!(@weak obj => move |button| {
+ let priv_ = imp::SessionVerification::from_instance(&obj);
+ button.set_loading(true);
+ priv_.emoji_not_match_btn.set_sensitive(false);
+ obj.request().emoji_match();
+ }));
+
+ self.emoji_not_match_btn
+ .connect_clicked(clone!(@weak obj => move |button| {
+ let priv_ = imp::SessionVerification::from_instance(&obj);
+ button.set_loading(true);
+ priv_.emoji_match_btn.set_sensitive(false);
+ obj.request().emoji_not_match();
+ }));
+
+ self.start_emoji_btn
+ .connect_clicked(clone!(@weak obj => move |button| {
+ button.set_loading(true);
+ obj.request().start_sas();
+ }));
+
+ self.bootstrap_button
+ .connect_clicked(clone!(@weak obj => move |button| {
+ button.set_loading(true);
+ obj.bootstrap_cross_signing();
+ }));
+ }
+
+ fn dispose(&self, obj: &Self::Type) {
+ obj.silent_cancel();
+ }
+ }
+
+ impl WidgetImpl for SessionVerification {}
+ impl BinImpl for SessionVerification {}
+}
+
+glib::wrapper! {
+ pub struct SessionVerification(ObjectSubclass<imp::SessionVerification>)
+ @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl SessionVerification {
+ pub fn new(request: &IdentityVerification, session: &Session) -> Self {
+ glib::Object::new(&[("request", request), ("session", session)])
+ .expect("Failed to create SessionVerification")
+ }
+
+ pub fn request(&self) -> IdentityVerification {
+ let priv_ = imp::SessionVerification::from_instance(self);
+ priv_.request.get().unwrap().upgrade().unwrap()
+ }
+
+ fn set_request(&self, request: IdentityVerification) {
+ let priv_ = imp::SessionVerification::from_instance(self);
+
+ priv_.request.set(request.downgrade()).unwrap()
+ }
+
+ pub fn session(&self) -> Session {
+ let priv_ = imp::SessionVerification::from_instance(self);
+ priv_.session.get().unwrap().upgrade().unwrap()
+ }
+
+ fn set_session(&self, session: Session) {
+ let priv_ = imp::SessionVerification::from_instance(self);
+
+ priv_.session.set(session.downgrade()).unwrap()
+ }
+
+ /// Returns the parent GtkWindow containing this widget.
+ fn parent_window(&self) -> Option<Window> {
+ self.root()?.downcast().ok()
+ }
+
+ fn reset(&self) {
+ let priv_ = imp::SessionVerification::from_instance(self);
+ priv_.emoji_not_match_btn.set_loading(false);
+ priv_.emoji_not_match_btn.set_sensitive(true);
+ priv_.emoji_match_btn.set_loading(false);
+ priv_.emoji_match_btn.set_sensitive(true);
+ priv_.start_emoji_btn.set_loading(false);
+ priv_.start_emoji_btn.set_sensitive(true);
+ priv_.bootstrap_button.set_loading(false);
+
+ while let Some(child) = priv_.emoji_row_1.first_child() {
+ priv_.emoji_row_1.remove(&child);
+ }
+
+ while let Some(child) = priv_.emoji_row_2.first_child() {
+ priv_.emoji_row_2.remove(&child);
+ }
+ }
+
+ pub fn start_verification(&self) {
+ let priv_ = imp::SessionVerification::from_instance(self);
+ let request = self.request();
+
+ self.reset();
+
+ let handler = request.connect_notify_local(
+ Some("mode"),
+ clone!(@weak self as obj => move |_, _| {
+ obj.update_view();
+ }),
+ );
+
+ priv_.mode_handler.replace(Some(handler));
+
+ let weak_obj = self.downgrade();
+ spawn!(clone!(@weak request => async move {
+ if let Err(error) = request.start().await {
+ if let Some(obj) = weak_obj.upgrade() {
+ obj.show_error();
+ }
+ error!("Verification failed: {}", error);
+ }
+ }));
+ }
+
+ /// Cancel the verification request without telling the user about it
+ fn silent_cancel(&self) {
+ let priv_ = imp::SessionVerification::from_instance(self);
+
+ if let Some(handler) = priv_.mode_handler.take() {
+ self.request().disconnect(handler);
+ }
+
+ debug!("Verification request was silently canceled");
+
+ self.request().cancel();
+ }
+
+ fn update_view(&self) {
+ let priv_ = imp::SessionVerification::from_instance(self);
+ let request = self.request();
+ match request.mode() {
+ VerificationMode::IdentityNotFound => {
+ priv_.main_stack.set_visible_child_name("bootstrap");
+ }
+ VerificationMode::Requested => {
+ priv_.main_stack.set_visible_child_name("wait-for-device");
+ }
+ VerificationMode::QrV1 => {
+ if let Some(qrcode) = request.qr_code() {
+ priv_.qrcode.set_qrcode(qrcode);
+ priv_.main_stack.set_visible_child_name("qrcode");
+ } else {
+ warn!("Failed to get qrcode for QrVerification");
+ request.start_sas();
+ }
+ }
+ VerificationMode::SasV1 => {
+ // TODO: implement sas fallback when emojis arn't supported
+ if let Some(emoji) = request.emoji() {
+ for (index, emoji) in emoji.iter().enumerate() {
+ if index < 4 {
+ priv_.emoji_row_1.append(&Emoji::new(*emoji));
+ } else {
+ priv_.emoji_row_2.append(&Emoji::new(*emoji));
+ }
+ }
+ priv_.main_stack.set_visible_child_name("emoji");
+ } else {
+ warn!("Failed to get emoji for SasVerification");
+ self.show_error();
+ }
+ }
+ VerificationMode::Unavailable => {
+ self.show_error();
+ }
+ VerificationMode::Completed => {
+ priv_.main_stack.set_visible_child_name("completed");
+ }
+ VerificationMode::Cancelled => {
+ self.show_error();
+ }
+ }
+ }
+
+ fn show_error(&self) {
+ let error_message = if let Some(info) = self.request().cancel_info() {
+ match info.cancel_code() {
+ CancelCode::User => Some(gettext("You cancelled the verificaiton process.")),
+ CancelCode::Timeout => Some(gettext(
+ "The verification process failed because it reached a timeout.",
+ )),
+ _ => match info.cancel_code().as_str() {
+ "m.mismatched_sas" => Some(gettext("The emoji did not match.")),
+ _ => None,
+ },
+ }
+ } else {
+ None
+ };
+
+ let error_message = error_message.unwrap_or_else(|| {
+ gettext("An unknown error occured during the verification process.")
+ });
+
+ let error = Error::new(move |_| {
+ let error_label = gtk::LabelBuilder::new()
+ .label(&error_message)
+ .wrap(true)
+ .build();
+ Some(error_label.upcast())
+ });
+
+ if let Some(window) = self.session().parent_window() {
+ window.append_error(&error);
+ }
+ self.silent_cancel();
+ self.start_verification();
+ }
+
+ fn show_recovery(&self) {
+ let priv_ = imp::SessionVerification::from_instance(self);
+
+ self.silent_cancel();
+
+ priv_.main_stack.set_visible_child_name("recovery");
+ }
+
+ fn previous(&self) {
+ let priv_ = imp::SessionVerification::from_instance(self);
+
+ match priv_.main_stack.visible_child_name().unwrap().as_str() {
+ "recovery" => {
+ self.silent_cancel();
+ self.start_verification();
+ }
+ "recovery-passphrase" | "recovery-key" => {
+ priv_.main_stack.set_visible_child_name("recovery");
+ }
+ "wait-for-device" | "complete" => {
+ self.silent_cancel();
+ self.activate_action("session.logout", None);
+ }
+ "emoji" | "qrcode" => {
+ self.silent_cancel();
+ self.start_verification();
+ }
+ _ => {}
+ }
+ }
+
+ fn bootstrap_cross_signing(&self) {
+ spawn!(clone!(@weak self as obj => async move {
+ let priv_ = imp::SessionVerification::from_instance(&obj);
+ let dialog = AuthDialog::new(obj.parent_window().as_ref(), &obj.session());
+
+ let result = dialog
+ .authenticate(move |client, auth_data| async move {
+ if let Some(auth) = auth_data {
+ let auth = Some(auth.as_matrix_auth_data());
+ client.bootstrap_cross_signing(auth).await
+ } else {
+ client.bootstrap_cross_signing(None).await
+ }
+ })
+ .await;
+
+
+ let error_message = match result {
+ Some(Ok(_)) => None,
+ Some(Err(error)) => {
+ error!("Failed to bootstap cross singing: {}", error);
+ Some(gettext("An error occured during the creation of the encryption keys."))
+ },
+ None => {
+ error!("Failed to bootstap cross singing: User cancelled the authentication");
+ Some(gettext("You cancelled the authentication needed to create the encryption keys."))
+ },
+ };
+
+ if let Some(error_message) = error_message {
+ let error = Error::new(move |_| {
+ let error_label = gtk::LabelBuilder::new()
+ .label(&error_message)
+ .wrap(true)
+ .build();
+ Some(error_label.upcast())
+ });
+
+ if let Some(window) = obj.session().parent_window() {
+ window.append_error(&error);
+ }
+
+ obj.silent_cancel();
+ obj.start_verification();
+ } else {
+ priv_
+ .main_stack
+ .set_visible_child_name("completed");
+ }
+ }));
+ }
+}
diff --git a/src/session/verification/to_device_handler.rs b/src/session/verification/to_device_handler.rs
new file mode 100644
index 00000000..ba330844
--- /dev/null
+++ b/src/session/verification/to_device_handler.rs
@@ -0,0 +1,95 @@
+use crate::session::{verification::IdentityVerification, Session};
+use gtk::{glib, prelude::*, subclass::prelude::*};
+use matrix_sdk::ruma::api::client::r0::sync::sync_events::ToDevice;
+
+mod imp {
+ use super::*;
+ use glib::object::WeakRef;
+ use once_cell::sync::{Lazy, OnceCell};
+ use std::cell::RefCell;
+
+ #[derive(Debug, Default)]
+ pub struct ToDeviceHandler {
+ pub session: OnceCell<WeakRef<Session>>,
+ pub verifications: RefCell<Vec<IdentityVerification>>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for ToDeviceHandler {
+ const NAME: &'static str = "ToDeviceHandler";
+ type Type = super::ToDeviceHandler;
+ type ParentType = glib::Object;
+ }
+
+ impl ObjectImpl for ToDeviceHandler {
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![glib::ParamSpec::new_object(
+ "session",
+ "Session",
+ "The session",
+ Session::static_type(),
+ glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+ )]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn set_property(
+ &self,
+ obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "session" => obj.set_session(value.get().unwrap()),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "session" => obj.session().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+ }
+}
+
+glib::wrapper! {
+ pub struct ToDeviceHandler(ObjectSubclass<imp::ToDeviceHandler>);
+}
+
+impl ToDeviceHandler {
+ pub fn new(session: &Session) -> Self {
+ glib::Object::new(&[("session", session)]).expect("Failed to create ToDeviceHandler")
+ }
+
+ pub fn session(&self) -> Session {
+ let priv_ = imp::ToDeviceHandler::from_instance(self);
+ priv_.session.get().unwrap().upgrade().unwrap()
+ }
+
+ fn set_session(&self, session: Session) {
+ let priv_ = imp::ToDeviceHandler::from_instance(self);
+ priv_.session.set(session.downgrade()).unwrap()
+ }
+
+ pub fn handle_response_to_device(&self, to_device: ToDevice) {
+ let priv_ = imp::ToDeviceHandler::from_instance(self);
+
+ for verification in &*priv_.verifications.borrow() {
+ // TODO: handle incomming requests
+ verification.handle_response_to_device(to_device.clone());
+ }
+ }
+
+ /// Add a new `IdentityVerification` request that should be tracked
+ pub fn add_request(&self, request: IdentityVerification) {
+ let priv_ = imp::ToDeviceHandler::from_instance(self);
+
+ priv_.verifications.borrow_mut().push(request);
+ }
+}
diff --git a/src/window.rs b/src/window.rs
index af118dd9..6a07ad27 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -123,7 +123,7 @@ impl Window {
if let Some(child) = priv_.sessions.first_child() {
priv_.sessions.set_visible_child(&child);
} else {
- self.switch_to_login_page();
+ self.switch_to_login_page(true);
}
}
@@ -181,13 +181,15 @@ impl Window {
priv_.main_stack.set_visible_child(&priv_.sessions.get());
}
- pub fn switch_to_login_page(&self) {
+ pub fn switch_to_login_page(&self, clean: bool) {
let priv_ = imp::Window::from_instance(self);
+ if clean {
+ priv_.login.clean();
+ }
priv_
.login
- .get()
.show_back_to_session_button(priv_.sessions.get().pages().n_items() > 0);
- priv_.main_stack.set_visible_child(&priv_.login.get());
+ priv_.main_stack.set_visible_child(&*priv_.login);
}
/// This appends a new error to the list of errors
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]