Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,30 @@

### Added

1. Add lazy loading of texts and make them scrollable.


### Changed



### Fixed


---

## [v0.4.0]

### Added

1. Add lazy loading of texts and make them scrollable.
2. Add participants property and a modifiable username attribute to Conversation.
3. Add option to choose username from participants during chat import.

### Changed

1. Set minimum size and initial geometry for main window.
2. Align messages according to username.

### Fixed

1. Fix missing UI cues during exceptions.
Expand Down Expand Up @@ -44,7 +62,7 @@
- Main area containing messages with metadata and correct alignment.
2. Add QAbstractListModel models for chat list and message list.
3. Add message service module to read messages from DB.
4. Add chat list and chat messages views with forementioned models.
4. Add chat list and chat messages views with aforementioned models.
5. Add message list delegate to display messages with correct alignment.

### Changed
Expand Down
3 changes: 2 additions & 1 deletion src/memorytext/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show() # IMPORTANT!!!!! Windows are hidden by default.

window.setMinimumSize(500, 500)
window.setGeometry(0, 0, 800, 600)
# Start the event loop.
app.exec()

Expand Down
6 changes: 4 additions & 2 deletions src/memorytext/io/import_wa.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
from pathlib import Path


def import_whatsapp(path: str | Path, title: str, tz: str = "Etc/UTC"):
conversation = Conversation.from_whatsapp(path, title, tz)
def import_whatsapp(
path: str | Path, title: str, tz: str = "Etc/UTC", username: str | None = None
):
conversation = Conversation.from_whatsapp(path, title, tz, username)
engine = get_db()
with Session(engine) as session:
try:
Expand Down
6 changes: 5 additions & 1 deletion src/memorytext/models/chat_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@


class ChatList(QAbstractListModel):
ParticipantsRole = Qt.ItemDataRole.UserRole + 4

def __init__(self, chat_list=[], parent=None):
super().__init__(parent)
self._items = chat_list
Expand All @@ -13,11 +15,13 @@ def rowCount(self, parent=QModelIndex()): # pyright: ignore[reportIncompatibleM
def data(self, index, role=Qt.ItemDataRole.DisplayRole): # pyright: ignore[reportIncompatibleMethodOverride]
if not index.isValid():
return None
conversation_id, title = self._items[index.row()]
conversation_id, title, participants = self._items[index.row()]
if role == Qt.ItemDataRole.DisplayRole:
return title
if role == Qt.ItemDataRole.UserRole:
return conversation_id
if role == self.ParticipantsRole:
return participants
return None

def get_id(self, index):
Expand Down
16 changes: 14 additions & 2 deletions src/memorytext/models/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,21 @@ class Conversation(Base):
messages: Mapped[List["Message"]] = relationship(
back_populates="conversation", cascade="all, delete-orphan"
)
username: Mapped[Optional[str]] = mapped_column(default=None)

@property
def participants(self):
participant_names = {m.sender for m in self.messages}
return participant_names

@classmethod
def from_whatsapp(cls, path: str | Path, title: str, tz: str = "Etc/UTC"):
def from_whatsapp(
cls,
path: str | Path,
title: str,
tz: str = "Etc/UTC",
username: str | None = None,
):
dt_fmt = WHATSAPP_DT_FMT
messages = parsers.parse_chat(path)
messages = normalize.normalize_timestamps(messages, dt_fmt, tz)
Expand All @@ -59,7 +71,7 @@ def from_whatsapp(cls, path: str | Path, title: str, tz: str = "Etc/UTC"):
)
for m in messages
]
return cls(title=title, tz=tz, messages=messages2)
return cls(title=title, tz=tz, messages=messages2, username=username)


class Tag(Base):
Expand Down
9 changes: 6 additions & 3 deletions src/memorytext/models/message_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from collections.abc import Callable
from memorytext.models.core import Message

from memorytext.services import chat_service


