diff --git a/src/controller/mainController.py b/src/controller/mainController.py index bf4acbb1..c7cc6736 100644 --- a/src/controller/mainController.py +++ b/src/controller/mainController.py @@ -139,6 +139,7 @@ class Controller(object): widgetUtils.connect_event(self.view, widgetUtils.MENU, self.delete, self.view.delete) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.follow, menuitem=self.view.follow) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.send_dm, self.view.dm) + widgetUtils.connect_event(self.view, widgetUtils.MENU, self.showUserProfile, self.view.details) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.get_more_items, menuitem=self.view.load_previous_items) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.clear_buffer, menuitem=self.view.clear) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.remove_buffer, self.view.deleteTl) @@ -1094,3 +1095,11 @@ class Controller(object): handler = self.get_handler(buffer.session.type) if handler: handler.update_profile(buffer.session) + + def showUserProfile(self, *args): + """Displays a user's profile.""" + log.debug("Showing user profile...") + buffer = self.get_best_buffer() + handler = self.get_handler(type=buffer.session.type) + if handler and hasattr(handler, 'showUserProfile'): + handler.showUserProfile(buffer=buffer) diff --git a/src/controller/mastodon/handler.py b/src/controller/mastodon/handler.py index b4876626..553c424a 100644 --- a/src/controller/mastodon/handler.py +++ b/src/controller/mastodon/handler.py @@ -10,6 +10,7 @@ from wxUI.dialogs.mastodon import dialogs from wxUI.dialogs import userAliasDialogs from wxUI import commonMessageDialogs from wxUI.dialogs.mastodon import updateProfile as update_profile_dialogs +from wxUI.dialogs.mastodon import showUserProfile from sessions.mastodon.utils import html_filter from . import userActions, settings @@ -44,7 +45,7 @@ class Handler(object): addAlias=_("Add a&lias"), addToList=None, removeFromList=None, - details=None, + details=_("Show user profile"), favs=None, # In buffer Menu. trends=None, @@ -287,4 +288,36 @@ class Handler(object): if data[key] == updated_data[key]: del updated_data[key] log.debug(f"Updating users profile with: {updated_data}") - call_threaded(session.api_call, "account_update_credentials", _("Update profile"), report_success=True, **updated_data) \ No newline at end of file + call_threaded(session.api_call, "account_update_credentials", _("Update profile"), report_success=True, **updated_data) + + def showUserProfile(self, buffer): + """Displays user profile in a dialog.""" + log.debug("Scraping for users in handler") + if not hasattr(buffer, 'get_item'): + return # Tell user? + item = buffer.get_item() + + if hasattr(item, 'username'): + # item is an account dict + users = [(item.display_name, item.username, item.id)] + elif hasattr(item, 'mentions'): + # statuse + if item.reblog: + item = item.reblog + users = [(user.display_name, user.username, user.id) for user in item.mentions] + users.insert(0, (item.account.display_name, item.account.username, item.account.id)) + elif hasattr(item, 'account'): + # Notifications + users = [(item.account.display_name, item.account.username, item.account.id)] + else: + dialogs.no_user() + return + + users = list(set(users)) + selectedUser = showUserProfile.selectUserDialog(users) + log.debug(f"Selected user = {selectedUser}") + user = buffer.session.api.account(selectedUser[2]) + dlg = showUserProfile.ShowUserProfile( + user.display_name, user.url, html_filter(user.note), user.header, user.avatar, + [(field.name, html_filter(field.value)) for field in user.fields], False, False, False) + dlg.ShowModal() diff --git a/src/wxUI/dialogs/mastodon/dialogs.py b/src/wxUI/dialogs/mastodon/dialogs.py index a42f6ffb..1f945d82 100644 --- a/src/wxUI/dialogs/mastodon/dialogs.py +++ b/src/wxUI/dialogs/mastodon/dialogs.py @@ -52,4 +52,11 @@ def no_followers(): def no_following(): dlg = wx.MessageDialog(None, _("This user is not following anyone. {0} can't create a timeline.").format(application.name), _(u"Error"), wx.ICON_ERROR) dlg.ShowModal() - dlg.Destroy() \ No newline at end of file + dlg.Destroy() + + dlg.Destroy() + +def no_user(): + dlg = wx.MessageDialog(None, _("The focused item has no user in it. {} ca't open a user profile").format(application.name), _(u"Error"), wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() diff --git a/src/wxUI/dialogs/mastodon/showUserProfile.py b/src/wxUI/dialogs/mastodon/showUserProfile.py new file mode 100644 index 00000000..71db5ebd --- /dev/null +++ b/src/wxUI/dialogs/mastodon/showUserProfile.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +def _(s): + return s +"""Wx dialogs for showing a user's profile.""" + +import wx +import os +import requests +from io import BytesIO +from typing import Tuple + + +def selectUserDialog(users: list) -> tuple: + """Choose a user from a possible list of users""" + if len(users) == 1: + return users[0] + dlg = wx.Dialog(None, title=_("Select user")) + label = wx.StaticText(dlg, label="Select a user: ") + choice = wx.Choice(dlg, choices=[f"{name}: @{username}" for name, username, id in users]) + ok = wx.Button(dlg, wx.ID_OK, _("OK")) + ok.SetDefault() + cancel = wx.Button(dlg, wx.ID_CANCEL, _("Cancel")) + dlg.SetEscapeId(cancel.GetId()) + + #sizers + sizer = wx.GridSizer(2, 2, 5, 5) + sizer.Add(label, wx.SizerFlags().Center()) + sizer.Add(choice, wx.SizerFlags().Center()) + sizer.Add(ok, wx.SizerFlags().Center()) + sizer.Add(cancel, wx.SizerFlags().Center()) + + if dlg.ShowModal() == wx.ID_CANCEL: + return + # return the selected user + return users[choice.GetSelection()] + + +def returnTrue(): + return True + + +class ShowUserProfile(wx.Dialog): + """ + A dialog for Showing user profile + layout is: + ``` + header + avatar + name + bio + meta data + ``` + """ + + def __init__(self, display_name: str, url: str, note: str, header: str, avatar: str, fields: list, locked: bool, bot: bool, discoverable: bool): + """Initialize update profile dialog + Parameters: + - display_name: The user's display name to show in the display name field + - username: The user's username + - note: The users bio to show in the bio field + - header: the users header pic link + - avatar: The users avatar pic link + """ + super().__init__(parent=None) + self.SetTitle(_("{}'s Profile").format(display_name)) + self.panel = wx.Panel(self) + wrapper = wx.BoxSizer(wx.VERTICAL) + sizer = wx.GridSizer(2, 11, 5, 5) + + # create widgets + nameLabel = wx.StaticText(self.panel, label=_("Name: ")) + name = self.createTextCtrl(display_name, size=(200, 30)) + sizer.Add(nameLabel, wx.SizerFlags().Center()) + sizer.Add(name, wx.SizerFlags().Center()) + + usernameLabel = wx.StaticText(self.panel, label=_("Username: ")) + username = self.createTextCtrl(username, size=(200, 30)) + sizer.Add(usernameLabel, wx.SizerFlags().Center()) + sizer.Add(username, wx.SizerFlags().Center()) + + bioLabel = wx.StaticText(self.panel, label=_("Bio: ")) + bio = self.createTextCtrl(note, (400, 60)) + sizer.Add(bioLabel, wx.SizerFlags().Center()) + sizer.Add(bio, wx.SizerFlags().Center()) + + # header + headerLabel = wx.StaticText(self.panel, label=_("Header: ")) + try: + response = requests.get(header) + except requests.exceptions.RequestException: + # Create empty image + headerImage = wx.StaticBitmap() + else: + image_bytes = BytesIO(response.content) + image = wx.Image(image_bytes, wx.BITMAP_TYPE_ANY) + image.Rescale(300, 100, wx.IMAGE_QUALITY_HIGH) + headerImage = wx.StaticBitmap(self.panel, bitmap=image.ConvertToBitmap()) + + headerImage.AcceptsFocusFromKeyboard = returnTrue + sizer.Add(headerLabel, wx.SizerFlags().Center()) + sizer.Add(headerImage, wx.SizerFlags().Center()) + + # avatar + avatarLabel = wx.StaticText(self.panel, label=_("Avatar")) + try: + response = requests.get(avatar) + except requests.exceptions.RequestException: + # Create empty image + avatarImage = wx.StaticBitmap() + else: + image_bytes = BytesIO(response.content) + image = wx.Image(image_bytes, wx.BITMAP_TYPE_ANY) + image.Rescale(150, 150, wx.IMAGE_QUALITY_HIGH) + avatarImage = wx.StaticBitmap(self.panel, bitmap=image.ConvertToBitmap()) + + avatarImage.AcceptsFocusFromKeyboard = returnTrue + sizer.Add(avatarLabel, wx.SizerFlags().Center()) + sizer.Add(avatarImage, wx.SizerFlags().Center()) + + self.fields = [] + for num, (label, content) in enumerate(fields): + labelSizer = wx.BoxSizer(wx.HORIZONTAL) + labelLabel = wx.StaticText(self.panel, label=_("Field {} - Label: ").format(num)) + labelSizer.Add(labelLabel, wx.SizerFlags().Center().Border(wx.ALL, 5)) + labelText = self.createTextCtrl(label, (230, 30), True) + labelSizer.Add(labelText, wx.SizerFlags().Expand().Border(wx.ALL, 5)) + sizer.Add(labelSizer, 0, wx.CENTER) + + contentSizer = wx.BoxSizer(wx.HORIZONTAL) + contentLabel = wx.StaticText(self.panel, label=_("Content: ")) + contentSizer.Add(contentLabel, wx.SizerFlags().Center()) + contentText = self.createTextCtrl(content, (400, 60), True) + contentSizer.Add(contentText, wx.SizerFlags().Center()) + sizer.Add(contentSizer, 0, wx.CENTER | wx.LEFT, 10) + + close = wx.Button(self.panel, wx.ID_CLOSE, _("Close")) + self.SetEscapeId(close.GetId()) + close.SetDefault() + sizer.Add(close, wx.SizerFlags().Center()) + wrapper.Add(sizer, 0, wx.CENTER) # For padding + self.panel.SetSizerAndFit(wrapper) + sizer.Fit(self) + self.Center() + + + def createTextCtrl(self, text: str, size: Tuple[int, int], multiline: bool = False) -> wx.TextCtrl: + """Creates a wx.TextCtrl and returns it + Parameters: + text: The value of the wx.TextCtrl + size: The size of the wx.TextCtrl + Returns: the created wx.TextCtrl object + """ + if not multiline: + style= wx.TE_PROCESS_ENTER | wx.TE_PROCESS_TAB | wx.TE_READONLY + else: + style= wx.TE_PROCESS_ENTER | wx.TE_PROCESS_TAB | wx.TE_READONLY | wx.TE_MULTILINE + textCtrl = wx.TextCtrl(self.panel, value=text, size=size, style=style) + textCtrl.AcceptsFocusFromKeyboard = returnTrue + return textCtrl