aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO7
-rw-r--r--include/Body.hpp25
-rw-r--r--plugins/Matrix.hpp9
-rw-r--r--src/Body.cpp114
-rw-r--r--src/QuickMedia.cpp63
-rw-r--r--src/plugins/Matrix.cpp37
6 files changed, 207 insertions, 48 deletions
diff --git a/TODO b/TODO
index afe2acf..45a0f49 100644
--- a/TODO
+++ b/TODO
@@ -134,4 +134,9 @@ If manga page fails to download then show "failed to download image" as text and
Use <img src to add custom emojis, and add setting for adding/removing custom emoji.
Use window title when room name changes in matrix.
Prev token is incorrect if additional messages have been fetched and comparing to get_previous_messages set token, because prev token is not set when fetching additional messages.
-Use unsigned.transaction_id to match messages we post with messages in sync response (for filtering what we send). \ No newline at end of file
+Use unsigned.transaction_id to match messages we post with messages in sync response (for filtering what we send).
+Create multiple BodyItem types. BodyItem has a lot of fields and most of them are not always used.
+Have a list of redacted events so when fetching previous events, we can filter out the redacted events (directly in the matrix plugin).
+Add grid view to matrix and navigate between them using alt+arrow keys.
+Show reactions in pinned messages tab?
+Remove display names from reactions if there are many reactions, and instead group them into: reaction (#number of this type of reaction); for example: 👍 2.
diff --git a/include/Body.hpp b/include/Body.hpp
index 45121f8..56ef262 100644
--- a/include/Body.hpp
+++ b/include/Body.hpp
@@ -31,6 +31,11 @@ namespace QuickMedia {
CIRCLE
};
+ struct Reaction {
+ std::unique_ptr<Text> text;
+ void *userdata = nullptr;
+ };
+
class BodyItem {
public:
BodyItem(std::string _title);
@@ -88,6 +93,25 @@ namespace QuickMedia {
dirty_author = true;
}
+ void add_reaction(std::string text, void *userdata) {
+ sf::String str = sf::String::fromUtf8(text.begin(), text.end());
+ Reaction reaction;
+ reaction.text = std::make_unique<Text>(std::move(str), false, 14, 0.0f);
+ reaction.userdata = userdata;
+ reactions.push_back(std::move(reaction));
+ }
+
+ // Returns true if reaction is found
+ bool remove_reaction_by_userdata(void *userdata) {
+ for(auto it = reactions.begin(); it != reactions.end(); ++it) {
+ if(it->userdata == userdata) {
+ reactions.erase(it);
+ return true;
+ }
+ }
+ return false;
+ }
+
const std::string& get_title() const { return title; }
const std::string& get_description() const { return description; }
const std::string& get_author() const { return author; }
@@ -124,6 +148,7 @@ namespace QuickMedia {
std::shared_ptr<BodyItem> embedded_item; // Used by matrix for example to display reply message body. Note: only the first level of embedded items is rendered (not recursive, this is done on purpose)
ThumbnailMaskType thumbnail_mask_type = ThumbnailMaskType::NONE;
sf::Vector2i thumbnail_size;
+ std::vector<Reaction> reactions; // TODO: Move to a different body item type
private:
// TODO: Clean up these strings when set in text, and get_title for example should return |title_text.getString()|
// TODO: Use sf::String instead, removes the need to convert to utf32 every time the text is dirty (for example when resizing window)
diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp
index 8e96c54..1a0ffe0 100644
--- a/plugins/Matrix.hpp
+++ b/plugins/Matrix.hpp
@@ -40,8 +40,9 @@ namespace QuickMedia {
AUDIO,
FILE,
REDACTION,
- UNIMPLEMENTED,
- MEMBERSHIP
+ REACTION,
+ MEMBERSHIP,
+ UNIMPLEMENTED
};
bool is_visual_media_message_type(MessageType message_type);
@@ -50,7 +51,8 @@ namespace QuickMedia {
NONE,
REPLY,
EDIT,
- REDACTION
+ REDACTION,
+ REACTION
};
class MatrixEvent {
@@ -71,6 +73,7 @@ namespace QuickMedia {
bool cache = false;
time_t timestamp = 0; // In milliseconds
MessageType type;
+ // TODO: Store body item ref here
};
struct RoomData {
diff --git a/src/Body.cpp b/src/Body.cpp
index 430c28f..0b4ec0f 100644
--- a/src/Body.cpp
+++ b/src/Body.cpp
@@ -17,6 +17,11 @@ static const float padding_y = 5.0f;
static const float embedded_item_padding_y = 0.0f;
static const double thumbnail_fade_duration_sec = 0.1;
+const float reaction_background_padding_x = 7.0f;
+const float reaction_background_padding_y = 3.0f;
+const float reaction_spacing_x = 5.0f;
+static const float reaction_padding_y = 7.0f;
+
namespace QuickMedia {
BodyItem::BodyItem(std::string _title) :
visible(true),
@@ -75,6 +80,13 @@ namespace QuickMedia {
}
thumbnail_mask_type = other.thumbnail_mask_type;
thumbnail_size = other.thumbnail_size;
+ reactions.clear();
+ for(auto &reaction : other.reactions) {
+ Reaction reaction_copy;
+ reaction_copy.text = std::make_unique<Text>(*reaction.text);
+ reaction_copy.userdata = reaction.userdata;
+ reactions.push_back(std::move(reaction_copy));
+ }
title = other.title;
description = other.description;
author = other.author;
@@ -549,7 +561,7 @@ namespace QuickMedia {
if(body_item->title_text)
body_item->title_text->setString(std::move(str));
else
- body_item->title_text = std::make_unique<Text>(std::move(str), false, 16, width - 50 - image_padding_x * 2.0f);
+ body_item->title_text = std::make_unique<Text>(std::move(str), false, 16, width);
body_item->title_text->setFillColor(body_item->get_title_color());
body_item->title_text->updateGeometry();
}
@@ -560,7 +572,7 @@ namespace QuickMedia {
if(body_item->description_text)
body_item->description_text->setString(std::move(str));
else
- body_item->description_text = std::make_unique<Text>(std::move(str), false, 14, width - 50 - image_padding_x * 2.0f);
+ body_item->description_text = std::make_unique<Text>(std::move(str), false, 14, width);
body_item->description_text->setFillColor(body_item->get_description_color());
body_item->description_text->updateGeometry();
}
@@ -571,7 +583,7 @@ namespace QuickMedia {
if(body_item->author_text)
body_item->author_text->setString(std::move(str));
else
- body_item->author_text = std::make_unique<Text>(std::move(str), true, 14, width - 50 - image_padding_x * 2.0f);
+ body_item->author_text = std::make_unique<Text>(std::move(str), true, 14, width);
body_item->author_text->setFillColor(body_item->get_author_color());
body_item->author_text->updateGeometry();
}
@@ -706,7 +718,6 @@ namespace QuickMedia {
only_show_thumbnail = true;
}
- bool has_thumbnail_texture = false;
// TODO: Verify if this is safe. The thumbnail is being modified in another thread
if(item_thumbnail->loading_state == LoadingState::APPLIED_TO_TEXTURE && item_thumbnail->texture.getNativeHandle() != 0) {
image.setTexture(item_thumbnail->texture, true);
@@ -723,7 +734,6 @@ namespace QuickMedia {
else
window.draw(image);
text_offset_x += image_padding_x + new_image_size.x;
- has_thumbnail_texture = true;
// We want the next image fallback to have the same size as the successful image rendering, because its likely the image fallback will have the same size (for example thumbnails on youtube)
//image_fallback.setSize(sf::Vector2f(width_ratio * image_size.x, height_ratio * image_size.y));
} else if(!item->thumbnail_url.empty() && !only_show_thumbnail) {
@@ -753,9 +763,7 @@ namespace QuickMedia {
loading_icon.setRotation(elapsed_time_sec * 400.0);
loading_icon.setColor(sf::Color(255, 255, 255, fallback_fade_alpha));
window.draw(loading_icon);
-
- if(!has_thumbnail_texture)
- text_offset_x += image_padding_x + content_size.x;
+ text_offset_x += image_padding_x + content_size.x;
}
}
@@ -820,6 +828,31 @@ namespace QuickMedia {
item_pos.y += item->description_text->getHeight() - 2.0f;
}
+ if(!item->reactions.empty()) {
+ sf::RoundedRectangleShape reaction_background(sf::Vector2f(1.0f, 1.0f), 10.0f, 10);
+ reaction_background.setFillColor(sf::Color(33, 38, 44));
+ float reaction_offset_x = 0.0f;
+ item_pos.y += reaction_padding_y;
+ float reaction_max_height = 0.0f;
+ for(auto &reaction : item->reactions) {
+ reaction.text->setMaxWidth(size.x - text_offset_x - image_padding_x);
+ reaction.text->updateGeometry();
+ reaction_max_height = std::max(reaction_max_height, reaction.text->getHeight());
+ if(text_offset_x + reaction_offset_x + reaction.text->getWidth() + reaction_background_padding_x * 2.0f > size.x) {
+ reaction_offset_x = 0.0f;
+ item_pos.y += reaction.text->getHeight() + reaction_padding_y + 6.0f;
+ reaction_max_height = reaction.text->getHeight();
+ }
+ reaction.text->setPosition(std::floor(item_pos.x + text_offset_x + reaction_offset_x + reaction_background_padding_x), std::floor(item_pos.y + padding_y - 4.0f + reaction_background_padding_y));
+ reaction_background.setPosition(std::floor(item_pos.x + text_offset_x + reaction_offset_x), std::floor(item_pos.y + padding_y));
+ reaction_background.setSize(sf::Vector2f(reaction.text->getWidth() + reaction_background_padding_x * 2.0f, reaction.text->getHeight() + reaction_background_padding_y * 2.0f));
+ window.draw(reaction_background);
+ reaction_offset_x += reaction.text->getWidth() + reaction_background_padding_x * 2.0f + reaction_spacing_x;
+ reaction.text->draw(window);
+ }
+ item_pos.y += reaction_max_height + reaction_padding_y;
+ }
+
if(item->timestamp_text) {
item->timestamp_text->setPosition(std::floor(item_pos.x + size.x - item->timestamp_text->getLocalBounds().width - padding_x), timestamp_text_y + 8.0f);
window.draw(*item->timestamp_text);
@@ -844,27 +877,11 @@ namespace QuickMedia {
}
float Body::get_item_height(BodyItem *item, float width, bool load_texture, bool include_embedded_item) {
- if(load_texture)
- update_dirty_state(item, width);
- float item_height = 0.0f;
- if(item->title_text) {
- item_height += item->title_text->getHeight() - 2.0f;
- }
- if(item->author_text) {
- item_height += item->author_text->getHeight() - 2.0f;
- }
- if(include_embedded_item && item->embedded_item_status != FetchStatus::NONE) {
- if(item->embedded_item)
- item_height += (get_item_height(item->embedded_item.get(), width, load_texture, false) + 4.0f + embedded_item_padding_y * 2.0f);
- else
- item_height += (embedded_item_load_text.getLocalBounds().height + 4.0f + embedded_item_padding_y * 2.0f);
- }
- if(item->description_text) {
- item_height += item->description_text->getHeight() - 2.0f;
- }
+ float image_height = 0.0f;
+ float text_offset_x = padding_x;
if(draw_thumbnails && !item->thumbnail_url.empty()) {
sf::Vector2i content_size = get_item_thumbnail_size(item);
- float image_height = content_size.y;
+ image_height = content_size.y;
std::shared_ptr<ThumbnailData> item_thumbnail;
auto item_thumbnail_it = item_thumbnail_textures.find(item->thumbnail_url);
@@ -899,10 +916,51 @@ namespace QuickMedia {
sf::Vector2f image_size_f(image_size.x, image_size.y);
auto new_image_size = clamp_to_size(image_size_f, to_vec2f(content_size));
image_height = new_image_size.y;
+ text_offset_x += image_padding_x + new_image_size.x;
+ } else {
+ text_offset_x += image_padding_x + content_size.x;
}
+ }
- item_height = std::max(item_height, image_height);
+ if(load_texture)
+ update_dirty_state(item, width - text_offset_x - image_padding_x);
+
+ float item_height = 0.0f;
+ if(item->title_text) {
+ item_height += item->title_text->getHeight() - 2.0f;
+ }
+ if(item->author_text) {
+ item_height += item->author_text->getHeight() - 2.0f;
}
+ if(include_embedded_item && item->embedded_item_status != FetchStatus::NONE) {
+ if(item->embedded_item)
+ item_height += (get_item_height(item->embedded_item.get(), width, load_texture, false) + 4.0f + embedded_item_padding_y * 2.0f);
+ else
+ item_height += (embedded_item_load_text.getLocalBounds().height + 4.0f + embedded_item_padding_y * 2.0f);
+ }
+ if(item->description_text) {
+ item_height += item->description_text->getHeight() - 2.0f;
+ }
+
+ if(!item->reactions.empty()) {
+ float reaction_offset_x = 0.0f;
+ item_height += reaction_padding_y;
+ float reaction_max_height = 0.0f;
+ for(auto &reaction : item->reactions) {
+ reaction.text->setMaxWidth(width - text_offset_x - image_padding_x);
+ reaction.text->updateGeometry();
+ reaction_max_height = std::max(reaction_max_height, reaction.text->getHeight());
+ if(text_offset_x + reaction_offset_x + reaction.text->getWidth() + reaction_background_padding_x * 2.0f > width) {
+ reaction_offset_x = 0.0f;
+ item_height += reaction.text->getHeight() + reaction_padding_y + 6.0f;
+ reaction_max_height = reaction.text->getHeight();
+ }
+ reaction_offset_x += reaction.text->getWidth() + reaction_background_padding_x * 2.0f + reaction_spacing_x;
+ }
+ item_height += reaction_max_height + reaction_padding_y;
+ }
+
+ item_height = std::max(item_height, image_height);
return item_height + padding_y * 2.0f;
}
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 1bb7a04..d9b1041 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -3076,7 +3076,7 @@ namespace QuickMedia {
body_item->url = message->url;
body_item->set_author_color(message->user->display_name_color);
body_item->userdata = (void*)message; // Note: message has to be valid as long as body_item is used!
- if(message->related_event_type == RelatedEventType::REDACTION || message->related_event_type == RelatedEventType::EDIT)
+ if(message->related_event_type == RelatedEventType::REDACTION || message->related_event_type == RelatedEventType::EDIT || message->related_event_type == RelatedEventType::REACTION)
body_item->visible = false;
if(message_contains_user_mention(message->body, my_display_name) || message_contains_user_mention(message->body, my_user_id))
body_item->set_description_color(sf::Color(255, 100, 100));
@@ -3218,8 +3218,8 @@ namespace QuickMedia {
body_item->embedded_item = nullptr;
body_item->embedded_item_status = FetchStatus::NONE;
message->type = MessageType::REDACTION;
- message->related_event_id.clear();
- message->related_event_type = RelatedEventType::NONE;
+ //message->related_event_id.clear();
+ //message->related_event_type = RelatedEventType::NONE;
body_item->thumbnail_url = current_room->get_user_avatar_url(message->user);
body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
body_item->set_description("Message deleted");
@@ -3253,7 +3253,7 @@ namespace QuickMedia {
}
};
- // TODO: Optimize with hash map?
+ // TODO: Optimize find_body_item_by_event_id hash map?
auto modify_related_messages_in_current_room = [&set_body_as_deleted, &unreferenced_events, &find_body_item_by_event_id, &tabs](Messages &messages) {
if(messages.empty())
return;
@@ -3279,6 +3279,53 @@ namespace QuickMedia {
}
};
+ std::vector<std::shared_ptr<Message>> unresolved_reactions;
+ // TODO: Optimize find_body_item_by_event_id hash map?
+ auto process_reactions = [&tabs, &find_body_item_by_event_id, &unresolved_reactions, &current_room](Messages &messages) {
+ if(messages.empty())
+ return;
+
+ auto &body_items = tabs[MESSAGES_TAB_INDEX].body->items;
+
+ // TODO: Check in |messages| instead
+ for(auto it = unresolved_reactions.begin(); it != unresolved_reactions.end();) {
+ auto body_item = find_body_item_by_event_id(body_items.data(), body_items.size(), (*it)->related_event_id);
+ if(body_item) {
+ body_item->add_reaction(current_room->get_user_display_name((*it)->user) + ": " + (*it)->body, (*it).get());
+ it = unresolved_reactions.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ for(auto &message : messages) {
+ if(message->type == MessageType::REACTION) {
+ auto body_item = find_body_item_by_event_id(body_items.data(), body_items.size(), message->related_event_id);
+ if(body_item)
+ body_item->add_reaction(current_room->get_user_display_name(message->user) + ": " + message->body, message.get());
+ else
+ unresolved_reactions.push_back(message);
+ } else if(message->type == MessageType::REDACTION) {
+ auto body_item = find_body_item_by_event_id(body_items.data(), body_items.size(), message->related_event_id);
+ if(body_item && static_cast<Message*>(body_item->userdata)) {
+ Message *reaction_message = static_cast<Message*>(body_item->userdata);
+ if(reaction_message->type == MessageType::REACTION) {
+ auto body_item = find_body_item_by_event_id(body_items.data(), body_items.size(), reaction_message->related_event_id);
+ if(body_item)
+ body_item->remove_reaction_by_userdata(reaction_message);
+ }
+ } else {
+ for(auto it = unresolved_reactions.begin(); it != unresolved_reactions.end(); ++it) {
+ if(message->related_event_id == (*it)->event_id) {
+ unresolved_reactions.erase(it);
+ break;
+ }
+ }
+ }
+ }
+ }
+ };
+
auto pinned_body_items_contains_event = [&tabs](const std::string &event_id) {
for(auto &body_item : tabs[PINNED_TAB_INDEX].body->items) {
if(static_cast<PinnedEventData*>(body_item->userdata)->event_id == event_id)
@@ -3345,6 +3392,7 @@ namespace QuickMedia {
auto me = matrix->get_me(current_room);
tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(messages_to_body_items(current_room, all_messages, current_room->get_user_display_name(me), me->user_id));
modify_related_messages_in_current_room(all_messages);
+ process_reactions(all_messages);
tabs[MESSAGES_TAB_INDEX].body->select_last_item();
std::vector<std::string> pinned_events;
@@ -4059,6 +4107,8 @@ namespace QuickMedia {
if((selected_tab == MESSAGES_TAB_INDEX || selected_tab == PINNED_TAB_INDEX) && event.key.code == sf::Keyboard::Enter) {
BodyItem *selected = tabs[selected_tab].body->get_selected();
if(selected) {
+ Message *mess = static_cast<Message*>(selected->userdata);
+ fprintf(stderr, "Mess type: %d\n", mess->type);
if(!display_url_or_image(selected))
display_url_or_image(selected->embedded_item.get());
}
@@ -4268,6 +4318,7 @@ namespace QuickMedia {
cleanup_tasks();
tabs.clear();
unreferenced_events.clear();
+ unresolved_reactions.clear();
all_messages.clear();
new_page = PageType::CHAT;
matrix->stop_sync();
@@ -4388,6 +4439,7 @@ namespace QuickMedia {
}
add_new_messages_to_current_room(sync_data.messages);
modify_related_messages_in_current_room(sync_data.messages);
+ process_reactions(sync_data.messages);
process_pinned_events(sync_data.pinned_events);
if(set_read_marker_future.ready()) {
@@ -4418,6 +4470,7 @@ namespace QuickMedia {
tabs[MESSAGES_TAB_INDEX].body->set_selected_item(selected_item_index);
}
modify_related_messages_in_current_room(new_messages);
+ process_reactions(new_messages);
// TODO: Do not loop all items, only loop the new items
resolve_unreferenced_events_with_body_items(tabs[MESSAGES_TAB_INDEX].body->items.data(), tabs[MESSAGES_TAB_INDEX].body->items.size());
}
@@ -4653,6 +4706,8 @@ namespace QuickMedia {
filter_sent_messages(all_messages_new);
add_new_messages_to_current_room(all_messages_new);
modify_related_messages_in_current_room(all_messages_new);
+ unresolved_reactions.clear();
+ process_reactions(all_messages_new);
std::vector<std::string> pinned_events;
matrix->get_all_pinned_events(current_room, pinned_events);
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index 6dabad9..e5e4922 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -1754,19 +1754,30 @@ namespace QuickMedia {
std::string related_event_id;
const rapidjson::Value &relates_to_json = GetMember(*content_json, "m.relates_to");
if(relates_to_json.IsObject()) {
- const rapidjson::Value &replaces_event_id_json = GetMember(relates_to_json, "event_id");
+ const rapidjson::Value &relates_to_event_id_json = GetMember(relates_to_json, "event_id");
const rapidjson::Value &rel_type_json = GetMember(relates_to_json, "rel_type");
- if(replaces_event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.replace") == 0) {
- related_event_id = replaces_event_id_json.GetString();
+ const rapidjson::Value &in_reply_to_json = GetMember(relates_to_json, "m.in_reply_to");
+ const rapidjson::Value &key_json = GetMember(relates_to_json, "key");
+ if(relates_to_event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.replace") == 0) {
+ related_event_id = relates_to_event_id_json.GetString();
related_event_type = RelatedEventType::EDIT;
- } else {
- const rapidjson::Value &in_reply_to_json = GetMember(relates_to_json, "m.in_reply_to");
- if(in_reply_to_json.IsObject()) {
- const rapidjson::Value &in_reply_to_event_id = GetMember(in_reply_to_json, "event_id");
- if(in_reply_to_event_id.IsString()) {
- related_event_id = in_reply_to_event_id.GetString();
- related_event_type = RelatedEventType::REPLY;
- }
+ } else if(in_reply_to_json.IsObject()) {
+ const rapidjson::Value &in_reply_to_event_id = GetMember(in_reply_to_json, "event_id");
+ if(in_reply_to_event_id.IsString()) {
+ related_event_id = in_reply_to_event_id.GetString();
+ related_event_type = RelatedEventType::REPLY;
+ }
+ } else if(relates_to_event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.annotation") == 0 && key_json.IsString()) {
+ if(strcmp(type_json.GetString(), "m.reaction") == 0) {
+ auto message = std::make_shared<Message>();
+ message->type = MessageType::REACTION;
+ message->user = user;
+ message->event_id = event_id_str;
+ message->body = key_json.GetString();
+ message->related_event_id = relates_to_event_id_json.GetString();
+ message->related_event_type = RelatedEventType::REACTION;
+ message->timestamp = timestamp;
+ return message;
}
}
}
@@ -1799,7 +1810,9 @@ namespace QuickMedia {
}
if(strcmp(type_json.GetString(), "m.room.message") == 0 || strcmp(type_json.GetString(), "m.sticker") == 0) {
-
+ } else if(strcmp(type_json.GetString(), "m.reaction") == 0) {
+ // An old reaction that has been removed. New reactions are removed with m.redact
+ return nullptr;
} else if(strcmp(type_json.GetString(), "m.room.member") == 0) {
std::string user_display_name = room_data->get_user_display_name(user);
std::string sender_display_name = room_data->get_user_display_name(user_sender);