完善定时提醒稳定性与管理能力

This commit is contained in:
2026-06-02 22:04:09 +08:00
parent c794e32023
commit 6c2926b57a
20 changed files with 1531 additions and 89 deletions
+263 -14
View File
@@ -24,6 +24,7 @@
#include <QContextMenuEvent>
#include <QCursor>
#include <QDialog>
#include <QFrame>
#include <QGuiApplication>
#include <QHideEvent>
#include <QList>
@@ -32,6 +33,7 @@
#include <QPixmap>
#include <QPointF>
#include <QPointer>
#include <QPushButton>
#include <QRandomGenerator>
#include <QScreen>
#include <QSet>
@@ -333,6 +335,7 @@ void PetWindow::resumeAnimation()
void PetWindow::showBubbleMessage(const QString &message)
{
hideReminderActions();
m_chatBubble->showMessage(message, bubbleAnchorPosition());
}
@@ -352,8 +355,47 @@ void PetWindow::openSettingsDialog()
[this](const QString &reminderId, QString *errorMessage) {
return m_reminderManager && m_reminderManager->cancelReminder(reminderId, errorMessage);
},
[this](const QString &reminderId, const QString &title, const QDateTime &remindAt, const ReminderRecurrence &recurrence, ReminderItem *updatedItem, QString *errorMessage) {
if (!m_reminderManager)
{
if (errorMessage != nullptr)
{
*errorMessage = QStringLiteral("提醒功能初始化失败。");
}
return false;
}
if (!m_reminderManager->updateReminder(reminderId, title, remindAt, recurrence, errorMessage))
{
return false;
}
if (updatedItem != nullptr)
{
const QVector<ReminderItem> reminders = m_reminderManager->allReminders();
bool found = false;
for (const ReminderItem &item : reminders)
{
if (item.id == reminderId)
{
*updatedItem = item;
found = true;
break;
}
}
if (!found)
{
if (errorMessage != nullptr)
{
*errorMessage = QStringLiteral("提醒已更新,但没有找到更新后的记录。");
}
return false;
}
}
return true;
},
[this](QString *errorMessage) {
return m_reminderManager && m_reminderManager->clearFinishedReminders(errorMessage);
return m_reminderManager && m_reminderManager->pruneFinishedReminders(20, errorMessage);
},
[this](const QString &soundId, double volume) {
if (m_reminderSoundPlayer)
@@ -402,6 +444,10 @@ void PetWindow::activateFromExternalInstance()
raise();
activateWindow();
if (m_reminderManager)
{
m_reminderManager->checkDueRemindersNow();
}
updateBubblePosition();
}
@@ -410,7 +456,7 @@ void PetWindow::setSettingsFallbackInContextMenuEnabled(bool enabled)
m_settingsFallbackInContextMenuEnabled = enabled;
}
void PetWindow::setTrayNotificationCallback(std::function<void(const QString &, const QString &)> callback)
void PetWindow::setTrayNotificationCallback(std::function<bool(const QString &, const QString &)> callback)
{
if (m_notificationDispatcher)
{
@@ -547,37 +593,240 @@ bool PetWindow::handleReminderChatMessage(const QString &message)
}
void PetWindow::handleTriggeredReminder(const ReminderItem &item)
{
playReminderSound();
if (shouldNotifyOnlyForReminder())
{
showReminderNotification(item);
return;
}
enqueueVisibleTriggeredReminder(item);
}
void PetWindow::playReminderSound()
{
if (m_appConfig.reminderSoundEnabled && m_reminderSoundPlayer)
{
m_reminderSoundPlayer->play(m_appConfig.reminderSoundId, m_appConfig.reminderSoundVolume);
}
}
if (!isVisible())
void PetWindow::showReminderNotification(const ReminderItem &item)
{
if (m_notificationDispatcher)
{
if (m_notificationDispatcher)
const bool shown = m_notificationDispatcher->showReminder(QStringLiteral("定时提醒"), QStringLiteral("到时间啦:%1").arg(item.title));
if (!shown)
{
m_notificationDispatcher->showReminder(QStringLiteral("定时提醒"), QStringLiteral("到时间啦:%1").arg(item.title));
Logger::warning(QStringLiteral("Reminder notification backend unavailable: id=%1").arg(item.id));
}
return;
}
if (m_dragging)
Logger::warning(QStringLiteral("Reminder notification dispatcher is unavailable: id=%1").arg(item.id));
}
bool PetWindow::shouldNotifyOnlyForReminder() const
{
return !isVisible() || hasActiveAIRequest() || m_streamingChatActive;
}
void PetWindow::enqueueVisibleTriggeredReminder(const ReminderItem &item)
{
m_pendingVisibleTriggeredReminders.append(item);
showNextTriggeredReminder();
}
void PetWindow::showNextTriggeredReminder()
{
if (m_hasActiveTriggeredReminder || m_dragging || m_pendingVisibleTriggeredReminders.isEmpty())
{
m_deferredTriggeredReminders.append(item);
return;
}
const ReminderItem item = m_pendingVisibleTriggeredReminders.takeFirst();
showTriggeredReminder(item);
}
void PetWindow::finishActiveTriggeredReminder(bool hideBubble)
{
hideReminderActions();
m_hasActiveTriggeredReminder = false;
if (hideBubble && m_chatBubble)
{
m_chatBubble->hideBubble();
}
showNextTriggeredReminder();
}
void PetWindow::showTriggeredReminder(const ReminderItem &item)
{
const QString reminderState = m_clips.contains(QStringLiteral("happy"))
? QStringLiteral("happy")
: QStringLiteral("talk");
playState(reminderState, false);
showBubbleMessage(QStringLiteral("到时间啦:%1").arg(item.title));
hideReminderActions();
m_chatBubble->showMessage(QStringLiteral("到时间啦:%1").arg(item.title), bubbleAnchorPosition(), 0);
showReminderActions(item);
}
void PetWindow::ensureReminderActionPanel()
{
if (m_reminderActionPanel)
{
return;
}
auto *panel = new QFrame();
panel->setObjectName(QStringLiteral("ReminderActionPanel"));
panel->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
panel->setAttribute(Qt::WA_TranslucentBackground);
panel->setAttribute(Qt::WA_ShowWithoutActivating);
panel->setStyleSheet(QStringLiteral(
"QFrame#ReminderActionPanel {"
"background: #ffffff;"
"border: 1px solid #c7cdd4;"
"border-radius: 8px;"
"}"
"QPushButton {"
"background: #f1f4f7;"
"border: 1px solid #aeb6bf;"
"border-radius: 6px;"
"padding: 7px 12px;"
"color: #202124;"
"}"
"QPushButton:hover {"
"background: #e4e9ee;"
"}"
"QPushButton:pressed {"
"background: #d5dce3;"
"}"));
auto *layout = new QVBoxLayout(panel);
layout->setContentsMargins(8, 8, 8, 8);
layout->setSpacing(8);
auto *dismissButton = new QPushButton(QStringLiteral("知道了"), panel);
auto *snoozeButton = new QPushButton(QStringLiteral("5分钟后再提醒"), panel);
layout->addWidget(dismissButton);
layout->addWidget(snoozeButton);
connect(dismissButton, &QPushButton::clicked, this, [this]() {
finishActiveTriggeredReminder(true);
});
connect(snoozeButton, &QPushButton::clicked, this, [this]() {
if (!m_hasActiveTriggeredReminder)
{
finishActiveTriggeredReminder(true);
return;
}
const ReminderItem item = m_activeTriggeredReminder;
hideReminderActions();
snoozeTriggeredReminder(item);
});
m_reminderActionPanel.reset(panel);
}
void PetWindow::showReminderActions(const ReminderItem &item)
{
m_activeTriggeredReminder = item;
m_hasActiveTriggeredReminder = true;
ensureReminderActionPanel();
if (!m_reminderActionPanel)
{
return;
}
m_reminderActionPanel->adjustSize();
updateReminderActionPosition();
m_reminderActionPanel->show();
m_reminderActionPanel->raise();
}
void PetWindow::hideReminderActions()
{
m_hasActiveTriggeredReminder = false;
if (m_reminderActionPanel)
{
m_reminderActionPanel->hide();
}
}
void PetWindow::updateReminderActionPosition()
{
if (!m_reminderActionPanel)
{
return;
}
m_reminderActionPanel->adjustSize();
const QSize panelSize = m_reminderActionPanel->sizeHint();
const QRect petGeometry = frameGeometry();
constexpr int PanelSideSpacing = 8;
QPoint position(
petGeometry.right() + PanelSideSpacing,
petGeometry.center().y() - panelSize.height() / 2);
if (QScreen *screen = screenForPopup(this))
{
const QRect availableGeometry = screen->availableGeometry();
const int maxX = qMax(availableGeometry.left(), availableGeometry.right() - panelSize.width());
const int maxY = qMax(availableGeometry.top(), availableGeometry.bottom() - panelSize.height());
if (position.x() > maxX)
{
position.setX(petGeometry.left() - panelSize.width() - PanelSideSpacing);
}
position.setX(qBound(
availableGeometry.left(),
position.x(),
maxX));
position.setY(qBound(
availableGeometry.top(),
position.y(),
maxY));
}
m_reminderActionPanel->move(position);
}
void PetWindow::snoozeTriggeredReminder(const ReminderItem &item)
{
m_hasActiveTriggeredReminder = false;
if (!m_reminderManager)
{
playState(QStringLiteral("error"), false);
showBubbleMessage(QStringLiteral("提醒功能初始化失败。"));
QTimer::singleShot(1200, this, [this]() {
showNextTriggeredReminder();
});
return;
}
ReminderItem snoozedItem;
QString errorMessage;
if (!m_reminderManager->snoozeReminder(item, 5, &snoozedItem, &errorMessage))
{
playState(QStringLiteral("error"), false);
showBubbleMessage(errorMessage.isEmpty() ? QStringLiteral("创建稍后提醒失败。") : errorMessage);
QTimer::singleShot(1200, this, [this]() {
showNextTriggeredReminder();
});
return;
}
playState(QStringLiteral("talk"), false);
showBubbleMessage(QStringLiteral("已延后提醒:%1,时间:%2").arg(snoozedItem.title, reminderDisplayTime(snoozedItem.remindAt)));
QTimer::singleShot(1200, this, [this]() {
showNextTriggeredReminder();
});
}
bool PetWindow::submitAiChatMessage(const QString &message)
@@ -620,6 +869,7 @@ bool PetWindow::submitAiChatMessage(const QString &message)
stopAnimationPrewarm();
playState(QStringLiteral("think"), false);
hideReminderActions();
m_streamingAssistantText.clear();
m_streamBubbleUpdateTimer.stop();
m_streamingChatActive = true;
@@ -844,6 +1094,7 @@ void PetWindow::flushStreamingBubble(bool finalUpdate)
return;
}
hideReminderActions();
m_chatBubble->showMessage(
m_streamingAssistantText,
bubbleAnchorPosition(),
@@ -888,6 +1139,7 @@ void PetWindow::hideEvent(QHideEvent *event)
{
m_chatBubble->hideBubble();
}
hideReminderActions();
if (m_chatInputDialog)
{
m_chatInputDialog->hide();
@@ -911,6 +1163,7 @@ void PetWindow::showEvent(QShowEvent *event)
if (m_reminderManager)
{
m_reminderManager->start();
m_reminderManager->checkDueRemindersNow();
}
scheduleAnimationPrewarm();
}
@@ -976,12 +1229,7 @@ void PetWindow::mouseReleaseEvent(QMouseEvent *event)
m_dragging = false;
playResolvedState(m_stateMachine.endDrag(), false);
scheduleAnimationPrewarm();
const QVector<ReminderItem> deferredReminders = m_deferredTriggeredReminders;
m_deferredTriggeredReminders.clear();
for (const ReminderItem &item : deferredReminders)
{
showTriggeredReminder(item);
}
showNextTriggeredReminder();
event->accept();
return;
}
@@ -1087,6 +1335,7 @@ void PetWindow::addStateTestActions(QMenu *menu)
void PetWindow::updateBubblePosition()
{
m_chatBubble->updateAnchorPosition(bubbleAnchorPosition());
updateReminderActionPosition();
}
QPoint PetWindow::bubbleAnchorPosition() const