[fractal] fractal-gtk: Redesign Login Flow



commit 66e95aa15157b05e971740341b7cf14c3cff77c4
Author: Christopher Davis <brainblasted disroot org>
Date:   Sat Mar 30 01:49:26 2019 -0400

    fractal-gtk: Redesign Login Flow
    
    This is a complete overhaul over the interface for login.
    
    * widgetifies the login view (removes app/connect/login.rs)
    * Instead of asking for the identity server, uses .well-known
    * No longer assumes a homeserver
    
    Closes #448

 fractal-gtk/res/app.css              |  11 ++-
 fractal-gtk/res/resources.xml        |   1 +
 fractal-gtk/res/ui/login_flow.ui     | 177 +++++++++++++++++++++++++++++++++--
 fractal-gtk/res/ui/main_window.ui    |   6 +-
 fractal-gtk/src/actions/login.rs     | 129 +++++++++++++++++++++++++
 fractal-gtk/src/actions/mod.rs       |   3 +
 fractal-gtk/src/app/backend_loop.rs  |   1 +
 fractal-gtk/src/app/connect/login.rs |  80 ----------------
 fractal-gtk/src/app/connect/mod.rs   |   2 -
 fractal-gtk/src/app/mod.rs           |   6 ++
 fractal-gtk/src/appop/login.rs       | 107 ---------------------
 fractal-gtk/src/appop/mod.rs         |   3 +-
 fractal-gtk/src/appop/state.rs       |   8 +-
 fractal-gtk/src/globals.rs           |   1 +
 fractal-gtk/src/widgets/login.rs     | 164 ++++++++++++++++++++++++++++++++
 fractal-gtk/src/widgets/mod.rs       |   2 +
 16 files changed, 492 insertions(+), 209 deletions(-)
---
diff --git a/fractal-gtk/res/app.css b/fractal-gtk/res/app.css
index 4680520d..b968a482 100644
--- a/fractal-gtk/res/app.css
+++ b/fractal-gtk/res/app.css
@@ -317,4 +317,13 @@ stack.titlebar:not(headerbar) > box > separator {
 }
 .badge-grey {
   background-color: #D9D9D9;
-}
\ No newline at end of file
+}
+
+button.forgot-password {
+  padding: 0px;
+  outline-offset: 0px;
+}
+
+.error-label {
+  color: @error_color;
+}
diff --git a/fractal-gtk/res/resources.xml b/fractal-gtk/res/resources.xml
index e5cb1da5..ae63d0d0 100644
--- a/fractal-gtk/res/resources.xml
+++ b/fractal-gtk/res/resources.xml
@@ -14,6 +14,7 @@
       <file preprocess="xml-stripblanks">ui/invite_user.ui</file>
       <file preprocess="xml-stripblanks">ui/join_room.ui</file>
       <file preprocess="xml-stripblanks">ui/leave_room.ui</file>
+      <file preprocess="xml-stripblanks">ui/login_flow.ui</file>
       <file preprocess="xml-stripblanks">ui/main_window.ui</file>
       <file preprocess="xml-stripblanks">ui/scroll_widget.ui</file>
       <file preprocess="xml-stripblanks">ui/members.ui</file>
diff --git a/fractal-gtk/res/ui/login_flow.ui b/fractal-gtk/res/ui/login_flow.ui
index a7a0e922..548870d4 100644
--- a/fractal-gtk/res/ui/login_flow.ui
+++ b/fractal-gtk/res/ui/login_flow.ui
@@ -5,6 +5,7 @@
   <object class="GtkStack" id="login_flow_stack">
     <property name="can_focus">False</property>
     <property name="hhomogeneous">True</property>
+    <property name="transition_type">GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT</property>
     <child>
       <object class="GtkBox" id="login_greeter">
         <property name="visible">True</property>
@@ -22,10 +23,7 @@
             <property name="margin_end">18</property>
             <property name="margin_top">18</property>
             <property name="pixel_size">128</property>
-            <property name="icon_name">user-available-symbolic</property>
-            <style>
-              <class name="dim-label"/>
-            </style>
+            <property name="icon_name">chat-icon</property>
           </object>
         </child>
         <child>
