Browse Source

login: Use AdwNavigationView

fractal-7
Kévin Commaille 2 years ago
parent
commit
ec6a025679
No known key found for this signature in database
GPG Key ID: 29A48C1F03620416
  1. 6
      po/POTFILES.in
  2. 16
      src/login/greeter.rs
  3. 17
      src/login/greeter.ui
  4. 17
      src/login/homeserver_page.rs
  5. 206
      src/login/homeserver_page.ui
  6. 10
      src/login/method_page.rs
  7. 278
      src/login/method_page.ui
  8. 190
      src/login/mod.rs
  9. 224
      src/login/mod.ui
  10. 144
      src/login/session_setup_view.rs
  11. 481
      src/login/session_setup_view.ui
  12. 12
      src/login/sso_page.rs
  13. 108
      src/login/sso_page.ui
  14. 9
      src/main.rs
  15. 390
      src/session_setup_view/mod.ui
  16. 4
      src/ui-resources.gresource.xml
  17. 43
      src/window.rs
  18. 9
      src/window.ui

6
po/POTFILES.in

@ -29,7 +29,6 @@ src/components/user_profile_dialog.ui
src/contrib/qr_code_scanner/mod.ui
src/contrib/qr_code.rs
src/error_page.ui
src/greeter.ui
src/identity_verification_view/accept_request_page.rs
src/identity_verification_view/accept_request_page.ui
src/identity_verification_view/cancelled_page.rs
@ -52,6 +51,7 @@ src/identity_verification_view/scan_qr_code_page.ui
src/identity_verification_view/wait_for_other_page.rs
src/identity_verification_view/wait_for_other_page.ui
src/login/advanced_dialog.ui
src/login/greeter.ui
src/login/homeserver_page.rs
src/login/homeserver_page.ui
src/login/idp_button.rs
@ -59,6 +59,8 @@ src/login/method_page.rs
src/login/method_page.ui
src/login/mod.rs
src/login/mod.ui
src/login/session_setup_view.rs
src/login/session_setup_view.ui
src/login/sso_page.ui
src/secret/linux.rs
src/session/model/session.rs
@ -169,8 +171,6 @@ src/session/view/sidebar/mod.ui
src/session/view/sidebar/room_row.rs
src/session/view/sidebar/row.rs
src/session_list/mod.rs
src/session_setup_view/mod.rs
src/session_setup_view/mod.ui
src/shortcuts.ui
src/user_facing_error.rs
src/utils/matrix.rs

16
src/greeter.rs → src/login/greeter.rs

