Merge branch 'avatar2' into 'master'

Avatars

Closes #14

See merge request schmiddi-on-mobile/flare!127
This commit is contained in:
Julian Schmidhuber committed 2024-02-25 12:18:02 +00:00
commit fdafbb02cd
11 files changed
+188 -11

No files matched your search

+4
View File
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Avatars for contacts and groups.
### Fixed
- Persistent typing messages
Generated
+5 -5
View File
@@ -2073,7 +2073,7 @@ dependencies = [
[[package]]
name = "libsignal-service"
version = "0.1.0"
source = "git+https://github.com/whisperfish/libsignal-service-rs?rev=a2e7540a71866a62028ad0205574a5feb0e717ec#a2e7540a71866a62028ad0205574a5feb0e717ec"
source = "git+https://github.com/whisperfish/libsignal-service-rs?rev=1a8cdf81#1a8cdf81e66477077bd130cdd0313762e3d5e3c8"
dependencies = [
"aes",
"aes-gcm",
@@ -2108,7 +2108,7 @@ dependencies = [
[[package]]
name = "libsignal-service-hyper"
version = "0.1.0"
source = "git+https://github.com/whisperfish/libsignal-service-rs?rev=a2e7540a71866a62028ad0205574a5feb0e717ec#a2e7540a71866a62028ad0205574a5feb0e717ec"
source = "git+https://github.com/whisperfish/libsignal-service-rs?rev=1a8cdf81#1a8cdf81e66477077bd130cdd0313762e3d5e3c8"
dependencies = [
"async-trait",
"async-tungstenite",
@@ -2881,7 +2881,7 @@ checksum = "94e851c7654eed9e68d7d27164c454961a616cf8c203d500607ef22c737b51bb"
[[package]]
name = "presage"
version = "0.6.0-dev"
source = "git+https://github.com/Schmiddiii/presage?rev=75011227faac60773bb1422d365ecc7fed95484a#75011227faac60773bb1422d365ecc7fed95484a"
source = "git+https://github.com/Schmiddiii/presage?rev=03ffb349a125518fdc6087ebfb6a5d82a1600da6#03ffb349a125518fdc6087ebfb6a5d82a1600da6"
dependencies = [
"base64",
"futures",
@@ -2900,7 +2900,7 @@ dependencies = [
[[package]]
name = "presage-store-cipher"
version = "0.1.0"
source = "git+https://github.com/Schmiddiii/presage?rev=75011227faac60773bb1422d365ecc7fed95484a#75011227faac60773bb1422d365ecc7fed95484a"
source = "git+https://github.com/Schmiddiii/presage?rev=03ffb349a125518fdc6087ebfb6a5d82a1600da6#03ffb349a125518fdc6087ebfb6a5d82a1600da6"
dependencies = [
"blake3",
"chacha20poly1305",
@@ -2917,7 +2917,7 @@ dependencies = [
[[package]]
name = "presage-store-sled"
version = "0.6.0-dev"
source = "git+https://github.com/Schmiddiii/presage?rev=75011227faac60773bb1422d365ecc7fed95484a#75011227faac60773bb1422d365ecc7fed95484a"
source = "git+https://github.com/Schmiddiii/presage?rev=03ffb349a125518fdc6087ebfb6a5d82a1600da6#03ffb349a125518fdc6087ebfb6a5d82a1600da6"
dependencies = [
"async-trait",
"base64",
+3 -3
View File
@@ -30,9 +30,9 @@ tokio = { version = "1.35.1", features = ["rt", "macros", "rt-multi-thread", "ti
regex = "1.10.3"
lazy_static = "1.4.0"
presage = { git = "https://github.com/Schmiddiii/presage", rev = "75011227faac60773bb1422d365ecc7fed95484a" }
presage-store-sled = { git = "https://github.com/Schmiddiii/presage", rev = "75011227faac60773bb1422d365ecc7fed95484a" }
libsignal-service = { git = "https://github.com/whisperfish/libsignal-service-rs", rev = "a2e7540a71866a62028ad0205574a5feb0e717ec" }
presage = { git = "https://github.com/Schmiddiii/presage", rev = "03ffb349a125518fdc6087ebfb6a5d82a1600da6" }
presage-store-sled = { git = "https://github.com/Schmiddiii/presage", rev = "03ffb349a125518fdc6087ebfb6a5d82a1600da6" }
libsignal-service = { git = "https://github.com/whisperfish/libsignal-service-rs", rev = "1a8cdf81" }
async-trait = "0.1.77"
sled = "0.34.7"
@@ -56,6 +56,25 @@
}
]
},
{
"name": "protobuf",
"buildsystem": "autotools",
"config-opts": [
"DIST_LANG=cpp"
],
"cleanup": [
"/bin/protoc*",
"/lib/libprotoc*",
"/lib/libprotobuf-lite*"
],
"sources": [
{
"type": "archive",
"url": "https://github.com/protocolbuffers/protobuf/releases/download/v3.17.3/protobuf-all-3.17.3.tar.gz",
"sha256": "77ad26d3f65222fd96ccc18b055632b0bfedf295cb748b712a98ba1ac0b704b2"
}
]
},
{
"name": "flare",
"buildsystem": "meson",
+1
View File
@@ -13,6 +13,7 @@ template $FlChannelItem: Box {
// Avatar
Adw.Avatar {
text: bind template.channel as <$FlChannel>.title;
custom-image: bind template.channel as <$FlChannel>.avatar;
icon-name: "user-bookmarks-symbolic";
show-initials: bind $not(template.channel as <$FlChannel>.is-self) as <bool>;
size: 44;
+1
View File
@@ -68,6 +68,7 @@ template $FlMessageItem: $ContextMenuBin {
]
text: bind template.message as <$FlTextMessage>.sender as <$FlContact>.title;
custom-image: bind template.message as <$FlTextMessage>.sender as <$FlContact>.avatar;
show-initials: true;
valign: start;
size: 32;
+43 -1
View File
@@ -5,8 +5,9 @@ use std::{
};
use gdk::{
glib::clone,
glib::{clone, Bytes},
prelude::{ObjectExt, SettingsExt},
Paintable, Texture,
};
use gio::subclass::prelude::ObjectSubclassIsExt;
use glib::{Cast, Object};
@@ -107,6 +108,7 @@ impl Channel {
.group_context
.swap(&RefCell::new(Some(group_context_v2.clone())));
s.initialize_participants().await;
s.initialize_avatar().await;
s
}
@@ -488,6 +490,33 @@ impl Channel {
}
}
async fn initialize_avatar(&self) {
// TODO: Do in background.
let Some(context) = self.group_context() else {
return;
};
let Some(avatar) = self
.manager()
.retrieve_group_avatar(context)
.await
.ok()
.flatten()
.and_then(|b| Texture::from_bytes(&Bytes::from_owned(b)).ok())
else {
log::debug!("Failed to fetch group avatar; it may not have a profile picture set",);
return;
};
self.imp().group_avatar.replace(Some(avatar.into()));
self.notify("avatar");
}
pub fn avatar(&self) -> Option<Paintable> {
self.property("avatar")
}
pub fn phone_number(&self) -> Option<String> {
self.property("phone-number")
}
@@ -530,6 +559,7 @@ mod imp {
glib::{ParamSpecBoolean, ParamSpecUInt},
prelude::*,
subclass::prelude::*,
Paintable,
};
use glib::{
once_cell::sync::Lazy, subclass::Signal, ParamSpec, ParamSpecObject, ParamSpecString, Value,
@@ -551,6 +581,8 @@ mod imp {
pub(super) group: RefCell<Option<Group>>,
pub(super) group_context: RefCell<Option<GroupContextV2>>,
pub(super) group_avatar: RefCell<Option<Paintable>>,
pub(super) participants: RefCell<Vec<Contact>>,
pub(super) manager: RefCell<Option<Manager>>,
@@ -606,6 +638,9 @@ mod imp {
ParamSpecObject::builder::<DisplayMessage>("last-message")
.read_only()
.build(),
ParamSpecObject::builder::<Paintable>("avatar")
.read_only()
.build(),
ParamSpecString::builder("title").read_only().build(),
ParamSpecBoolean::builder("is-contact").read_only().build(),
ParamSpecBoolean::builder("is-self").read_only().build(),
@@ -639,6 +674,13 @@ mod imp {
.unwrap_or(false)
})
.to_value(),
"avatar" => {
if let Some(contact) = self.contact.borrow().as_ref() {
contact.property("avatar")
} else {
self.group_avatar.borrow().as_ref().to_value()
}
}
"title" => {
let title = if let Some(group) = self.group.borrow().as_ref() {
group.title.clone()
+39 -1
View File
@@ -1,6 +1,9 @@
use std::cell::RefCell;
use gdk::glib::clone;
use gdk::{
glib::{clone, Bytes},
Paintable, Texture,
};
use gio::subclass::prelude::ObjectSubclassIsExt;
use glib::{Object, ObjectExt};
use gtk::{gio, glib};
@@ -102,9 +105,32 @@ impl Contact {
let profile = manager.retrieve_profile_by_uuid(uuid, key).await.ok();
obj.profile.replace(profile);
self.notify("title");
let Some(avatar) = manager
.retrieve_profile_avatar_by_uuid(uuid, key)
.await
.ok()
.flatten()
.and_then(|b| Texture::from_bytes(&Bytes::from_owned(b)).ok())
else {
log::debug!(
"Failed to fetch avatar for {}; they may not have a profile picture set",
self.title()
);
return;
};
self.set_avatar(avatar);
}
}
pub fn avatar(&self) -> Option<Paintable> {
self.property("avatar")
}
fn set_avatar(&self, image: Texture) {
self.set_property("avatar", image);
}
pub fn uuid(&self) -> Uuid {
if let Some(a) = self.address() {
a.uuid
@@ -157,6 +183,7 @@ impl Contact {
mod imp {
use std::cell::RefCell;
use gdk::Paintable;
use gdk::{prelude::*, subclass::prelude::*};
use glib::{
once_cell::sync::Lazy, ParamSpec, ParamSpecBoolean, ParamSpecObject, ParamSpecString, Value,
@@ -175,6 +202,8 @@ mod imp {
pub(super) uuid: RefCell<Option<Uuid>>,
pub(super) profile: RefCell<Option<Profile>>,
pub(crate) avatar: RefCell<Option<Paintable>>,
manager: RefCell<Option<Manager>>,
channel: RefCell<Option<Channel>>,
}
@@ -193,6 +222,7 @@ mod imp {
.construct_only()
.build(),
ParamSpecObject::builder::<Channel>("channel").build(),
ParamSpecObject::builder::<Paintable>("avatar").build(),
ParamSpecBoolean::builder("is-self").read_only().build(),
ParamSpecString::builder("title").read_only().build(),
]
@@ -204,6 +234,7 @@ mod imp {
match pspec.name() {
"manager" => self.manager.borrow().as_ref().to_value(),
"channel" => self.channel.borrow().as_ref().to_value(),
"avatar" => self.avatar.borrow().as_ref().to_value(),
"is-self" => {
if let Some(contact) = self.contact.borrow().as_ref() {
(contact.uuid == self.manager.borrow().as_ref().unwrap().uuid()).to_value()
@@ -275,6 +306,13 @@ mod imp {
self.channel.replace(obj);
}
"avatar" => {
let obj = value
.get::<Option<Paintable>>()
.expect("Property `avatar` of `Contact` has to be of type `Paintable`");
self.avatar.replace(obj);
}
_ => unimplemented!(),
}
}
+23
View File
@@ -551,6 +551,29 @@ impl Manager {
log::trace!("`Manager::retrieve_profile_by_uuid` finished");
r
}
pub(super) async fn retrieve_profile_avatar_by_uuid(
&self,
uuid: Uuid,
profile_key: ProfileKey,
) -> Result<Option<Vec<u8>>, PresageError> {
log::trace!("`Manager::retrieve_profile_avatar_by_uuid` start");
let r = self
.internal()
.retrieve_profile_avatar_by_uuid(uuid, profile_key)
.await;
log::trace!("`Manager::retrieve_profile_avatar_by_uuid` finished");
r
}
pub(super) async fn retrieve_group_avatar(
&self,
context: GroupContextV2,
) -> Result<Option<Vec<u8>>, PresageError> {
log::trace!("`Manager::retrieve_group_avatar` start");
let r = self.internal().retrieve_group_avatar(context).await;
log::trace!("`Manager::retrieve_group_avatar` finished");
r
}
pub(super) async fn send_message(
&self,
+49 -1
View File
@@ -6,7 +6,7 @@ use libsignal_service::{
content::ContentBody,
groups_v2::Group,
prelude::{phonenumber, Content, ProfileKey, Uuid},
proto::{AttachmentPointer, DataMessage},
proto::{AttachmentPointer, DataMessage, GroupContextV2},
push_service::DeviceInfo,
sender::{AttachmentSpec, AttachmentUploadError},
Profile, ServiceAddress,
@@ -70,6 +70,15 @@ enum Command {
UnlinkSecondary(i64, oneshot::Sender<Result<(), Error>>),
LinkedDevices(oneshot::Sender<Result<Vec<DeviceInfo>, Error>>),
RequestContacts(oneshot::Sender<Result<(), Error>>),
RetrieveProfileAvatarByUuid(
Uuid,
ProfileKey,
oneshot::Sender<Result<Option<Vec<u8>>, Error>>,
),
RetrieveGroupAvatar(
GroupContextV2,
oneshot::Sender<Result<Option<Vec<u8>>, Error>>,
),
}
#[derive(Debug)]
@@ -390,6 +399,35 @@ impl ManagerThread {
.expect("Command sending failed");
receiver.await.expect("Callback receiving failed")
}
pub async fn retrieve_profile_avatar_by_uuid(
&self,
uuid: Uuid,
profile_key: ProfileKey,
) -> Result<Option<Vec<u8>>, Error> {
let (sender, receiver) = oneshot::channel();
self.command_sender
.send(Command::RetrieveProfileAvatarByUuid(
uuid,
profile_key,
sender,
))
.await
.expect("Command sending failed");
receiver.await.expect("Callback receiving failed")
}
pub async fn retrieve_group_avatar(
&self,
context: GroupContextV2,
) -> Result<Option<Vec<u8>>, Error> {
let (sender, receiver) = oneshot::channel();
self.command_sender
.send(Command::RetrieveGroupAvatar(context, sender))
.await
.expect("Command sending failed");
receiver.await.expect("Callback receiving failed")
}
}
async fn setup_manager(
@@ -601,5 +639,15 @@ async fn handle_command(manager: &mut Manager<Store, Registered>, command: Comma
Command::RequestContacts(callback) => callback
.send(manager.request_contacts().await)
.expect("Callback sending failed"),
Command::RetrieveProfileAvatarByUuid(uuid, profile_key, callback) => callback
.send(
manager
.retrieve_profile_avatar_by_uuid(uuid, profile_key)
.await,
)
.expect("Callback sending failed"),
Command::RetrieveGroupAvatar(context, callback) => callback
.send(manager.retrieve_group_avatar(context).await)
.expect("Callback sending failed"),
}
}
+1
View File
@@ -72,6 +72,7 @@ pub mod imp {
let channel = binding.as_ref().expect("channel to be set at setup");
self.avatar.set_text(Some(&channel.title()));
self.avatar.set_show_initials(!channel.is_self());
self.avatar.set_custom_image(channel.avatar().as_ref());
let phone = channel.phone_number();
self.row_phone.set_visible(phone.is_some());