@@ -34,6 +32,8 @@
             <property name="can_focus">False</property>
             <property name="label" translatable="yes">Welcome to Fractal</property>
             <property name="margin_bottom">48</property>
+            <property name="wrap">True</property>
+            <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
             <attributes>
               <attribute name="weight" value="ultrabold"/>
               <attribute name="scale" value="1.7"/>
@@ -44,6 +44,7 @@
           <object class="GtkButton" id="login_button">
             <property name="visible">True</property>
             <property name="label" translatable="true">Log In</property>
+            <property name="action_name">login.server_chooser</property>
             <property name="height-request">48</property>
             <style>
               <class name="suggested-action"/>
@@ -55,6 +56,7 @@
             <property name="visible">True</property>
             <property name="label" translatable="yes">Create Account</property>
             <property name="height-request">48</property>
+            <property name="action_name">login.create-account</property>
           </object>
         </child>
       </object>
@@ -77,7 +79,6 @@
             <property name="halign">center</property>
             <property name="margin_start">18</property>
             <property name="margin_end">18</property>
-            <property name="margin_top">18</property>
             <property name="pixel_size">128</property>
             <property name="icon_name">network-server-symbolic</property>
             <style>
@@ -89,8 +90,10 @@
           <object class="GtkLabel">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <property name="label" translatable="yes">What is your Server?</property>
+            <property name="label" translatable="yes">What is your Provider?</property>
             <property name="margin_bottom">30</property>
+            <property name="wrap">True</property>
+            <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
             <attributes>
               <attribute name="weight" value="ultrabold"/>
               <attribute name="scale" value="1.7"/>
@@ -106,15 +109,19 @@
             <child>
               <object class="GtkEntry" id="server_chooser_entry">
                 <property name="visible">True</property>
+                <property name="halign">center</property>
                 <property name="max_width_chars">-1</property>
-                <property name="width_request">312</property>
+                <property name="width_request">300</property>
+                <property name="input_purpose">GTK_INPUT_PURPOSE_URL</property>
               </object>
             </child>
             <child>
               <object class="GtkLabel">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
-                <property name="label" translatable="yes">The domain of your Matrix server, e.g. 
myserver.co</property>
+                <property name="label" translatable="yes">Matrix provider domain, e.g. myserver.co</property>
+                <property name="wrap">True</property>
+                <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
                 <style>
                   <class name="dim-label"/>
                   <class name="small-label"/>
@@ -123,6 +130,19 @@
             </child>
           </object>
         </child>
+        <child>
+          <object class="GtkLabel" id="server_err_label">
+            <property name="visible">False</property>
+            <property name="can_focus">False</property>
+            <property name="no_show_all">True</property>
+            <property name="label" translatable="yes">The domain may not be empty.</property>
+            <property name="wrap">True</property>
+            <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
+            <style>
+              <class name="error-label"/>
+            </style>
+          </object>
+        </child>
       </object>
       <packing>
         <property name="name">server-chooser</property>
@@ -143,6 +163,8 @@
             <property name="label" translatable="yes">User ID</property>
             <property name="halign">end</property>
             <property name="valign">end</property>
+            <property name="wrap">True</property>
+            <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
             <style>
               <class name="dim-label"/>
             </style>
@@ -154,6 +176,8 @@
             <property name="can_focus">False</property>
             <property name="label" translatable="yes">Password</property>
             <property name="halign">end</property>
+            <property name="wrap">True</property>
+            <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
             <style>
               <class name="dim-label"/>
             </style>
@@ -183,6 +207,8 @@
                 <property name="can_focus">False</property>
                 <property name="label" translatable="yes">User name, email, or phone number</property>
                 <property name="halign">start</property>
+                <property name="wrap">True</property>
+                <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
                 <style>
                   <class name="dim-label"/>
                   <class name="small-font"/>
@@ -201,12 +227,147 @@
             <property name="max_width_chars">-1</property>
             <property name="width_request">232</property>
             <property name="can_focus">True</property>
+            <property name="visibility">False</property>
+            <property name="input_purpose">GTK_INPUT_PURPOSE_PASSWORD</property>
           </object>
           <packing>
             <property name="top-attach">3</property>
             <property name="left-attach">1</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkLinkButton" id="forgot_password">
