Browse Source

login: Add password login and session creation

Since the login flow for matrix will change soonish, therefore, we
implement only login via password.
merge-requests/1327/merge
Julian Sparber 5 years ago
parent
commit
37c8e18416
  1. 1
      Cargo.lock
  2. 1
      Cargo.toml
  3. 273
      data/resources/icons/scalable/status/welcome.svg
  4. 1
      data/resources/resources.gresource.xml
  5. 5
      data/resources/style.css
  6. 139
      data/resources/ui/login.ui
  7. 6
      data/resources/ui/window.ui
  8. 153
      src/login.rs
  9. 220
      src/session/mod.rs
  10. 16
      src/window.rs

1
Cargo.lock generated

@ -470,6 +470,7 @@ dependencies = [
"pretty_env_logger",
"serde_json",
"tokio",
"url",
]
[[package]]

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

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

After

Width:  |  Height:  |  Size: 14 KiB

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

5
data/resources/style.css

@ -1,3 +1,8 @@
/* Login */
.login {
min-width: 250px;
}
.title-header {
font-size: 36px;
font-weight: bold;

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

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

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

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

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

Loading…
Cancel
Save