@ -1,5 +1,5 @@
use adw::subclass::prelude::BinImpl;
use gtk::{glib, prelude::*, subclass::prelude::*, CompositeTemplate};
use adw::{prelude::*, subclass::prelude::*};
use gtk::{glib, CompositeTemplate};
use crate::components::OfflineBanner;
@ -9,7 +9,7 @@ mod imp {
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/org/gnome/Fractal/ui/greeter.ui")]
#[template(resource = "/org/gnome/Fractal/ui/login/greeter.ui")]
pub struct Greeter {
#[template_child]
pub login_button: TemplateChild<gtk::Button>,
@ -19,7 +19,7 @@ mod imp {
impl ObjectSubclass for Greeter {
const NAME: &'static str = "Greeter";
type Type = super::Greeter;
type ParentType = adw::Bin;
type ParentType = adw::NavigationPage;
fn class_init(klass: &mut Self::Class) {
OfflineBanner::ensure_type();
@ -36,22 +36,18 @@ mod imp {
impl ObjectImpl for Greeter {}
impl WidgetImpl for Greeter {}
impl BinImpl for Greeter {}
impl NavigationPageImpl for Greeter {}
impl AccessibleImpl for Greeter {}
}
glib::wrapper! {
/// The welcome screen of the app.
pub struct Greeter(ObjectSubclass<imp::Greeter>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
@extends gtk::Widget, adw::NavigationPage, @implements gtk::Accessible;
}
impl Greeter {
pub fn new() -> Self {
glib::Object::new()
}
pub fn default_widget(&self) -> gtk::Widget {
self.imp().login_button.get().upcast()
}
}

17
src/greeter.ui → src/login/greeter.ui

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="Greeter" parent="AdwBin">
<accessibility>
<relation name="labelled-by">title</relation>
</accessibility>
<child>
<template class="Greeter" parent="AdwNavigationPage">
<property name="tag">greeter</property>
<!-- Translators: Fractal is the application name. -->
<property name="title" translatable="yes">Welcome to Fractal</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
@ -47,7 +47,7 @@
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="vexpand">true</property>
<property name="vexpand">True</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
@ -116,7 +116,8 @@
<property name="can-shrink">true</property>
<property name="label" translatable="yes">_Log In</property>
<property name="use-underline">true</property>
<property name="action-name">win.show-login</property>
<property name="action-name">navigation.push</property>
<property name="action-target">'homeserver'</property>
</object>
</child>
<child>
@ -144,6 +145,6 @@
</object>
</property>
</object>
</child>
</property>
</template>
</interface>

17
src/login/homeserver_page.rs

@ -1,6 +1,6 @@
use adw::{prelude::*, subclass::prelude::BinImpl};
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{self, glib, glib::clone, subclass::prelude::*, CompositeTemplate};
use gtk::{self, glib, glib::clone, CompositeTemplate};
use matrix_sdk::{
config::RequestConfig, sanitize_server_name, Client, ClientBuildError, ClientBuilder,
};
@ -10,7 +10,10 @@ use url::{ParseError, Url};
use super::Login;
use crate::{
components::SpinnerButton, gettext_f, prelude::*, spawn, spawn_tokio, toast,
components::{OfflineBanner, SpinnerButton},
gettext_f,
prelude::*,
spawn, spawn_tokio, toast,
utils::BoundObjectWeakRef,
};
@ -38,9 +41,11 @@ mod imp {
impl ObjectSubclass for LoginHomeserverPage {
const NAME: &'static str = "LoginHomeserverPage";
type Type = super::LoginHomeserverPage;
type ParentType = adw::Bin;
type ParentType = adw::NavigationPage;
fn class_init(klass: &mut Self::Class) {
OfflineBanner::ensure_type();
Self::bind_template(klass);
Self::Type::bind_template_callbacks(klass);
}
@ -59,7 +64,7 @@ mod imp {
}
}
impl BinImpl for LoginHomeserverPage {}
impl NavigationPageImpl for LoginHomeserverPage {}
impl LoginHomeserverPage {
/// Set the parent `Login` object.
@ -86,7 +91,7 @@ mod imp {
glib::wrapper! {
/// The login page to provide the homeserver and login settings.
pub struct LoginHomeserverPage(ObjectSubclass<imp::LoginHomeserverPage>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
@extends gtk::Widget, adw::NavigationPage, @implements gtk::Accessible;
}
#[gtk::template_callbacks]

206
src/login/homeserver_page.ui

@ -1,107 +1,127 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="LoginHomeserverPage" parent="AdwBin">
<template class="LoginHomeserverPage" parent="AdwNavigationPage">
<property name="tag">homeserver</property>
<property name="title" translatable="yes">Homeserver</property>
<property name="child">
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">360</property>
<property name="margin-top">24</property>
<property name="margin-bottom">24</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="valign">center</property>
<property name="spacing">24</property>
<property name="accessible-role">form</property>
<accessibility>
<relation name="labelled-by">title</relation>
</accessibility>
<child>
<object class="GtkLabel" id="title">
<property name="label" translatable="yes">Homeserver</property>
<property name="wrap">true</property>
<property name="justify">center</property>
<property name="accessible-role">heading</property>
<accessibility>
<property name="level">1</property>
</accessibility>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkPicture">
<property name="file">resource:///org/gnome/Fractal/assets/homeserver.svg</property>
<property name="accessible-role">presentation</property>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkListBox">
<property name="accessible-role">group</property>
<style>
<class name="boxed-list"/>
</style>
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-title">False</property>
</object>
</child>
<property name="content">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="OfflineBanner" />
</child>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="vexpand">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">360</property>
<property name="margin-top">24</property>
<property name="margin-bottom">24</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="valign">center</property>
<property name="spacing">24</property>
<property name="accessible-role">form</property>
<accessibility>
<relation name="labelled-by">title</relation>
</accessibility>
<child>
<object class="AdwEntryRow" id="homeserver_entry">
<property name="selectable">false</property>
<signal name="changed" handler="update_next_state" swapped="yes"/>
<signal name="entry-activated" handler="fetch_homeserver_details" swapped="yes"/>
<object class="GtkLabel" id="title">
<property name="label" translatable="yes">Homeserver</property>
<property name="wrap">true</property>
<property name="justify">center</property>
<property name="accessible-role">heading</property>
<accessibility>
<relation name="described-by">homeserver_help</relation>
<property name="level">1</property>
</accessibility>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkPicture">
<property name="file">resource:///org/gnome/Fractal/assets/homeserver.svg</property>
<property name="accessible-role">presentation</property>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkListBox">
<property name="accessible-role">group</property>
<style>
<class name="boxed-list"/>
</style>
<child>
<object class="AdwEntryRow" id="homeserver_entry">
<property name="selectable">false</property>
<signal name="changed" handler="update_next_state" swapped="yes"/>
<signal name="entry-activated" handler="fetch_homeserver_details" swapped="yes"/>
<accessibility>
<relation name="described-by">homeserver_help</relation>
</accessibility>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="homeserver_help">
<style>
<class name="caption"/>
<class name="dim-label"/>
</style>
<property name="justify">left</property>
<property name="xalign">0.0</property>
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="wrap">true</property>
<property name="use-markup">true</property>
</object>
</child>
</object>
</child>
<child>
<object class="SpinnerButton" id="next_button">
<property name="content-label" translatable="yes">Next</property>
<property name="halign">center</property>
<signal name="clicked" handler="fetch_homeserver_details" swapped="yes"/>
<style>
<class name="suggested-action"/>
<class name="standalone-button"/>
<class name="pill"/>
</style>
</object>
</child>
<child>
<object class="GtkButton">
<property name="can-shrink">true</property>
<property name="halign">center</property>
<!-- Translators: As in 'Advanced Settings'. -->
<property name="label" translatable="yes">Advanced…</property>
<property name="action-name">login.open-advanced</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="homeserver_help">
<style>
<class name="caption"/>
<class name="dim-label"/>
</style>
<property name="justify">left</property>
<property name="xalign">0.0</property>
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="wrap">true</property>
<property name="use-markup">true</property>
</object>
</child>
</object>
</child>
<child>
<object class="SpinnerButton" id="next_button">
<property name="content-label" translatable="yes">Next</property>
<property name="halign">center</property>
<signal name="clicked" handler="fetch_homeserver_details" swapped="yes"/>
<style>
<class name="suggested-action"/>
<class name="standalone-button"/>
<class name="pill"/>
</style>
</object>
</child>
<child>
<object class="GtkButton">
<property name="can-shrink">true</property>
<property name="halign">center</property>
<!-- Translators: As in 'Advanced Settings'. -->
<property name="label" translatable="yes">Advanced…</property>
<property name="action-name">login.open-advanced</property>
</property>
</object>
</child>
</property>
</object>
</property>
</child>
</object>
</property>
</object>

10
src/login/method_page.rs

@ -1,6 +1,6 @@
use adw::{prelude::*, subclass::prelude::BinImpl};
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{self, glib, glib::clone, subclass::prelude::*, CompositeTemplate};
use gtk::{self, glib, glib::clone, CompositeTemplate};
use ruma::api::client::session::get_login_types::v3::LoginType;
use tracing::warn;
@ -40,7 +40,7 @@ mod imp {
impl ObjectSubclass for LoginMethodPage {
const NAME: &'static str = "LoginMethodPage";
type Type = super::LoginMethodPage;
type ParentType = adw::Bin;
type ParentType = adw::NavigationPage;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
@ -61,7 +61,7 @@ mod imp {
}
}
impl BinImpl for LoginMethodPage {}
impl NavigationPageImpl for LoginMethodPage {}
impl LoginMethodPage {
/// Set the parent `Login` object.
@ -93,7 +93,7 @@ mod imp {
glib::wrapper! {
/// The login page allowing to login via password or to choose a SSO provider.
pub struct LoginMethodPage(ObjectSubclass<imp::LoginMethodPage>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
@extends gtk::Widget, adw::NavigationPage, @implements gtk::Accessible;
}
#[gtk::template_callbacks]

278
src/login/method_page.ui

@ -1,148 +1,168 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="LoginMethodPage" parent="AdwBin">
<template class="LoginMethodPage" parent="AdwNavigationPage">
<property name="tag">method</property>
<property name="title" translatable="yes">Log In</property>
<property name="child">
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">360</property>
<property name="margin-top">24</property>
<property name="margin-bottom">24</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="valign">center</property>
<property name="spacing">30</property>
<property name="accessible-role">form</property>
<accessibility>
<relation name="labelled-by">title</relation>
<relation name="described-by">homeserver_url</relation>
</accessibility>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel" id="title">
<style>
<class name="title-1"/>
</style>
<property name="wrap">true</property>
<property name="justify">center</property>
<property name="accessible-role">heading</property>
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-title">False</property>
</object>
</child>
<property name="content">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="OfflineBanner" />
</child>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="vexpand">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">360</property>
<property name="margin-top">24</property>
<property name="margin-bottom">24</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="valign">center</property>
<property name="spacing">30</property>
<property name="accessible-role">form</property>
<accessibility>
<property name="level">1</property>
<relation name="labelled-by">title</relation>
<relation name="described-by">homeserver_url</relation>
</accessibility>
</object>
</child>
<child>
<object class="GtkBox">
<property name="spacing">6</property>
<property name="halign">center</property>
<binding name="visible">
<lookup name="autodiscovery">
<lookup name="login">LoginMethodPage</lookup>
</lookup>
</binding>
<property name="tooltip-text" translatable="yes">Homeserver URL</property>
<child>
<object class="GtkImage">
<property name="icon-name">home-symbolic</property>
<property name="accessible-role">presentation</property>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel" id="title">
<style>
<class name="title-1"/>
</style>
<property name="wrap">true</property>
<property name="justify">center</property>
<property name="accessible-role">heading</property>
<accessibility>
<property name="level">1</property>
</accessibility>
</object>
</child>
<child>
<object class="GtkBox">
<property name="spacing">6</property>
<property name="halign">center</property>
<binding name="visible">
<lookup name="autodiscovery">
<lookup name="login">LoginMethodPage</lookup>
</lookup>
</binding>
<property name="tooltip-text" translatable="yes">Homeserver URL</property>
<child>
<object class="GtkImage">
<property name="icon-name">home-symbolic</property>
<property name="accessible-role">presentation</property>
</object>
</child>
<child>
<object class="GtkLabel" id="homeserver_url">
<style>
<class name="body"/>
</style>
<binding name="label">
<lookup name="homeserver">
<lookup name="login">LoginMethodPage</lookup>
</lookup>
</binding>
<property name="ellipsize">end</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="homeserver_url">
<object class="GtkListBox">
<property name="accessible-role">group</property>
<style>
<class name="body"/>
<class name="boxed-list"/>
</style>
<child>
<object class="AdwEntryRow" id="username_entry">
<property name="title" translatable="yes">Matrix Username</property>
<property name="selectable">false</property>
<signal name="changed" handler="update_next_state" swapped="yes"/>
<signal name="entry-activated" handler="login_with_password" swapped="yes"/>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBox">
<property name="accessible-role">group</property>
<style>
<class name="boxed-list"/>
</style>
<child>
<object class="AdwPasswordEntryRow" id="password_entry">
<property name="title" translatable="yes">Password</property>
<property name="selectable">false</property>
<signal name="changed" handler="update_next_state" swapped="yes"/>
<signal name="entry-activated" handler="login_with_password" swapped="yes"/>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="sso_idp_box">
<property name="visible">false</property>
<property name="spacing">12</property>
<property name="homogeneous">true</property>
<property name="hexpand">true</property>
<property name="vexpand">true</property>
<accessibility>
<property name="label" translatable="yes">Single Sign-On Providers</property>
</accessibility>
</object>
</child>
<child>
<object class="GtkButton" id="more_sso_btn">
<style>
<class name="standalone-button"/>
<class name="pill"/>
</style>
<property name="can-shrink">true</property>
<property name="halign">center</property>
<property name="action-name">login.sso</property>
<property name="action-target">@ms nothing</property>
</object>
</child>
<child>
<object class="SpinnerButton" id="next_button">
<property name="content-label" translatable="yes">Next</property>
<property name="halign">center</property>
<signal name="clicked" handler="login_with_password" swapped="yes"/>
<style>
<class name="suggested-action"/>
<class name="standalone-button"/>
<class name="pill"/>
</style>
<binding name="label">
<lookup name="homeserver">
<lookup name="login">LoginMethodPage</lookup>
</lookup>
</binding>
<property name="ellipsize">end</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBox">
<property name="accessible-role">group</property>
<style>
<class name="boxed-list"/>
</style>
<child>
<object class="AdwEntryRow" id="username_entry">
<property name="title" translatable="yes">Matrix Username</property>
<property name="selectable">false</property>
<signal name="changed" handler="update_next_state" swapped="yes"/>
<signal name="entry-activated" handler="login_with_password" swapped="yes"/>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBox">
<property name="accessible-role">group</property>
<style>
<class name="boxed-list"/>
</style>
<child>
<object class="AdwPasswordEntryRow" id="password_entry">
<property name="title" translatable="yes">Password</property>
<property name="selectable">false</property>
<signal name="changed" handler="update_next_state" swapped="yes"/>
<signal name="entry-activated" handler="login_with_password" swapped="yes"/>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="sso_idp_box">
<property name="visible">false</property>
<property name="spacing">12</property>
<property name="homogeneous">true</property>
<property name="hexpand">true</property>
<property name="vexpand">true</property>
<accessibility>
<property name="label" translatable="yes">Single Sign-On Providers</property>
</accessibility>
</object>
</child>
<child>
<object class="GtkButton" id="more_sso_btn">
<style>
<class name="standalone-button"/>
<class name="pill"/>
</style>
<property name="can-shrink">true</property>
<property name="halign">center</property>
<property name="action-name">login.sso</property>
<property name="action-target">@ms nothing</property>
</object>
</child>
<child>
<object class="SpinnerButton" id="next_button">
<property name="content-label" translatable="yes">Next</property>
<property name="halign">center</property>
<signal name="clicked" handler="login_with_password" swapped="yes"/>
<style>
<class name="suggested-action"/>
<class name="standalone-button"/>
<class name="pill"/>
</style>
</property>
</object>
</child>
</property>
</object>
</property>
</child>
</object>
</property>
</object>

190
src/login/mod.rs

@ -1,6 +1,6 @@
use adw::{prelude::*, subclass::prelude::BinImpl};
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{self, gio, glib, glib::clone, subclass::prelude::*, CompositeTemplate};
use gtk::{self, gio, glib, glib::clone, CompositeTemplate};
use matrix_sdk::Client;
use ruma::{
api::client::session::{get_login_types::v3::LoginType, login},
@ -10,19 +10,20 @@ use tracing::{error, warn};
use url::Url;
mod advanced_dialog;
mod greeter;
mod homeserver_page;
mod idp_button;
mod method_page;
mod session_setup_view;
mod sso_page;
use self::{
advanced_dialog::LoginAdvancedDialog, homeserver_page::LoginHomeserverPage,
method_page::LoginMethodPage, sso_page::LoginSsoPage,
advanced_dialog::LoginAdvancedDialog, greeter::Greeter, homeserver_page::LoginHomeserverPage,
method_page::LoginMethodPage, session_setup_view::SessionSetupView, sso_page::LoginSsoPage,
};
use crate::{
components::OfflineBanner, prelude::*, secret::store_session, session::model::Session,
session_setup_view::SessionSetupView, spawn, spawn_tokio, toast, Application, Window,
WindowPage, RUNTIME,
components::OfflineBanner, prelude::*, secret::store_session, session::model::Session, spawn,
spawn_tokio, toast, Application, Window, RUNTIME,
};
#[derive(Clone, Debug, Default, glib::Boxed)]
@ -33,6 +34,8 @@ pub struct BoxedLoginTypes(Vec<LoginType>);
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumString, strum::AsRefStr)]
#[strum(serialize_all = "kebab-case")]
enum LoginPage {
/// The greeter page.
Greeter,
/// The homeserver page.
Homeserver,
/// The page to select a login method.
@ -59,9 +62,9 @@ mod imp {
#[properties(wrapper_type = super::Login)]
pub struct Login {
#[template_child]
pub back_button: TemplateChild<gtk::Button>,
pub navigation: TemplateChild<adw::NavigationView>,
#[template_child]
pub main_stack: TemplateChild<gtk::Stack>,
pub greeter: TemplateChild<Greeter>,
#[template_child]
pub homeserver_page: TemplateChild<LoginHomeserverPage>,
#[template_child]
@ -116,6 +119,7 @@ mod imp {
}));
},
);
klass.install_action("login.open-advanced", None, move |widget, _, _| {
spawn!(clone!(@weak widget => async move {
widget.open_advanced_dialog().await;
@ -142,14 +146,10 @@ mod imp {
}));
obj.action_set_enabled("login.sso", monitor.is_network_available());
self.main_stack.connect_transition_running_notify(
clone!(@weak self as imp => move |stack|
if !stack.is_transition_running() {
// Focus the default widget when the transition has ended.
imp.grab_focus();
}
),
);
self.navigation
.connect_visible_page_notify(clone!(@weak obj => move |_| {
obj.visible_page_changed();
}));
}
fn dispose(&self) {
@ -163,6 +163,7 @@ mod imp {
impl WidgetImpl for Login {
fn grab_focus(&self) -> bool {
match self.visible_page() {
LoginPage::Greeter => self.greeter.grab_focus(),
LoginPage::Homeserver => self.homeserver_page.grab_focus(),
LoginPage::Method => self.method_page.grab_focus(),
LoginPage::Sso | LoginPage::Loading => false,
@ -182,10 +183,11 @@ mod imp {
impl AccessibleImpl for Login {}
impl Login {
/// The visible page of the login stack.
/// The visible page of the view.
pub(super) fn visible_page(&self) -> LoginPage {
self.main_stack
.visible_child_name()
self.navigation
.visible_page()
.and_then(|p| p.tag())
.and_then(|s| s.as_str().try_into().ok())
.unwrap()
}
@ -222,8 +224,8 @@ mod imp {
/// Get the session setup view, if any.
pub(super) fn session_setup(&self) -> Option<SessionSetupView> {
self.main_stack
.child_by_name(LoginPage::SessionSetup.as_ref())
self.navigation
.find_page(LoginPage::SessionSetup.as_ref())
.and_downcast()
}
}
@ -241,6 +243,40 @@ impl Login {
glib::Object::new()
}
/// Set the visible page.
fn set_visible_page(&self, page: LoginPage) {
let navigation = &self.imp().navigation;
if page == LoginPage::Greeter {
navigation.pop_to_tag(page.as_ref());
} else {
navigation.push_by_tag(page.as_ref());
}
}
/// The visible page changed.
fn visible_page_changed(&self) {
let imp = self.imp();
match imp.visible_page() {
LoginPage::Greeter => {
self.clean();
}
LoginPage::Homeserver => {
// Drop the client because it is bound to the homeserver.
self.drop_client();
// Drop the session because it is bound to the homeserver and account.
self.drop_session();
self.imp().method_page.clean();
}
LoginPage::Method => {
// Drop the session because it is bound to the account.
self.drop_session();
}
_ => {}
}
}
fn parent_window(&self) -> Window {
self.root()
.and_downcast()
@ -337,65 +373,6 @@ impl Login {
.any(|t| matches!(t, LoginType::Password(_)))
}
/// Set the visible page of the login stack.
fn set_visible_page(&self, visible_child: LoginPage) {
self.imp()
.main_stack
.set_visible_child_name(visible_child.as_ref());
}
/// The page to go back to for the current login stack page.
fn previous_page(&self) -> Option<LoginPage> {
match self.imp().visible_page() {
LoginPage::Homeserver => None,
LoginPage::Method => Some(LoginPage::Homeserver),
LoginPage::Sso | LoginPage::Loading | LoginPage::SessionSetup => {
if self.supports_password() {
Some(LoginPage::Method)
} else {
Some(LoginPage::Homeserver)
}
}
// The go-back button should be deactivated.
LoginPage::Completed => None,
}
}
/// Go back to the previous step.
#[template_callback]
fn go_previous(&self) {
let session_setup = self.imp().session_setup();
if let Some(session_setup) = &session_setup {
if session_setup.go_previous() {
// The session setup handled the action.
return;
}
}
let Some(previous_page) = self.previous_page() else {
self.parent_window().set_visible_page(WindowPage::Greeter);
self.clean();
return;
};
self.set_visible_page(previous_page);
match previous_page {
LoginPage::Homeserver => {
// Drop the client because it is bound to the homeserver.
self.drop_client();
// Drop the session because it is bound to the homeserver and account.
self.drop_session();
self.imp().method_page.clean();
}
LoginPage::Method => {
// Drop the session because it is bound to the account.
self.drop_session();
}
_ => {}
}
}
async fn open_advanced_dialog(&self) {
let dialog = LoginAdvancedDialog::new();
self.bind_property("autodiscovery", &dialog, "autodiscovery")
@ -455,7 +432,7 @@ impl Login {
Err(error) => {
warn!("Could not log in: {error}");
toast!(self, error.to_user_facing());
self.go_previous();
self.imp().navigation.pop();
}
}
}
@ -475,15 +452,22 @@ impl Login {
warn!("Could not create session: {error}");
toast!(self, error.to_user_facing());
self.go_previous();
self.imp().navigation.pop();
}
}
}
pub async fn init_session(&self, session: Session) {
self.set_visible_page(LoginPage::Loading);
let imp = self.imp();
let setup_view = SessionSetupView::new(&session);
setup_view.connect_completed(clone!(@weak self as obj => move |_| {
obj.show_completed();
}));
imp.navigation.push(&setup_view);
self.drop_client();
self.imp().session.replace(Some(session.clone()));
imp.session.replace(Some(session.clone()));
// Save ID of logging in session to GSettings
let settings = Application::default().settings();
@ -499,40 +483,14 @@ impl Login {
toast!(self, gettext("Could not store session"));
}
session.connect_ready(clone!(@weak self as obj => move |_| {
spawn!(clone!(@weak obj => async move {
obj.check_verification().await;
}));
}));
session.prepare().await;
}
/// Check whether the logged in session needs to be verified.
async fn check_verification(&self) {
let imp = self.imp();
let session = imp.session.borrow().clone().unwrap();
if session.is_verified().await {
self.finish_login();
return;
}
let setup_view = SessionSetupView::new(&session);
setup_view.connect_completed(clone!(@weak self as obj => move |_| {
obj.show_completed();
}));
imp.main_stack
.add_named(&setup_view, Some(LoginPage::SessionSetup.as_ref()));
self.set_visible_page(LoginPage::SessionSetup);
}
/// Show the completed page.
#[template_callback]
pub fn show_completed(&self) {
let imp = self.imp();
imp.back_button.set_visible(false);
self.set_visible_page(LoginPage::Completed);
imp.done_button.grab_focus();
}
@ -553,9 +511,6 @@ impl Login {
// Clean pages.
imp.homeserver_page.clean();
imp.method_page.clean();
if let Some(session_setup) = imp.session_setup() {
imp.main_stack.remove(&session_setup);
}
// Clean data.
self.set_autodiscovery(true);
@ -566,18 +521,17 @@ impl Login {
self.drop_session();
// Reinitialize UI.
self.set_visible_page(LoginPage::Homeserver);
imp.back_button.set_visible(true);
self.set_visible_page(LoginPage::Greeter);
self.unfreeze();
}
/// Freeze the login screen.
fn freeze(&self) {
self.imp().main_stack.set_sensitive(false);
self.imp().navigation.set_sensitive(false);
}
/// Unfreeze the login screen.
fn unfreeze(&self) {
self.imp().main_stack.set_sensitive(true);
self.imp().navigation.set_sensitive(true);
}
}

224
src/login/mod.ui

@ -1,150 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="Login" parent="AdwBin">
<child>
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-title">False</property>
<accessibility>
<property name="label" translatable="yes">Header Bar</property>
</accessibility>
<child type="start">
<object class="GtkButton" id="back_button">
<property name="icon-name">go-previous-symbolic</property>
<signal name="clicked" handler="go_previous" swapped="yes"/>
<property name="tooltip-text" translatable="yes">Back</property>
<accessibility>
<property name="label" translatable="yes">Back</property>
</accessibility>
</object>
</child>
<style>
<class name="flat"/>
</style>
<property name="child">
<object class="AdwNavigationView" id="navigation">
<child>
<object class="Greeter" id="greeter" />
</child>
<child>
<object class="LoginHomeserverPage" id="homeserver_page">
<property name="login">Login</property>
</object>
</child>
<property name="content">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="OfflineBanner" />
</child>
<child>
<object class="GtkStack" id="main_stack">
<property name="transition-type">crossfade</property>
<property name="vexpand">True</property>
<child>
<object class="GtkStackPage">
<property name="name">homeserver</property>
<property name="title" translatable="yes">Homeserver</property>
<property name="child">
<object class="LoginHomeserverPage" id="homeserver_page">
<property name="login">Login</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">method</property>
<property name="title" translatable="yes">Login Form</property>
<property name="child">
<object class="LoginMethodPage" id="method_page">
<property name="login">Login</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">sso</property>
<property name="title" translatable="yes">Single Sign-On</property>
<property name="child">
<object class="LoginSsoPage" id="sso_page"/>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">loading</property>
<property name="title" translatable="yes">Loading</property>
<property name="child">
<object class="Spinner" id="loading_page">
<property name="valign">center</property>
<property name="halign">center</property>
<style>
<class name="large"/>
</style>
</object>
</property>
<child>
<object class="LoginMethodPage" id="method_page">
<property name="login">Login</property>
</object>
</child>
<child>
<object class="LoginSsoPage" id="sso_page"/>
</child>
<child>
<object class="AdwNavigationPage">
<property name="tag">completed</property>
<property name="title" translatable="yes">Login Complete</property>
<property name="can-pop">false</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-title">False</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">completed</property>
<property name="title" translatable="yes">Login Complete</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">400</property>
<property name="tightening-threshold">300</property>
<property name="content">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="OfflineBanner" />
</child>
<child>
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="vexpand">True</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<property name="valign">center</property>
<child>
<object class="GtkLabel">
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<property name="label" translatable="yes">Login Complete</property>
<property name="accessible-role">heading</property>
<accessibility>
<property name="level">1</property>
</accessibility>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkPicture">
<property name="file">resource:///org/gnome/Fractal/assets/setup-complete.svg</property>
<property name="accessible-role">presentation</property>
<object class="AdwClamp">
<property name="maximum-size">400</property>
<property name="tightening-threshold">300</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<property name="valign">center</property>
<child>
<object class="GtkLabel">
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<property name="label" translatable="yes">Login Complete</property>
<property name="accessible-role">heading</property>
<accessibility>
<property name="level">1</property>
</accessibility>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkPicture">
<property name="file">resource:///org/gnome/Fractal/assets/setup-complete.svg</property>
<property name="accessible-role">presentation</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<property name="label" translatable="yes">This session is ready to send and receive secure messages.</property>
</object>
</child>
<child>
<object class="GtkButton" id="done_button">
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Start Chatting</property>
<property name="halign">center</property>
<signal name="clicked" handler="finish_login" swapped="yes"/>
<style>
<class name="suggested-action"/>
<class name="pill"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<property name="label" translatable="yes">This session is ready to send and receive secure messages.</property>
</object>
</child>
<child>
<object class="GtkButton" id="done_button">
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Start Chatting</property>
<property name="halign">center</property>
<signal name="clicked" handler="finish_login" swapped="yes"/>
<style>
<class name="suggested-action"/>
<class name="pill"/>
</style>
</object>
</child>
</property>
</object>
</property>
</object>
</property>
</child>
</object>
</child>
</property>
</object>
</child>
</property>
</object>
</property>
</child>
</object>
</child>
</property>
</template>
</interface>

144
src/session_setup_view/mod.rs → src/login/session_setup_view.rs

@ -1,9 +1,8 @@
use adw::subclass::prelude::*;
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{
glib,
glib::{clone, closure_local},
prelude::*,
CompositeTemplate,
};
use tracing::{debug, error};
@ -38,7 +37,7 @@ mod imp {
use super::*;
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/session_setup_view/mod.ui")]
#[template(resource = "/org/gnome/Fractal/ui/login/session_setup_view.ui")]
#[properties(wrapper_type = super::SessionSetupView)]
pub struct SessionSetupView {
/// The current session.
@ -48,7 +47,9 @@ mod imp {
#[property(get)]
pub verification: BoundObjectWeakRef<IdentityVerification>,
#[template_child]
pub main_stack: TemplateChild<gtk::Stack>,
pub stack: TemplateChild<gtk::Stack>,
#[template_child]
pub navigation: TemplateChild<adw::NavigationView>,
#[template_child]
pub send_request_btn: TemplateChild<SpinnerButton>,
#[template_child]
@ -68,7 +69,7 @@ mod imp {
impl ObjectSubclass for SessionSetupView {
const NAME: &'static str = "SessionSetupView";
type Type = super::SessionSetupView;
type ParentType = adw::Bin;
type ParentType = adw::NavigationPage;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
@ -97,14 +98,17 @@ mod imp {
fn constructed(&self) {
self.parent_constructed();
self.main_stack.connect_transition_running_notify(
clone!(@weak self as imp => move |stack|
self.stack
.connect_transition_running_notify(clone!(@weak self as imp => move |stack|
if !stack.is_transition_running() {
// Focus the default widget when the transition has ended.
imp.grab_focus();
}
),
);
));
self.navigation
.connect_visible_page_notify(clone!(@weak self as imp => move |_| {
imp.grab_focus();
}));
}
fn dispose(&self) {
@ -118,7 +122,7 @@ mod imp {
impl WidgetImpl for SessionSetupView {
fn grab_focus(&self) -> bool {
let Some(name) = self.main_stack.visible_child_name() else {
let Some(name) = self.visible_page_name() else {
return false;
};
@ -137,15 +141,32 @@ mod imp {
}
}
impl BinImpl for SessionSetupView {}
impl NavigationPageImpl for SessionSetupView {}
impl SessionSetupView {
/// The name of the visible page.
///
/// Returns `None` if the loading page is visible.
fn visible_page_name(&self) -> Option<glib::GString> {
if self
.stack
.visible_child_name()
.is_some_and(|name| name == "loading")
{
return None;
}
self.navigation.visible_page().and_then(|p| p.tag())
}
/// Set the current session.
fn set_session(&self, session: Option<Session>) {
self.session.set(session.as_ref());
fn set_session(&self, session: &Session) {
self.session.set(Some(session));
spawn!(clone!(@weak self as imp => async move {
imp.load().await;
session.connect_ready(clone!(@weak self as imp => move |_| {
spawn!(async move {
imp.load().await;
});
}));
}
@ -163,6 +184,13 @@ mod imp {
let Some(session) = self.session.upgrade() else {
return;
};
if session.is_verified().await {
// Nothing more to do.
self.obj().emit_by_name::<()>("completed", &[]);
return;
}
let client = session.client();
let client_clone = client.clone();
@ -185,7 +213,8 @@ mod imp {
if !has_identity {
self.set_identity_state(IdentityState::Missing);
self.obj().show_bootstrap();
self.navigation.replace_with_tags(&["bootstrap"]);
self.finish_loading();
return;
}
@ -206,7 +235,8 @@ mod imp {
if !has_sessions {
self.set_identity_state(IdentityState::NoSessions);
self.obj().show_bootstrap();
self.navigation.replace_with_tags(&["bootstrap"]);
self.finish_loading();
return;
}
@ -229,9 +259,14 @@ mod imp {
if let Some(verification) = verification_list.ongoing_session_verification() {
self.set_verification(Some(verification));
} else {
self.obj().choose_method();
}
self.finish_loading();
}
/// Stop showing the loading page.
fn finish_loading(&self) {
self.stack.set_visible_child_name("setup");
}
/// Set the ongoing identity verification.
@ -270,10 +305,11 @@ mod imp {
glib::Propagation::Stop
}),
);
let remove_handler = verification.connect_dismiss(clone!(@weak obj => move |_| {
obj.choose_method();
obj.imp().set_verification(None);
}));
let remove_handler =
verification.connect_dismiss(clone!(@weak self as imp => move |_| {
imp.navigation.pop();
imp.set_verification(None);
}));
self.verification.set(
verification,
@ -296,7 +332,7 @@ mod imp {
glib::wrapper! {
/// A view with the different flows to verify a session.
pub struct SessionSetupView(ObjectSubclass<imp::SessionSetupView>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
@extends gtk::Widget, adw::NavigationPage, @implements gtk::Accessible;
}
#[gtk::template_callbacks]
@ -305,24 +341,9 @@ impl SessionSetupView {
glib::Object::builder().property("session", session).build()
}
/// Reset the UI to its initial state.
fn reset(&self) {
let imp = self.imp();
imp.bootstrap_setup_btn.set_loading(false);
imp.send_request_btn.set_loading(false);
}
/// Show the page to choose a verification method.
fn choose_method(&self) {
self.reset();
let imp = self.imp();
imp.set_verification(None);
imp.main_stack.set_visible_child_name("choose-method");
}
/// Show the verification flow.
fn show_verification(&self) {
self.imp().main_stack.set_visible_child_name("verification");
self.imp().navigation.push_by_tag("verification");
}
/// Show the recovery flow.
@ -330,7 +351,7 @@ impl SessionSetupView {
fn show_recovery(&self) {
let imp = self.imp();
imp.set_verification(None);
imp.main_stack.set_visible_child_name("recovery");
imp.navigation.push_by_tag("recovery");
}
/// Show the bootstrap page.
@ -338,7 +359,7 @@ impl SessionSetupView {
fn show_bootstrap(&self) {
let imp = self.imp();
imp.set_verification(None);
imp.main_stack.set_visible_child_name("bootstrap");
imp.navigation.push_by_tag("bootstrap");
}
/// Update the bootstrap page according to the current state.
@ -373,33 +394,6 @@ impl SessionSetupView {
}
}
/// Go to the previous step.
///
/// Return `true` if the action was handled, `false` if the stack cannot go
/// back.
pub fn go_previous(&self) -> bool {
let imp = self.imp();
let Some(page) = imp.main_stack.visible_child_name() else {
return false;
};
match &*page {
"verification" => {
self.choose_method();
true
}
"bootstrap" => {
if self.identity_state() == IdentityState::CanVerify {
self.choose_method();
true
} else {
false
}
}
_ => false,
}
}
/// Create a new encryption user identity.
#[template_callback]
fn bootstrap_cross_signing(&self) {
@ -440,7 +434,7 @@ impl SessionSetupView {
if let Some(error_message) = error_message {
toast!(self, error_message);
self.reset();
self.imp().bootstrap_setup_btn.set_loading(false);
} else {
self.emit_by_name::<()>("completed", &[]);
}
@ -456,16 +450,18 @@ impl SessionSetupView {
self.imp().send_request_btn.set_loading(true);
spawn!(clone!(@weak self as obj, @weak session => async move {
let imp = obj.imp();
match session.verification_list().create(None).await {
Ok(verification) => {
obj.imp().set_verification(Some(verification));
obj.show_verification();
Ok(_) => {
// The verification should be shown automatically.
}
Err(()) => {
toast!(obj, gettext("Could not send a new verification request"));
obj.reset();
}
}
imp.send_request_btn.set_loading(false);
}));
}

481
src/login/session_setup_view.ui

@ -0,0 +1,481 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="SessionSetupView" parent="AdwNavigationPage">
<property name="tag">session-setup</property>
<property name="title" translatable="yes">Session Setup</property>
<child>
<object class="GtkStack" id="stack">
<property name="transition-type">slide-left</property>
<property name="vexpand">True</property>
<child>
<object class="GtkStackPage">
<property name="name">loading</property>
<property name="title" translatable="yes">Loading</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-title">False</property>
</object>
</child>
<property name="content">
<object class="Spinner" id="loading_page">
<property name="valign">center</property>
<property name="halign">center</property>
<style>
<class name="large"/>
</style>
</object>
</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">setup</property>
<property name="child">
<object class="AdwNavigationView" id="navigation">
<child>
<object class="AdwNavigationPage">
<property name="tag">choose-method</property>
<property name="title" translatable="yes">Verify Your New Session</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-title">False</property>
</object>
</child>
<property name="content">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="vexpand">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">400</property>
<property name="tightening-threshold">300</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<property name="valign">center</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel" id="choose_method_title">
<property name="label" translatable="yes">Verify Your New Session</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="choose_method_description">
<property name="label" translatable="yes">Verifying your session allows you and the people you chat with to be sure that no one is trying to impersonate you or intercept your conversations.</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="SpinnerButton" id="send_request_btn">
<property name="can-shrink">true</property>
<property name="content-label" translatable="yes">Verify With Another Session</property>
<property name="halign">center</property>
<signal name="clicked" handler="send_request" swapped="yes"/>
<style>
<class name="pill"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">No other devices logged into this account?</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="visible">False</property><!-- Not supported for now -->
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Recovery</property>
<property name="halign">center</property>
<signal name="clicked" handler="show_recovery" swapped="yes"/>
<style>
<class name="pill"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="choose_bootstrap_btn">
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Reset</property>
<property name="halign">center</property>
<signal name="clicked" handler="show_bootstrap" swapped="yes"/>
<style>
<class name="pill"/>
</style>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</child>
<child>
<object class="AdwNavigationPage">
<property name="tag">verification</property>
<property name="title" translatable="yes">Identity Verification</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-title">False</property>
</object>
</child>
<property name="content">
<object class="IdentityVerificationView" id="verification_page"/>
</property>
</object>
</property>
</object>
</child>
<child>
<object class="AdwNavigationPage">
<property name="tag">bootstrap</property>
<property name="title" translatable="yes">Set Up Encryption Identity</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-title">False</property>
</object>
</child>
<property name="content">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="vexpand">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">400</property>
<property name="tightening-threshold">300</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<property name="valign">center</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel" id="bootstrap_title">
<property name="label" translatable="yes">Set Up Encryption Identity</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="bootstrap_label">
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="SpinnerButton" id="bootstrap_setup_btn">
<property name="halign">center</property>
<signal name="clicked" handler="bootstrap_cross_signing" swapped="yes"/>
<style>
<class name="suggested-action"/>
<class name="pill"/>
</style>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</child>
<child>
<object class="AdwNavigationPage">
<property name="tag">recovery</property>
<property name="title" translatable="yes">Encryption Identity Recovery</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-title">False</property>
</object>
</child>
<property name="content">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="vexpand">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">400</property>
<property name="tightening-threshold">300</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<property name="valign">center</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel" id="recovery_title">
<property name="label" translatable="yes">Recovery</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Without another device you need a recovery passphrase or key to access your messages</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Recovery Passphrase</property>
<property name="halign">center</property>
<style>
<class name="pill"/>
</style>
</object>
</child>
<child>
<object class="GtkButton">
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Recovery Key</property>
<property name="halign">center</property>
<style>
<class name="pill"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">If you don’t have any of these you can reset your identity, but be aware this makes your old messages inaccessible forever.</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="can-shrink">true</property>
<property name="halign">center</property>
<property name="label" translatable="yes">Reset Identity</property>
<style>
<class name="destructive-action"/>
<class name="pill"/>
</style>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</child>
<child>
<object class="AdwNavigationPage">
<property name="tag">recovery-passphrase</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-title">False</property>
</object>
</child>
<property name="content">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="vexpand">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">400</property>
<property name="tightening-threshold">300</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<property name="valign">center</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Recovery Passphrase</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Your Recovery Passphrase was set up when you first created this account.</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">If you opted for a Recovery Key instead go back and choose that option.</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="AdwPasswordEntryRow">
<style>
<class name="card"/>
</style>
<property name="title" translatable="yes">Passphrase</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Next</property>
<property name="halign">center</property>
<style>
<class name="suggested-action"/>
<class name="pill"/>
</style>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</child>
<child>
<object class="AdwNavigationPage">
<property name="tag">recovery-key</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-title">False</property>
</object>
</child>
<property name="content">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="vexpand">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">400</property>
<property name="tightening-threshold">300</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<property name="valign">center</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Recovery Key</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Your Recovery Key was set up when you first created this account.</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">If you opted for a Recovery Passphrase instead go back and choose that option.</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="GtkTextView">
</object>
</child>
<child>
<object class="GtkListBox">
</object>
</child>
<child>
<object class="GtkButton">
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Next</property>
<property name="halign">center</property>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</child>
</object>
</property>
</object>
</child>
</object>
</child>
</template>
</interface>

12
src/login/sso_page.rs

@ -1,5 +1,5 @@
use adw::subclass::prelude::BinImpl;
use gtk::{self, glib, subclass::prelude::*, CompositeTemplate};
use adw::subclass::prelude::*;
use gtk::{self, glib, CompositeTemplate};
mod imp {
use glib::subclass::InitializingObject;
@ -14,7 +14,7 @@ mod imp {
impl ObjectSubclass for LoginSsoPage {
const NAME: &'static str = "LoginSsoPage";
type Type = super::LoginSsoPage;
type ParentType = adw::Bin;
type ParentType = adw::NavigationPage;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
@ -27,13 +27,13 @@ mod imp {
impl ObjectImpl for LoginSsoPage {}
impl WidgetImpl for LoginSsoPage {}
impl BinImpl for LoginSsoPage {}
impl NavigationPageImpl for LoginSsoPage {}
}
glib::wrapper! {
/// A widget handling the login flows.
/// A page shown while the user is logging in via SSO.
pub struct LoginSsoPage(ObjectSubclass<imp::LoginSsoPage>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
@extends gtk::Widget, adw::NavigationPage, @implements gtk::Accessible;
}
impl LoginSsoPage {

108
src/login/sso_page.ui

@ -1,54 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="LoginSsoPage" parent="AdwBin">
<accessibility>
<relation name="labelled-by">title</relation>
<relation name="described-by">instructions</relation>
</accessibility>
<template class="LoginSsoPage" parent="AdwNavigationPage">
<property name="tag">sso</property>
<property name="title" translatable="yes">Single Sign-On</property>
<property name="child">
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">360</property>
<property name="margin-top">24</property>
<property name="margin-bottom">24</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">36</property>
<property name="valign">center</property>
<child>
<object class="GtkLabel" id="title">
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<property name="label" translatable="yes">Single Sign-On</property>
<property name="accessible-role">heading</property>
<accessibility>
<property name="level">1</property>
</accessibility>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="instructions">
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<property name="label" translatable="yes">Please follow the steps in the browser.</property>
<style>
<class name="title-4"/>
</style>
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-title">False</property>
</object>
</child>
<property name="content">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="OfflineBanner" />
</child>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="vexpand">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">360</property>
<property name="margin-top">24</property>
<property name="margin-bottom">24</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">36</property>
<property name="valign">center</property>
<child>
<object class="GtkLabel" id="title">
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<property name="label" translatable="yes">Single Sign-On</property>
<property name="accessible-role">heading</property>
<accessibility>
<property name="level">1</property>
</accessibility>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="instructions">
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<property name="label" translatable="yes">Please follow the steps in the browser.</property>
<style>
<class name="title-4"/>
</style>
</object>
</child>
</object>
</property>
</object>
</child>
</property>
</object>
</property>
</child>
</object>
</property>
</object>

9
src/main.rs

@ -12,7 +12,6 @@ mod components;
mod config;
mod contrib;
mod error_page;
mod greeter;
mod i18n;
mod identity_verification_view;
mod intent;
@ -21,7 +20,6 @@ mod prelude;
mod secret;
mod session;
mod session_list;
mod session_setup_view;
mod system_settings;
mod user_facing_error;
mod utils;
@ -32,12 +30,7 @@ use gtk::{gdk::Display, gio, IconTheme};
use once_cell::sync::Lazy;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
use self::{
application::*,
config::*,
i18n::*,
window::{Window, WindowPage},
};
use self::{application::*, config::*, i18n::*, window::Window};
/// The default tokio runtime to be used for async tasks
pub static RUNTIME: Lazy<tokio::runtime::Runtime> =

390
src/session_setup_view/mod.ui

@ -1,390 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="SessionSetupView" parent="AdwBin">
<accessibility>
<property name="label" translatable="yes">Session Verification</property>
</accessibility>
<child>
<object class="GtkStack" id="main_stack">
<property name="transition-type">crossfade</property>
<property name="vexpand">True</property>
<child>
<object class="GtkStackPage">
<property name="name">choose-method</property>
<property name="title" translatable="yes">Verify Your New Session</property>
<property name="child">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">400</property>
<property name="tightening-threshold">300</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<property name="valign">center</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel" id="choose_method_title">
<property name="label" translatable="yes">Verify Your New Session</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="choose_method_description">
<property name="label" translatable="yes">Verifying your session allows you and the people you chat with to be sure that no one is trying to impersonate you or intercept your conversations.</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="SpinnerButton" id="send_request_btn">
<property name="can-shrink">true</property>
<property name="content-label" translatable="yes">Verify With Another Session</property>
<property name="halign">center</property>
<signal name="clicked" handler="send_request" swapped="yes"/>
<style>
<class name="pill"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">No other devices logged into this account?</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="visible">False</property><!-- Not supported for now -->
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Recovery</property>
<property name="halign">center</property>
<signal name="clicked" handler="show_recovery" swapped="yes"/>
<style>
<class name="pill"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="choose_bootstrap_btn">
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Reset</property>
<property name="halign">center</property>
<signal name="clicked" handler="show_bootstrap" swapped="yes"/>
<style>
<class name="pill"/>
</style>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">verification</property>
<property name="title" translatable="yes">Identity Verification</property>
<property name="child">
<object class="IdentityVerificationView" id="verification_page"/>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">bootstrap</property>
<property name="title" translatable="yes">Set Up Encryption Identity</property>
<property name="child">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">400</property>
<property name="tightening-threshold">300</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<property name="valign">center</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel" id="bootstrap_title">
<property name="label" translatable="yes">Set Up Encryption Identity</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="bootstrap_label">
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="SpinnerButton" id="bootstrap_setup_btn">
<property name="halign">center</property>
<signal name="clicked" handler="bootstrap_cross_signing" swapped="yes"/>
<style>
<class name="suggested-action"/>
<class name="pill"/>
</style>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">recovery</property>
<property name="title" translatable="yes">Encryption Identity Recovery</property>
<property name="child">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">400</property>
<property name="tightening-threshold">300</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<property name="valign">center</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel" id="recovery_title">
<property name="label" translatable="yes">Recovery</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Without another device you need a recovery passphrase or key to access your messages</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Recovery Passphrase</property>
<property name="halign">center</property>
<style>
<class name="pill"/>
</style>
</object>
</child>
<child>
<object class="GtkButton">
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Recovery Key</property>
<property name="halign">center</property>
<style>
<class name="pill"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">If you don’t have any of these you can reset your identity, but be aware this makes your old messages inaccessible forever.</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="can-shrink">true</property>
<property name="halign">center</property>
<property name="label" translatable="yes">Reset Identity</property>
<style>
<class name="destructive-action"/>
<class name="pill"/>
</style>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">recovery-passphrase</property>
<property name="child">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">400</property>
<property name="tightening-threshold">300</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<property name="valign">center</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Recovery Passphrase</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Your Recovery Passphrase was set up when you first created this account.</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">If you opted for a Recovery Key instead go back and choose that option.</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="AdwPasswordEntryRow">
<style>
<class name="card"/>
</style>
<property name="title" translatable="yes">Passphrase</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Next</property>
<property name="halign">center</property>
<style>
<class name="suggested-action"/>
<class name="pill"/>
</style>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">recovery-key</property>
<property name="child">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">True</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">400</property>
<property name="tightening-threshold">300</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<property name="valign">center</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Recovery Key</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Your Recovery Key was set up when you first created this account.</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">If you opted for a Recovery Passphrase instead go back and choose that option.</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="justify">center</property>
</object>
</child>
<child>
<object class="GtkTextView">
</object>
</child>
<child>
<object class="GtkListBox">
</object>
</child>
<child>
<object class="GtkButton">
<property name="can-shrink">true</property>
<property name="label" translatable="yes">Next</property>
<property name="halign">center</property>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</child>
</object>
</child>
</template>
</interface>

4
src/ui-resources.gresource.xml

@ -41,7 +41,6 @@
<file compressed="true" preprocess="xml-stripblanks">components/video_player.ui</file>
<file compressed="true" preprocess="xml-stripblanks">contrib/qr_code_scanner/mod.ui</file>
<file compressed="true" preprocess="xml-stripblanks">error_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks">greeter.ui</file>
<file compressed="true" preprocess="xml-stripblanks">identity_verification_view/accept_request_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks">identity_verification_view/cancelled_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks">identity_verification_view/choose_method_page.ui</file>
@ -55,10 +54,12 @@
<file compressed="true" preprocess="xml-stripblanks">identity_verification_view/scan_qr_code_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks">identity_verification_view/wait_for_other_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/advanced_dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/greeter.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/homeserver_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/idp_button.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/method_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/mod.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/session_setup_view.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/sso_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/account_settings/general_page/change_password_subpage.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/account_settings/general_page/deactivate_account_subpage.ui</file>
@ -134,7 +135,6 @@
<file compressed="true" preprocess="xml-stripblanks">session/view/sidebar/mod.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/sidebar/room_row.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/sidebar/verification_row.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session_setup_view/mod.ui</file>
<file compressed="true" preprocess="xml-stripblanks">shortcuts.ui</file>
<file compressed="true" preprocess="xml-stripblanks">window.ui</file>
</gresource>

43
src/window.rs

@ -10,7 +10,6 @@ use crate::{
account_switcher::{AccountSwitcherButton, AccountSwitcherPopover},
components::{OfflineBanner, Spinner},
error_page::ErrorPage,
greeter::Greeter,
intent,
login::Login,
prelude::*,
@ -30,8 +29,6 @@ use crate::{
pub enum WindowPage {
/// The loading page.
Loading,
/// The welcome screen.
Greeter,
/// The login view.
Login,
/// The session view.
@ -54,8 +51,6 @@ mod imp {
#[template_child]
pub loading: TemplateChild<gtk::WindowHandle>,
#[template_child]
pub greeter: TemplateChild<Greeter>,
#[template_child]
pub login: TemplateChild<Login>,
#[template_child]
pub error_page: TemplateChild<ErrorPage>,
@ -108,9 +103,6 @@ mod imp {
);
klass.install_action("win.new-session", None, |obj, _, _| {
obj.set_visible_page(WindowPage::Greeter);
});
klass.install_action("win.show-login", None, |obj, _, _| {
obj.set_visible_page(WindowPage::Login);
});
klass.install_action("win.show-session", None, |obj, _, _| {
@ -148,11 +140,6 @@ mod imp {
self.load_window_size();
self.main_stack.connect_visible_child_notify(
clone!(@weak obj => move |_| obj.set_default_by_child()),
);
obj.set_default_by_child();
self.main_stack.connect_transition_running_notify(
clone!(@weak self as imp => move |stack|
if !stack.is_transition_running() {
@ -175,7 +162,8 @@ mod imp {
obj.action_set_enabled("win.show-session", n_items > 0);
if removed > 0 && n_items == 0 {
obj.set_visible_page(WindowPage::Greeter);
// There are no more sessions.
obj.set_visible_page(WindowPage::Login);
return;
}
@ -208,21 +196,15 @@ mod imp {
if session_list.state() == LoadingState::Ready {
if session_list.is_empty() {
obj.set_visible_page(WindowPage::Greeter);
obj.set_visible_page(WindowPage::Login);
}
} else {
session_list.connect_state_notify(clone!(@weak obj => move |session_list| {
if session_list.state() == LoadingState::Ready && session_list.is_empty() {
obj.set_visible_page(WindowPage::Greeter);
obj.set_visible_page(WindowPage::Login);
}
}));
}
let monitor = gio::NetworkMonitor::default();
monitor.connect_network_changed(clone!(@weak obj => move |_, available| {
obj.action_set_enabled("win.show-login", available);
}));
obj.action_set_enabled("win.show-login", monitor.is_network_available());
}
}
@ -244,7 +226,6 @@ mod imp {
fn grab_focus(&self) -> bool {
match self.visible_page() {
WindowPage::Loading => false,
WindowPage::Greeter => self.greeter.grab_focus(),
WindowPage::Login => self.login.grab_focus(),
WindowPage::Session => self.session.grab_focus(),
WindowPage::Error => self.error_page.grab_focus(),
@ -415,22 +396,6 @@ impl Window {
imp.session.set_session(None::<Session>);
}
/// Change the default widget of the window based on the visible child.
///
/// These are the default widgets:
///
/// - `Greeter` screen => `Log In` button.
fn set_default_by_child(&self) {
let imp = self.imp();
let default_widget = match imp.visible_page() {
WindowPage::Greeter => Some(imp.greeter.default_widget()),
_ => None,
};
self.set_default_widget(default_widget.as_ref());
}
/// Set the visible page of the window.
pub fn set_visible_page(&self, name: WindowPage) {
self.imp().main_stack.set_visible_child_name(name.as_ref());

9
src/window.ui

@ -51,15 +51,6 @@
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">greeter</property>
<property name="title" translatable="yes">Welcome to Fractal</property>
<property name="child">
<object class="Greeter" id="greeter"/>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">login</property>

Loading…
Cancel
Save