+            <property name="label" translatable="yes">Forgot Password?</property>
+            <property name="uri">https://riot.im/app/#/login</property>
+            <property name="halign">start</property>
+            <style>
+              <class name="forgot-password"/>
+            </style>
+          </object>
+          <packing>
+            <property name="top-attach">4</property>
+            <property name="left-attach">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="credentials_err_label">
+            <property name="visible">False</property>
+            <property name="can_focus">False</property>
+            <property name="no_show_all">True</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">Invalid username or password</property>
+            <property name="wrap">True</property>
+            <property name="wrap_mode">PANGO_WRAP_WORD_CHAR</property>
+            <style>
+              <class name="error-label"/>
+            </style>
+          </object>
+          <packing>
+            <property name="top-attach">5</property>
+            <property name="left-attach">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="name">credentials</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkStack" id="login_flow_headers">
+    <property name="can_focus">False</property>
+    <property name="hhomogeneous">True</property>
+    <property name="visible_child_name" bind-source="login_flow_stack" bind-property="visible-child-name" 
bind-flags="sync-create"/>
+    <property name="transition_duration" bind-source="login_flow_stack" bind-property="transition-duration" 
bind-flags="sync-create"/>
+    <property name="transition_type" bind-source="login_flow_stack" bind-property="transition-type" 
bind-flags="sync-create"/>
+    <child>
+      <object class="GtkHeaderBar" id="login_greeter_header">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="show_close_button">True</property>
+        <property name="title" translatable="yes">Fractal</property>
+      </object>
+      <packing>
+        <property name="name">greeter</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHeaderBar" id="login_server_chooser_header">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="show_close_button">True</property>
+        <property name="width_request">360</property>
+        <property name="title" translatable="yes">Choose Provider</property>
+        <child>
+          <object class="GtkButton">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="action_name">login.back</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="icon_name">go-previous-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="pack_type">start</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="action_name">login.credentials</property>
+            <property name="label" translatable="yes">Next</property>
+            <style>
+              <class name="suggested-action"/>
+            </style>
+          </object>
+          <packing>
+            <property name="pack_type">end</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="name">server-chooser</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHeaderBar" id="login_credentials_header">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="show_close_button">True</property>
+        <property name="title" translatable="yes">Log In</property>
+        <child>
+          <object class="GtkButton">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="action_name">login.back</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="icon_name">go-previous-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="pack_type">start</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="action_name">login.login</property>
+            <property name="label" translatable="yes">Log In</property>
+            <style>
+              <class name="suggested-action"/>
+            </style>
+          </object>
+          <packing>
+            <property name="pack_type">end</property>
+          </packing>
+        </child>
       </object>
       <packing>
         <property name="name">credentials</property>
diff --git a/fractal-gtk/res/ui/main_window.ui b/fractal-gtk/res/ui/main_window.ui
index db4626b7..680e3f52 100644
--- a/fractal-gtk/res/ui/main_window.ui
+++ b/fractal-gtk/res/ui/main_window.ui
@@ -608,7 +608,7 @@
               </packing>
             </child>
             <child>
-              <object class="GtkBox"> <!--login titlebar-->
+              <object class="GtkBox">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <property name="orientation">vertical</property>
@@ -621,8 +621,8 @@
                 </child>
               </object>
               <packing>
-                <property name="name">login</property>
-                <property name="title">login</property>
+                <property name="name">loading</property>
+                <property name="title">loading</property>
                 <property name="position">1</property>
               </packing>
             </child>
