[fractal/fractal-next] login: Add password login and session creation
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] login: Add password login and session creation
- Date: Sat, 27 Mar 2021 09:01:32 +0000 (UTC)
commit 37c8e184166dc6669314735006cbfee1e00f68ad
Author: Julian Sparber <julian sparber net>
Date: Wed Mar 24 11:49:25 2021 +0100
login: Add password login and session creation
Since the login flow for matrix will change soonish, therefore, we
implement only login via password.
Cargo.lock | 1 +
Cargo.toml | 1 +
data/resources/icons/scalable/status/welcome.svg | 273 +++++++++++++++++++++++
data/resources/resources.gresource.xml | 1 +
data/resources/style.css | 5 +
data/resources/ui/login.ui | 139 +++++++++++-
data/resources/ui/window.ui | 6 +-
src/login.rs | 153 ++++++++++++-
src/session/mod.rs | 220 +++++++++++++++++-
src/window.rs | 16 +-
10 files changed, 779 insertions(+), 36 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index 79bc950f..68fe1bf0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -470,6 +470,7 @@ dependencies = [
"pretty_env_logger",
"serde_json",
"tokio",
+ "url",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 1e6e60e0..3b799dd2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,7 @@ gtk-macros = "0.2"
once_cell = "1.5"
serde_json = "1.0"
tokio = { version = "1.2", features = ["rt", "rt-multi-thread"] }
+url = "2.2"
[dependencies.gtk]
package = "gtk4"
diff --git a/data/resources/icons/scalable/status/welcome.svg
b/data/resources/icons/scalable/status/welcome.svg
new file mode 100644
index 00000000..915dec91
--- /dev/null
+++ b/data/resources/icons/scalable/status/welcome.svg
@@ -0,0 +1,273 @@
+<?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">
+ <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)" />
+ <linearGradient
+ id="linearGradient8161">
+ <stop
+ style="stop-color:#ed333b;stop-opacity:1;"
+ offset="0"
+ id="stop8157" />
+ <stop
+ style="stop-color:#f76d5f;stop-opacity:1"
+ 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
+ style="stop-color:#33d17a;stop-opacity:1"
+ offset="0"
+ id="stop8141" />
+ <stop
+ style="stop-color:#8ff0a4;stop-opacity:1"
+ offset="1"
+ id="stop8143" />
+ </linearGradient>
+ </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" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1">
+ <g
+ id="g9553"
+ transform="translate(-57.074405,-5.2916687)">
+ <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>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 75e043dd..6d2f72a4 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -9,5 +9,6 @@
<file compressed="true" preprocess="xml-stripblanks" alias="window.ui">ui/window.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>
</gresource>
</gresources>
diff --git a/data/resources/style.css b/data/resources/style.css
index c3d88b6e..6f75520d 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -1,3 +1,8 @@
+/* Login */
+.login {
+ min-width: 250px;
+}
+
.title-header {
font-size: 36px;
font-weight: bold;
diff --git a/data/resources/ui/login.ui b/data/resources/ui/login.ui
index 94347e78..0a0efc4e 100644
--- a/data/resources/ui/login.ui
+++ b/data/resources/ui/login.ui
@@ -3,15 +3,136 @@
<template class="FrctlLogin" parent="AdwBin">
<child>
<object class="GtkBox">
- <property name="orientation">vertical</property>
- <child>
- <object class="GtkHeaderBar" id="headerbar" />
- </child>
- <child>
- <object class="AdwStatusPage">
- <!-- TODO implement login -->
- </object>
- </child>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkHeaderBar">
+ <property name="title-widget">
+ <object class="AdwWindowTitle">
+ <property name="title">Fractal</property>
+ </object>
+ </property>
+ <child type="end">
+ <object class="GtkButton">
+ <property name="action_name">login.next</property>
+ <property name="child">
+ <object class="GtkStack" id="next_stack">
+ <child>
+ <object class="GtkLabel" id="next_label">
+ <property name="use_underline">True</property>
+ <property name="label" translatable="yes">_Next</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="next_spinner">
+ <property name="spinning">True</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="main_stack">
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">greeter</property>
+ <property name="child">
+ <object class="AdwStatusPage">
+ <property name="icon-name">welcome</property>
+ <property name="title" translatable="yes">Welcome to Fractal</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel" id="error_message">
+ <property name="visible">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox">
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="focusable">False</property>
+ <property name="selectable">False</property>
+ <property name="activatable">False</property>
+ <property name="child">
+ <object class="GtkEntry" id="homeserver_entry">
+ <property name="activates-default">True</property>
+ <property name="input_purpose">GTK_INPUT_PURPOSE_URL</property>
+ <property name="placeholder-text">Homeserver</property>
+ <property name="margin-top">6</property>
+ <property name="margin-bottom">6</property>
+ <property name="margin-start">6</property>
+ <property name="margin-end">6</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="focusable">False</property>
+ <property name="selectable">False</property>
+ <property name="activatable">False</property>
+ <property name="child">
+ <object class="GtkEntry" id="username_entry">
+ <property name="activates-default">True</property>
+ <property name="placeholder-text">Matrix Username</property>
+ <property name="margin-top">6</property>
+ <property name="margin-bottom">6</property>
+ <property name="margin-start">6</property>
+ <property name="margin-end">6</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="focusable">False</property>
+ <property name="selectable">False</property>
+ <property name="activatable">False</property>
+ <property name="child">
+ <object class="GtkPasswordEntry" id="password_entry">
+ <property name="activates-default">True</property>
+ <property name="show-peek-icon">True</property>
+ <property name="placeholder-text">Password</property>
+ <property name="margin-top">6</property>
+ <property name="margin-bottom">6</property>
+ <property name="margin-start">6</property>
+ <property name="margin-end">6</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <style>
+ <class name="content"/>
+ <class name="login"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLinkButton" id="forgot_password">
+ <property name="use_underline">True</property>
+ <property name="label" translatable="yes">_Forgot Password?</property>
+ <property name="uri">https://app.element.io/#/forgot_password</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
</object>
</child>
</template>
diff --git a/data/resources/ui/window.ui b/data/resources/ui/window.ui
index 95c2b884..3a83e4e6 100644
--- a/data/resources/ui/window.ui
+++ b/data/resources/ui/window.ui
@@ -5,15 +5,11 @@
<property name="default-height">400</property>
<child>
<object class="GtkStack" id="main_stack">
- <property name="visible-child">session</property>
+ <property name="visible-child">login</property>
<property name="transition-type">crossfade</property>
<child>
<object class="FrctlLogin" id="login" />
</child>
- <!-- We currently only support one session, add other sessions to the main_Stack -->
- <child>
- <object class="FrctlSession" id="session" />
- </child>
</object>
</child>
</template>
diff --git a/src/login.rs b/src/login.rs
index bd3bfe4c..7f3c646d 100644
--- a/src/login.rs
+++ b/src/login.rs
@@ -1,18 +1,37 @@
+use crate::FrctlSession;
+
use adw;
use adw::subclass::prelude::BinImpl;
+use gettextrs::gettext;
use gtk::subclass::prelude::*;
use gtk::{self, prelude::*};
-use gtk::{glib, CompositeTemplate};
+use gtk::{glib, glib::clone, CompositeTemplate};
+use log::debug;
mod imp {
use super::*;
- use glib::subclass::InitializingObject;
+ use glib::subclass::{InitializingObject, Signal};
+ use once_cell::sync::Lazy;
- #[derive(Debug, CompositeTemplate)]
+ #[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/org/gnome/FractalNext/login.ui")]
pub struct FrctlLogin {
#[template_child]
- pub headerbar: TemplateChild<gtk::HeaderBar>,
+ pub next_stack: TemplateChild<gtk::Stack>,
+ #[template_child]
+ pub next_label: TemplateChild<gtk::Label>,
+ #[template_child]
+ pub next_spinner: TemplateChild<gtk::Spinner>,
+ #[template_child]
+ pub main_stack: TemplateChild<gtk::Stack>,
+ #[template_child]
+ pub homeserver_entry: TemplateChild<gtk::Entry>,
+ #[template_child]
+ pub username_entry: TemplateChild<gtk::Entry>,
+ #[template_child]
+ pub password_entry: TemplateChild<gtk::PasswordEntry>,
+ #[template_child]
+ pub error_message: TemplateChild<gtk::Label>,
}
#[glib::object_subclass]
@@ -21,14 +40,10 @@ mod imp {
type Type = super::FrctlLogin;
type ParentType = adw::Bin;
- fn new() -> Self {
- Self {
- headerbar: TemplateChild::default(),
- }
- }
-
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
+ klass.set_accessible_role(gtk::AccessibleRole::Group);
+ klass.install_action("login.next", None, move |widget, _, _| widget.forward());
}
fn instance_init(obj: &InitializingObject<Self>) {
@@ -36,8 +51,35 @@ mod imp {
}
}
- impl ObjectImpl for FrctlLogin {}
+ impl ObjectImpl for FrctlLogin {
+ fn signals() -> &'static [Signal] {
+ static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
+ vec![Signal::builder(
+ "new-session",
+ &[FrctlSession::static_type().into()],
+ <()>::static_type().into(),
+ )
+ .build()]
+ });
+ SIGNALS.as_ref()
+ }
+
+ fn constructed(&self, obj: &Self::Type) {
+ obj.action_set_enabled("login.next", false);
+
+ self.parent_constructed(obj);
+
+ self.homeserver_entry
+ .connect_changed(clone!(@weak obj => move |_| obj.enable_next_action()));
+ self.username_entry
+ .connect_changed(clone!(@weak obj => move |_| obj.enable_next_action()));
+ self.password_entry
+ .connect_changed(clone!(@weak obj => move |_| obj.enable_next_action()));
+ }
+ }
+
impl WidgetImpl for FrctlLogin {}
+
impl BinImpl for FrctlLogin {}
}
@@ -50,4 +92,93 @@ impl FrctlLogin {
pub fn new() -> Self {
glib::Object::new(&[]).expect("Failed to create FrctlLogin")
}
+
+ fn enable_next_action(&self) {
+ let priv_ = imp::FrctlLogin::from_instance(&self);
+ let homeserver = priv_.homeserver_entry.get_text();
+ let username = priv_.username_entry.get_text_length();
+ let password = priv_.password_entry.get_text().len();
+
+ self.action_set_enabled(
+ "login.next",
+ homeserver.len() != 0
+ && url::Url::parse(homeserver.as_str()).is_ok()
+ && username != 0
+ && password != 0,
+ );
+ }
+
+ fn forward(&self) {
+ self.login();
+ }
+
+ fn login(&self) {
+ let priv_ = imp::FrctlLogin::from_instance(&self);
+ let homeserver = priv_.homeserver_entry.get_text().to_string();
+ let username = priv_.username_entry.get_text().to_string();
+ let password = priv_.password_entry.get_text().to_string();
+
+ self.freeze();
+
+ let session = FrctlSession::new(homeserver);
+
+ session.connect_ready(clone!(@weak self as obj, @strong session => move |_| {
+ if let Some(error) = session.get_error() {
+ let error_message = &imp::FrctlLogin::from_instance(&obj).error_message;
+ // TODO: show more specific error
+ error_message.set_text(&gettext("⚠️ The Login failed."));
+ error_message.show();
+ debug!("Failed to create a new session: {:?}", error);
+
+ obj.unfreeze();
+ } else {
+ debug!("A new session is ready");
+ obj.emit_by_name("new-session", &[&session]).unwrap();
+ obj.clean();
+ }
+ }));
+
+ session.login_with_password(username, password);
+ }
+
+ fn clean(&self) {
+ let priv_ = imp::FrctlLogin::from_instance(&self);
+ priv_.homeserver_entry.set_text("");
+ priv_.username_entry.set_text("");
+ priv_.password_entry.set_text("");
+ self.unfreeze();
+ }
+
+ fn freeze(&self) {
+ let priv_ = imp::FrctlLogin::from_instance(&self);
+
+ self.action_set_enabled("login.next", false);
+ priv_
+ .next_stack
+ .set_visible_child(&priv_.next_spinner.get());
+ priv_.main_stack.set_sensitive(false);
+ }
+
+ fn unfreeze(&self) {
+ let priv_ = imp::FrctlLogin::from_instance(&self);
+
+ self.action_set_enabled("login.next", true);
+ priv_.next_stack.set_visible_child(&priv_.next_label.get());
+ priv_.main_stack.set_sensitive(true);
+ }
+
+ pub fn connect_new_session<F: Fn(&Self, &FrctlSession) + 'static>(
+ &self,
+ f: F,
+ ) -> glib::SignalHandlerId {
+ self.connect_local("new-session", true, move |values| {
+ let obj = values[0].get::<Self>().unwrap().unwrap();
+ let session = values[1].get::<FrctlSession>().unwrap().unwrap();
+
+ f(&obj, &session);
+
+ None
+ })
+ .unwrap()
+ }
}
diff --git a/src/session/mod.rs b/src/session/mod.rs
index a387511a..57c9ee8a 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -3,15 +3,28 @@ mod sidebar;
use self::content::FrctlContent;
use self::sidebar::FrctlSidebar;
+
+use crate::RUNTIME;
+
use adw;
use adw::subclass::prelude::BinImpl;
use gtk::subclass::prelude::*;
use gtk::{self, prelude::*};
-use gtk::{glib, CompositeTemplate};
+use gtk::{glib, glib::clone, CompositeTemplate};
+use gtk_macros::send;
+use log::error;
+use matrix_sdk::api::r0::{
+ filter::{FilterDefinition, RoomFilter},
+ session::login,
+};
+use matrix_sdk::{self, Client, ClientConfig, SyncSettings};
+use std::time::Duration;
mod imp {
use super::*;
- use glib::subclass::InitializingObject;
+ use glib::subclass::{InitializingObject, Signal};
+ use once_cell::sync::{Lazy, OnceCell};
+ use std::cell::RefCell;
#[derive(Debug, CompositeTemplate)]
#[template(resource = "/org/gnome/FractalNext/session.ui")]
@@ -20,6 +33,9 @@ mod imp {
pub sidebar: TemplateChild<FrctlSidebar>,
#[template_child]
pub content: TemplateChild<FrctlContent>,
+ pub homeserver: OnceCell<String>,
+ /// Contains the error if something went wrong
+ pub error: RefCell<Option<matrix_sdk::Error>>,
}
#[glib::object_subclass]
@@ -32,6 +48,8 @@ mod imp {
Self {
sidebar: TemplateChild::default(),
content: TemplateChild::default(),
+ homeserver: OnceCell::new(),
+ error: RefCell::new(None),
}
}
@@ -44,18 +62,210 @@ mod imp {
}
}
- impl ObjectImpl for FrctlSession {}
+ impl ObjectImpl for FrctlSession {
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![glib::ParamSpec::string(
+ "homeserver",
+ "Homeserver",
+ "The matrix homeserver of this session",
+ None,
+ 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.get_name() {
+ "homeserver" => {
+ let homeserver = value
+ .get()
+ .expect("type conformity checked by `Object::set_property`");
+ let _ = self.homeserver.set(homeserver.unwrap());
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn get_property(
+ &self,
+ _obj: &Self::Type,
+ _id: usize,
+ pspec: &glib::ParamSpec,
+ ) -> glib::Value {
+ match pspec.get_name() {
+ "homeserver" => self.homeserver.get().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn signals() -> &'static [Signal] {
+ static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
+ vec![Signal::builder("ready", &[], <()>::static_type().into()).build()]
+ });
+ SIGNALS.as_ref()
+ }
+
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+ }
+ }
impl WidgetImpl for FrctlSession {}
impl BinImpl for FrctlSession {}
}
+/// Enum containing the supported methods to create a `FrctlSession`.
+#[derive(Clone, Debug)]
+enum CreationMethod {
+ /// Restore a previous session: `matrix_sdk::Session`
+ SessionRestore(matrix_sdk::Session),
+ /// Password Login: `username`, 'password`
+ Password(String, String),
+}
+
glib::wrapper! {
pub struct FrctlSession(ObjectSubclass<imp::FrctlSession>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
}
impl FrctlSession {
- pub fn new() -> Self {
- glib::Object::new(&[]).expect("Failed to create FrctlSession")
+ pub fn new(homeserver: String) -> Self {
+ glib::Object::new(&[("homeserver", &homeserver)]).expect("Failed to create FrctlSession")
+ }
+
+ pub fn login_with_password(&self, username: String, password: String) {
+ let method = CreationMethod::Password(username, password);
+ self.login(method);
+ }
+
+ pub fn login_with_previous_session(&self, session: matrix_sdk::Session) {
+ let method = CreationMethod::SessionRestore(session);
+ self.login(method);
+ }
+
+ fn login(&self, method: CreationMethod) {
+ let priv_ = &imp::FrctlSession::from_instance(self);
+ let homeserver = priv_.homeserver.get().unwrap();
+
+ let sender = self.setup();
+
+ let config = ClientConfig::new().timeout(Duration::from_secs(15));
+ // Please note the homeserver needs to be a valid url or the client will panic!
+ let client = Client::new_with_config(homeserver.as_str(), config);
+
+ if let Err(error) = client {
+ send!(sender, Err(error));
+ return;
+ }
+
+ let client = client.unwrap();
+
+ RUNTIME.block_on(async {
+ tokio::spawn(async move {
+ let success = match method {
+ CreationMethod::SessionRestore(_session) => {
+ todo!("Implement session restore")
+ }
+ CreationMethod::Password(username, password) => {
+ // FIXME: client won't return if the homeserver isn't any real domain, I think
+ // it has to do something with the dns lookup, therefore, we add a timeout of
+ // 15s for the login and return a mocked Error.
+ let response = tokio::time::timeout(
+ Duration::from_secs(15),
+ client.login(&username, &password, None, Some("Fractal Next")),
+ )
+ .await;
+
+ if let Err(_) = response {
+ send!(
+ sender,
+ Err(matrix_sdk::Error::Http(
+ matrix_sdk::HttpError::NotClientRequest
+ ))
+ );
+ return;
+ }
+
+ let response = response.unwrap();
+
+ let success = response.is_ok();
+ send!(sender, response);
+ success
+ }
+ };
+
+ if success {
+ // We need the filter or else left rooms won't be shown
+ let mut room_filter = RoomFilter::empty();
+ room_filter.include_leave = true;
+
+ let mut filter = FilterDefinition::empty();
+ filter.room = room_filter;
+
+ let sync_settings = SyncSettings::new()
+ .timeout(Duration::from_secs(30))
+ .full_state(true)
+ .filter(filter.into());
+ client.sync(sync_settings).await;
+ }
+ });
+ });
+ }
+
+ fn setup(&self) -> glib::SyncSender<matrix_sdk::Result<login::Response>> {
+ let (sender, receiver) = glib::MainContext::sync_channel::<
+ matrix_sdk::Result<login::Response>,
+ >(Default::default(), 100);
+ receiver.attach(
+ None,
+ clone!(@weak self as obj => move |result| {
+ match result {
+ Err(error) => {
+ let priv_ = &imp::FrctlSession::from_instance(&obj);
+ priv_.error.replace(Some(error));
+ }
+ Ok(response) => {
+ // TODO: store this session to the SecretService so we can use it for the next login
+ let _session = matrix_sdk::Session {
+ access_token: response.access_token,
+ user_id: response.user_id,
+ device_id: response.device_id,
+ };
+ }
+ }
+
+ obj.emit_by_name("ready", &[]).unwrap();
+
+ glib::Continue(false)
+ }),
+ );
+ sender
+ }
+
+ /// Returns and consumes the `error` that was generated when the session failed to login,
+ /// on a successful login this will be `None`.
+ /// Unfortunatly it's not possible to connect the Error direclty to the `ready` signals.
+ pub fn get_error(&self) -> Option<matrix_sdk::Error> {
+ let priv_ = &imp::FrctlSession::from_instance(self);
+ priv_.error.take()
+ }
+
+ 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().unwrap();
+
+ f(&obj);
+
+ None
+ })
+ .unwrap()
}
}
diff --git a/src/window.rs b/src/window.rs
index 04905606..bbae15ee 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -6,7 +6,7 @@ use adw::subclass::prelude::AdwApplicationWindowImpl;
use glib::signal::Inhibit;
use gtk::subclass::prelude::*;
use gtk::{self, prelude::*};
-use gtk::{gio, glib, CompositeTemplate};
+use gtk::{gio, glib, glib::clone, CompositeTemplate};
use log::warn;
mod imp {
@@ -20,10 +20,6 @@ mod imp {
pub main_stack: TemplateChild<gtk::Stack>,
#[template_child]
pub login: TemplateChild<FrctlLogin>,
- // Eventually we want to create the session dynamically, since we want multi account
- // support
- #[template_child]
- pub session: TemplateChild<FrctlSession>,
pub settings: gio::Settings,
}
@@ -37,7 +33,6 @@ mod imp {
Self {
main_stack: TemplateChild::default(),
login: TemplateChild::default(),
- session: TemplateChild::default(),
settings: gio::Settings::new(APP_ID),
}
}
@@ -66,6 +61,9 @@ mod imp {
// load latest window state
obj.load_window_size();
+ self.login.connect_new_session(
+ clone!(@weak obj => move |_login, session| obj.add_session(session)),
+ );
}
}
@@ -95,6 +93,12 @@ impl FrctlWindow {
.expect("Failed to create FrctlWindow")
}
+ pub fn add_session(&self, session: &FrctlSession) {
+ let priv_ = &imp::FrctlWindow::from_instance(self);
+ priv_.main_stack.add_child(session);
+ priv_.main_stack.set_visible_child(session);
+ }
+
pub fn save_window_size(&self) -> Result<(), glib::BoolError> {
let settings = &imp::FrctlWindow::from_instance(self).settings;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]