Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
410 changes: 410 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions aardvark-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ gtk = { version = "0.9", package = "gtk4", features = ["gnome_47"] }
sourceview = { package = "sourceview5", version = "0.9" }
tracing = "0.1"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
ashpd = { version = "0.9", default-features = false, features = ["tracing", "async-std"] }
futures-util = "0.3"

[dependencies.adw]
package = "libadwaita"
Expand Down
11 changes: 11 additions & 0 deletions aardvark-app/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use gtk::{gio, glib, glib::Properties};

use crate::AardvarkWindow;
use crate::config;
use crate::system_settings::SystemSettings;

mod imp {
use super::*;
Expand All @@ -35,6 +36,8 @@ mod imp {
pub struct AardvarkApplication {
#[property(get)]
pub service: Service,
#[property(get)]
pub system_settings: SystemSettings,
}

#[glib::object_subclass]
Expand Down Expand Up @@ -149,3 +152,11 @@ impl AardvarkApplication {
about.present(Some(&window));
}
}

impl Default for AardvarkApplication {
fn default() -> Self {
gio::Application::default()
.and_downcast::<AardvarkApplication>()
.unwrap()
}
}
55 changes: 55 additions & 0 deletions aardvark-app/src/components/avatar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use adw::{prelude::*, subclass::prelude::*};
use gtk::{glib, glib::GString};

mod imp {
use super::*;

#[derive(Default, glib::Properties)]
#[properties(wrapper_type = super::Avatar)]
pub struct Avatar {
#[property(name = "emoji", get = Self::emoji, set = Self::set_emoji, type = GString)]
label: gtk::Label,
}

#[glib::object_subclass]
impl ObjectSubclass for Avatar {
const NAME: &'static str = "Avatar";
type Type = super::Avatar;
type ParentType = adw::Bin;
}

#[glib::derived_properties]
impl ObjectImpl for Avatar {
fn constructed(&self) {
self.parent_constructed();
self.obj().set_child(Some(&self.label));
self.obj().add_css_class("avatar");
self.obj().set_valign(gtk::Align::Center);
self.obj().set_halign(gtk::Align::Center);
}
}

impl Avatar {
fn emoji(&self) -> GString {
self.label.label()
}

fn set_emoji(&self, emoji: &str) {
self.label.set_label(emoji);
}
}

impl WidgetImpl for Avatar {}
impl BinImpl for Avatar {}
}

glib::wrapper! {
pub struct Avatar(ObjectSubclass<imp::Avatar>)
@extends gtk::Widget, adw::Bin;
}

impl Avatar {
pub fn new() -> Self {
glib::Object::new()
}
}
2 changes: 2 additions & 0 deletions aardvark-app/src/components/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod avatar;
mod multiline_entry;
mod zoom_level_selector;

pub use self::avatar::Avatar;
pub use self::multiline_entry::MultilineEntry;
pub use self::zoom_level_selector::ZoomLevelSelector;
265 changes: 265 additions & 0 deletions aardvark-app/src/connection_popover/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
/* window.rs
*
* Copyright 2024 The Aardvark Developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/

use std::cell::RefCell;

use adw::prelude::ActionRowExt;
use adw::subclass::prelude::*;
use gettextrs::gettext;
use gtk::glib;
use gtk::prelude::*;

use crate::AardvarkApplication;
use crate::components::Avatar;
use crate::system_settings::ClockFormat;
use aardvark_doc::{author::Author, author::COLORS, authors::Authors};

mod imp {
use super::*;

#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::ConnectionPopover)]
pub struct ConnectionPopover {
author_list_box: gtk::ListBox,
#[property(get, set = Self::set_model)]
model: RefCell<Option<Authors>>,
}

#[glib::object_subclass]
impl ObjectSubclass for ConnectionPopover {
const NAME: &'static str = "AardvarkConnectionPopover";
type Type = super::ConnectionPopover;
type ParentType = gtk::Popover;
}

#[glib::derived_properties]
impl ObjectImpl for ConnectionPopover {
fn constructed(&self) {
let scrollview = gtk::ScrolledWindow::builder()
.child(&self.author_list_box)
.hscrollbar_policy(gtk::PolicyType::Never)
.propagate_natural_height(true)
.propagate_natural_width(true)
.max_content_height(300)
.build();
self.obj().set_child(Some(&scrollview));
self.author_list_box
.set_selection_mode(gtk::SelectionMode::None);
self.obj().add_css_class("connection-popover");

let css_provider = gtk::CssProvider::new();
let style: String = COLORS
.iter()
.map(|(color_name, color_hex)| {
format!(".bg-{color_name} {{ background-color: {color_hex}; }}")
})
.collect();
css_provider.load_from_string(&style);
gtk::style_context_add_provider_for_display(
&self.obj().display(),
&css_provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
}
}

impl ConnectionPopover {
fn set_model(&self, model: Option<Authors>) {
self.author_list_box.bind_model(model.as_ref(), |author| {
let author = author.downcast_ref::<Author>().unwrap();
let row = adw::ActionRow::builder()
.selectable(false)
.activatable(false)
.can_focus(false)
.can_target(false)
.build();
let avatar = Avatar::new();
row.add_prefix(&avatar);
if author.is_this_device() {
let this_device_label = gtk::Label::builder()
.label("This Device")
.valign(gtk::Align::Start)
.margin_top(6)
.css_classes(["this-device-pill"])
.build();
row.add_suffix(&this_device_label);
}
author
.bind_property("name", &row, "title")
.sync_create()
.build();
// FIXME: format last seen according to the mockups
//author.bind_property ("last-seen", row, "subtitle").sync_create().build();
author
.bind_property("emoji", &avatar, "emoji")
.sync_create()
.build();
author
.bind_property("is-online", &row, "subtitle")
.sync_create()
.transform_to(|binding, is_online: bool| {
let author: Author = binding.source().unwrap().downcast().unwrap();
if is_online {
Some("Online".to_string())
//Some(format_last_seen(&glib::DateTime::now_local().unwrap()))
} else {
Some(format_last_seen(&author.last_seen().unwrap()))
}
})
.build();
avatar.add_css_class(&format!("bg-{}", author.color()));

row.upcast()
});

self.model.replace(model);
}
}

impl WidgetImpl for ConnectionPopover {}
impl PopoverImpl for ConnectionPopover {}
}

glib::wrapper! {
pub struct ConnectionPopover(ObjectSubclass<imp::ConnectionPopover>)
@extends gtk::Widget, gtk::Popover;
}

impl ConnectionPopover {
pub fn new<P: IsA<Authors>>(model: &P) -> Self {
glib::Object::builder().property("model", model).build()
}
}

// This was copied from Fractal
// See: https://gitlab.gnome.org/World/fractal/-/blob/main/src/session/model/user_sessions_list/user_session.rs#L258
fn format_last_seen(datetime: &glib::DateTime) -> String {
let clock_format = AardvarkApplication::default()
.system_settings()
.clock_format();
let use_24 = clock_format == ClockFormat::TwentyFourHours;

// This was ported from Nautilus and simplified for our use case.
// See: https://gitlab.gnome.org/GNOME/nautilus/-/blob/1c5bd3614a35cfbb49de087bc10381cdef5a218f/src/nautilus-file.c#L5001
let now = glib::DateTime::now_local().unwrap();
let format;
let days_ago = {
let today_midnight =
glib::DateTime::from_local(now.year(), now.month(), now.day_of_month(), 0, 0, 0f64)
.expect("constructing GDateTime works");

let date = glib::DateTime::from_local(
datetime.year(),
datetime.month(),
datetime.day_of_month(),
0,
0,
0f64,
)
.expect("constructing GDateTime works");

today_midnight.difference(&date).as_days()
};

// Show only the time if date is on today
if days_ago == 0 {
if use_24 {
// Translators: Time in 24h format, i.e. "23:04".
// Do not change the time format as it will follow the system settings.
// See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
format = gettext("Last seen at %H:%M");
} else {
// Translators: Time in 12h format, i.e. "11:04 PM".
// Do not change the time format as it will follow the system settings.
// See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
format = gettext("Last seen at %I:%M %p");
}
}
// Show the word "Yesterday" and time if date is on yesterday
else if days_ago == 1 {
if use_24 {
// Translators: this a time in 24h format, i.e. "Last seen yesterday at 23:04".
// Do not change the time format as it will follow the system settings.
// See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
// xgettext:no-c-format
format = gettext("Last seen yesterday at %H:%M");
} else {
// Translators: this is a time in 12h format, i.e. "Last seen Yesterday at 11:04
// PM".
// Do not change the time format as it will follow the system settings.
// See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
// xgettext:no-c-format
format = gettext("Last seen yesterday at %I:%M %p");
}
}
// Show a week day and time if date is in the last week
else if days_ago > 1 && days_ago < 7 {
if use_24 {
// Translators: this is the name of the week day followed by a time in 24h
// format, i.e. "Last seen Monday at 23:04".
// Do not change the time format as it will follow the system settings.
// See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
// xgettext:no-c-format
format = gettext("Last seen %A at %H:%M");
} else {
// Translators: this is the week day name followed by a time in 12h format, i.e.
// "Last seen Monday at 11:04 PM".
// Do not change the time format as it will follow the system settings.
// See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
// xgettext:no-c-format
format = gettext("Last seen %A at %I:%M %p");
}
} else if datetime.year() == now.year() {
if use_24 {
// Translators: this is the month and day and the time in 24h format, i.e. "Last
// seen February 3 at 23:04".
// Do not change the time format as it will follow the system settings.
// See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
// xgettext:no-c-format
format = gettext("Last seen %B %-e at %H:%M");
} else {
// Translators: this is the month and day and the time in 12h format, i.e. "Last
// seen February 3 at 11:04 PM".
// Do not change the time format as it will follow the system settings.
// See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
// xgettext:no-c-format
format = gettext("Last seen %B %-e at %I:%M %p");
}
} else if use_24 {
// Translators: this is the full date and the time in 24h format, i.e. "Last
// seen February 3 2015 at 23:04".
// Do not change the time format as it will follow the system settings.
// See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
// xgettext:no-c-format
format = gettext("Last seen %B %-e %Y at %H:%M");
} else {
// Translators: this is the full date and the time in 12h format, i.e. "Last
// seen February 3 2015 at 11:04 PM".
// Do not change the time format as it will follow the system settings.
// See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
// xgettext:no-c-format
format = gettext("Last seen %B %-e %Y at %I:%M %p");
}

datetime
.format(&format)
.expect("formatting GDateTime works")
.into()
}
3 changes: 3 additions & 0 deletions aardvark-app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
mod application;
mod components;
mod config;
mod connection_popover;
mod system_settings;
mod textbuffer;
mod window;

Expand All @@ -34,6 +36,7 @@ use tracing_subscriber::prelude::*;

use self::application::AardvarkApplication;
use self::config::{GETTEXT_PACKAGE, LOCALEDIR, PKGDATADIR};
use self::connection_popover::ConnectionPopover;
use self::textbuffer::AardvarkTextBuffer;
use self::window::AardvarkWindow;

Expand Down
Empty file added aardvark-app/src/store.rs
Empty file.
Loading