diff --git a/fractal-gtk/src/actions/login.rs b/fractal-gtk/src/actions/login.rs
new file mode 100644
index 00000000..0b82498e
--- /dev/null
+++ b/fractal-gtk/src/actions/login.rs
@@ -0,0 +1,129 @@
+use log::{debug, warn};
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use gio::prelude::*;
+use gio::SimpleAction;
+use gio::SimpleActionGroup;
+use gtk::prelude::*;
+
+use crate::globals;
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum LoginState {
+    Greeter,
+    ServerChooser,
+    Credentials,
+}
+
+impl From<String> for LoginState {
+    fn from(v: String) -> LoginState {
+        match v.as_str() {
+            "greeter" => LoginState::Greeter,
+            "server-chooser" => LoginState::ServerChooser,
+            "credentials" => LoginState::Credentials,
+            _ => panic!("Invalid back state type"),
+        }
+    }
+}
+
+impl ToString for LoginState {
+    fn to_string(&self) -> String {
+        let str = match self {
+            LoginState::Greeter => "greeter",
+            LoginState::ServerChooser => "server-chooser",
+            LoginState::Credentials => "credentials",
+        };
+
+        String::from(str)
+    }
+}
+
+pub fn new(
+    stack: &gtk::Stack,
+    headers: &gtk::Stack,
+    server_entry: &gtk::Entry,
+    err_label: &gtk::Label,
+) -> SimpleActionGroup {
+    let actions = SimpleActionGroup::new();
+
+    let create_account = SimpleAction::new("create-account", None);
+    let server_chooser = SimpleAction::new("server_chooser", None);
+    let credentials = SimpleAction::new("credentials", None);
+    let back = SimpleAction::new("back", None);
+    let login = SimpleAction::new("login", None);
+
+    actions.add_action(&create_account);
+    actions.add_action(&server_chooser);
+    actions.add_action(&credentials);
+    actions.add_action(&back);
+    actions.add_action(&login);
+
+    let stack_weak = stack.downgrade();
+    create_account.connect_activate(move |_, _| {
+        let stack = upgrade_weak!(stack_weak);
+
+        let toplevel = stack
+            .get_toplevel()
+            .expect("Could not grab toplevel widget")
+            .downcast::<gtk::Window>()
+            .expect("Could not cast toplevel to GtkWindow");
+        let uri = globals::RIOT_REGISTER_URL;
+        if let Err(e) = gtk::show_uri_on_window(&toplevel, uri, gtk::get_current_event_time()) {
+            warn!("Could not show {}: {}", uri, e)
+        }
+    });
+
+    let back_history: Rc<RefCell<Vec<LoginState>>> = Rc::new(RefCell::new(vec![]));
+
+    let back_weak = Rc::downgrade(&back_history);
+    let stack_weak = stack.downgrade();
+    server_chooser.connect_activate(move |_, _| {
+        let stack = upgrade_weak!(stack_weak);
+        let back = upgrade_weak!(back_weak);
+
+        let state = LoginState::ServerChooser;
+        stack.set_visible_child_name(&state.to_string());
+        back.borrow_mut().push(state);
+    });
+
+    let back_weak = Rc::downgrade(&back_history);
+    let stack_weak = stack.downgrade();
+    let server_weak = server_entry.downgrade();
+    let err_weak = err_label.downgrade();
+    credentials.connect_activate(move |_, _| {
+        let stack = upgrade_weak!(stack_weak);
+        let back = upgrade_weak!(back_weak);
+        let server_entry = upgrade_weak!(server_weak);
+        let err_label = upgrade_weak!(err_weak);
+
+        server_entry.get_text().map(|txt| {
+            if !txt.is_empty() {
+                err_label.hide();
+                let state = LoginState::Credentials;
+                stack.set_visible_child_name(&state.to_string());
+                back.borrow_mut().push(state);
+            } else {
+                err_label.show();
+            }
+        });
+    });
+
+    let stack_weak = stack.downgrade();
+    back.connect_activate(move |_, _| {
+        let stack = upgrade_weak!(stack_weak);
+        back_history.borrow_mut().pop();
+        if let Some(state) = back_history.borrow().last() {
+            debug!("Go back to state {}", state.to_string());
+            stack.set_visible_child_name(&state.to_string());
+        } else {
+            debug!("There is no state to go back to. Go back to state greeter");
+            stack.set_visible_child_name(&LoginState::Greeter.to_string());
+        }
+    });
+
+    stack.insert_action_group("login", &actions);
+    headers.insert_action_group("login", &actions);
+
+    actions
+}
diff --git a/fractal-gtk/src/actions/mod.rs b/fractal-gtk/src/actions/mod.rs
index 70697a83..72235b56 100644
--- a/fractal-gtk/src/actions/mod.rs
+++ b/fractal-gtk/src/actions/mod.rs
@@ -7,12 +7,15 @@ use gtk::WidgetExt;
 
 pub mod account_settings;
 pub mod global;
