Merge pull request #555 from Arfs6/create-show-user-profile

created show user profile dialogh
This commit is contained in:
Manuel Cortez 2023-10-10 16:48:01 -06:00 committed by GitHub
commit e6ad42de48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 366 additions and 35 deletions

View File

@ -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)
@ -985,9 +986,9 @@ class Controller(object):
def repeat_item(self, *args, **kwargs):
output.speak(self.get_current_buffer().get_message())
def execute_action(self, action):
def execute_action(self, action, kwargs={}):
if hasattr(self, action):
getattr(self, action)()
getattr(self, action)(**kwargs)
def update_buffers(self):
for i in self.buffers[:]:
@ -1094,3 +1095,44 @@ 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)
def openPostTimeline(self, *args, user=None):
"""Opens selected user's posts timeline
Parameters:
args: Other argument. Useful when binding to widgets.
user: if specified, open this user timeline. It is currently mandatory, but could be optional when user selection is implemented in handler
"""
buffer = self.get_best_buffer()
handler = self.get_handler(type=buffer.session.type)
if handler and hasattr(handler, 'openPostTimeline'):
handler.openPostTimeline(self, buffer, user)
def openFollowersTimeline(self, *args, user=None):
"""Opens selected user's followers timeline
Parameters:
args: Other argument. Useful when binding to widgets.
user: if specified, open this user timeline. It is currently mandatory, but could be optional when user selection is implemented in handler
"""
buffer = self.get_best_buffer()
handler = self.get_handler(type=buffer.session.type)
if handler and hasattr(handler, 'openFollowersTimeline'):
handler.openFollowersTimeline(self, buffer, user)
def openFollowingTimeline(self, *args, user=None):
"""Opens selected user's following timeline
Parameters:
args: Other argument. Useful when binding to widgets.
user: if specified, open this user timeline. It is currently mandatory, but could be optional when user selection is implemented in handler
"""
buffer = self.get_best_buffer()
handler = self.get_handler(type=buffer.session.type)
if handler and hasattr(handler, 'openFollowingTimeline'):
handler.openFollowingTimeline(self, buffer, user)

View File