class MessageList(QAbstractListModel):
IsSameSenderRole = Qt.ItemDataRole.UserRole + 1
Expand All @@ -24,6 +26,7 @@ def __init__(
self._conversation_id = conversation_id
self._message_service = message_service
self._items, self._total = self._message_service(conversation_id, limit, 0)
self._username = chat_service.get_username(conversation_id)

def rowCount(self, parent=QModelIndex()): # pyright: ignore[reportIncompatibleMethodOverride]
return len(self._items)
Expand All @@ -43,10 +46,10 @@ def data(self, index, role=Qt.ItemDataRole.DisplayRole): # pyright: ignore[repo

# Alignment
if role == Qt.ItemDataRole.TextAlignmentRole:
if m.sender == "Meera":
return Qt.AlignmentFlag.AlignLeft
else:
if m.sender == chat_service.get_username(self._conversation_id):
return Qt.AlignmentFlag.AlignRight
else:
return Qt.AlignmentFlag.AlignLeft

# Sender Name
if role == self.SenderRole:
Expand Down
47 changes: 43 additions & 4 deletions src/memorytext/services/chat_service.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations
from sqlalchemy.orm import Session
from sqlalchemy import select
from sqlalchemy.orm import Session, selectinload
from sqlalchemy import select, update

from memorytext.models.core import Conversation
from memorytext.db import get_db
Expand All @@ -9,7 +9,46 @@
def get_chats(count: int = 10):
engine = get_db()
with Session(engine) as session:
stmt = select(Conversation)
stmt = select(Conversation).options(selectinload(Conversation.messages))
conversations = session.scalars(stmt).fetchmany(count)
chat_list = [(c.id, c.title) for c in conversations]
chat_list = [(c.id, c.title, c.participants) for c in conversations]
return chat_list


def get_participants(title: str):
engine = get_db()
with Session(engine) as session:
stmt = (
select(Conversation)
.where(Conversation.title == title)
.options(selectinload(Conversation.messages))
)
conversation = session.scalar(stmt)
if conversation:
participants = conversation.participants
return participants
return set()


def set_username(title: str, username: str):
engine = get_db()
with Session(engine) as session:
stmt = (
update(Conversation)
.where(Conversation.title == title)
.values(username=username)
)
try:
session.execute(stmt)
session.commit()
except Exception:
session.rollback()
raise


def get_username(conversation_id: int | None):
engine = get_db()
with Session(engine) as session:
stmt = select(Conversation.username).where(Conversation.id == conversation_id)
username = session.scalar(stmt)
return username
74 changes: 73 additions & 1 deletion src/memorytext/windows/import_window.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
from __future__ import annotations
from typing import TYPE_CHECKING

from PySide6.QtWidgets import (
QComboBox,
QFileDialog,
QMainWindow,
QFormLayout,
QMainWindow,
QHBoxLayout,
QVBoxLayout,
QLineEdit,
QPushButton,
QWidget,
QLabel,
)
from PySide6.QtCore import Slot
from sqlalchemy.exc import IntegrityError
from memorytext.io.import_wa import import_whatsapp
from memorytext.services import chat_service

if TYPE_CHECKING:
from collections.abc import Callable


class ImportWindow(QMainWindow):
Expand All @@ -36,6 +46,7 @@ def __init__(self, parent=None):
layout.addRow(self.file_pick)
layout.addRow("Title:", self.title_box)
layout.addRow(self.import_button, self.cancel_button)

container = QWidget()
container.setLayout(layout)

Expand All @@ -60,6 +71,10 @@ def import_chat(self):
if self.file_path and title:
try:
import_whatsapp(self.file_path, title)
self.select_user = SelectUserWindow(
service=chat_service.get_participants, title=title
)
self.select_user.show()
self.parent().sidebar.update_data() # pyright: ignore [reportOptionalMemberAccess, reportAttributeAccessIssue]
except IntegrityError:
self.error_window = ErrorWindow(
Expand All @@ -74,6 +89,63 @@ def import_chat(self):
self.close()


class SelectUserWindow(QMainWindow):
def __init__(self, service: Callable[[str], set[str]], title: str):
super().__init__()
self.service = service
self.chat_title = title
self.initUI()

def initUI(self):
self.participants = self.service(self.chat_title)
self.setWindowTitle("Select your username")
self.setGeometry(100, 100, 300, 200)

button_layout = QHBoxLayout()
layout = QVBoxLayout()

self.comboBox = QComboBox()
self.comboBox.addItems(self.participants) # pyright: ignore[reportArgumentType]
layout.addWidget(self.comboBox)

self.default_select = str(next((name for name in self.participants), None))
self.comboBox.setCurrentText(self.default_select)

self.label = QLabel(f"Selected username: {self.default_select}")
layout.addWidget(self.label)

self.ok_button = QPushButton("Ok")
button_layout.addWidget(self.ok_button)

self.cancel_button = QPushButton("Cancel")
button_layout.addWidget(self.cancel_button)

layout.addLayout(button_layout)

self.cancel_button.clicked.connect(self.close)
self.ok_button.clicked.connect(self.set_username)
self.comboBox.currentTextChanged.connect(self.on_selection_changed)

container = QWidget()
container.setLayout(layout)

self.setCentralWidget(container)

@Slot(str)
def on_selection_changed(self, text):
self.label.setText(f"Selected username: {text}")

@Slot()
def set_username(self):
try:
username = self.comboBox.currentText()
chat_service.set_username(self.chat_title, username)
except Exception as e:
self.error_window = ErrorWindow(error=f"Error: {e}")
self.error_window.show()
self.close()


class ErrorWindow(QMainWindow):
def __init__(self, error: str):
super().__init__()
Expand Down
2 changes: 2 additions & 0 deletions src/memorytext/windows/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
QMainWindow,
QHBoxLayout,
QVBoxLayout,
# QLayout,
QWidget,
QPushButton,
)
Expand All @@ -17,6 +18,7 @@ def __init__(self):
super().__init__()

main_layout = QHBoxLayout()

central_widget = QWidget()
central_widget.setLayout(main_layout)
self.setCentralWidget(central_widget)
Expand Down
Loading