diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cf600d3b..e9a7dd14 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ jobs: - uses: actions/checkout@v2 - run: sudo apt-get update - run: sudo apt-get remove libunwind-14-dev - - run: sudo apt-get install -y build-essential gettext cmake valac libgee-0.8-dev libsqlite3-dev libgtk-4-dev libnotify-dev libgpgme-dev libsoup2.4-dev libgcrypt20-dev libqrencode-dev libgspell-1-dev libnice-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libsrtp2-dev libwebrtc-audio-processing-dev + - run: sudo apt-get install -y build-essential gettext cmake valac libgee-0.8-dev libsqlite3-dev libgtk-4-dev libnotify-dev libgpgme-dev libsoup2.4-dev libgcrypt20-dev libqrencode-dev libgspell-1-dev libnice-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libsrtp2-dev libwebrtc-audio-processing-dev libadwaita-1-dev - run: ./configure --with-tests --with-libsignal-in-tree - run: make - run: build/xmpp-vala-test diff --git a/cmake/FindAdwaita.cmake b/cmake/FindAdwaita.cmake new file mode 100644 index 00000000..8202eea1 --- /dev/null +++ b/cmake/FindAdwaita.cmake @@ -0,0 +1,11 @@ +include(PkgConfigWithFallback) +find_pkg_config_with_fallback(Adwaita + PKG_CONFIG_NAME libadwaita-1 + LIB_NAMES libadwaita-1 + INCLUDE_NAMES adwaita.h + ) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Adwaita + REQUIRED_VARS Adwaita_LIBRARY + VERSION_VAR Adwaita_VERSION) diff --git a/libdino/src/entity/conversation.vala b/libdino/src/entity/conversation.vala index 9376dca9..353daeae 100644 --- a/libdino/src/entity/conversation.vala +++ b/libdino/src/entity/conversation.vala @@ -42,9 +42,10 @@ public class Conversation : Object { public enum Setting { DEFAULT, ON, OFF } public Setting send_typing { get; set; default = Setting.DEFAULT; } - public Setting send_marker { get; set; default = Setting.DEFAULT; } + public int pinned { get; set; default = 0; } + private Database? db; public Conversation(Jid jid, Account account, Type type) { @@ -74,6 +75,7 @@ public class Conversation : Object { notify_setting = (NotifySetting) row[db.conversation.notification]; send_typing = (Setting) row[db.conversation.send_typing]; send_marker = (Setting) row[db.conversation.send_marker]; + pinned = row[db.conversation.pinned]; notify.connect(on_update); } @@ -91,7 +93,8 @@ public class Conversation : Object { .value(db.conversation.active_last_changed, (long) active_last_changed.to_unix()) .value(db.conversation.notification, notify_setting) .value(db.conversation.send_typing, send_typing) - .value(db.conversation.send_marker, send_marker); + .value(db.conversation.send_marker, send_marker) + .value(db.conversation.pinned, pinned); if (read_up_to != null) { insert.value(db.conversation.read_up_to, read_up_to.id); } @@ -197,6 +200,8 @@ public class Conversation : Object { update.set(db.conversation.send_typing, send_typing); break; case "send-marker": update.set(db.conversation.send_marker, send_marker); break; + case "pinned": + update.set(db.conversation.pinned, pinned); break; } update.perform(); } diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala index 6a30f6dc..0fef0134 100644 --- a/libdino/src/plugin/interfaces.vala +++ b/libdino/src/plugin/interfaces.vala @@ -154,6 +154,7 @@ public interface ConversationItemWidgetInterface: Object { public delegate void MessageActionEvoked(Object button, Plugins.MetaConversationItem evoked_on, Object widget); public class MessageAction : Object { public string icon_name; + public string? tooltip; public Object? popover; public MessageActionEvoked? callback; } diff --git a/libdino/src/service/content_item_store.vala b/libdino/src/service/content_item_store.vala index 6a9e691f..740dc2a9 100644 --- a/libdino/src/service/content_item_store.vala +++ b/libdino/src/service/content_item_store.vala @@ -44,11 +44,8 @@ public class ContentItemStore : StreamInteractionModule, Object { Gee.TreeSet items = new Gee.TreeSet(ContentItem.compare_func); foreach (var row in select) { - int id = row[db.content_item.id]; - int content_type = row[db.content_item.content_type]; - int foreign_id = row[db.content_item.foreign_id]; - DateTime time = new DateTime.from_unix_utc(row[db.content_item.time]); - items.add(get_item(conversation, id, content_type, foreign_id, time)); + ContentItem content_item = get_item_from_row(row, conversation); + items.add(content_item); } Gee.List ret = new ArrayList(); @@ -58,6 +55,14 @@ public class ContentItemStore : StreamInteractionModule, Object { return ret; } + private ContentItem get_item_from_row(Row row, Conversation conversation) throws Error { + int id = row[db.content_item.id]; + int content_type = row[db.content_item.content_type]; + int foreign_id = row[db.content_item.foreign_id]; + DateTime time = new DateTime.from_unix_utc(row[db.content_item.time]); + return get_item(conversation, id, content_type, foreign_id, time); + } + private ContentItem get_item(Conversation conversation, int id, int content_type, int foreign_id, DateTime time) throws Error { switch (content_type) { case 1: @@ -112,6 +117,91 @@ public class ContentItemStore : StreamInteractionModule, Object { return item.size > 0 ? item[0] : null; } + public string? get_message_id_for_content_item(Conversation conversation, ContentItem content_item) { + Message? message = get_message_for_content_item(conversation, content_item); + if (message == null) return null; + + if (message.edit_to != null) return message.edit_to; + + if (conversation.type_ == Conversation.Type.CHAT) { + return message.stanza_id; + } else { + return message.server_id; + } + } + + public Jid? get_message_sender_for_content_item(Conversation conversation, ContentItem content_item) { + Message? message = get_message_for_content_item(conversation, content_item); + if (message == null) return null; + + // No need to look at edit_to, because it's the same sender JID. + + return message.from; + } + + public Message? get_message_for_content_item(Conversation conversation, ContentItem content_item) { + FileItem? file_item = content_item as FileItem; + if (file_item != null) { + if (file_item.file_transfer.provider != 0 || file_item.file_transfer.info == null) return null; + + int message_db_id = int.parse(file_item.file_transfer.info); + return stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(message_db_id, conversation); + } + MessageItem? message_item = content_item as MessageItem; + if (message_item != null) { + return message_item.message; + } + return null; + } + + public ContentItem? get_content_item_for_message_id(Conversation conversation, string message_id) { + Row? row = get_content_item_row_for_message_id(conversation, message_id); + if (row != null) { + return get_item_from_row(row, conversation); + } + return null; + } + + public int get_content_item_id_for_message_id(Conversation conversation, string message_id) { + Row? row = get_content_item_row_for_message_id(conversation, message_id); + if (row != null) { + return row[db.content_item.id]; + } + return -1; + } + + private Row? get_content_item_row_for_message_id(Conversation conversation, string message_id) { + var content_item_row = db.content_item.select(); + + Message? message = null; + if (conversation.type_ == Conversation.Type.CHAT) { + message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(message_id, conversation); + } else { + message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_server_id(message_id, conversation); + } + if (message == null) return null; + + RowOption file_transfer_row = db.file_transfer.select() + .with(db.file_transfer.account_id, "=", conversation.account.id) + .with(db.file_transfer.counterpart_id, "=", db.get_jid_id(conversation.counterpart)) + .with(db.file_transfer.info, "=", message.id.to_string()) + .order_by(db.file_transfer.time, "DESC") + .single().row(); + + if (file_transfer_row.is_present()) { + content_item_row.with(db.content_item.foreign_id, "=", file_transfer_row[db.file_transfer.id]) + .with(db.content_item.content_type, "=", 2); + } else { + content_item_row.with(db.content_item.foreign_id, "=", message.id) + .with(db.content_item.content_type, "=", 1); + } + RowOption content_item_row_option = content_item_row.single().row(); + if (content_item_row_option.is_present()) { + return content_item_row_option.inner; + } + return null; + } + public ContentItem? get_latest(Conversation conversation) { Gee.List items = get_n_latest(conversation, 1); if (items.size > 0) { diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index bfd85f06..96b3b82d 100644 --- a/libdino/src/service/database.vala +++ b/libdino/src/service/database.vala @@ -7,7 +7,7 @@ using Dino.Entities; namespace Dino { public class Database : Qlite.Database { - private const int VERSION = 24; + private const int VERSION = 25; public class AccountTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; @@ -244,10 +244,11 @@ public class Database : Qlite.Database { public Column notification = new Column.Integer("notification") { min_version=3 }; public Column send_typing = new Column.Integer("send_typing") { min_version=3 }; public Column send_marker = new Column.Integer("send_marker") { min_version=3 }; + public Column pinned = new Column.Integer("pinned") { default="0", min_version=25 }; internal ConversationTable(Database db) { base(db, "conversation"); - init({id, account_id, jid_id, resource, active, active_last_changed, last_active, type_, encryption, read_up_to, read_up_to_item, notification, send_typing, send_marker}); + init({id, account_id, jid_id, resource, active, active_last_changed, last_active, type_, encryption, read_up_to, read_up_to_item, notification, send_typing, send_marker, pinned}); } } diff --git a/libdino/src/service/history_sync.vala b/libdino/src/service/history_sync.vala index ed5a04af..c7bfee88 100644 --- a/libdino/src/service/history_sync.vala +++ b/libdino/src/service/history_sync.vala @@ -204,19 +204,18 @@ public class Dino.HistorySync { var query_params = new Xmpp.MessageArchiveManagement.V2.MamQueryParams.query_latest(mam_server, latest_message_time, latest_message_id); PageRequestResult page_result = yield get_mam_page(account, query_params, null); - - if (page_result.page_result == PageResult.Duplicate) { - // No new messages - return null; - } + debug("[%s | %s] Latest page result: %s", account.bare_jid.to_string(), mam_server.to_string(), page_result.page_result.to_string()); if (page_result.page_result == PageResult.Error) { - debug("[%s | %s] Failed fetching latest page %s", mam_server.to_string(), mam_server.to_string(), page_result.page_result.to_string()); return null; } + // If we get PageResult.Duplicate, we still want to update the db row to the latest message. + // Catchup finished within first page. Update latest db entry. - if (page_result.page_result in new PageResult[] { PageResult.TargetReached, PageResult.NoMoreMessages } && latest_row_id != -1) { + if (latest_row_id != -1 && + page_result.page_result in new PageResult[] { PageResult.TargetReached, PageResult.NoMoreMessages, PageResult.Duplicate }) { + if (page_result.stanzas == null || page_result.stanzas.is_empty) return null; string latest_mam_id = page_result.query_result.last; @@ -258,7 +257,7 @@ public class Dino.HistorySync { .value(db.mam_catchup.server_jid, mam_server.to_string()) .value(db.mam_catchup.from_id, from_id) .value(db.mam_catchup.from_time, from_time) - .value(db.mam_catchup.from_end, false) + .value(db.mam_catchup.from_end, page_result.page_result == PageResult.NoMoreMessages) .value(db.mam_catchup.to_id, to_id) .value(db.mam_catchup.to_time, to_time) .perform(); diff --git a/libdino/src/service/message_processor.vala b/libdino/src/service/message_processor.vala index 8d544b45..770ae0a6 100644 --- a/libdino/src/service/message_processor.vala +++ b/libdino/src/service/message_processor.vala @@ -425,24 +425,8 @@ public class MessageProcessor : StreamInteractionModule, Object { new_message.type_ = MessageStanza.TYPE_CHAT; } - if (message.quoted_item_id > 0) { - ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(conversation, message.quoted_item_id); - if (content_item != null && content_item.type_ == MessageItem.TYPE) { - Message? quoted_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(((MessageItem) content_item).message.id, conversation); - if (quoted_message != null) { - Xep.Replies.set_reply_to(new_message, new Xep.Replies.ReplyTo(quoted_message.from, quoted_message.stanza_id)); - - string body_with_fallback = "> " + Dino.message_body_without_reply_fallback(quoted_message); - body_with_fallback = body_with_fallback.replace("\n", "\n> "); - body_with_fallback += "\n"; - long fallback_length = body_with_fallback.length; - body_with_fallback += message.body; - new_message.body = body_with_fallback; - var fallback_location = new Xep.FallbackIndication.FallbackLocation(0, (int)fallback_length); - Xep.FallbackIndication.set_fallback(new_message, new Xep.FallbackIndication.Fallback(Xep.Replies.NS_URI, new Xep.FallbackIndication.FallbackLocation[] { fallback_location })); - } - } - } + string? fallback = get_fallback_body_set_infos(message, new_message, conversation); + new_message.body = fallback == null ? message.body : fallback + message.body; build_message_stanza(message, new_message, conversation); pre_message_send(message, new_message, conversation); @@ -487,6 +471,37 @@ public class MessageProcessor : StreamInteractionModule, Object { } }); } + + public string? get_fallback_body_set_infos(Entities.Message message, MessageStanza new_stanza, Conversation conversation) { + if (message.quoted_item_id == 0) return null; + + ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(conversation, message.quoted_item_id); + if (content_item == null) return null; + + Jid? quoted_sender = stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_sender_for_content_item(conversation, content_item); + string? quoted_stanza_id = stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_id_for_content_item(conversation, content_item); + if (quoted_sender != null && quoted_stanza_id != null) { + Xep.Replies.set_reply_to(new_stanza, new Xep.Replies.ReplyTo(quoted_sender, quoted_stanza_id)); + } + + string fallback = "> "; + + if (content_item.type_ == MessageItem.TYPE) { + Message? quoted_message = ((MessageItem) content_item).message; + fallback += Dino.message_body_without_reply_fallback(quoted_message); + fallback = fallback.replace("\n", "\n> "); + } else if (content_item.type_ == FileItem.TYPE) { + FileTransfer? quoted_file = ((FileItem) content_item).file_transfer; + fallback += quoted_file.file_name; + } + fallback += "\n"; + + long fallback_length = fallback.length; + var fallback_location = new Xep.FallbackIndication.FallbackLocation(0, (int)fallback_length); + Xep.FallbackIndication.set_fallback(new_stanza, new Xep.FallbackIndication.Fallback(Xep.Replies.NS_URI, new Xep.FallbackIndication.FallbackLocation[] { fallback_location })); + + return fallback; + } } public abstract class MessageListener : Xmpp.OrderedListener { diff --git a/libdino/src/service/message_storage.vala b/libdino/src/service/message_storage.vala index fbdbcf8a..3dadab7b 100644 --- a/libdino/src/service/message_storage.vala +++ b/libdino/src/service/message_storage.vala @@ -116,9 +116,7 @@ public class MessageStorage : StreamInteractionModule, Object { .outer_join_with(db.message_correction, db.message_correction.message_id, db.message.id) .outer_join_with(db.reply, db.reply.message_id, db.message.id); - if (conversation.counterpart.resourcepart == null) { - query.with_null(db.message.counterpart_resource); - } else { + if (conversation.counterpart.resourcepart != null) { query.with(db.message.counterpart_resource, "=", conversation.counterpart.resourcepart); } diff --git a/libdino/src/service/reactions.vala b/libdino/src/service/reactions.vala index fa273f39..be98293f 100644 --- a/libdino/src/service/reactions.vala +++ b/libdino/src/service/reactions.vala @@ -10,7 +10,6 @@ public class Dino.Reactions : StreamInteractionModule, Object { public string id { get { return IDENTITY.id; } } public signal void reaction_added(Account account, int content_item_id, Jid jid, string reaction); -// [Signal(detailed=true)] public signal void reaction_removed(Account account, int content_item_id, Jid jid, string reaction); private StreamInteractor stream_interactor; @@ -27,7 +26,7 @@ public class Dino.Reactions : StreamInteractionModule, Object { this.db = database; stream_interactor.account_added.connect(on_account_added); - stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent_or_received.connect(on_new_message); + stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect(on_new_item); } public void add_reaction(Conversation conversation, ContentItem content_item, string reaction) { @@ -35,15 +34,19 @@ public class Dino.Reactions : StreamInteractionModule, Object { if (!reactions.contains(reaction)) { reactions.add(reaction); } - send_reactions(conversation, content_item, reactions); - reaction_added(conversation.account, content_item.id, conversation.account.bare_jid, reaction); + try { + send_reactions(conversation, content_item, reactions); + reaction_added(conversation.account, content_item.id, conversation.account.bare_jid, reaction); + } catch (SendError e) {} } public void remove_reaction(Conversation conversation, ContentItem content_item, string reaction) { Gee.List reactions = get_own_reactions(conversation, content_item); reactions.remove(reaction); - send_reactions(conversation, content_item, reactions); - reaction_removed(conversation.account, content_item.id, conversation.account.bare_jid, reaction); + try { + send_reactions(conversation, content_item, reactions); + reaction_removed(conversation.account, content_item.id, conversation.account.bare_jid, reaction); + } catch (SendError e) {} } public Gee.List get_item_reactions(Conversation conversation, ContentItem content_item) { @@ -80,35 +83,28 @@ public class Dino.Reactions : StreamInteractionModule, Object { return false; } - private void send_reactions(Conversation conversation, ContentItem content_item, Gee.List reactions) { - Message? message = null; + private void send_reactions(Conversation conversation, ContentItem content_item, Gee.List reactions) throws SendError { + string? message_id = stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_id_for_content_item(conversation, content_item); + if (message_id == null) throw new SendError.Misc("No message for content_item"); - FileItem? file_item = content_item as FileItem; - if (file_item != null) { - int message_id = int.parse(file_item.file_transfer.info); - message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(message_id, conversation); - } - MessageItem? message_item = content_item as MessageItem; - if (message_item != null) { - message = message_item.message; - } + XmppStream? stream = stream_interactor.get_stream(conversation.account); + if (stream == null) throw new SendError.NoStream(""); - if (message == null) { - return; - } + var reactions_module = stream.get_module(Xmpp.Xep.Reactions.Module.IDENTITY); - XmppStream stream = stream_interactor.get_stream(conversation.account); - if (conversation.type_ == Conversation.Type.GROUPCHAT || conversation.type_ == Conversation.Type.GROUPCHAT_PM) { - if (conversation.type_ == Conversation.Type.GROUPCHAT) { - stream.get_module(Xmpp.Xep.Reactions.Module.IDENTITY).send_reaction(stream, conversation.counterpart, "groupchat", message.server_id ?? message.stanza_id, reactions); - } else if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) { - stream.get_module(Xmpp.Xep.Reactions.Module.IDENTITY).send_reaction(stream, conversation.counterpart, "chat", message.server_id ?? message.stanza_id, reactions); - } + if (conversation.type_ == Conversation.Type.GROUPCHAT) { + reactions_module.send_reaction.begin(stream, conversation.counterpart, "groupchat", message_id, reactions); // We save the reaction when it gets reflected back to us + } else if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) { + reactions_module.send_reaction(stream, conversation.counterpart, "chat", message_id, reactions); } else if (conversation.type_ == Conversation.Type.CHAT) { - stream.get_module(Xmpp.Xep.Reactions.Module.IDENTITY).send_reaction(stream, conversation.counterpart, "chat", message.stanza_id, reactions); int64 now_millis = GLib.get_real_time () / 1000; - save_chat_reactions(conversation.account, conversation.account.bare_jid, content_item.id, now_millis, reactions); + reactions_module.send_reaction.begin(stream, conversation.counterpart, "chat", message_id, reactions, (_, res) => { + try { + reactions_module.send_reaction.end(res); + save_chat_reactions(conversation.account, conversation.account.bare_jid, content_item.id, now_millis, reactions); + } catch (SendError e) {} + }); } } @@ -251,11 +247,11 @@ public class Dino.Reactions : StreamInteractionModule, Object { Message reaction_message = yield stream_interactor.get_module(MessageProcessor.IDENTITY).parse_message_stanza(account, stanza); Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(reaction_message); - Message? message = get_message_for_reaction(conversation, message_id); - var reaction_info = new ReactionInfo() { account=account, from_jid=from_jid, reactions=reactions, stanza=stanza, received_time=new DateTime.now() }; + int content_item_id = stream_interactor.get_module(ContentItemStore.IDENTITY).get_content_item_id_for_message_id(conversation, message_id); + var reaction_info = new ReactionInfo() { conversation=conversation, from_jid=from_jid, reactions=reactions, stanza=stanza, received_time=new DateTime.now() }; - if (message != null) { - process_reaction_for_message(message.id, reaction_info); + if (content_item_id != -1) { + process_reaction_for_message(content_item_id, reaction_info); return; } @@ -267,45 +263,33 @@ public class Dino.Reactions : StreamInteractionModule, Object { reaction_infos[message_id].add(reaction_info); } - private void on_new_message(Message message, Conversation conversation) { - Gee.List? reaction_info_list = null; - if (conversation.type_ == Conversation.Type.CHAT) { - reaction_info_list = reaction_infos[message.stanza_id]; - } else { - reaction_info_list = reaction_infos[message.server_id]; - } + /* + * When we get a new ContentItem, check if we have any reactions cached that apply to it. + * If so, process the reactions, map and store them. + */ + private void on_new_item(ContentItem item, Conversation conversation) { + string? stanza_id = stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_id_for_content_item(conversation, item); + if (stanza_id == null) return; + + Gee.List? reaction_info_list = reaction_infos[stanza_id]; if (reaction_info_list == null) return; + Message? message = stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_for_content_item(conversation, item); + if (message == null) return; + // Check if the (or potentially which) reaction fits the message - ReactionInfo? reaction_info = null; - foreach (ReactionInfo info in reaction_info_list) { - if (!info.account.equals(conversation.account)) return; - switch (info.stanza.type_) { - case MessageStanza.TYPE_CHAT: - Jid counterpart = message.from.equals_bare(conversation.account.bare_jid) ? info.stanza.from: info.stanza.to; - if (message.type_ != Message.Type.CHAT || !counterpart.equals_bare(conversation.counterpart)) continue; - break; - case MessageStanza.TYPE_GROUPCHAT: - if (message.type_ != Message.Type.GROUPCHAT || !message.from.equals_bare(conversation.counterpart)) continue; - break; - default: - break; + var applicable_reactions = new ArrayList(); + applicable_reactions.add_all_iterator(reaction_info_list.filter(info => info.conversation.equals(conversation))); + + foreach (ReactionInfo applicable_reaction in applicable_reactions) { + reaction_info_list.remove(applicable_reaction); + if (reaction_info_list.is_empty) { + reaction_infos.unset(stanza_id); } - reaction_info = info; + debug("Got ContentItem for reaction %s", stanza_id); + process_reaction_for_message(item.id, applicable_reaction); } - if (reaction_info == null) return; - reaction_info_list.remove(reaction_info); - if (reaction_info_list.is_empty) { - if (conversation.type_ == Conversation.Type.GROUPCHAT) { - reaction_infos.unset(message.server_id); - } else { - reaction_infos.unset(message.stanza_id); - } - } - - debug("Got message for reaction %s", message.stanza_id); - process_reaction_for_message(message.id, reaction_info); } private Message? get_message_for_reaction(Conversation conversation, string message_id) { @@ -317,30 +301,12 @@ public class Dino.Reactions : StreamInteractionModule, Object { } } - private void process_reaction_for_message(int message_db_id, ReactionInfo reaction_info) { - Account account = reaction_info.account; + private void process_reaction_for_message(int content_item_id, ReactionInfo reaction_info) { + Account account = reaction_info.conversation.account; MessageStanza stanza = reaction_info.stanza; Jid from_jid = reaction_info.from_jid; Gee.List reactions = reaction_info.reactions; - RowOption file_transfer_row = db.file_transfer.select() - .with(db.file_transfer.account_id, "=", account.id) - .with(db.file_transfer.info, "=", message_db_id.to_string()) - .single().row(); // TODO better - - var content_item_row = db.content_item.select(); - - if (file_transfer_row.is_present()) { - content_item_row.with(db.content_item.foreign_id, "=", file_transfer_row[db.file_transfer.id]) - .with(db.content_item.content_type, "=", 2); - } else { - content_item_row.with(db.content_item.foreign_id, "=", message_db_id) - .with(db.content_item.content_type, "=", 1); - } - var content_item_row_opt = content_item_row.single().row(); - if (!content_item_row_opt.is_present()) return; - int content_item_id = content_item_row_opt[db.content_item.id]; - // Get reaction time DateTime? reaction_time = null; DelayedDelivery.MessageFlag? delayed_message_flag = DelayedDelivery.MessageFlag.get_flag(stanza); @@ -485,7 +451,7 @@ public class Dino.ReactionUsers { } public class Dino.ReactionInfo { - public Account account { get; set; } + public Conversation conversation { get; set; } public Jid from_jid { get; set; } public Gee.List reactions { get; set; } public MessageStanza stanza { get; set; } diff --git a/libdino/src/service/replies.vala b/libdino/src/service/replies.vala index 6a9bced4..97db70ee 100644 --- a/libdino/src/service/replies.vala +++ b/libdino/src/service/replies.vala @@ -77,22 +77,7 @@ public class Dino.Replies : StreamInteractionModule, Object { Xep.Replies.ReplyTo? reply_to = Xep.Replies.get_reply_to(stanza); if (reply_to == null) return; - Message? quoted_message = null; - if (conversation.type_ == Conversation.Type.GROUPCHAT) { - quoted_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_server_id(reply_to.to_message_id, conversation); - } else { - quoted_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(reply_to.to_message_id, conversation); - } - if (quoted_message == null) { - db.reply.upsert() - .value(db.reply.message_id, message.id, true) - .value(db.reply.quoted_message_stanza_id, reply_to.to_message_id) - .value(db.reply.quoted_message_from, reply_to.to_jid.to_string()) - .perform(); - return; - } - - ContentItem? quoted_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_foreign(conversation, 1, quoted_message.id); + ContentItem? quoted_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_content_item_for_message_id(conversation, reply_to.to_message_id); if (quoted_content_item == null) return; set_message_is_reply_to(message, quoted_content_item); diff --git a/libdino/src/service/util.vala b/libdino/src/service/util.vala index 9f389b3f..496994c8 100644 --- a/libdino/src/service/util.vala +++ b/libdino/src/service/util.vala @@ -1,4 +1,5 @@ using Dino.Entities; +using Qlite; namespace Dino { diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 044a9721..a2f63cb1 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -11,6 +11,7 @@ find_packages(MAIN_PACKAGES REQUIRED GObject GTK4 ICU + Adwaita ) set(RESOURCE_LIST @@ -89,7 +90,8 @@ set(RESOURCE_LIST unified_main_content.ui unified_window_placeholder.ui - theme.css + style.css + style-dark.css ) compile_gresources( @@ -110,6 +112,12 @@ set(MAIN_DEFINITIONS) if(GTK4_VERSION VERSION_GREATER_EQUAL "4.6") set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} GTK_4_6) endif() +if(GTK4_VERSION VERSION_GREATER_EQUAL "4.8") + set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} GTK_4_8) +endif() +if(Adwaita_VERSION VERSION_GREATER_EQUAL "1.2") + set(MAIN_DEFINITIONS ${MAIN_DEFINITIONS} Adw_1_2) +endif() vala_precompile(MAIN_VALA_C SOURCES @@ -199,7 +207,9 @@ SOURCES src/ui/util/label_hybrid.vala src/ui/util/sizing_bin.vala src/ui/util/size_request_box.vala - src/ui/util/scaling_image.vala + + src/ui/widgets/fixed_ratio_picture.vala + src/ui/widgets/natural_size_increase.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi diff --git a/main/data/conversation_content_view/view.ui b/main/data/conversation_content_view/view.ui index f6819b94..3415e6c9 100644 --- a/main/data/conversation_content_view/view.ui +++ b/main/data/conversation_content_view/view.ui @@ -31,7 +31,9 @@ end start diff --git a/main/data/conversation_list_titlebar_csd.ui b/main/data/conversation_list_titlebar_csd.ui index f0e887dd..afe10926 100644 --- a/main/data/conversation_list_titlebar_csd.ui +++ b/main/data/conversation_list_titlebar_csd.ui @@ -1,13 +1,15 @@ - + + False + False list-add-symbolic @@ -18,6 +20,7 @@ + False open-menu-symbolic diff --git a/main/data/conversation_row.ui b/main/data/conversation_row.ui index fcfd22f0..2eb9071b 100644 --- a/main/data/conversation_row.ui +++ b/main/data/conversation_row.ui @@ -88,20 +88,33 @@ - + slide-right 50 True + 15 - - False - False - 15 - 0.5 - - - - + + horizontal + 6 + + + False + False + 0.5 + + + + + + + + + view-pin-symbolic + 12 + False + + @@ -131,7 +144,7 @@ 5 diff --git a/main/data/conversation_view.ui b/main/data/conversation_view.ui index f13e583d..6e24f4b5 100644 --- a/main/data/conversation_view.ui +++ b/main/data/conversation_view.ui @@ -31,7 +31,8 @@ end end diff --git a/main/data/global_search.ui b/main/data/global_search.ui index a29c48de..610bcace 100644 --- a/main/data/global_search.ui +++ b/main/data/global_search.ui @@ -19,40 +19,13 @@ empty - - vertical - 10 - center - - - system-search-symbolic - large - 72 - - - - - - No active search - - - - - - - - - - Type to start a search - - - + + + system-search-symbolic + No active search + Type to start a search @@ -61,40 +34,13 @@ no-result - - vertical - 10 - center - - - face-uncertain-symbolic - large - 72 - - - - - - No matching messages - - - - - - - - - - Check the spelling or try to remove filters - - - + + + face-uncertain-symbolic + No matching messages + Check the spelling or try to remove filters diff --git a/main/data/im.dino.Dino.appdata.xml b/main/data/im.dino.Dino.appdata.xml index bfa25803..1b0476e7 100644 --- a/main/data/im.dino.Dino.appdata.xml +++ b/main/data/im.dino.Dino.appdata.xml @@ -17,13 +17,14 @@ Moderno cliente de chat XMPP Nowoczesny komunikator XMPP Client XMPP modèrn - Modernen XMPP-chatcliënt Een moderne XMPP-chatclient Moderne XMPP-sludreklient Šiuolaikinė XMPP pokalbių kliento programa Modernen XMPP Chat Client + 현대 XMPP 채팅 클라이언트 現代的な XMPP チャット クライアント Client di chat moderno per XMPP + Nútímalegt XMPP-spjallforrit Un modern client de conversationes XMPP Aplikasi chat XMPP modern Modern XMPP csevegőprogram @@ -33,6 +34,7 @@ XMPP txat bezero modernoa Un cliente de XMPP moderno Moderna XMPP-Retebabililo + Σύγχρονος XMPP Chat Client Modernes XMPP-Chat-Programm Moderní XMPP klient Client de xat XMPP modern @@ -50,13 +52,14 @@

Dino é um moderno chat de código aberto para desktop. Ele é focado em prover uma transparente e confiável experiência Jabber/XMPP, tendo em mente a sua privacidade.

Dino jest nowoczesnym, otwartym komunikatorem. Skupia się na prostej obsłudze sieci Jabber/XMPP dbając o twoją prywatność.

Dino es un client de chat liure e modèrn per l’ordenador de burèu. Ensaja de provesir una experiéncia neta e fisabla de Jabber/XMPP en téner en compte vòstra confidencialitat.

-

Dino is een moderne, vrije chattoepassing voor uw bureaublad. Ze biedt een eenvoudige en betrouwbare Jabber/XMPP-ervaring, met uw privacy in het achterhoofd.

Dino is een moderne, vrije chattoepassing voor je computer. Dino biedt een eenvoudige en betrouwbare Jabber-/XMPP-ervaring, met privacy in het achterhoofd.

Dino er en moderne friporg-sludringsklient for skrivebordet. Det fokuserer på rask og pålitelig XMPP-opplevelse, samtidig som det hegner om personvernet.

Dino yra šiuolaikinė atvirojo kodo kliento programa skirta darbalaukiui. Jos pagrindinis dėmesys yra pateikti tvarkingą ir patikimą Jabber/XMPP patyrimą nepamirštant apie jūsų privatumą.

Dino ass e modernen, quell-offene Chat Client fir den Desktop. Hien biet eng opgeraumt a robust Jabber/XMPP Erfarung a leet ee Schwéierpunkt op Privatsphär.

+

Dino는 데스크탑을 위한 현대 오픈소스 채팅 클라이언트입니다. 깔끔하고 신뢰 할 수 있는 Jabber/XMPP 경험을 개인정보 보호 중시와 함께 제공할 수 있도록 주력하고 있습니다.

Dino はオープンソースの現代的なデスクトップ向けチャットクライアントです。プライバシーを考慮しつつ、シンプルで信頼できる Jabber/XMPP エクスペリエンスの提供を第一に考えて開発されています。

Dino è un client di chat per il desktop, moderno e open-source. Si concentra nel fornire un'esperienza Jabber/XMPP pulita e affidabile tenendo presente la tua privacy.

+

Dino er nútímalegt spjallforrit og frjáls hugbúnaður fyrir skjáborðið. Það leggur áherslu á að veita hreina og áreiðanlega Jabber/XMPP upplifun með friðhelgi þína í huga.

Dino es un modern cliente de conversationes con fonte apert. It foca se sur provider un nett e fidibil experientie de Jabber/XMPP con un attention a confidentialitá.

Dino adalah aplikasi chat open source modern untuk PC. Menyediakan pengalaman Jabber / XMPP yang handal dengan tetap menjunjung privasi Anda.

A Dino egy modern, nyílt forráskódú csevegőprogram az asztali gépekre. Arra összpontosít, hogy tiszta és megbízható Jabber/XMPP-élményt nyújtson, miközben a magánszféra megőrzését is fontosnak tartja.

@@ -67,6 +70,7 @@

Dino mahaigainerako iturburu irekiko txat bezero moderno bat da. Jabber/XMPP esperientzia garbi eta fidagarri bat ematen du zure pribatutasuna kontuan hartzeaz gain.

Dino es un cliente de mensajería moderno y libre para escritorio y móvil. Está enfocado en proveer una experiencia Jabber/XMPP limpia y confiable teniendo la privacidad en mente.

Dino estas moderna malfermfonta retbabililo por la tabla komputilo. Ĝi celas provizi puran kaj fidindan sperton de Jabber/XMPP, protektante vian privatecon.

+

Το Dino είναι ένας σύγχρονος πελάτης συνομιλίας ανοιχτού κώδικα για desktop υπολογιστές. Επικεντρώνεται στην παροχή μιας καθαρής και αξιόπιστης εμπειρίας Jabber/XMPP έχοντας παράλληλα υπόψη την προστασία των προσωπικών δεδομένων σας.

Dino ist ein modernes, quelloffenes Chat-Programm für den Desktop. Es bietet ein aufgeräumtes und robustes Jabber-/XMPP-Erlebnis und legt einen Schwerpunkt auf Privatsphäre.

Dino je moderní open-source chatovací klient pro stolní počítače. Jeho cílem je poskytování čistého a spolehlivého prostředí Jabber/XMPP s důrazem na zachování vašeho soukromí.

Dino és un client de xat lliure i modern per a l'escriptori. Està centrat en proveir una experiència neta i fiable de Jabber/XMPP, sempre tenint en compte la vostra privacitat.

@@ -83,13 +87,14 @@

Suporte criptografia ponta a ponta com OMEMO e OpenPGP e permite configurar privacidade—características relacionadas às notificações de leitura, recebimento e escrita.

Obsługuje szyfrowanie od końca do końca za pomocą OMEMO i OpenPGP, a także daje kontrolę nad funkcjami wpływającymi na prywatność, jak powiadomienia o pisaniu czy odczytaniu wiadomości.

Compatible amb lo chiframent OMEMO e OpenPGP del cap a la fin e permet de configurar de foncionalitats ligadas amb la confidencialitat coma los acusats de lectura e las notificacions d’escritura.

-

Ze ondersteunt eind-tot-eind-versleuteling met OMEMO en OpenPGP, en laat u toe privacygerelateerde functies, gelijk leesbevestigingen en typmeldingen, in te stellen.

Dino ondersteunt end-to-endversleuteling met OMEMO en OpenPGP en staat je toe privacy-gerelateerde functies, zoals leesbevestigingen en aan-het-typenmeldingen, in te stellen.

Det støtter ende-til-ende -kryptering med OMEMO og OpenPGP, og tillater oppsett av personvernsrelaterte funksjoner som meldingskvitteringer og skrivevarsling.

Ji palaiko ištisinį šifravimą naudojant OMEMO ir OpenPGP bei leidžia konfigūruoti su privatumu susijusias ypatybes, tokias kaip pranešimus apie žinučių skaitymą ir rašymą.

Hien ënnerstëtz Enn-zu-Enn Verschlësselung mat OMEMO an OpenPGP an enthält Privatsphäre-Astellungen zu Liesbestätegungen an Tipp-Benoriichtegungen.

+

OMEMO와 OpenPGP를 통한 종단간 암호화를 지원하며 프라이버시와 관련된 읽음 확인이나 입력 알림기능을 설정할 수 있습니다.

OMEMO と OpenPGP を利用したエンドツーエンド暗号化に対応しており、既読状態の送信や入力通知などのプライバシー関連の設定も可能です。

Support la crittografia end-to-end tramite OMEMO e OpenPGP e permette di configurare le funzioni relative alla privacy come le ricevute di lettura e le notifiche di digitazione.

+

Það styður dulkóðun frá enda til enda með OMEMO og OpenPGP og gerir kleift að stilla persónuverndartengda eiginleika eins og lestrarkvittanir og innsláttartilkynningar.

It supporta ciffration terminal per OMEMO e OpenPGP e permisse configurar sensitiv functiones quam confirmation de lectada e notificationes de tippada.

Mendukung enkripsi end-to-end dengan OMEMO dan OpenPGP, dan memungkinkan pengaturan fitur terkait privasi seperti tanda pesan dibaca dan pemberitahuan pengetikan.

Támogatja az OMEMO és az OpenPGP használatával történő végpontok közötti titkosítást, és lehetővé teszi a magánszférához kapcsolódó funkciókat, mint például az olvasási visszaigazolást és a gépelési értesítéseket.

@@ -98,8 +103,9 @@

Se tukee päästä päähän -salausta OMEMO:n ja OpenPGP:n avulla ja mahdollistaa yksityisyyteen liittyvien ominaisuuksien, kuten lukukuittausten ja kirjoitusilmoitusten asetusten määrittämisen.

از رمزگذاری سرتاسر با اُمیمو و اُپن‌پی‌جی‌پی پشتیبانی می‌کند و اجازه تنظیم قابلیت‌های مربوط به حریم خصوصی را می‌دهد، از جمله: رسید خوانده‌شدن پیام‌ها و اعلان در حال نوشتن بودن.

Amaieratik amaierarako enkriptazioa onartzen du OMEMO eta OpenPGPrekin eta pribatutasun ezaugarriak konfiguratzea baimentzen du irakurtze markak eta idazketa jakinarazpenak bezala.

-

Soporta encriptación fin-a-fin a través de OMEMO y OpenPGP y permite configurar las características relacionadas con la privacidad, como confirmaciones de lectura y notificaciones de escritura.

+

Soporta cifrado fin-a-fin a través de OMEMO y OpenPGP y permite configurar características relacionadas con la privacidad, como confirmaciones de lectura y notificaciones de escritura.

Ĝi subtenas fin-al-finan ĉifradon per OMEMO kaj OpenPGP kaj permesas agordi funkciojn pri privateco kiel kvitancojn de legiteco kaj sciigojn pri tajpado.

+

Υποστηρίζει κρυπτογράφηση από άκρο σε άκρο με OMEMO και OpenPGP και επιτρέπει την ρύθμιση λειτουργιών που σχετίζονται με το απόρρητο, όπως αποδείξεις ανάγνωσης και ειδοποιήσεις πληκτρολόγησης.

Er unterstützt Ende-zu-Ende Verschlüsselung mit OMEMO und OpenPGP und enthält Privatsphäre-Einstellungen zu Lesebestätigungen und Tippbenachrichtigungen.

Podporuje šifrování end-to-end pomocí OMEMO a OpenPGP a umožňuje konfigurovat funkce související se soukromím, jako jsou potvrzení o přečtení a oznámení o psaní.

Implementa xifratge punt a punt amb OMEMO i OpenPGP, i permet configurar funcionalitats relacionades amb la privacitat com per exemple rebuts de lectura i notificacions d'escriptura.

@@ -116,13 +122,14 @@

Dino obtém o histórico do servidor e sincroniza mensagens com outros aparelhos.

Dino pobiera historię rozmów z serwera i synchronizuje wiadomości z innymi urządzeniami.

Dino recupèra l’istoric del servidor e sincroniza los messatges amb d’autres periferics.

-

Dino haalt de geschiedenis op van de server en synchroniseert berichten met andere apparaten.

Dino haalt de geschiedenis op van de server en synchroniseert berichten met andere apparaten.

Dino henter historikk fra tjeneren og synkroniserer meldinger med andre enheter.

Dino gauna istoriją iš serverio ir sinchronizuoja žinutes su kitais įrenginiais.

Dino rifft  Gespréichverläf vum Server of a synchroniséiert Noriichte mat anere Geräter.

+

Dino는 서버와 타 장치와의 메세지 동기화로부터 기록을 불러옵니다.

Dino はサーバーから履歴を取得し、ほかのデバイスとメッセージを同期します。

Dino recupera la cronologia dal server e sincronizza i messaggi con gli altri dispositivi.

+

Dino sækir feril af netþjóni og samstillir skilaboð með öðrum tækjum.

Dino obtene li diarium del servitore e sincronisa missages inter altri apparates.

Dino mengambil riwayat pesan dari server dan menyinkronkan pesan dengan perangkat lain.

A Dino lekéri az előzményeket a kiszolgálóról, és szinkronizálja az üzeneteket a többi eszközzel.

@@ -133,6 +140,7 @@

Dinok zerbitzaritik hartzen du historia eta beste gailuekin mezuak sinkronizatzen ditu.

Dino recupera el historial de mensajes desde el servidor y sincroniza los mensajes con otros dispositivos.

Dino prenas historion el la servilo kaj sinkronigas mesaĝojn kun aliaj aparatoj.

+

Το Dino ανακτά το ιστορικό από τον διακομιστή και συγχρονίζει τα μηνύματα με άλλες συσκευές.

Dino ruft Gesprächsverläufe vom Server ab und synchronisiert Nachrichten mit anderen Geräten.

Dino načítá historii ze serveru a synchronizuje zprávy s ostatními zařízeními.

Dino recupera l'historial del servidor i sincronitza els missatges amb altres dispositius.

@@ -140,13 +148,13 @@ - https://dino.im/img/appdata/2022-02_screenshot-main.png + https://dino.im/img/appdata/2022-02_screenshot-main.png - https://dino.im/img/appdata/2022-02_screenshot-call.png + https://dino.im/img/appdata/2022-02_screenshot-call.png - https://dino.im/img/appdata/start_chat.png + https://dino.im/img/appdata/start_chat.png dino @@ -155,21 +163,26 @@ https://github.com/dino/dino/issues https://dino.im/#donate https://hosted.weblate.org/projects/dino/ - appstream@dino.im - + + intense + intense + diff --git a/main/data/im.dino.Dino.appdata.xml.in b/main/data/im.dino.Dino.appdata.xml.in index c95ef12e..5c301df5 100644 --- a/main/data/im.dino.Dino.appdata.xml.in +++ b/main/data/im.dino.Dino.appdata.xml.in @@ -13,13 +13,13 @@ - https://dino.im/img/appdata/2022-02_screenshot-main.png + https://dino.im/img/appdata/2022-02_screenshot-main.png - https://dino.im/img/appdata/2022-02_screenshot-call.png + https://dino.im/img/appdata/2022-02_screenshot-call.png - https://dino.im/img/appdata/start_chat.png + https://dino.im/img/appdata/start_chat.png dino @@ -28,22 +28,26 @@ https://github.com/dino/dino/issues https://dino.im/#donate https://hosted.weblate.org/projects/dino/ - appstream@dino.im - - + + intense + intense + diff --git a/main/data/style-dark.css b/main/data/style-dark.css new file mode 100644 index 00000000..3bd0add0 --- /dev/null +++ b/main/data/style-dark.css @@ -0,0 +1,3 @@ +.dino-main .overlay-toolbar { + background-color: shade(@view_bg_color, 1.5); +} \ No newline at end of file diff --git a/main/data/theme.css b/main/data/style.css similarity index 87% rename from main/data/theme.css rename to main/data/style.css index 3d24750e..fffee8a3 100644 --- a/main/data/theme.css +++ b/main/data/style.css @@ -3,6 +3,10 @@ * It provides sane defaults for things that are very Dino-specific. */ +statuspage { + opacity: 0.5; +} + window.dino-main .dino-header-right { background: @theme_base_color; } @@ -39,7 +43,7 @@ window.dino-main .dino-conversation .highlight-once { } window.dino-main .dino-conversation .message-box.highlight { - background: alpha(@theme_fg_color, 0.04); + background: @window_bg_color; } window.dino-main .dino-conversation .message-box { @@ -88,11 +92,6 @@ window.dino-main .dino-sidebar > frame { transition: background .05s ease; } -window.dino-main .circular-button { - padding: 0; - border-radius: 1000px; -} - window.dino-main .dino-conversation .message-box.edit-mode { background: alpha(@theme_selected_bg_color, 0.1); } @@ -120,20 +119,20 @@ window.dino-main .dino-quote:hover { background: alpha(@theme_fg_color, 0.08); } -/* Message Menu */ +/* Overlay Toolbar */ -.message-menu-box { - background-color: @theme_base_color; - border: 1px solid alpha(@theme_fg_color, 0.15); - border-radius: 5px; +.dino-main .overlay-toolbar { + padding: 2px; + border-radius: 6px; + border-spacing: 0; } -.message-menu-button { - padding: 6px; - border: none; +.dino-main .overlay-toolbar > * { + margin-top: 0; + margin-bottom: 0; } -/* Fie Widget */ +/* File Widget */ window.dino-main .file-box-outer, window.dino-main .call-box-outer { @@ -149,7 +148,7 @@ window.dino-main .call-box { window.dino-main .file-image-widget { border: 1px solid alpha(@theme_fg_color, 0.1); - border-radius: 3px; + border-radius: 6px; } window.dino-main .file-image-widget .file-box-outer { @@ -168,6 +167,10 @@ window.dino-main .file-image-widget .file-box-outer button:hover { background: rgba(100, 100, 100, 0.5); } +.dino-main .file-image-widget picture { + border-radius: 6px; +} + /* Call widget */ window.dino-main .call-box-outer.incoming { @@ -185,38 +188,23 @@ window.dino-main .multiparty-participants { /* Reactions */ -window.dino-main menubutton.reaction-box image { - margin-left: 5px; - margin-right: 5px; +.dino-main .reaction-grid button { + min-height: 16px; + min-width: 30px; + padding: 4px; } -window.dino-main button.reaction-box, -window.dino-main menubutton.reaction-box > button { - border: 1px solid transparent; - padding: 3px 5px ; - border-radius: 10px; - background-color: alpha(@theme_fg_color, 0.07); - background-image: none; - box-shadow: none; - min-height: 0; - min-width: 0; +.dino-main .reaction-grid button.own-reaction, +.dino-main .reaction-grid .own-reaction button { + background-color: alpha(@accent_bg_color, 0.1); + border: 1px solid @accent_bg_color; + padding: 3px; + color: @accent_color; } -window.dino-main button.reaction-box.own-reaction, -window.dino-main menubutton.reaction-box.own-reaction > button { - color: mix(@theme_selected_bg_color, @theme_fg_color, 0.4); - border-color: @theme_selected_bg_color; - background-color: alpha(@theme_selected_bg_color, 0.05); -} - -window.dino-main button.reaction-box:hover, -window.dino-main menubutton.reaction-box:hover > button { - background-color: alpha(@theme_fg_color, 0.1); -} - -window.dino-main button.reaction-box.own-reaction:hover, -window.dino-main menubutton.reaction-box.own-reaction > button { - background-color: alpha(@theme_selected_bg_color, 0.2); +.dino-main .reaction-grid button.own-reaction:hover, +.dino-main .reaction-grid .own-reaction button:hover { + background-color: alpha(@accent_bg_color, 0.2); } /* Sidebar */ diff --git a/main/data/unified_main_content.ui b/main/data/unified_main_content.ui index 3fb7b6e5..d661b1bc 100644 --- a/main/data/unified_main_content.ui +++ b/main/data/unified_main_content.ui @@ -1,74 +1,27 @@ - - False - False - False - 300 + + slide + true + true - + + vertical - - content - - - never - - - - - - - - - - - placeholder - - - 20 - 20 - 20 - 20 - 10 - start - start - - - start - - - - - - 1 - 70 - 50 - end - Click here to start a conversation or join a channel. - - - - - - - - - - - - - + + False content - + + never + 1 + + + + @@ -77,54 +30,89 @@ placeholder - - vertical - 1 - 1 - center - center - - - im.dino.Dino-symbolic - 144 - 30 - - - - - - You have no open chats - - - - - - + + + 20 + 20 + 20 + 20 + 260 + + You have no open chats + Click + to start a chat or join a channel - - - - end - slide-left - - - - 400 + + + + + + vertical + + + end + true + true + false + natural + true + + + 600 + + + false + + + content + + + + + + + + + placeholder + + + im.dino.Dino-symbolic + True + True + + + + + + - + + + + + + + false + 400 + 400 + + + true + + + + + - \ No newline at end of file + diff --git a/main/data/unified_window_placeholder.ui b/main/data/unified_window_placeholder.ui index 997d7220..123920fe 100644 --- a/main/data/unified_window_placeholder.ui +++ b/main/data/unified_window_placeholder.ui @@ -2,63 +2,47 @@