initial scaffolding for inverted, custom message list
This commit is contained in:
@@ -9,6 +9,19 @@ public class KordophoneApp : Adw.Application
|
|||||||
Object (application_id: "net.buzzert.kordophone2", flags: ApplicationFlags.FLAGS_NONE);
|
Object (application_id: "net.buzzert.kordophone2", flags: ApplicationFlags.FLAGS_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void startup () {
|
||||||
|
base.startup ();
|
||||||
|
|
||||||
|
// Load CSS from resources
|
||||||
|
var provider = new Gtk.CssProvider ();
|
||||||
|
provider.load_from_resource ("/net/buzzert/kordophone2/style.css");
|
||||||
|
Gtk.StyleContext.add_provider_for_display (
|
||||||
|
Gdk.Display.get_default (),
|
||||||
|
provider,
|
||||||
|
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void activate () {
|
protected override void activate () {
|
||||||
window = new MainWindow ();
|
window = new MainWindow ();
|
||||||
window.set_default_size (1200, 1000);
|
window.set_default_size (1200, 1000);
|
||||||
|
|||||||
@@ -12,5 +12,8 @@ public class MainWindow : Adw.ApplicationWindow
|
|||||||
|
|
||||||
var conversation_list_page = new NavigationPage (new ConversationListView (), "Conversations");
|
var conversation_list_page = new NavigationPage (new ConversationListView (), "Conversations");
|
||||||
split_view.sidebar = conversation_list_page;
|
split_view.sidebar = conversation_list_page;
|
||||||
|
|
||||||
|
var message_list_page = new NavigationPage (new MessageListView (new MessageListModel ("123")), "Messages");
|
||||||
|
split_view.content = message_list_page;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,8 @@ public class ConversationRow : Adw.ActionRow {
|
|||||||
subtitle = conversation.last_message_preview;
|
subtitle = conversation.last_message_preview;
|
||||||
subtitle_lines = 1;
|
subtitle_lines = 1;
|
||||||
|
|
||||||
|
add_css_class("conversation-row");
|
||||||
|
|
||||||
unread_badge = new Label(conversation.unread_count.to_string());
|
unread_badge = new Label(conversation.unread_count.to_string());
|
||||||
unread_badge.add_css_class("badge");
|
unread_badge.add_css_class("badge");
|
||||||
unread_badge.add_css_class("accent");
|
unread_badge.add_css_class("accent");
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ dependencies = [
|
|||||||
dependency('gio-unix-2.0', required : true)
|
dependency('gio-unix-2.0', required : true)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
gnome = import('gnome')
|
||||||
|
resources = gnome.compile_resources(
|
||||||
|
'kordophone-resources',
|
||||||
|
'resources/kordophone.gresource.xml',
|
||||||
|
source_dir: 'resources'
|
||||||
|
)
|
||||||
|
|
||||||
sources = [
|
sources = [
|
||||||
'application/kordophone-application.vala',
|
'application/kordophone-application.vala',
|
||||||
'application/main-window.vala',
|
'application/main-window.vala',
|
||||||
@@ -17,11 +24,16 @@ sources = [
|
|||||||
'conversation-list/conversation-list-model.vala',
|
'conversation-list/conversation-list-model.vala',
|
||||||
'conversation-list/conversation-row.vala',
|
'conversation-list/conversation-row.vala',
|
||||||
|
|
||||||
|
'message-list/message-list-view.vala',
|
||||||
|
'message-list/message-list-model.vala',
|
||||||
|
|
||||||
'models/conversation.vala',
|
'models/conversation.vala',
|
||||||
|
'models/message.vala',
|
||||||
]
|
]
|
||||||
|
|
||||||
executable('kordophone',
|
executable('kordophone',
|
||||||
sources,
|
sources,
|
||||||
|
resources,
|
||||||
dependencies : dependencies,
|
dependencies : dependencies,
|
||||||
vala_args: ['--pkg', 'posix'],
|
vala_args: ['--pkg', 'posix'],
|
||||||
install : true
|
install : true
|
||||||
|
|||||||
72
src/message-list/message-list-model.vala
Normal file
72
src/message-list/message-list-model.vala
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using GLib;
|
||||||
|
using Gee;
|
||||||
|
|
||||||
|
public class MessageListModel : Object, ListModel
|
||||||
|
{
|
||||||
|
public SortedSet<Message> messages {
|
||||||
|
owned get { return _messages.read_only_view; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _conversation_guid;
|
||||||
|
private SortedSet<Message> _messages;
|
||||||
|
|
||||||
|
public MessageListModel(string conversation_guid) {
|
||||||
|
_messages = new TreeSet<Message>((a, b) => {
|
||||||
|
// Sort by date in descending order (newest first)
|
||||||
|
return (int)(b.date - a.date);
|
||||||
|
});
|
||||||
|
|
||||||
|
Repository.get_instance().messages_updated.connect(got_messages_updated);
|
||||||
|
_conversation_guid = conversation_guid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load_messages() {
|
||||||
|
try {
|
||||||
|
Message[] messages = Repository.get_instance().get_messages(_conversation_guid);
|
||||||
|
|
||||||
|
// Clear existing set
|
||||||
|
uint old_count = _messages.size;
|
||||||
|
_messages.clear();
|
||||||
|
|
||||||
|
// Notify of removal
|
||||||
|
if (old_count > 0) {
|
||||||
|
items_changed(0, old_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each conversation
|
||||||
|
uint position = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < messages.length; i++) {
|
||||||
|
var message = messages[i];
|
||||||
|
_messages.add(message);
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify of additions
|
||||||
|
if (position > 0) {
|
||||||
|
items_changed(0, 0, position);
|
||||||
|
}
|
||||||
|
} catch (Error e) {
|
||||||
|
warning("Failed to load messages: %s", e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void got_messages_updated(string conversation_guid) {
|
||||||
|
if (conversation_guid == _conversation_guid) {
|
||||||
|
load_messages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListModel implementation
|
||||||
|
public Type get_item_type() {
|
||||||
|
return typeof(Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint get_n_items() {
|
||||||
|
return _messages.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object? get_item(uint position) {
|
||||||
|
return _messages.to_array()[position];
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/message-list/message-list-view.vala
Normal file
73
src/message-list/message-list-view.vala
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using Adw;
|
||||||
|
using Gtk;
|
||||||
|
|
||||||
|
public class MessageListView : Adw.Bin
|
||||||
|
{
|
||||||
|
private Adw.ToolbarView container;
|
||||||
|
|
||||||
|
private MessageDrawingArea message_drawing_area = new MessageDrawingArea();
|
||||||
|
private ScrolledWindow scrolled_window = new ScrolledWindow();
|
||||||
|
|
||||||
|
public MessageListView(MessageListModel model) {
|
||||||
|
container = new Adw.ToolbarView();
|
||||||
|
set_child(container);
|
||||||
|
|
||||||
|
scrolled_window.set_child(message_drawing_area);
|
||||||
|
scrolled_window.add_css_class("message-list-scroller");
|
||||||
|
container.set_content(scrolled_window);
|
||||||
|
|
||||||
|
var header_bar = new Adw.HeaderBar();
|
||||||
|
header_bar.set_title_widget(new Label("Messages"));
|
||||||
|
container.add_top_bar(header_bar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class MessageDrawingArea : Widget {
|
||||||
|
public MessageDrawingArea() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SizeRequestMode get_request_mode() {
|
||||||
|
return SizeRequestMode.HEIGHT_FOR_WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void measure(Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
|
||||||
|
GLib.message("Measure orientation: %s, for_size: %d", orientation.to_string(), for_size);
|
||||||
|
|
||||||
|
if (orientation == Orientation.HORIZONTAL) {
|
||||||
|
// Horizontal, so we take up the full width provided
|
||||||
|
minimum = 0;
|
||||||
|
natural = for_size;
|
||||||
|
} else {
|
||||||
|
GLib.message("Vertical measure for width: %d", for_size);
|
||||||
|
minimum = 1500;
|
||||||
|
natural = 1500;
|
||||||
|
}
|
||||||
|
|
||||||
|
minimum_baseline = -1;
|
||||||
|
natural_baseline = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void snapshot(Snapshot snapshot) {
|
||||||
|
var width = get_width();
|
||||||
|
var height = get_height();
|
||||||
|
|
||||||
|
GLib.message("Snapshot width: %d, height: %d", width, height);
|
||||||
|
|
||||||
|
var rect = Graphene.Rect().init(0, 0, width, height);
|
||||||
|
snapshot.append_color({1.0f, 0.0f, 0.0f, 1.0f}, rect);
|
||||||
|
|
||||||
|
// Create a text layout
|
||||||
|
var layout = create_pango_layout("Hello World!");
|
||||||
|
layout.set_width(width * Pango.SCALE);
|
||||||
|
|
||||||
|
// Set text attributes
|
||||||
|
var font_desc = Pango.FontDescription.from_string("Sans 14");
|
||||||
|
layout.set_font_description(font_desc);
|
||||||
|
|
||||||
|
// Draw the text in white
|
||||||
|
snapshot.append_layout(layout, Gdk.RGBA() { red = 1.0f, green = 1.0f, blue = 1.0f, alpha = 1.0f });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
16
src/models/message.vala
Normal file
16
src/models/message.vala
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using GLib;
|
||||||
|
|
||||||
|
public class Message : Object
|
||||||
|
{
|
||||||
|
public string guid { get; set; default = ""; }
|
||||||
|
public string content { get; set; default = ""; }
|
||||||
|
public int64 date { get; set; default = 0; }
|
||||||
|
public string?sender { get; set; default = null; }
|
||||||
|
|
||||||
|
public Message.from_hash_table(HashTable<string, Variant> message_data) {
|
||||||
|
guid = message_data["guid"].get_string();
|
||||||
|
content = message_data["content"].get_string();
|
||||||
|
date = message_data["date"].get_int64();
|
||||||
|
sender = message_data["sender"].get_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/resources/kordophone.gresource.xml
Normal file
6
src/resources/kordophone.gresource.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<gresources>
|
||||||
|
<gresource prefix="/net/buzzert/kordophone2">
|
||||||
|
<file>style.css</file>
|
||||||
|
</gresource>
|
||||||
|
</gresources>
|
||||||
16
src/resources/style.css
Normal file
16
src/resources/style.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/* Kordophone application styles */
|
||||||
|
|
||||||
|
.conversation-row {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid alpha(#000, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversation-row:selected {
|
||||||
|
background-color: alpha(@accent_bg_color, 0.50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-list-scroller {
|
||||||
|
/* Invert the y-axis, so the messages are drawn bottom-to-top */
|
||||||
|
/* Individual messages are drawn upside down in the custom renderer */
|
||||||
|
transform: scale(1, -1);
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ using Gee;
|
|||||||
public class Repository : Object
|
public class Repository : Object
|
||||||
{
|
{
|
||||||
public signal void conversations_updated();
|
public signal void conversations_updated();
|
||||||
|
public signal void messages_updated(string conversation_guid);
|
||||||
|
|
||||||
public static Repository get_instance() {
|
public static Repository get_instance() {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
@@ -43,6 +44,21 @@ public class Repository : Object
|
|||||||
|
|
||||||
return returned_conversations;
|
return returned_conversations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Message[] get_messages(string conversation_guid, string last_message_id = "") throws Error {
|
||||||
|
if (dbus_repository == null) {
|
||||||
|
throw new Error(1337, 1, "Repository not connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
var messages = dbus_repository.get_messages(conversation_guid, last_message_id);
|
||||||
|
Message[] returned_messages = new Message[messages.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < messages.length; i++) {
|
||||||
|
returned_messages[i] = new Message.from_hash_table(messages[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return returned_messages;
|
||||||
|
}
|
||||||
|
|
||||||
private async void connect_to_dbus() {
|
private async void connect_to_dbus() {
|
||||||
bool connected = false;
|
bool connected = false;
|
||||||
@@ -65,6 +81,10 @@ public class Repository : Object
|
|||||||
dbus_repository.conversations_updated.connect(() => {
|
dbus_repository.conversations_updated.connect(() => {
|
||||||
conversations_updated();
|
conversations_updated();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dbus_repository.messages_updated.connect((conversation_guid) => {
|
||||||
|
messages_updated(conversation_guid);
|
||||||
|
});
|
||||||
|
|
||||||
// Initial load
|
// Initial load
|
||||||
conversations_updated();
|
conversations_updated();
|
||||||
|
|||||||
Reference in New Issue
Block a user