diff --git a/database.py b/database.py index c861556..7ab9369 100644 --- a/database.py +++ b/database.py @@ -123,3 +123,19 @@ class LxmfConversationReadState(BaseModel): # define table name class Meta: table_name = "lxmf_conversation_read_state" + + +class LxmfUserIcon(BaseModel): + + id = BigAutoField() + destination_hash = CharField(unique=True) # unique destination hash + icon_name = CharField() # material design icon name for the destination hash + foreground_colour = CharField() # hex colour to use for foreground (icon colour) + background_colour = CharField() # hex colour to use for background (background colour) + + created_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) + updated_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) + + # define table name + class Meta: + table_name = "lxmf_user_icons" diff --git a/meshchat.py b/meshchat.py index e203699..ca36d97 100644 --- a/meshchat.py +++ b/meshchat.py @@ -74,6 +74,7 @@ class ReticulumMeshChat: database.CustomDestinationDisplayName, database.LxmfMessage, database.LxmfConversationReadState, + database.LxmfUserIcon, ]) # init config @@ -1432,6 +1433,16 @@ class ReticulumMeshChat: else: other_user_hash = source_hash + # find lxmf user icon from database + lxmf_user_icon = None + db_lxmf_user_icon = database.LxmfUserIcon.get_or_none(database.LxmfUserIcon.destination_hash == other_user_hash) + if db_lxmf_user_icon is not None: + lxmf_user_icon = { + "icon_name": db_lxmf_user_icon.icon_name, + "foreground_colour": db_lxmf_user_icon.foreground_colour, + "background_colour": db_lxmf_user_icon.background_colour, + } + # add to conversations conversations.append({ "display_name": self.get_lxmf_conversation_name(other_user_hash), @@ -1439,6 +1450,7 @@ class ReticulumMeshChat: "destination_hash": other_user_hash, "is_unread": self.is_lxmf_conversation_unread(other_user_hash), "failed_messages_count": self.lxmf_conversation_failed_messages_count(other_user_hash), + "lxmf_user_icon": lxmf_user_icon, # we say the conversation was updated when the latest message was created # otherwise this will go crazy when sending a message, as the updated_at on the latest message changes very frequently "updated_at": created_at, @@ -2000,6 +2012,26 @@ class ReticulumMeshChat: "updated_at": db_lxmf_message.updated_at, } + # updates the lxmf user icon for the provided destination hash + def update_lxmf_user_icon(self, destination_hash: str, icon_name: str, foreground_colour: str, background_colour: str): + + # log + print(f"updating lxmf user icon for {destination_hash} to icon_name={icon_name}, foreground_colour={foreground_colour}, background_colour={background_colour}") + + # prepare data to insert or update + data = { + "destination_hash": destination_hash, + "icon_name": icon_name, + "foreground_colour": foreground_colour, + "background_colour": background_colour, + "updated_at": datetime.now(timezone.utc), + } + + # upsert to database + query = database.LxmfUserIcon.insert(data) + query = query.on_conflict(conflict_target=[database.LxmfUserIcon.destination_hash], update=data) + query.execute() + # handle an lxmf delivery from reticulum # NOTE: cant be async, as Reticulum doesn't await it def on_lxmf_delivery(self, lxmf_message: LXMF.LXMessage): @@ -2008,6 +2040,20 @@ class ReticulumMeshChat: # upsert lxmf message to database self.db_upsert_lxmf_message(lxmf_message) + # get icon appearance if available + try: + message_fields = lxmf_message.get_fields() + if LXMF.FIELD_ICON_APPEARANCE in message_fields: + icon_appearance = message_fields[LXMF.FIELD_ICON_APPEARANCE] + icon_name = icon_appearance[0] + foreground_colour = "#" + icon_appearance[1].hex() + background_colour = "#" + icon_appearance[2].hex() + self.update_lxmf_user_icon(lxmf_message.source_hash.hex(), icon_name, foreground_colour, background_colour) + except Exception as e: + print("failed to update lxmf user icon from lxmf message") + print(e) + pass + # find message from database db_lxmf_message = database.LxmfMessage.get_or_none(database.LxmfMessage.hash == lxmf_message.hash.hex()) if db_lxmf_message is None: diff --git a/package-lock.json b/package-lock.json index 8380527..2278eee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.13.2", "license": "MIT", "dependencies": { + "@mdi/js": "^7.4.47", "@vitejs/plugin-vue": "^5.1.2", "click-outside-vue3": "^4.0.1", "electron-prompt": "^1.7.0", @@ -877,6 +878,11 @@ "node": ">= 10.0.0" } }, + "node_modules/@mdi/js": { + "version": "7.4.47", + "resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz", + "integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", diff --git a/package.json b/package.json index abadfdc..e0c9b60 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ } }, "dependencies": { + "@mdi/js": "^7.4.47", "@vitejs/plugin-vue": "^5.1.2", "click-outside-vue3": "^4.0.1", "electron-prompt": "^1.7.0", diff --git a/src/frontend/components/MaterialDesignIcon.vue b/src/frontend/components/MaterialDesignIcon.vue new file mode 100644 index 0000000..4e44239 --- /dev/null +++ b/src/frontend/components/MaterialDesignIcon.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/frontend/components/messages/MessagesSidebar.vue b/src/frontend/components/messages/MessagesSidebar.vue index afe5b4a..5e6d222 100644 --- a/src/frontend/components/messages/MessagesSidebar.vue +++ b/src/frontend/components/messages/MessagesSidebar.vue @@ -22,7 +22,10 @@