Update comprehensive documentation and complete feature implementation

- Updated README.md with all new features: media uploads, post details, thread expansion, blocked/muted management, custom emoji support
- Added detailed keyboard shortcuts documentation for all timeline tabs (Ctrl+1-0)
- Documented poll creation/voting accessibility features and media upload functionality
- Updated CLAUDE.md with complete implementation status and recent feature additions
- Added sound pack creation guide with security measures and installation methods
- Documented accessibility patterns including fake headers for single-item navigation
- Updated technology stack to include numpy dependency for audio processing
- Marked all high and medium priority todo items as completed
- Project now feature-complete with excellent accessibility support

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Storm Dragon
2025-07-21 16:53:16 -04:00
parent c19d2ff162
commit ff32d6a10b
12 changed files with 347 additions and 19 deletions

View File

@ -0,0 +1,217 @@
"""
Post details dialog showing favorites, boosts, and other interaction details
"""
from PySide6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QTextEdit,
QTabWidget, QListWidget, QListWidgetItem, QDialogButtonBox,
QWidget, QGroupBox, QPushButton
)
from PySide6.QtCore import Qt, Signal, QThread
from PySide6.QtGui import QFont
from typing import List, Dict, Any, Optional
from activitypub.client import ActivityPubClient
from models.user import User
from audio.sound_manager import SoundManager
class FetchDetailsThread(QThread):
"""Background thread for fetching post interaction details"""
details_loaded = Signal(dict) # Emitted with details data
details_failed = Signal(str) # Emitted with error message
def __init__(self, client: ActivityPubClient, post_id: str):
super().__init__()
self.client = client
self.post_id = post_id
def run(self):
"""Fetch favorites and boosts in background"""
try:
details = {
'favourited_by': [],
'reblogged_by': []
}
# Fetch who favorited this post
try:
favourited_by_data = self.client.get_status_favourited_by(self.post_id)
details['favourited_by'] = favourited_by_data
except Exception as e:
print(f"Failed to fetch favorites: {e}")
# Fetch who boosted this post
try:
reblogged_by_data = self.client.get_status_reblogged_by(self.post_id)
details['reblogged_by'] = reblogged_by_data
except Exception as e:
print(f"Failed to fetch boosts: {e}")
self.details_loaded.emit(details)
except Exception as e:
self.details_failed.emit(str(e))
class PostDetailsDialog(QDialog):
"""Dialog showing detailed post interaction information"""
def __init__(self, post, client: ActivityPubClient, sound_manager: SoundManager, parent=None):
super().__init__(parent)
self.post = post
self.client = client
self.sound_manager = sound_manager
self.setWindowTitle("Post Details")
self.setModal(True)
self.resize(600, 500)
self.setup_ui()
self.load_details()
def setup_ui(self):
"""Setup the post details UI"""
layout = QVBoxLayout(self)
# Post content section
content_group = QGroupBox("Post Content")
content_group.setAccessibleName("Post Content")
content_layout = QVBoxLayout(content_group)
# Author info
author_label = QLabel(f"@{self.post.account.username} ({self.post.account.display_name or self.post.account.username})")
author_label.setAccessibleName("Post Author")
author_font = QFont()
author_font.setBold(True)
author_label.setFont(author_font)
content_layout.addWidget(author_label)
# Post content
content_text = QTextEdit()
content_text.setAccessibleName("Post Content")
content_text.setPlainText(self.post.get_content_text())
content_text.setReadOnly(True)
content_text.setMaximumHeight(100)
# Enable keyboard navigation in read-only text
content_text.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse)
content_layout.addWidget(content_text)
# Stats
stats_text = f"Replies: {self.post.replies_count} | Boosts: {self.post.reblogs_count} | Favorites: {self.post.favourites_count}"
stats_label = QLabel(stats_text)
stats_label.setAccessibleName("Post Statistics")
content_layout.addWidget(stats_label)
layout.addWidget(content_group)
# Tabs for interaction details
self.tabs = QTabWidget()
self.tabs.setAccessibleName("Interaction Details")
# Favorites tab
self.favorites_list = QListWidget()
self.favorites_list.setAccessibleName("Users Who Favorited")
# Add fake header for single-item navigation
fake_header = QListWidgetItem("Users who favorited this post:")
fake_header.setFlags(Qt.ItemIsEnabled) # Not selectable
self.favorites_list.addItem(fake_header)
self.tabs.addTab(self.favorites_list, f"Favorites ({self.post.favourites_count})")
# Boosts tab
self.boosts_list = QListWidget()
self.boosts_list.setAccessibleName("Users Who Boosted")
# Add fake header for single-item navigation
fake_header = QListWidgetItem("Users who boosted this post:")
fake_header.setFlags(Qt.ItemIsEnabled) # Not selectable
self.boosts_list.addItem(fake_header)
self.tabs.addTab(self.boosts_list, f"Boosts ({self.post.reblogs_count})")
layout.addWidget(self.tabs)
# Loading indicator
self.status_label = QLabel("Loading interaction details...")
self.status_label.setAccessibleName("Loading Status")
layout.addWidget(self.status_label)
# Button box
button_box = QDialogButtonBox(QDialogButtonBox.Close)
button_box.setAccessibleName("Dialog Buttons")
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)
def load_details(self):
"""Load detailed interaction information"""
if not self.client or not hasattr(self.post, 'id'):
self.status_label.setText("Cannot load details: No post ID or API client")
return
# Start background fetch
self.fetch_thread = FetchDetailsThread(self.client, self.post.id)
self.fetch_thread.details_loaded.connect(self.on_details_loaded)
self.fetch_thread.details_failed.connect(self.on_details_failed)
self.fetch_thread.start()
def on_details_loaded(self, details: dict):
"""Handle successful details loading"""
self.status_label.setText("")
# Populate favorites list
favourited_by = details.get('favourited_by', [])
if favourited_by:
for account_data in favourited_by:
try:
user = User.from_api_dict(account_data)
display_name = user.display_name or user.username
item_text = f"@{user.username} ({display_name})"
item = QListWidgetItem(item_text)
item.setData(Qt.UserRole, user)
self.favorites_list.addItem(item)
except Exception as e:
print(f"Error parsing favorite user: {e}")
else:
item = QListWidgetItem("No one has favorited this post yet")
self.favorites_list.addItem(item)
# Populate boosts list
reblogged_by = details.get('reblogged_by', [])
if reblogged_by:
for account_data in reblogged_by:
try:
user = User.from_api_dict(account_data)
display_name = user.display_name or user.username
item_text = f"@{user.username} ({display_name})"
item = QListWidgetItem(item_text)
item.setData(Qt.UserRole, user)
self.boosts_list.addItem(item)
except Exception as e:
print(f"Error parsing boost user: {e}")
else:
item = QListWidgetItem("No one has boosted this post yet")
self.boosts_list.addItem(item)
# Update tab titles with actual counts
actual_favorites = len(favourited_by)
actual_boosts = len(reblogged_by)
self.tabs.setTabText(0, f"Favorites ({actual_favorites})")
self.tabs.setTabText(1, f"Boosts ({actual_boosts})")
# Play success sound
self.sound_manager.play_success()
def on_details_failed(self, error_message: str):
"""Handle details loading failure"""
self.status_label.setText(f"Failed to load details: {error_message}")
# Add error items to lists
error_item_fav = QListWidgetItem(f"Error loading favorites: {error_message}")
self.favorites_list.addItem(error_item_fav)
error_item_boost = QListWidgetItem(f"Error loading boosts: {error_message}")
self.boosts_list.addItem(error_item_boost)
# Play error sound
self.sound_manager.play_error()