+pub mod login;
 pub mod room_history;
 pub mod room_settings;
 
 pub use self::account_settings as AccountSettings;
 pub use self::global as Global;
 pub use self::global::AppState;
+pub use self::login as Login;
+pub use self::login::LoginState;
 pub use self::room_history as RoomHistory;
 pub use self::room_settings as RoomSettings;
 
diff --git a/fractal-gtk/src/app/backend_loop.rs b/fractal-gtk/src/app/backend_loop.rs
index 4b70e155..6e7b5a9f 100644
--- a/fractal-gtk/src/app/backend_loop.rs
+++ b/fractal-gtk/src/app/backend_loop.rs
@@ -239,6 +239,7 @@ pub fn backend_loop(rx: Receiver<BKResponse>) {
                     let error = i18n("Can’t login, try again");
                     let st = AppState::Login;
                     APPOP!(show_error, (error));
+                    APPOP!(logout);
                     APPOP!(set_state, (st));
                 }
                 Ok(BKResponse::AttachFileError(err)) => {
diff --git a/fractal-gtk/src/app/connect/mod.rs b/fractal-gtk/src/app/connect/mod.rs
index c37f95e7..037eb15a 100644
--- a/fractal-gtk/src/app/connect/mod.rs
+++ b/fractal-gtk/src/app/connect/mod.rs
@@ -6,7 +6,6 @@ mod headerbar;
 mod invite;
 mod join_room;
 mod leave_room;
-mod login;
 mod markdown;
 mod new_room;
 mod roomlist_search;
@@ -17,7 +16,6 @@ use crate::app::App;
 impl App {
     pub fn connect_gtk(&self) {
         self.connect_headerbars();
-        self.connect_login_view();
 
         self.connect_send();
         self.connect_markdown();
diff --git a/fractal-gtk/src/app/mod.rs b/fractal-gtk/src/app/mod.rs
index 34ffaff4..e690b26f 100644
--- a/fractal-gtk/src/app/mod.rs
+++ b/fractal-gtk/src/app/mod.rs
@@ -16,6 +16,7 @@ use crate::backend::Backend;
 use crate::actions;
 use crate::globals;
 use crate::uibuilder;
+use crate::widgets;
 
 mod connect;
 mod windowstate;
@@ -148,6 +149,11 @@ impl App {
 
         let op = Arc::new(Mutex::new(AppOp::new(ui.clone(), apptx)));
 
+        // Add login view to the main stack
+        let login = widgets::LoginWidget::new(&op);
+        stack.add_named(&login.container, "login");
+        stack_header.add_named(&login.headers, "login");
+
         unsafe {
             OP = Some(Arc::downgrade(&op));
         }
diff --git a/fractal-gtk/src/appop/login.rs b/fractal-gtk/src/appop/login.rs
index 21a265d9..b338d5b1 100644
--- a/fractal-gtk/src/appop/login.rs
+++ b/fractal-gtk/src/appop/login.rs
@@ -25,7 +25,6 @@ use crate::widgets::ErrorDialog;
 impl AppOp {
     pub fn bk_login(&mut self, uid: String, token: String, device: Option<String>) {
         self.logged_in = true;
-        self.clean_login();
         if let Err(_) = self.store_token(uid.clone(), token) {
             error!("Can't store the token using libsecret");
         }
@@ -72,112 +71,6 @@ impl AppOp {
         backend_loop(rx);
     }
 
-    pub fn clean_login(&self) {
-        let user_entry: gtk::Entry = self
-            .ui
-            .builder
-            .get_object("login_username")
-            .expect("Can't find login_username in ui file.");
-        let pass_entry: gtk::Entry = self
-            .ui
-            .builder
-            .get_object("login_password")
-            .expect("Can't find login_password in ui file.");
-        let server_entry: gtk::Entry = self
-            .ui
-            .builder
-            .get_object("login_server")
-            .expect("Can't find login_server in ui file.");
-        let idp_entry: gtk::Entry = self
-            .ui
-            .builder
-            .get_object("login_idp")
-            .expect("Can't find login_idp in ui file.");
-
-        user_entry.set_text("");
-        pass_entry.set_text("");
-        server_entry.set_text(globals::DEFAULT_HOMESERVER);
-        idp_entry.set_text(globals::DEFAULT_IDENTITYSERVER);
-    }
-
-    pub fn login(&mut self) {
-        let user_entry: gtk::Entry = self
-            .ui
-            .builder
-            .get_object("login_username")
-            .expect("Can't find login_username in ui file.");
-        let pass_entry: gtk::Entry = self
-            .ui
-            .builder
-            .get_object("login_password")
-            .expect("Can't find login_password in ui file.");
-        let server_entry: gtk::Entry = self
-            .ui
-            .builder
-            .get_object("login_server")
-            .expect("Can't find login_server in ui file.");
-        let idp_entry: gtk::Entry = self
-            .ui
-            .builder
-            .get_object("login_idp")
-            .expect("Can't find login_idp in ui file.");
-        let login_error: gtk::Label = self
-            .ui
-            .builder
-            .get_object("login_error_msg")
-            .expect("Can't find login_error_msg in ui file.");
-
-        let username = user_entry.get_text();
-        let password = pass_entry.get_text();
-        let server = server_entry.get_text();
-        let identity = idp_entry.get_text();
-
-        if username.clone().unwrap_or_default().is_empty()
-            || password.clone().unwrap_or_default().is_empty()
-        {
-            login_error.set_text(i18n("Invalid username or password").as_str());
-            login_error.show();
-            return;
-        } else {
-            login_error.set_text(i18n("Unknown Error").as_str());
-            login_error.hide();
-        }
-
-        /* FIXME: validate server and identity same as username and passwod */
-
-        self.set_state(AppState::Loading);
-        self.since = None;
-        self.connect(username, password, server, identity);
-    }
-
-    pub fn set_login_pass(&self, username: &str, password: &str, server: &str, identity: &str) {
-        let user_entry: gtk::Entry = self
-            .ui
-            .builder
-            .get_object("login_username")
-            .expect("Can't find login_username in ui file.");
-        let pass_entry: gtk::Entry = self
-            .ui
-            .builder
-            .get_object("login_password")
-            .expect("Can't find login_password in ui file.");
-        let server_entry: gtk::Entry = self
-            .ui
-            .builder
-            .get_object("login_server")
-            .expect("Can't find login_server in ui file.");
-        let idp_entry: gtk::Entry = self
-            .ui
-            .builder
-            .get_object("login_idp")
-            .expect("Can't find login_idp in ui file.");
-
-        user_entry.set_text(username);
-        pass_entry.set_text(password);
-        server_entry.set_text(server);
-        idp_entry.set_text(identity);
-    }
-
     #[allow(dead_code)]
     pub fn register(&mut self) {
         let user_entry: gtk::Entry = self
diff --git a/fractal-gtk/src/appop/mod.rs b/fractal-gtk/src/appop/mod.rs
index 9b3e79b6..af5fd931 100644
--- a/fractal-gtk/src/appop/mod.rs
+++ b/fractal-gtk/src/appop/mod.rs
@@ -33,7 +33,7 @@ mod notify;
 mod room;
 mod room_settings;
 mod start_chat;
-mod state;
+pub mod state;
 mod sync;
 mod user;
 
@@ -135,7 +135,6 @@ impl AppOp {
             if let Ok((token, uid)) = self.get_token() {
                 self.set_token(Some(token), Some(uid), Some(pass.2));
             } else {
-                self.set_login_pass(&pass.0, &pass.1, &pass.2, &pass.3);
                 self.connect(Some(pass.0), Some(pass.1), Some(pass.2), Some(pass.3));
             }
         } else {
diff --git a/fractal-gtk/src/appop/state.rs b/fractal-gtk/src/appop/state.rs
index cc33fa1a..7c9699ea 100644
--- a/fractal-gtk/src/appop/state.rs
+++ b/fractal-gtk/src/appop/state.rs
@@ -20,10 +20,7 @@ impl AppOp {
             .expect("Can't find room_header_bar in ui file.");
 
         let widget_name = match self.state {
-            AppState::Login => {
-                self.clean_login();
-                "login"
-            }
+            AppState::Login => "login",
             AppState::NoRoom => {
                 self.set_state_no_room(&headerbar);
                 self.leaflet.set_visible_child_name("sidebar");
@@ -53,7 +50,7 @@ impl AppOp {
         let bar_name = match self.state {
             AppState::Login => "login",
             AppState::Directory => "back",
-            AppState::Loading => "login",
+            AppState::Loading => "loading",
             AppState::AccountSettings => "account-settings",
             AppState::RoomSettings => "room-settings",
             AppState::MediaViewer => "media-viewer",
@@ -68,7 +65,6 @@ impl AppOp {
 
         //set focus for views
         let widget_focus = match self.state {
-            AppState::Login => "login_username",
             AppState::Directory => "directory_search_entry",
             _ => "",
         };
diff --git a/fractal-gtk/src/globals.rs b/fractal-gtk/src/globals.rs
index dd627f7a..0657f9d0 100644
--- a/fractal-gtk/src/globals.rs
+++ b/fractal-gtk/src/globals.rs
@@ -6,6 +6,7 @@ pub static MINUTES_TO_SPLIT_MSGS: i64 = 30;
 pub static DEFAULT_HOMESERVER: &'static str = "https://matrix.org";;
 pub static DEFAULT_IDENTITYSERVER: &'static str = "https://vector.im";;
 pub static PLACEHOLDER_TEXT: &'static str = "Matrix username, email or phone number";
+pub static RIOT_REGISTER_URL: &'static str = "https://riot.im/app/#/register";;
 
 pub static MAX_IMAGE_SIZE: (i32, i32) = (600, 400);
 pub static MAX_STICKER_SIZE: (i32, i32) = (200, 130);
diff --git a/fractal-gtk/src/widgets/login.rs b/fractal-gtk/src/widgets/login.rs
new file mode 100644
index 00000000..4ed2a0c9
--- /dev/null
+++ b/fractal-gtk/src/widgets/login.rs
@@ -0,0 +1,164 @@
+use gio::prelude::*;
+use gtk::prelude::*;
+use log::info;
+
+use crate::actions;
+use crate::actions::global::AppState;
+use crate::actions::login::LoginState;
+use crate::appop::AppOp;
+
+use fractal_api::backend::register::get_well_known;
+
+use std::sync::{Arc, Mutex};
+
+#[derive(Debug, Clone)]
+pub struct LoginWidget {
+    pub container: gtk::Stack,
+    pub headers: gtk::Stack,
+    pub server_entry: gtk::Entry,
+    pub username_entry: gtk::Entry,
+    pub password_entry: gtk::Entry,
+    server_err_label: gtk::Label,
+    credentials_err_label: gtk::Label,
+    actions: gio::SimpleActionGroup,
+}
+
+impl LoginWidget {
+    pub fn new(op: &Arc<Mutex<AppOp>>) -> Self {
+        let widget = Self::default();
+
+        let weak_server = widget.server_entry.downgrade();
+        let weak_username = widget.username_entry.downgrade();
+        let weak_password = widget.password_entry.downgrade();
+        let weak_err = widget.credentials_err_label.downgrade();
+
+        // Grab the focus for each state
+        let weak_ser = weak_server.clone();
+        let weak_user = weak_username.clone();
+        widget
+            .container
+            .connect_property_visible_child_name_notify(move |container| {
+                let server = upgrade_weak!(weak_ser);
+                let username = upgrade_weak!(weak_user);
+
+                let state: LoginState = container.get_visible_child_name().unwrap().into();
+
+                match state {
+                    LoginState::ServerChooser => server.grab_focus(),
+                    LoginState::Credentials => username.grab_focus(),
+                    _ => (),
+                }
+            });
+
+        let op = op.clone();
+        let weak_server = widget.server_entry.downgrade();
+
+        let login = widget
+            .actions
+            .lookup_action("login")
+            .expect("Could not find 'login' action for LoginWidget")
+            .downcast::<gio::SimpleAction>()
+            .expect("Could not cast action 'login' to SimpleAction");
+
+        let weak_pass = weak_password.clone();
+        login.connect_activate(move |_, _| {
+            let server_entry = upgrade_weak!(weak_server);
+            let username_entry = upgrade_weak!(weak_username);
+            let password_entry = upgrade_weak!(weak_pass);
+            let err_label = upgrade_weak!(weak_err);
+
+            if let Some(txt) = server_entry.get_text() {
+                let username = username_entry.get_text().unwrap_or_default();
+                let password = password_entry.get_text().unwrap_or_default();
+
+                let txt = format!(
+                    "{}{}",
+                    "https://";,
+                    String::from(txt)
+                        .trim()
+                        .trim_start_matches("http://";)
+                        .trim_start_matches("https://";)
+                );
+
+                if !password.is_empty() && !username.is_empty() {
+                    // take the user's homeserver value if the
+                    // well-known request fails
+                    let mut homeserver_url = txt.clone();
+                    let mut idserver = None;
+                    match get_well_known(&txt) {
+                        Ok(response) => {
+                            info!("Got well-known response from {}: {:#?}", &txt, response);
+                            homeserver_url = response.homeserver.unwrap_or(txt);
+                            idserver = response.identity_server;
+                        }
+                        Err(e) => info!("Failed to .well-known request: {:#?}", e),
+                    };
+
+                    err_label.hide();
+                    op.lock().unwrap().set_state(AppState::Loading);
+                    op.lock().unwrap().since = None;
+                    op.lock().unwrap().connect(
+                        Some(username),
+                        Some(password),
+                        Some(homeserver_url),
+                        idserver,
+                    );
+                } else {
+                    err_label.show();
+                }
+            }
+        });
+
+        let credentials = widget
+            .actions
+            .lookup_action("credentials")
+            .expect("Could not find 'credentials' action for LoginWidget")
+            .downcast::<gio::SimpleAction>()
+            .expect("Could not cast action 'credentials' to SimpleAction");
+        widget
+            .server_entry
+            .connect_activate(move |_| credentials.activate(None));
+
+        widget.username_entry.connect_activate(move |_| {
+            let password_entry = upgrade_weak!(weak_password);
+            password_entry.grab_focus();
+        });
+
+        widget
+            .password_entry
+            .connect_activate(move |_| login.activate(None));
+
+        widget
+    }
+}
+
+impl Default for LoginWidget {
+    fn default() -> Self {
+        let builder = gtk::Builder::new_from_resource("/org/gnome/Fractal/ui/login_flow.ui");
+
+        let container: gtk::Stack = builder.get_object("login_flow_stack").unwrap();
+        let headers: gtk::Stack = builder.get_object("login_flow_headers").unwrap();
+        let server_entry = builder.get_object("server_chooser_entry").unwrap();
+        let username_entry = builder.get_object("username_entry").unwrap();
+        let password_entry = builder.get_object("password_entry").unwrap();
+
+        let server_err_label = builder.get_object("server_err_label").unwrap();
+        let credentials_err_label = builder.get_object("credentials_err_label").unwrap();
+
+        let actions = actions::Login::new(&container, &headers, &server_entry, &server_err_label);
+
+        container.show_all();
+        headers.show_all();
+
+        LoginWidget {
+            container,
+            headers,
+            server_entry,
+            username_entry,
+            password_entry,
+            server_err_label,
+            credentials_err_label,
+            actions,
+        }
+    }
+}
diff --git a/fractal-gtk/src/widgets/mod.rs b/fractal-gtk/src/widgets/mod.rs
index 81b422f1..7cca372b 100644
--- a/fractal-gtk/src/widgets/mod.rs
+++ b/fractal-gtk/src/widgets/mod.rs
@@ -6,6 +6,7 @@ pub mod error_dialog;
 pub mod file_dialog;
 pub mod image;
 mod inline_player;
+mod login;
 pub mod media_viewer;
 mod member;
 pub mod members_list;
@@ -31,6 +32,7 @@ pub use self::divider::NewMessageDivider;
 pub use self::error_dialog as ErrorDialog;
 pub use self::file_dialog as FileDialog;
 pub use self::inline_player::AudioPlayerWidget;
+pub use self::login::LoginWidget;
 pub use self::media_viewer::MediaViewer;
 pub use self::member::MemberBox;
 pub use self::members_list::MembersList;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]