@ -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,
@ -185,38 +186,52 @@ class Handler(object):
return
user = u.user
if action == "posts":
if user.statuses_count == 0:
dialogs.no_posts()
return
if user.id in buffer.session.settings["other_buffers"]["timelines"]:
commonMessageDialogs.timeline_exist()
return
timelines_position =controller.view.search("timelines", buffer.session.get_name())
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=buffer.session.type, buffer_title=_("Timeline for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="account_statuses", name="%s-timeline" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="tweet_timeline.ogg", id=user.id))
buffer.session.settings["other_buffers"]["timelines"].append(user.id)
buffer.session.sound.play("create_timeline.ogg")
self.openPostTimeline(controller, buffer, user)
elif action == "followers":
if user.followers_count == 0:
dialogs.no_followers()
return
if user.id in buffer.session.settings["other_buffers"]["followers_timelines"]:
commonMessageDialogs.timeline_exist()
return
timelines_position =controller.view.search("timelines", buffer.session.get_name())
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Followers for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_followers", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id))
buffer.session.settings["other_buffers"]["followers_timelines"].append(user.id)
buffer.session.sound.play("create_timeline.ogg")
self.openFollowersTimeline(controller, buffer, user)
elif action == "following":
if user.following_count == 0:
dialogs.no_following()
return
if user.id in buffer.session.settings["other_buffers"]["following_timelines"]:
commonMessageDialogs.timeline_exist()
return
timelines_position =controller.view.search("timelines", buffer.session.get_name())
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Following for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_following", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id))
buffer.session.settings["other_buffers"]["following_timelines"].append(user.id)
buffer.session.sound.play("create_timeline.ogg")
self.openFollowingTimeline(controller, buffer, user)
def openPostTimeline(self, controller, buffer, user):
"""Opens post timeline for user"""
if user.statuses_count == 0:
dialogs.no_posts()
return
if user.id in buffer.session.settings["other_buffers"]["timelines"]:
commonMessageDialogs.timeline_exist()
return
timelines_position =controller.view.search("timelines", buffer.session.get_name())
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=buffer.session.type, buffer_title=_("Timeline for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="account_statuses", name="%s-timeline" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="tweet_timeline.ogg", id=user.id))
buffer.session.settings["other_buffers"]["timelines"].append(user.id)
buffer.session.sound.play("create_timeline.ogg")
buffer.session.settings.write()
def openFollowersTimeline(self, controller, buffer, user):
"""Open followers timeline for user"""
if user.followers_count == 0:
dialogs.no_followers()
return
if user.id in buffer.session.settings["other_buffers"]["followers_timelines"]:
commonMessageDialogs.timeline_exist()
return
timelines_position =controller.view.search("timelines", buffer.session.get_name())
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Followers for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_followers", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id))
buffer.session.settings["other_buffers"]["followers_timelines"].append(user.id)
buffer.session.sound.play("create_timeline.ogg")
buffer.session.settings.write()
def openFollowingTimeline(self, controller, buffer, user):
"""Open following timeline for user"""
if user.following_count == 0:
dialogs.no_following()
return
if user.id in buffer.session.settings["other_buffers"]["following_timelines"]:
commonMessageDialogs.timeline_exist()
return
timelines_position =controller.view.search("timelines", buffer.session.get_name())
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Following for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_following", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id))
buffer.session.settings["other_buffers"]["following_timelines"].append(user.id)
buffer.session.sound.play("create_timeline.ogg")
buffer.session.settings.write()
def account_settings(self, buffer, controller):
@ -287,4 +302,43 @@ 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)
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.
This works as long as the focused item hass a 'account' key."""
if not hasattr(buffer, 'get_item'):
return # Tell user?
item = buffer.get_item()
if not item:
return # empty buffer
log.debug(f"Opening user profile. dictionary: {item}")
mentionedUsers = list()
holdUser = item.account if item.get('account') else None
if item.get('username'): # account dict
holdUser = item
elif isinstance(item.get('mentions'), list):
# mentions in statuses
if item.reblog:
item = item.reblog
mentionedUsers = [(user.acct, user.id) for user in item.mentions]
holdUser = item.account
if not holdUser:
dialogs.no_user()
return
if len(mentionedUsers) == 0:
user = holdUser
else:
mentionedUsers.insert(0, (holdUser.display_name, holdUser.username, holdUser.id))
mentionedUsers = list(set(mentionedUsers))
selectedUser = showUserProfile.selectUserDialog(mentionedUsers)
if not selectedUser:
return # Canceled selection
elif selectedUser[-1] == holdUser.id:
user = holdUser
else: # We don't have this user's dictionary, get it!
user = buffer.session.api.account(selectedUser[-1])
dlg = showUserProfile.ShowUserProfile(user)
dlg.ShowModal()

View File

@ -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()
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()

View File

@ -0,0 +1,228 @@
# -*- coding: utf-8 -*-
"""Wx dialogs for showing a user's profile."""
from io import BytesIO
from pubsub import pub
from typing import Tuple
import requests
import wx
from sessions.mastodon.utils import html_filter
def _(s):
return s
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: ")
choiceList = []
for user in users:
if len(user) == 3: # (display_name, username, id)
choiceList.append(f"{user[0]}: @{user[1]}")
else: # (acct, id)
choiceList.append(f"{user[0]}")
choice = wx.Choice(dlg, choices=choiceList)
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, user):
"""Initialize update profile dialog
Parameters:
- user: user dictionary
"""
super().__init__(parent=None)
self.user = user
self.SetTitle(_("{}'s Profile").format(user.display_name))
self.panel = wx.Panel(self)
wrapperSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer = wx.GridSizer(12, 2, 5, 5)
# create widgets
nameLabel = wx.StaticText(self.panel, label=_("Name: "))
name = self.createTextCtrl(user.display_name, size=(200, 30))
mainSizer.Add(nameLabel, wx.SizerFlags().Center())
mainSizer.Add(name, wx.SizerFlags().Center())
urlLabel = wx.StaticText(self.panel, label=_("URL: "))
url = self.createTextCtrl(user.url, size=(200, 30))
mainSizer.Add(urlLabel, wx.SizerFlags().Center())
mainSizer.Add(url, wx.SizerFlags().Center())
bioLabel = wx.StaticText(self.panel, label=_("Bio: "))
bio = self.createTextCtrl(html_filter(user.note), (400, 60))
mainSizer.Add(bioLabel, wx.SizerFlags().Center())
mainSizer.Add(bio, wx.SizerFlags().Center())
joinLabel = wx.StaticText(self.panel, label=_("Joined at: "))
joinText = self.createTextCtrl(user.created_at.strftime('%d %B, %Y'), (80, 30))
mainSizer.Add(joinLabel, wx.SizerFlags().Center())
mainSizer.Add(joinText, wx.SizerFlags().Center())
actions = wx.Button(self.panel, label=_("Actions"))
actions.Bind(wx.EVT_BUTTON, self.onAction)
mainSizer.Add(actions, wx.SizerFlags().Center())
# header
headerLabel = wx.StaticText(self.panel, label=_("Header: "))
try:
response = requests.get(user.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
mainSizer.Add(headerLabel, wx.SizerFlags().Center())
mainSizer.Add(headerImage, wx.SizerFlags().Center())
# avatar
avatarLabel = wx.StaticText(self.panel, label=_("Avatar: "))
try:
response = requests.get(user.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
mainSizer.Add(avatarLabel, wx.SizerFlags().Center())
mainSizer.Add(avatarImage, wx.SizerFlags().Center())
self.fields = []
for num, field in enumerate(user.fields):
labelSizer = wx.BoxSizer(wx.HORIZONTAL)
labelLabel = wx.StaticText(self.panel, label=_("Field {} - Label: ").format(num + 1))
labelSizer.Add(labelLabel, wx.SizerFlags().Center().Border(wx.ALL, 5))
labelText = self.createTextCtrl(html_filter(field.name), (230, 30), True)
labelSizer.Add(labelText, wx.SizerFlags().Expand().Border(wx.ALL, 5))
mainSizer.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(html_filter(field.value), (400, 60), True)
contentSizer.Add(contentText, wx.SizerFlags().Center())
mainSizer.Add(contentSizer, 0, wx.CENTER | wx.LEFT, 10)
bullSwitch = {True: _('Yes'), False: _('No'), None: _('No')}
privateSizer = wx.BoxSizer(wx.HORIZONTAL)
privateLabel = wx.StaticText(self.panel, label=_("Private account: "))
private = self.createTextCtrl(bullSwitch[user.locked], (30, 30))
privateSizer.Add(privateLabel, wx.SizerFlags().Center())
privateSizer.Add(private, wx.SizerFlags().Center())
mainSizer.Add(privateSizer, 0, wx.ALL | wx.CENTER)
botSizer = wx.BoxSizer(wx.HORIZONTAL)
botLabel = wx.StaticText(self.panel, label=_("Bot account: "))
botText = self.createTextCtrl(bullSwitch[user.bot], (30, 30))
botSizer.Add(botLabel, wx.SizerFlags().Center())
botSizer.Add(botText, wx.SizerFlags().Center())
mainSizer.Add(botSizer, 0, wx.ALL | wx.CENTER)
discoverSizer = wx.BoxSizer(wx.HORIZONTAL)
discoverLabel = wx.StaticText(self.panel, label=_("Discoverable account: "))
discoverText = self.createTextCtrl(bullSwitch[user.discoverable], (30, 30))
discoverSizer.Add(discoverLabel, wx.SizerFlags().Center())
discoverSizer.Add(discoverText, wx.SizerFlags().Center())
mainSizer.Add(discoverSizer, 0, wx.ALL | wx.CENTER)
posts = wx.Button(self.panel, label=_("{} posts. Click to open posts timeline").format(user.statuses_count))
# posts.SetToolTip(_("Click to open {}'s posts").format(user.display_name))
posts.Bind(wx.EVT_BUTTON, self.onPost)
mainSizer.Add(posts, wx.SizerFlags().Center())
following = wx.Button(self.panel, label=_("{} following. Click to open Following timeline").format(user.following_count))
mainSizer.Add(following, wx.SizerFlags().Center())
following.Bind(wx.EVT_BUTTON, self.onFollowing)
followers = wx.Button(self.panel, label=_("{} followers. Click to open followers timeline").format(user.followers_count))
mainSizer.Add(followers, wx.SizerFlags().Center())
followers.Bind(wx.EVT_BUTTON, self.onFollowers)
close = wx.Button(self.panel, wx.ID_CLOSE, _("Close"))
self.SetEscapeId(close.GetId())
close.SetDefault()
wrapperSizer.Add(mainSizer, 0, wx.CENTER)
wrapperSizer.Add(close, wx.SizerFlags().Center())
self.panel.SetSizer(wrapperSizer)
wrapperSizer.Fit(self.panel)
self.panel.Center()
mainSizer.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
def onAction(self, *args):
"""Opens the Open timeline dialog"""
pub.sendMessage('execute-action', action='follow')
def onPost(self, *args):
"""Open this user's timeline"""
pub.sendMessage('execute-action', action='openPostTimeline', kwargs=dict(user=self.user))
def onFollowing(self, *args):
"""Open following timeline for this user"""
pub.sendMessage('execute-action', action='openFollowingTimeline', kwargs=dict(user=self.user))
def onFollowers(self, *args):
"""Open followers timeline for this user"""
pub.sendMessage('execute-action', action='openFollowersTimeline', kwargs=dict(user=self.user))