Add sender annotations
This commit is contained in:
@@ -37,6 +37,7 @@ sources = [
|
|||||||
'transcript/layouts/bubble-layout.vala',
|
'transcript/layouts/bubble-layout.vala',
|
||||||
'transcript/layouts/chat-item-layout.vala',
|
'transcript/layouts/chat-item-layout.vala',
|
||||||
'transcript/layouts/date-item-layout.vala',
|
'transcript/layouts/date-item-layout.vala',
|
||||||
|
'transcript/layouts/sender-annotation-layout.vala',
|
||||||
'transcript/layouts/text-bubble-layout.vala',
|
'transcript/layouts/text-bubble-layout.vala',
|
||||||
|
|
||||||
'models/conversation.vala',
|
'models/conversation.vala',
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ private struct BubbleLayoutConstants {
|
|||||||
private abstract class BubbleLayout : Object, ChatItemLayout
|
private abstract class BubbleLayout : Object, ChatItemLayout
|
||||||
{
|
{
|
||||||
public bool from_me { get; set; }
|
public bool from_me { get; set; }
|
||||||
|
public float vertical_padding { get; set; }
|
||||||
|
|
||||||
protected float max_width;
|
protected float max_width;
|
||||||
protected Widget parent;
|
protected Widget parent;
|
||||||
protected BubbleLayoutConstants constants;
|
protected BubbleLayoutConstants constants;
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ using Gtk;
|
|||||||
interface ChatItemLayout : Object
|
interface ChatItemLayout : Object
|
||||||
{
|
{
|
||||||
public abstract bool from_me { get; set; }
|
public abstract bool from_me { get; set; }
|
||||||
|
public abstract float vertical_padding { get; set; }
|
||||||
|
|
||||||
public abstract float get_height();
|
public abstract float get_height();
|
||||||
public abstract float get_width();
|
public abstract float get_width();
|
||||||
|
|
||||||
public abstract void draw(Snapshot snapshot);
|
public abstract void draw(Snapshot snapshot);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using Gtk;
|
|||||||
|
|
||||||
class DateItemLayout : Object, ChatItemLayout {
|
class DateItemLayout : Object, ChatItemLayout {
|
||||||
public bool from_me { get; set; }
|
public bool from_me { get; set; }
|
||||||
|
public float vertical_padding { get; set; }
|
||||||
|
|
||||||
private Pango.Layout layout;
|
private Pango.Layout layout;
|
||||||
private float max_width;
|
private float max_width;
|
||||||
|
|||||||
56
src/transcript/layouts/sender-annotation-layout.vala
Normal file
56
src/transcript/layouts/sender-annotation-layout.vala
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using Gtk;
|
||||||
|
|
||||||
|
private class SenderAnnotationLayout : Object, ChatItemLayout
|
||||||
|
{
|
||||||
|
public string sender;
|
||||||
|
public float max_width;
|
||||||
|
public bool from_me { get; set; default = false; }
|
||||||
|
public float vertical_padding { get; set; default = 0.0f; }
|
||||||
|
|
||||||
|
private Pango.Layout layout;
|
||||||
|
private BubbleLayoutConstants constants;
|
||||||
|
private const float padding = 10.0f;
|
||||||
|
|
||||||
|
public SenderAnnotationLayout(string sender, float max_width, Widget parent) {
|
||||||
|
this.sender = sender;
|
||||||
|
this.max_width = max_width;
|
||||||
|
this.constants = BubbleLayoutConstants(parent.get_scale_factor());
|
||||||
|
|
||||||
|
layout = parent.create_pango_layout(sender);
|
||||||
|
var font_desc = Pango.FontDescription.from_string("Sans 8");
|
||||||
|
layout.set_font_description(font_desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float get_height() {
|
||||||
|
Pango.Rectangle ink_rect, logical_rect;
|
||||||
|
layout.get_pixel_extents(out ink_rect, out logical_rect);
|
||||||
|
|
||||||
|
return logical_rect.height + padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float get_width() {
|
||||||
|
return max_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw(Snapshot snapshot) {
|
||||||
|
snapshot.save();
|
||||||
|
|
||||||
|
Pango.Rectangle ink_rect, logical_rect;
|
||||||
|
layout.get_pixel_extents(out ink_rect, out logical_rect);
|
||||||
|
|
||||||
|
|
||||||
|
snapshot.translate(Graphene.Point() {
|
||||||
|
x = constants.text_padding + constants.tail_width,
|
||||||
|
y = get_height() - logical_rect.height,
|
||||||
|
});
|
||||||
|
|
||||||
|
snapshot.append_layout(layout, Gdk.RGBA() {
|
||||||
|
red = 1.0f,
|
||||||
|
green = 1.0f,
|
||||||
|
blue = 1.0f,
|
||||||
|
alpha = 0.8f
|
||||||
|
});
|
||||||
|
|
||||||
|
snapshot.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ private class TextBubbleLayout : BubbleLayout
|
|||||||
var size = font_desc.get_size();
|
var size = font_desc.get_size();
|
||||||
font_desc.set_size((int)(size));
|
font_desc.set_size((int)(size));
|
||||||
layout.set_font_description(font_desc);
|
layout.set_font_description(font_desc);
|
||||||
|
layout.set_wrap(Pango.WrapMode.WORD_CHAR);
|
||||||
|
|
||||||
// Set max width
|
// Set max width
|
||||||
layout.set_width((int)text_available_width * Pango.SCALE);
|
layout.set_width((int)text_available_width * Pango.SCALE);
|
||||||
|
|||||||
@@ -9,8 +9,16 @@ public class MessageListModel : Object, ListModel
|
|||||||
owned get { return _messages.read_only_view; }
|
owned get { return _messages.read_only_view; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool is_group_chat {
|
||||||
|
get {
|
||||||
|
return participants.size > 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string conversation_guid { get; private set; }
|
public string conversation_guid { get; private set; }
|
||||||
|
|
||||||
private SortedSet<Message> _messages;
|
private SortedSet<Message> _messages;
|
||||||
|
private HashSet<string> participants = new HashSet<string>();
|
||||||
|
|
||||||
public MessageListModel(string conversation_guid) {
|
public MessageListModel(string conversation_guid) {
|
||||||
_messages = new TreeSet<Message>((a, b) => {
|
_messages = new TreeSet<Message>((a, b) => {
|
||||||
@@ -29,6 +37,7 @@ public class MessageListModel : Object, ListModel
|
|||||||
// Clear existing set
|
// Clear existing set
|
||||||
uint old_count = _messages.size;
|
uint old_count = _messages.size;
|
||||||
_messages.clear();
|
_messages.clear();
|
||||||
|
participants.clear();
|
||||||
|
|
||||||
// Notify of removal
|
// Notify of removal
|
||||||
if (old_count > 0) {
|
if (old_count > 0) {
|
||||||
@@ -41,6 +50,8 @@ public class MessageListModel : Object, ListModel
|
|||||||
for (int i = 0; i < messages.length; i++) {
|
for (int i = 0; i < messages.length; i++) {
|
||||||
var message = messages[i];
|
var message = messages[i];
|
||||||
_messages.add(message);
|
_messages.add(message);
|
||||||
|
participants.add(message.sender);
|
||||||
|
|
||||||
position++;
|
position++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public class TranscriptView : Adw.Bin
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void reload_messages() {
|
private void reload_messages() {
|
||||||
|
transcript_drawing_area.show_sender = _model.is_group_chat;
|
||||||
transcript_drawing_area.set_messages(_model.messages);
|
transcript_drawing_area.set_messages(_model.messages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ using Gee;
|
|||||||
|
|
||||||
private class TranscriptDrawingArea : Widget
|
private class TranscriptDrawingArea : Widget
|
||||||
{
|
{
|
||||||
|
public bool show_sender = true;
|
||||||
|
|
||||||
private SortedSet<Message> _messages = new TreeSet<Message>();
|
private SortedSet<Message> _messages = new TreeSet<Message>();
|
||||||
private ArrayList<ChatItemLayout> _chat_items = new ArrayList<ChatItemLayout>();
|
private ArrayList<ChatItemLayout> _chat_items = new ArrayList<ChatItemLayout>();
|
||||||
|
|
||||||
private const float bubble_padding = 10.0f;
|
|
||||||
private const float bubble_margin = 18.0f;
|
private const float bubble_margin = 18.0f;
|
||||||
|
|
||||||
public TranscriptDrawingArea() {
|
public TranscriptDrawingArea() {
|
||||||
@@ -32,7 +33,7 @@ private class TranscriptDrawingArea : Widget
|
|||||||
// compute total message layout height
|
// compute total message layout height
|
||||||
float total_height = 0.0f;
|
float total_height = 0.0f;
|
||||||
_chat_items.foreach((chat_item) => {
|
_chat_items.foreach((chat_item) => {
|
||||||
total_height += chat_item.get_height() + bubble_padding;
|
total_height += chat_item.get_height() + chat_item.vertical_padding;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -50,9 +51,12 @@ private class TranscriptDrawingArea : Widget
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override void snapshot(Snapshot snapshot) {
|
public override void snapshot(Snapshot snapshot) {
|
||||||
var container_width = get_width();
|
|
||||||
float y_offset = 0;
|
float y_offset = 0;
|
||||||
_chat_items.foreach((chat_item) => {
|
var container_width = get_width();
|
||||||
|
|
||||||
|
// Draw each item in reverse order, since the messages are in reverse order
|
||||||
|
for (int i = _chat_items.size - 1; i >= 0; i--) {
|
||||||
|
var chat_item = _chat_items[i];
|
||||||
var item_width = chat_item.get_width();
|
var item_width = chat_item.get_width();
|
||||||
var item_height = chat_item.get_height();
|
var item_height = chat_item.get_height();
|
||||||
|
|
||||||
@@ -73,38 +77,45 @@ private class TranscriptDrawingArea : Widget
|
|||||||
chat_item.draw(snapshot);
|
chat_item.draw(snapshot);
|
||||||
snapshot.restore();
|
snapshot.restore();
|
||||||
|
|
||||||
y_offset -= item_height + bubble_padding;
|
y_offset -= item_height + chat_item.vertical_padding;
|
||||||
|
}
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recompute_message_layouts() {
|
private void recompute_message_layouts() {
|
||||||
var container_width = get_width();
|
var container_width = get_width();
|
||||||
float max_width = container_width * 0.90f;
|
float max_width = container_width * 0.90f;
|
||||||
|
|
||||||
_chat_items.clear();
|
|
||||||
|
|
||||||
var reversed_messages = _messages
|
|
||||||
.order_by((a, b) => b.date.compare(a.date)); // reverse order
|
|
||||||
|
|
||||||
DateTime? last_date = null;
|
DateTime? last_date = null;
|
||||||
reversed_messages.foreach((message) => {
|
string? last_sender = null;
|
||||||
// Remember everything in here is backwards.
|
ArrayList<ChatItemLayout> items = new ArrayList<ChatItemLayout>();
|
||||||
|
_messages.foreach((message) => {
|
||||||
if (last_date == null) {
|
// Date Annotation
|
||||||
last_date = message.date;
|
DateTime date = message.date;
|
||||||
} else if (last_date.difference(message.date) > (TimeSpan.MINUTE * 25)) {
|
if (last_date != null && date.difference(last_date) > (TimeSpan.MINUTE * 25)) {
|
||||||
var date_item = new DateItemLayout(last_date.to_local().format("%b %d, %Y at %H:%M"), this, max_width);
|
var date_item = new DateItemLayout(date.to_local().format("%b %d, %Y at %H:%M"), this, max_width);
|
||||||
_chat_items.add(date_item);
|
items.add(date_item);
|
||||||
last_date = message.date;
|
last_date = date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sender Annotation
|
||||||
|
if (show_sender && !message.from_me && last_sender != message.sender) {
|
||||||
|
var sender_bubble = new SenderAnnotationLayout(message.sender, max_width, this);
|
||||||
|
items.add(sender_bubble);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text Bubble
|
||||||
var text_bubble = new TextBubbleLayout(message, this, max_width);
|
var text_bubble = new TextBubbleLayout(message, this, max_width);
|
||||||
_chat_items.add(text_bubble);
|
text_bubble.vertical_padding = (last_sender == message.sender) ? 0.0f : 10.0f;
|
||||||
|
items.add(text_bubble);
|
||||||
|
|
||||||
|
last_sender = message.sender;
|
||||||
|
last_date = date;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_chat_items.clear();
|
||||||
|
_chat_items.add_all(items);
|
||||||
|
|
||||||
queue_draw();
|
queue_draw();
|
||||||
queue_resize();
|
queue_resize();
|
||||||
|
|||||||
Reference in New Issue
Block a user