mirror of
https://gitlab.com/schmiddi-on-mobile/flare.git
synced 2026-10-28 06:27:46 -04:00
Merge branch 'avatar2' into 'master'
Avatars Closes #14 See merge request schmiddi-on-mobile/flare!127
This commit is contained in:
commit
fdafbb02cd
11 files changed
+188
-11
No files matched your search
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
Reference in new issue
Block a user