diff --git a/CMakeLists.txt b/CMakeLists.txt index 7683d68..c2bdad1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,13 +9,14 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) - # 查找模块 find_package(Qt6 COMPONENTS +Core Protobuf Widgets Network WebSockets +Multimedia QUIET) set(QT_VERSION_MAJOR 6) @@ -57,7 +58,9 @@ endif() # 链接动态库 target_link_libraries(ClientChat PRIVATE +Qt6::Core Qt${QT_VERSION_MAJOR}::Widgets Qt6::Network Qt6::WebSockets +Qt6::Multimedia ) \ No newline at end of file diff --git a/CMakeUserPresets.json b/CMakeUserPresets.json index d04f08a..20b3da0 100644 --- a/CMakeUserPresets.json +++ b/CMakeUserPresets.json @@ -10,7 +10,7 @@ "CMAKE_CXX_FLAGS": "-DQT_QML_DEBUG" }, "environment": { - "QML_DEBUG_ARGS": "-qmljsdebugger=file:{7e8c2a2f-a445-4c9b-bb42-24fd265a3e1e},block" + "QML_DEBUG_ARGS": "-qmljsdebugger=file:{74e0e0b6-0fbf-4a0d-93e9-c8349734743b},block" } }, { @@ -70,7 +70,7 @@ ], "vendor": { "qt-project.org/Presets": { - "checksum": "0wzuyL8qCbVtFSbq9ZO9Fr8MNQU=" + "checksum": "Dlc1I+gqV2Q029C9SawBnzSygCA=" } } } \ No newline at end of file diff --git a/SelfInfoWidget.cpp b/SelfInfoWidget.cpp index 47064d8..4298e73 100644 --- a/SelfInfoWidget.cpp +++ b/SelfInfoWidget.cpp @@ -368,8 +368,12 @@ void SelfInfoWidget::clickGetVerifyCodeBtn() //给服务器发送请求 DataCenter* dataCenter = DataCenter::getInstance(); - connect(dataCenter, &DataCenter::getVerifyCodeDone, this, [=]() { + connect(dataCenter, &DataCenter::getVerifyCodeDone, this, [=](bool ok) { //不需要做其他的处理,只需要提示一下,验证码已经发送出去了 + if (!ok) { + return; + } + LOG() << "ok: " << ok; Toast::showMessage("验证码已发送,请注意查收"); }); dataCenter->getVerifyCodeAsync(email); diff --git a/addfrienddialog.cpp b/addfrienddialog.cpp index 4d9b0f9..b162177 100644 --- a/addfrienddialog.cpp +++ b/addfrienddialog.cpp @@ -1,5 +1,8 @@ #include "addfrienddialog.h" +#include "model/datacenter.h" + +using namespace model; ///////////////////////////////////////// //表示一个好友搜索的结果 @@ -23,6 +26,7 @@ FriendResultItem::FriendResultItem(const UserInfo& userInfo) avatarBtn->setFixedSize(50, 50); avatarBtn->setIconSize(QSize(50, 50)); avatarBtn->setIcon(userInfo.avatar); + avatarBtn->setStyleSheet("QPushButton { background-color: transparent; }"); // 4. 创建昵称 QLabel* nameLabel = new QLabel(); @@ -55,10 +59,22 @@ FriendResultItem::FriendResultItem(const UserInfo& userInfo) layout->addWidget(addBtn, 0, 2, 2, 1); // 8. 连接信号槽 - //connect(addBtn, &QPushButton::clicked, this, &FriendResultItem::clickAddBtn); + connect(addBtn, &QPushButton::clicked, this, &FriendResultItem::clickAddBtn); } +void FriendResultItem::clickAddBtn() +{ + //发送好友申请 + DataCenter* dataCenter = DataCenter::getInstance(); + dataCenter->addFriendApplyAsync(this->userInfo.userId); + + //设置按钮为禁用状态 + addBtn->setEnabled(false); + addBtn->setText("申请已发送"); + addBtn->setStyleSheet("QPushButton { border: none; color: rgb(255, 255, 255); background-color: rgb(200, 200, 200); border-radius: 10px;}"); +} + ///////////////////////////////////////// //整个搜索好友的窗口 ///////////////////////////////////////// @@ -115,8 +131,8 @@ AddFriendDialog::AddFriendDialog(QWidget *parent) } #endif // -// // 6. 连接信号槽 -// connect(searchBtn, &QPushButton::clicked, this, &AddFriendDialog::clickSearchBtn); + // 6. 连接信号槽 + connect(searchBtn, &QPushButton::clicked, this, &AddFriendDialog::clickSearchBtn); } @@ -170,3 +186,32 @@ void AddFriendDialog::setSearchKey(const QString& searcheKey) searchEdit->setText(searcheKey); } +void AddFriendDialog::clickSearchBtn() +{ + //拿到输入框的内容 + const QString& text = searchEdit->text(); + if (text.isEmpty()) { + return; + } + + //给服务器发送请求 + DataCenter* dataCenter = DataCenter::getInstance(); + connect(dataCenter, &DataCenter::searchUserDone, this, &AddFriendDialog::clickSearchDone, Qt::UniqueConnection); + dataCenter->searchUserAsync(text); +} + +void AddFriendDialog::clickSearchDone() +{ + //拿到DataCenter中的搜索结果 + DataCenter* dataCenter = DataCenter::getInstance(); + QList* searchResult = dataCenter->getSearchUserResult(); + if (searchResult == nullptr) { + return; + } + + this->clear(); + for (const auto& u : *searchResult) { + this->addResult(u); + } +} + diff --git a/addfrienddialog.h b/addfrienddialog.h index 117159a..b13906c 100644 --- a/addfrienddialog.h +++ b/addfrienddialog.h @@ -21,6 +21,7 @@ class FriendResultItem : public QWidget { Q_OBJECT public: FriendResultItem(const UserInfo& userInfo); + void clickAddBtn(); private: const UserInfo& userInfo; @@ -50,6 +51,9 @@ public: // void setSearchKey(const QString& searcheKey); + void clickSearchBtn(); + void clickSearchDone(); + private: //整个窗口的网格布局 QGridLayout* layout; diff --git a/choosefrienddialog.cpp b/choosefrienddialog.cpp index 3688383..b7d2850 100644 --- a/choosefrienddialog.cpp +++ b/choosefrienddialog.cpp @@ -1,5 +1,9 @@ #include "choosefrienddialog.h" +#include "model/datacenter.h" +#include "toast.h" + +using namespace model; //////////////////////////////////////////////// /// 选择好友窗口中的一个 元素/好友项 @@ -85,8 +89,9 @@ void ChooseFriendItem::leaveEvent(QEvent* event) this->update(); } -ChooseFriendDialog::ChooseFriendDialog(QWidget *parent) - : QDialog(parent) +ChooseFriendDialog::ChooseFriendDialog(QWidget* parent, const QString& userId) + : QDialog(parent), + userId(userId) { // 1. 设置窗口的基本属性 this->setWindowTitle("选择好友"); @@ -107,7 +112,8 @@ ChooseFriendDialog::ChooseFriendDialog(QWidget *parent) // 4. 针对右侧窗口进行初始化 initRight(layout); - + //加载数据到窗口 + initData(); } void ChooseFriendDialog::initLeft(QHBoxLayout* layout) @@ -213,14 +219,75 @@ void ChooseFriendDialog::initRight(QHBoxLayout* layout) } #endif - //// 8. 添加信号槽, 处理 ok 和 cancel 的点击 - //connect(okBtn, &QPushButton::clicked, this, &ChooseFriendDialog::clickOkBtn); - //connect(cancelBtn, &QPushButton::clicked, this, [=]() { - // this->close(); - // }); + // 8. 添加信号槽, 处理 ok 和 cancel 的点击 + connect(okBtn, &QPushButton::clicked, this, &ChooseFriendDialog::clickOkBtn); + connect(cancelBtn, &QPushButton::clicked, this, [=]() { + this->close(); + }); } +void ChooseFriendDialog::clickOkBtn() +{ + //根据选中好友列表中的元素,得到所有的要创建群聊会话的用户id列表 + QList userIdList = generateMemberList(); + if (userIdList.size() < 3) { + Toast::showMessage("群聊中的成员不足三个,无法创建群聊"); + return; + } + + //发送网络请求,创建群聊 + DataCenter* dataCenter = DataCenter::getInstance(); + dataCenter->createGroupChatSessionAsync(userIdList); + + //关闭当前窗口 + this->close(); +} + +QList ChooseFriendDialog::generateMemberList() +{ + QList result; + + //把自己添加到结果中 + DataCenter* dataCenter = DataCenter::getInstance(); + if (dataCenter->getMyselfsync() == nullptr) { + LOG() << "个人信息尚未加载..."; + return result; + } + result.push_back(dataCenter->getMyselfsync()->userId); + + //遍历选中的列表 + QVBoxLayout* layout = dynamic_cast(selectedContainer->layout()); + for (int i = 0; i < layout->count(); ++i) { + auto* item = layout->itemAt(i); + if (item == nullptr || item->widget() == nullptr) { + continue; + } + auto* chooseFriendItem = dynamic_cast(item->widget()); + result.push_back(chooseFriendItem->getUserId()); + } + return result; +} + + +void ChooseFriendDialog::initData() +{ + //遍历好友列表,把好友列表中的元素添加到这个窗口界面上 + DataCenter* dataCenter = DataCenter::getInstance(); + QList* friendList = dataCenter->getFriendList(); + if (friendList == nullptr) { + LOG() << "加载数据时发现好友列表为空"; + return; + } + for (auto it = friendList->begin(); it != friendList->end(); ++it) { + if (it->userId == this->userId) { + this->addSelectedFriend(it->userId, it->avatar, it->nickname); + this->addFriend(it->userId, it->avatar, it->nickname, true); + } + this->addFriend(it->userId, it->avatar, it->nickname, false); + } +} + void ChooseFriendDialog::addFriend(const QString& userId, const QIcon& avatar, const QString& name, bool checked) { ChooseFriendItem* item = new ChooseFriendItem(this, userId, avatar, name, checked); @@ -271,4 +338,4 @@ void ChooseFriendDialog::deleteSelectedFriend(const QString& userId) //已找到,取消勾选状态 chooseFriendItem->getCheckBox()->setChecked(false); } -} +} \ No newline at end of file diff --git a/choosefrienddialog.h b/choosefrienddialog.h index a4c9104..e2473b4 100644 --- a/choosefrienddialog.h +++ b/choosefrienddialog.h @@ -50,17 +50,23 @@ class ChooseFriendDialog : public QDialog Q_OBJECT public: - ChooseFriendDialog(QWidget *parent); + ChooseFriendDialog(QWidget *parent, const QString& userId); void initLeft(QHBoxLayout* layout); void initRight(QHBoxLayout* layout); + void initData(); void addFriend(const QString& userId, const QIcon& avatar, const QString& name, bool checked); void addSelectedFriend(const QString& userId, const QIcon& avatar, const QString& name); void deleteSelectedFriend(const QString& userId); + void clickOkBtn(); + QList generateMemberList(); private: QWidget* totalContainer; QWidget* selectedContainer; + + //当前选择窗口是点击哪个用户弹出的 + QString userId; }; diff --git a/debug.h b/debug.h index 4626e4c..6bb6ba0 100644 --- a/debug.h +++ b/debug.h @@ -3,10 +3,14 @@ #define TEST_UI 0 -#define TEST_SKIP_LOGIN 1 - -#define TEST_NETWORK 1 - #define TEST_GROUP_SESSION_DETAIL 1 +#define TEST_SKIP_LOGIN 0 + +#define TEST_NETWORK 0 + +#define TEST_GROUP_SESSION_DETAIL 0 + +#define DEPOLY 1 + #endif // DEBUG_H diff --git a/groupsessiondetailwidget.cpp b/groupsessiondetailwidget.cpp index 7c48b40..5ce1e3d 100644 --- a/groupsessiondetailwidget.cpp +++ b/groupsessiondetailwidget.cpp @@ -1,4 +1,7 @@ #include "groupsessiondetailwidget.h" +#include "model/datacenter.h" + +using namespace model; GroupSessionDetailWidget::GroupSessionDetailWidget(QWidget *parent) : QDialog(parent) @@ -64,7 +67,7 @@ GroupSessionDetailWidget::GroupSessionDetailWidget(QWidget *parent) vlayout->addLayout(hlayout); //创建群聊名字的label - QLabel* groupNameLabel = new QLabel(); + groupNameLabel = new QLabel(); groupNameLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); groupNameLabel->setFixedHeight(50); groupNameLabel->setStyleSheet("QLabel { font-size: 18px; }"); @@ -98,6 +101,35 @@ GroupSessionDetailWidget::GroupSessionDetailWidget(QWidget *parent) this->addMember(item); } #endif + + //从服务器加载数据 + initData(); +} + +void GroupSessionDetailWidget::initData() +{ + DataCenter* dataCenter = DataCenter::getInstance(); + connect(dataCenter, &DataCenter::getMemberListDone, this, &GroupSessionDetailWidget::initMembers); + dataCenter->getMemberListAsync(dataCenter->getCurrentSessionId()); +} + +void GroupSessionDetailWidget::initMembers(const QString& chatSessionId) +{ + //根据刚才拿到的成员列表,把成员列表渲染在界面上 + DataCenter* dataCenter = DataCenter::getInstance(); + QList* memberList = dataCenter->getMemberList(chatSessionId); + if (memberList == nullptr) { + LOG() << "获取的成员列表为空 chatSessionId= " << chatSessionId; + return; + } + //遍历成员列表 + for (const auto& u : *memberList) { + AvatarItem* avatarItem = new AvatarItem(u.avatar, u.nickname); + this->addMember(avatarItem); + } + + //群聊名称,此处暂时先设置为固定值 + groupNameLabel->setText("新的群聊"); } void GroupSessionDetailWidget::addMember(AvatarItem* avatarItem) diff --git a/groupsessiondetailwidget.h b/groupsessiondetailwidget.h index 7c54947..e25a7fd 100644 --- a/groupsessiondetailwidget.h +++ b/groupsessiondetailwidget.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -21,10 +22,14 @@ class GroupSessionDetailWidget : public QDialog public: GroupSessionDetailWidget(QWidget* parent); + void initData(); + void initMembers(const QString& chatSessionId); + void addMember(AvatarItem* avatarItem); private: QGridLayout* glayout; + QLabel* groupNameLabel; //表示当前所要添加的AvatarItem处在的行和列 int curRow = 0; diff --git a/historymessagewidget.cpp b/historymessagewidget.cpp index 5c3f6f5..5dc19f6 100644 --- a/historymessagewidget.cpp +++ b/historymessagewidget.cpp @@ -1,5 +1,10 @@ #include "historymessagewidget.h" +#include "model/datacenter.h" +#include "toast.h" +#include "soundrecorder.h" + +using namespace model; //工厂函数 HistoryItem* HistoryItem::makeHistoryItem(const Message& message) @@ -43,15 +48,15 @@ HistoryItem* HistoryItem::makeHistoryItem(const Message& message) } else if (message.messageType == model::MessageType::IMAGE_TYPE) { // 图片消息 - //contentWidget = new ImageButton(message.fileId, message.content); + contentWidget = new ImageButton(message.fileId, message.content); } else if (message.messageType == model::MessageType::FILE_TYPE) { // 文件消息 - //contentWidget = new FileLabel(message.fileId, message.fileName); + contentWidget = new FileLabel(message.fileId, message.fileName); } else if (message.messageType == model::MessageType::SPEECH_TYPE) { // 语音消息 - //contentWidget = new SpeechLabel(message.fileId); + contentWidget = new SpeechLabel(message.fileId); } else { LOG() << "错误的消息类型! messageType=" << message.messageType; @@ -168,7 +173,7 @@ HistoryMessageWidget::HistoryMessageWidget(QWidget *parent) endTimeEdit->show(); }); - //connect(searchBtn, &QPushButton::clicked, this, &HistoryMessageWidget::clickSearchBtn); + connect(searchBtn, &QPushButton::clicked, this, &HistoryMessageWidget::clickSearchBtn); // 构造测试数据 #if TEST_UI @@ -211,6 +216,48 @@ void HistoryMessageWidget::clear() } } +void HistoryMessageWidget::clickSearchBtn() +{ + DataCenter* dataCenter = DataCenter::getInstance(); + connect(dataCenter, &DataCenter::searchMessageDone, this, &HistoryMessageWidget::clickSearchBtnDone, Qt::UniqueConnection); + + //此处需要根据单选框的选中情况,执行不同逻辑 + if (keyRadioBtn->isChecked()) { + //按照关键词搜索 + const QString& searchKey = searchEdit->text(); + if (searchKey.isEmpty()) { + return; + } + dataCenter->searchMessageAsync(searchKey); + } + else { + //安时间搜索 + auto begTime = begTimeEdit->dateTime(); + auto endTime = endTimeEdit->dateTime(); + if (begTime >= endTime) { + Toast::showMessage("时间设置有误"); + return; + } + dataCenter->searchMessageByTimeAsync(begTime, endTime); + } +} + +void HistoryMessageWidget::clickSearchBtnDone() +{ + //从DataCenter中拿到消息搜索结果列表 + DataCenter* dataCenter = DataCenter::getInstance(); + QList* messageResult = dataCenter->getSearchMessageReuslt(); + if (messageResult == nullptr) { + return; + } + + //把结果列表的数据,显示到界面上 + this->clear(); + for (const Message& m : *messageResult) { + this->addHistoryMessage(m); + } +} + void HistoryMessageWidget::initScrollArea(QGridLayout* layout) { // 1. 创建滚动区域对象 @@ -236,3 +283,134 @@ void HistoryMessageWidget::initScrollArea(QGridLayout* layout) layout->addWidget(scrollArea, 2, 0, 1, 9); } + +ImageButton::ImageButton(const QString& fileId, const QByteArray& content) + :fileId(fileId) +{ + this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + this->setStyleSheet("QPushButton { border: none; }"); + + if (!content.isEmpty()) { + // 直接显示到界面上 + this->updateUI(fileId, content); + } + else { + // 通过网络来获取 + DataCenter* dataCenter = DataCenter::getInstance(); + connect(dataCenter, &DataCenter::getSingleFileDone, this, &ImageButton::updateUI); + dataCenter->getSingleFileAsync(fileId); + } + +} + +void ImageButton::updateUI(const QString& fileId, const QByteArray& content) +{ + if (this->fileId != fileId) { + return; + } + // 如果图片尺寸太大, 需要进行缩放. + QImage image; + image.loadFromData(content); + + int width = image.width(); + int height = image.height(); + if (image.width() >= 300) { + // 进行缩放, 缩放之后, 宽度就是固定的 300 + width = 300; + height = ((double)image.height() / image.width()) * width; + } + this->resize(width, height); + this->setIconSize(QSize(width, height)); + QPixmap pixmap = QPixmap::fromImage(image); + this->setIcon(QIcon(pixmap)); + +} + +FileLabel::FileLabel(const QString& fileId, const QString& fileName) + :fileId(fileId), filename(fileName) +{ + this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + this->setText("[文件] " + fileName); + this->setWordWrap(true); + // 自动调整尺寸让能够显示下文字内容 + this->adjustSize(); + this->setAlignment(Qt::AlignTop | Qt::AlignLeft); + + // if (!content.isEmpty()) { + // // 原则上来说, 这个条件不会触发. 因为这里的 content 是来自于请求服务器的 历史消息列表的 接口. + // // 请求这个接口的过程中, 拿到的 Message 对象(不是文本), content 必然为 空, 通过 fileId 做二次请求. + // return; + // } + + // 需要从网络加载数据了 + DataCenter* dataCenter = DataCenter::getInstance(); + connect(dataCenter, &DataCenter::getSingleFileDone, this, &FileLabel::getContentDone); + dataCenter->getSingleFileAsync(this->fileId); + +} + +void FileLabel::getContentDone(const QString& fileId, const QByteArray& fileContent) +{ + if (fileId != this->fileId) { + return; + } + + this->content = fileContent; + this->loadDone = true; +} + +void FileLabel::mousePressEvent(QMouseEvent* event) +{ + (void)event; + if (!this->loadDone) { + // 说明数据还没准备好. + Toast::showMessage("文件内容加载中, 请稍后尝试!"); + return; + } + + // 弹出一个对话框, 让用户来选择当前要保存的位置 + QString filePath = QFileDialog::getSaveFileName(this, "另存为", QDir::homePath(), "*"); + if (filePath.isEmpty()) { + // 用户取消了保存 + LOG() << "用户取消了保存"; + return; + } + + writeByteArrayToFile(filePath, content); +} + +SpeechLabel::SpeechLabel(const QString& fileId) + :fileId(fileId) +{ + this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + this->setText("[语音]"); + this->setAlignment(Qt::AlignLeft | Qt::AlignTop); + // 这两个操作不太需要了. 此处只有 语音 两个字 + this->setWordWrap(true); + this->adjustSize(); + + DataCenter* dataCenter = DataCenter::getInstance(); + connect(dataCenter, &DataCenter::getSingleFileDone, this, &SpeechLabel::getContentDone); + dataCenter->getSingleFileAsync(fileId); + +} + +void SpeechLabel::getContentDone(const QString& fileId, const QByteArray& content) +{ + if (fileId != this->fileId) { + return; + } + this->content = content; + this->loadDone = true; +} + +void SpeechLabel::mousePressEvent(QMouseEvent* event) +{ + (void)event; + if (!this->loadDone) { + Toast::showMessage("文件内容加载中, 稍后重试"); + return; + } + SoundRecorder* soundRecorder = SoundRecorder::getInstance(); + soundRecorder->startPlay(this->content); +} diff --git a/historymessagewidget.h b/historymessagewidget.h index 9d58a87..8aad1ed 100644 --- a/historymessagewidget.h +++ b/historymessagewidget.h @@ -43,6 +43,9 @@ public: //清空窗口中所有的历史消息 void clear(); + void clickSearchBtn(); + void clickSearchBtnDone(); + private: void initScrollArea(QGridLayout* layout); @@ -56,3 +59,50 @@ private: //持有所有的历史消息结果的容器对象 QWidget* container; }; + +//////////////////////////////////////////////////////////////////// +/// 展示图片历史消息 +//////////////////////////////////////////////////////////////////// +class ImageButton : public QPushButton +{ +public: + ImageButton(const QString& fileId, const QByteArray& content); + void updateUI(const QString& fileId, const QByteArray& content); + +private: + QString fileId; +}; + +//////////////////////////////////////////////////////////////////// +/// 展示文件历史消息 +//////////////////////////////////////////////////////////////////// +class FileLabel : public QLabel { +public: + FileLabel(const QString& fileId, const QString& filename); + void getContentDone(const QString& fileId, const QByteArray& fileContent); + + void mousePressEvent(QMouseEvent* event) override; + +private: + QString fileId; + QByteArray content; + QString filename; + bool loadDone = false; +}; + +//////////////////////////////////////////////////////////////////// +/// 展示语音历史消息 +//////////////////////////////////////////////////////////////////// +class SpeechLabel : public QLabel { +public: + SpeechLabel(const QString& fileId); + + void getContentDone(const QString& fileId, const QByteArray& content); + + void mousePressEvent(QMouseEvent* event); + +private: + QString fileId; + QByteArray content; + bool loadDone = false; +}; \ No newline at end of file diff --git a/loginwidget.cpp b/loginwidget.cpp index 145c002..4ad5088 100644 --- a/loginwidget.cpp +++ b/loginwidget.cpp @@ -1,5 +1,10 @@ #include "loginwidget.h" +#include "mainwidget.h" +#include "model/datacenter.h" + +using namespace model; + LoginWidget::LoginWidget(QWidget *parent) : QWidget(parent) { @@ -46,12 +51,12 @@ LoginWidget::LoginWidget(QWidget *parent) verifyCodeEdit->setPlaceholderText("输入验证码"); verifyCodeEdit->setStyleSheet(editStyle); - // 7. 创建显示验证码图片的控件 (此处先用 QPushButton 来表示一下, 后续进一步编写这里的逻辑) + // 创建显示验证码图片的控件 (此处先用 QPushButton 来表示一下, 后续进一步编写这里的逻辑) // 后续会自定义 QWidget, 通过画图 api 来实现这里的验证码功能. - QPushButton* verifyCodeWidget = new QPushButton(); - verifyCodeWidget->setText("验证码"); - verifyCodeWidget->setStyleSheet("QWidget { border: none; }"); - //verifyCodeWidget = new VerifyCodeWidget(this); + //* verifyCodeWidget = new QPushButton(); + //idget->setText("验证码"); + //idget->setStyleSheet("QWidget { border: none; }"); + verifyCodeWidget = new VerifyCodeWidget(this); // 创建登录按钮 submitBtn = new QPushButton(); @@ -88,24 +93,24 @@ LoginWidget::LoginWidget(QWidget *parent) // 处理信号槽 - //connect(switchModeBtn, &QPushButton::clicked, this, &LoginWidget::switchMode); - connect(switchModeBtn, &QPushButton::clicked, this, [=]() { - if (isLoginMode) { - //切换到注册模式 - this->setWindowTitle("注册"); - titleLabel->setText("注册"); - submitBtn->setText("注册"); - switchModeBtn->setText("登录"); - } - else { - //切换到登录模式 - this->setWindowTitle("登录"); - titleLabel->setText("登录"); - submitBtn->setText("登录"); - switchModeBtn->setText("注册"); - } - isLoginMode = !isLoginMode; - }); + connect(switchModeBtn, &QPushButton::clicked, this, &LoginWidget::switchMode); + //connect(switchModeBtn, &QPushButton::clicked, this, [=]() { + // if (isLoginMode) { + // //切换到注册模式 + // this->setWindowTitle("注册"); + // titleLabel->setText("注册"); + // submitBtn->setText("注册"); + // switchModeBtn->setText("登录"); + // } + // else { + // //切换到登录模式 + // this->setWindowTitle("登录"); + // titleLabel->setText("登录"); + // submitBtn->setText("登录"); + // switchModeBtn->setText("注册"); + // } + // isLoginMode = !isLoginMode; + // }); connect(phoneModeBtn, &QPushButton::clicked, this, [=]() { // 此处还可以把 isLoginMode 这个值传到新的窗口中, 让新的窗口决定自己是登录状态还是注册状态. 大家自行尝试实现. @@ -116,8 +121,99 @@ LoginWidget::LoginWidget(QWidget *parent) this->close(); }); - //connect(submitBtn, &QPushButton::clicked, this, &LoginWidget::clickSubmitBtn); - connect(submitBtn, &QPushButton::clicked, this, [=]() { + connect(submitBtn, &QPushButton::clicked, this, &LoginWidget::clickSubmitBtn); + /*connect(submitBtn, &QPushButton::clicked, this, [=]() { Toast::showMessage("Just a test message..."); - }); -} \ No newline at end of file + });*/ +} + +void LoginWidget::switchMode() +{ + if (isLoginMode) { + //切换到注册模式 + this->setWindowTitle("注册"); + titleLabel->setText("注册"); + submitBtn->setText("注册"); + switchModeBtn->setText("登录"); + } + else { + //切换到登录模式 + this->setWindowTitle("登录"); + titleLabel->setText("登录"); + submitBtn->setText("登录"); + switchModeBtn->setText("注册"); + } + isLoginMode = !isLoginMode; +} + +void LoginWidget::clickSubmitBtn() +{ + //先从输入框拿到必要的内容 + const QString& username = usernameEdit->text(); + const QString& password = passwordEdit->text(); + const QString& verifyCode = verifyCodeEdit->text(); + if (username.isEmpty()) { + Toast::showMessage("用户名不能为空"); + return; + } + else if (password.isEmpty()) { + Toast::showMessage("密码不能为空"); + return; + } + else if (verifyCode.isEmpty()) { + Toast::showMessage("验证码不能为空"); + return; + } + + //对比验证码是否正确 + if (!verifyCodeWidget->checkVerifyCode(verifyCode)) { + Toast::showMessage("验证码不正确"); + verifyCodeEdit->clear(); + return; + } + + //真正发送网络请求 + DataCenter* dataCenter = DataCenter::getInstance(); + if (isLoginMode) { + //登录 + connect(dataCenter, &DataCenter::userLoginDone, this, &LoginWidget::userLoginDone); + dataCenter->userLoginAsync(username, password); + } + else { + //注册 + connect(dataCenter, &DataCenter::userRegisterDone, this, &LoginWidget::userRegisterDone); + dataCenter->userRegisterAsync(username, password); + } +} + +void LoginWidget::userLoginDone(bool ok, const QString& reason) +{ + //此处区分登录是否成功 + //不成功反馈失败原因 + if (!ok) { + Toast::showMessage("登录失败:" + reason); + return; + } + + //登录成功,要跳转到主页面 + MainWidget* mainWidget = MainWidget::getInstance(); + mainWidget->show(); + + this->close(); +} + +void LoginWidget::userRegisterDone(bool ok, const QString& reason) +{ + if (!ok) { + Toast::showMessage("注册失败:" + reason); + return; + } + Toast::showMessage("注册成功!"); + + //切换到登录界面 + this->switchMode(); + + //输入框清空一下 + verifyCodeEdit->clear(); + verifyCodeWidget->refreshVerifyCode(); +} diff --git a/loginwidget.h b/loginwidget.h index 076419a..13c93b2 100644 --- a/loginwidget.h +++ b/loginwidget.h @@ -7,6 +7,7 @@ #include #include "phoneloginwidget.h" +#include "verifycodewidget.h" #include "toast.h" class LoginWidget : public QWidget @@ -16,11 +17,11 @@ class LoginWidget : public QWidget public: explicit LoginWidget(QWidget *parent); - /*void switchMode(); + void switchMode(); void clickSubmitBtn(); void userLoginDone(bool ok, const QString& reason); - void userRegisterDone(bool ok, const QString& reason);*/ + void userRegisterDone(bool ok, const QString& reason); private: bool isLoginMode = true; @@ -29,7 +30,7 @@ private: QLineEdit* usernameEdit; QLineEdit* passwordEdit; QLineEdit* verifyCodeEdit; - //VerifyCodeWidget* verifyCodeWidget; + VerifyCodeWidget* verifyCodeWidget; QPushButton* submitBtn; QPushButton* phoneModeBtn; diff --git a/main.cpp b/main.cpp index a0f5740..4cfa7e6 100644 --- a/main.cpp +++ b/main.cpp @@ -6,10 +6,25 @@ #include "loginwidget.h" #include "model/datacenter.h" +FILE* output = nullptr; + +void msgHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) { + (void)type; + (void)context; + const QByteArray& log = msg.toUtf8(); + fprintf(output, "%s\n", log.constData()); + fflush(output); +} + int main(int argc, char *argv[]) { QApplication a(argc, argv); +#if DEPOLY + output = fopen("./log.txt", "a"); + qInstallMessageHandler(msgHandler); +#endif + qputenv("QT_QPA_PLATFORM", "windows:darkmode=0"); LOG() << "Hello"; @@ -31,7 +46,7 @@ int main(int argc, char *argv[]) #if TEST_SKIP_LOGIN - QPalette palette; + QPalette palette; palette.setColor(QPalette::WindowText, Qt::black);// 窗口文字颜色 QApplication::setPalette(palette); @@ -39,6 +54,10 @@ int main(int argc, char *argv[]) MainWidget* w = MainWidget::getInstance(); w->show(); #else + QPalette palette; + palette.setColor(QPalette::WindowText, Qt::black);// 窗口文字颜色 + QApplication::setPalette(palette); + LoginWidget* loginWidget = new LoginWidget(nullptr); loginWidget->show(); #endif diff --git a/mainwidget.cpp b/mainwidget.cpp index cce311a..df1e201 100644 --- a/mainwidget.cpp +++ b/mainwidget.cpp @@ -39,7 +39,8 @@ MainWidget::MainWidget(QWidget *parent) initRightWindow(); //初始化信号槽 initSignalSlot(); - + //初始化websocket + initWebSocket(); } MainWidget::~MainWidget() @@ -246,14 +247,28 @@ void MainWidget::initSignalSlot() ///////////////////////////////////// connect(extraBtn, &QPushButton::clicked, this, [=]() { //判定当前的会话是单聊还是群聊 -#if TEST_GROUP_SESSION_DETAIL - bool isSingleChat = false; //要根据当前选中的实际的会话来确定 -#else - bool isSingleChat = true; //要根据当前选中的实际的会话来确定 -#endif - if (isSingleChat) { - //说明是单聊 - SessionDetailWidget* sessionDetailWidget = new SessionDetailWidget(this); +//#if TEST_GROUP_SESSION_DETAIL +// bool isSingleChat = false; //要根据当前选中的实际的会话来确定 +//#else +// bool isSingleChat = true; //要根据当前选中的实际的会话来确定 +//#endif + + //获取到当前会话的详细信息, 通过会话中的userId属性 + ChatSessionInfo* chatSessionInfo = dataCenter->findChatSessionById(dataCenter->getCurrentSessionId()); + if (chatSessionInfo == nullptr) { + LOG() << "当前会话不存在, 无法弹出会话详情对话框"; + return; + } + bool isSingleChat = chatSessionInfo->userId != ""; + + if (isSingleChat) { + //说明是单聊 + UserInfo* userInfo = dataCenter->findFriendById(chatSessionInfo->userId); + if (userInfo == nullptr) { + LOG() << "单聊会话对应的用户不存在,无法弹出会话详情窗口"; + return; + } + SessionDetailWidget* sessionDetailWidget = new SessionDetailWidget(this, *userInfo); sessionDetailWidget->exec(); } else { @@ -316,6 +331,94 @@ void MainWidget::initSignalSlot() UserInfo* myself = dataCenter->getMyselfsync(); userAvatar->setIcon(myself->avatar); }); + + ///////////////////////////////////// + // 处理删除好友 + ///////////////////////////////////// + connect(dataCenter, &DataCenter::deleteFriendDone, this, [=]() { + //更新会话列表和好友列表 + this->updateFriendList(); + this->updateChatSessionList(); + + LOG() << "删除好友完成"; + }); + + connect(dataCenter, &DataCenter::clearCurrentSession, this, [=]() { + sessionTitleLabel->setText(""); + messageShowArea->clear(); + dataCenter->setCurrentChatSessionId(""); + + LOG() << "清空当前会话完成"; + }); + + ///////////////////////////////////// + // 处理添加好友申请 + ///////////////////////////////////// + connect(dataCenter, &DataCenter::addFriendApplyDone, this, [=]() { + Toast::showMessage("好友申请已发送"); + }); + + ///////////////////////////////////// + // 处理添加好友申请的推送数据 + ///////////////////////////////////// + connect(dataCenter, &DataCenter::receiveFriendApplyDone, this, [=]() { + this->updateApplyList(); + Toast::showMessage("收到新的好友申请"); + }); + + ///////////////////////////////////// + // 处理同意好友申请 + ///////////////////////////////////// + connect(dataCenter, &DataCenter::acceptFriendApplyDone, this, [=]() { + this->updateApplyList(); + this->updateFriendList(); + Toast::showMessage("好友已经添加完成"); + }); + + ///////////////////////////////////// + // 处理好友申请结果的推送数据 + ///////////////////////////////////// + connect(dataCenter, &DataCenter::receiveFriendProcessDone, this, [=](const QString& nickname, bool agree) { + if (agree) { + //同意 + this->updateFriendList(); + Toast::showMessage(nickname + " 已经同意了你的好友申请"); + } + else { + Toast::showMessage(nickname + " 拒绝了你的好友申请"); + } + }); + + ///////////////////////////////////// + // 处理拒绝好友申请 + ///////////////////////////////////// + connect(dataCenter, &DataCenter::rejectFriendApplyDone, this, [=]() { + //需要更新好友申请列表,刚才拒绝的这一项是需要删除的 + this->updateApplyList(); + Toast::showMessage("好友申请已拒绝"); + }); + + ///////////////////////////////////// + // 处理创建群聊的响应信号 + ///////////////////////////////////// + connect(dataCenter, &DataCenter::createGroupChatSessionDone, this, [=]() { + Toast::showMessage("创建群聊会话请求已经发送成功"); + }); + + ///////////////////////////////////// + // 处理创建会话的推送数据 + ///////////////////////////////////// + connect(dataCenter, &DataCenter::receiveSessionCreateDone, this, [=]() { + this->updateChatSessionList(); + //通知用户入群 + Toast::showMessage("您已被拉入到新的群聊中"); + }); +} + +void MainWidget::initWebSocket() +{ + DataCenter* dataCenter = DataCenter::getInstance(); + dataCenter->initWebsocket(); } void MainWidget::switchTabToSession() diff --git a/mainwidget.h b/mainwidget.h index a288e56..23ad224 100644 --- a/mainwidget.h +++ b/mainwidget.h @@ -89,6 +89,7 @@ public: void initRightWindow(); void initSignalSlot(); + void initWebSocket(); void switchTabToSession(); void switchTabToFriend(); diff --git a/messageeditarea.cpp b/messageeditarea.cpp index 1d7e94e..9d25ea6 100644 --- a/messageeditarea.cpp +++ b/messageeditarea.cpp @@ -1,7 +1,7 @@ #include "messageeditarea.h" #include "mainwidget.h" - +#include "soundrecorder.h" #include "model/datacenter.h" #include "toast.h" using namespace model; @@ -182,6 +182,15 @@ MessageEditArea::MessageEditArea(QWidget *parent) vlayout->addWidget(sendTextButton, 0, Qt::AlignRight | Qt::AlignVCenter); + //添加录制中 + tipLabel = new QLabel(); + tipLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + tipLabel->setText("录音中..."); + tipLabel->setAlignment(Qt::AlignCenter); + tipLabel->setFont(QFont("微软雅黑", 24, 600)); + vlayout->addWidget(tipLabel); + tipLabel->hide(); + //统一初始化信号槽 initSignalSlot(); } @@ -192,6 +201,9 @@ void MessageEditArea::initSignalSlot() //关联“显示历史消息窗口”信号槽 connect(showHistoryBtn, &QPushButton::clicked, this, [=]() { + if (dataCenter->getCurrentSessionId().isEmpty()) { + return; + } HistoryMessageWidget* historyMessageWidget = new HistoryMessageWidget(this); historyMessageWidget->exec(); }); @@ -202,6 +214,18 @@ void MessageEditArea::initSignalSlot() //关联收到消息的信号槽 connect(dataCenter, &DataCenter::receiveMessageDone, this, &MessageEditArea::addOtherMessage); + + //关联发送图片 + connect(sendImageBtn, &QPushButton::clicked, this, &MessageEditArea::clickSendImageBtn); + + //关联发送文件 + connect(sendFileBtn, &QPushButton::clicked, this, &MessageEditArea::clickSendFileBtn); + + //关联发送语音 + connect(sendSpeechBtn, &QPushButton::pressed, this, &MessageEditArea::soundRecordPressed); + connect(sendSpeechBtn, &QPushButton::released, this, &MessageEditArea::soundRecordReleased); + SoundRecorder* soundRecorder = SoundRecorder::getInstance(); + connect(soundRecorder, &SoundRecorder::soundRecordDone, this, &MessageEditArea::soundSpeech); } void MessageEditArea::sendTextMessage() @@ -267,6 +291,112 @@ void MessageEditArea::addOtherMessage(const model::Message& message) Toast::showMessage("收到新消息!"); } +void MessageEditArea::clickSendImageBtn() +{ + DataCenter* dataCenter = DataCenter::getInstance(); + //判定当前是否有选中的会话 + if (dataCenter->getCurrentSessionId().isEmpty()) { + //没有选中会话 + LOG() << "当前尚未选中任何会话"; + return; + } + + //弹出文件对话框 + QString filter = "Image Files (*.png *.jpg *.jpeg *.mp4)"; + QString imagePath = QFileDialog::getOpenFileName(this, "选择图片", QDir::homePath(), filter); + if (imagePath.isEmpty()) { + LOG() << "用户取消选择"; + return; + } + + //读取图片内容 + QByteArray imageContent = loadFileToByteArray(imagePath); + + //发送请求 + dataCenter->sendImageMessageAsync(dataCenter->getCurrentSessionId(), imageContent); +} + +void MessageEditArea::clickSendFileBtn() +{ + DataCenter* dataCenter = DataCenter::getInstance(); + //判定当前是否有选中的会话 + if (dataCenter->getCurrentSessionId().isEmpty()) { + //没有选中会话 + LOG() << "当前尚未选中任何会话"; + return; + } + + //弹出对话框,选择文件 + QString filter = "*"; + QString path = QFileDialog::getOpenFileName(this, "选择文件", QDir::homePath(), filter); + if (path.isEmpty()) { + LOG() << "用户取消选择"; + return; + } + + //读取文件内容(暂时没有考虑非常大的文件) + //如果针对大文件,编写专门的网络通信接口,实现分片传输效果 + QByteArray content = loadFileToByteArray(path); + + //传输文件,还需要获取到文件名 + QFileInfo fileInfo(path); + const QString& filename = fileInfo.fileName(); + + //发送消息 + dataCenter->sendFileMessageAsync(dataCenter->getCurrentSessionId(), filename, content); +} + +void MessageEditArea::soundRecordPressed() +{ + DataCenter* dataCenter = DataCenter::getInstance(); + //判定当前是否有选中的会话 + if (dataCenter->getCurrentSessionId().isEmpty()) { + //没有选中会话 + LOG() << "当前尚未选中任何会话"; + return; + } + + //切换语音按钮的图标 + sendSpeechBtn->setIcon(QIcon(":/resource/image/sound_active.png")); + + //开始录音 + SoundRecorder* soundRecorder = SoundRecorder::getInstance(); + soundRecorder->startRecord(); + tipLabel->show(); + textEdit->hide(); +} + +void MessageEditArea::soundRecordReleased() +{ + DataCenter* dataCenter = DataCenter::getInstance(); + //判定当前是否有选中的会话 + if (dataCenter->getCurrentSessionId().isEmpty()) { + //没有选中会话 + LOG() << "当前尚未选中任何会话"; + return; + } + + //切换语音按钮的图标 + sendSpeechBtn->setIcon(QIcon(":/resource/image/sound.png")); + + //开始录音 + SoundRecorder* soundRecorder = SoundRecorder::getInstance(); + tipLabel->hide(); + textEdit->show(); + soundRecorder->stopRecord(); +} + +void MessageEditArea::soundSpeech(const QString& path) +{ + DataCenter* dataCenter = DataCenter::getInstance(); + QByteArray content = loadFileToByteArray(path); + if (content.isEmpty()) { + LOG() << "语音文件加载失败"; + return; + } + dataCenter->sendSpeechMessageAsync(dataCenter->getCurrentSessionId(), content); +} + //bool MessageEditArea::eventFilter(QObject* obj, QEvent* event) //{ diff --git a/messageeditarea.h b/messageeditarea.h index 7443a0c..41b302a 100644 --- a/messageeditarea.h +++ b/messageeditarea.h @@ -30,7 +30,13 @@ public: //花式按钮事件 //bool eventFilter(QObject* obj, QEvent* event) override; + void clickSendImageBtn(); + void clickSendFileBtn(); + void soundRecordPressed(); + void soundRecordReleased(); + + void soundSpeech(const QString& path); private: QPushButton* sendImageBtn; QPushButton* sendFileBtn; @@ -38,6 +44,7 @@ private: QPushButton* showHistoryBtn; QPlainTextEdit* textEdit; QPushButton* sendTextButton; + QLabel* tipLabel; //花式按钮 //QGraphicsDropShadowEffect* shadowEffect; diff --git a/messageshowarea.cpp b/messageshowarea.cpp index c405d20..41063d5 100644 --- a/messageshowarea.cpp +++ b/messageshowarea.cpp @@ -1,9 +1,13 @@ #include "messageshowarea.h" -#include "mainwidget.h" -#include "userinfowidget.h" +#include #include +#include "mainwidget.h" +#include "QFileDialog" +#include "toast.h" +#include "userinfowidget.h" #include "model/datacenter.h" +#include "soundrecorder.h" MessageShowArea::MessageShowArea() { //初始化基本属性 @@ -151,13 +155,13 @@ MessageItem *MessageItem::makeMessageItem(bool isLeft, const Message &message) contentWidget = makeTextMessageItem(isLeft, message.content); break; case model::IMAGE_TYPE: - contentWidget = makeImageMessageItem(); + contentWidget = makeImageMessageItem(isLeft, message.fileId, message.content); break; case model::FILE_TYPE: - contentWidget = makeFileMessageItem(); + contentWidget = makeFileMessageItem(isLeft, message); break; case model::SPEECH_TYPE: - contentWidget = makeSpeechMessageItem(); + contentWidget = makeSpeechMessageItem(isLeft, message); break; default: LOG() << "error messageType: " << message.messageType; @@ -193,32 +197,38 @@ MessageItem *MessageItem::makeMessageItem(bool isLeft, const Message &message) QWidget *MessageItem::makeTextMessageItem(bool isLeft, const QString &text) { - MessageContentLabel* messageContentLabel = new MessageContentLabel(text, isLeft); + MessageContentLabel* messageContentLabel = new MessageContentLabel(text, isLeft, TEXT_TYPE, "", QByteArray()); return messageContentLabel; } -QWidget *MessageItem::makeImageMessageItem() +QWidget *MessageItem::makeImageMessageItem(bool isLeft, const QString& fileId, const QByteArray& content) { - return nullptr; + MessageImageLabel* messageImageLabel = new MessageImageLabel(fileId, content, isLeft); + return messageImageLabel; } -QWidget *MessageItem::makeFileMessageItem() +QWidget *MessageItem::makeFileMessageItem(bool isLeft, const Message& message) { - return nullptr; + MessageContentLabel* messageContentLabel = new MessageContentLabel("[文件] " + message.fileName, isLeft, message.messageType, + message.fileId, message.content); + return messageContentLabel; } -QWidget *MessageItem::makeSpeechMessageItem() +QWidget *MessageItem::makeSpeechMessageItem(bool isLeft, const Message& message) { - return nullptr; + MessageContentLabel* messageContentLabel = new MessageContentLabel("[语音]", isLeft, message.messageType, message.fileId, message.content); + return messageContentLabel; } //////////////////////////////////////////// /// 创建类表示“文本消息”正文部分 +//这个类也表示文件消息 //////////////////////////////////////////// -MessageContentLabel::MessageContentLabel(const QString &text, bool isLeft) - :isLeft(isLeft) +MessageContentLabel::MessageContentLabel(const QString& text, bool isLeft, model::MessageType messageType, const QString& fileId, + const QByteArray& content) + :isLeft(isLeft), messageType(messageType), fileId(fileId), content(content) { this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -232,6 +242,21 @@ MessageContentLabel::MessageContentLabel(const QString &text, bool isLeft) this->label->setAlignment(Qt::AlignCenter | Qt::AlignLeft); this->label->setWordWrap(true); this->label->setStyleSheet("QLabel { padding: 0 10px; line-height: 1.2; background-color: transparent; }"); + + //针对文件消息,且content为空,通过网络加载数据 + if (messageType == model::TEXT_TYPE) { + return; + } + + if (this->content.isEmpty()) { + DataCenter* dataCenter = DataCenter::getInstance(); + connect(dataCenter, &DataCenter::getSingleFileDone, this, &MessageContentLabel::updateUI); + dataCenter->getSingleFileAsync(this->fileId); + } + else { + //content 不为空,说明这个数据已经是现成的,加载状态直接改为true即可 + this->loadContentDone = true; + } } //这个函数会在该控件被显示时,自动的调用到 @@ -307,3 +332,181 @@ void MessageContentLabel::paintEvent(QPaintEvent *event) //注意高度要涵盖之前的名字和时间的label的高度,以及留一点的冗余的空间 parent->setFixedHeight(height + 50); } + +void MessageContentLabel::mousePressEvent(QMouseEvent* event) +{ + //鼠标点击之后,触发文件另存为 + if (event->button() == Qt::LeftButton) { + //左键按下 + if (this->messageType == FILE_TYPE) { + //真正触发另存为 + if (!this->loadContentDone) { + Toast::showMessage("数据尚未加载成功,请稍后重试"); + return; + } + saveAsFile(this->content); + } + else if (this->messageType == SPEECH_TYPE) { + if (!this->loadContentDone) { + Toast::showMessage("数据尚未加载成功,请稍后重试"); + return; + } + SoundRecorder* soundRecorder = SoundRecorder::getInstance(); + this->label->setText("播放中..."); + connect(soundRecorder, &SoundRecorder::soundPlayDone, this, &MessageContentLabel::playDone, Qt::UniqueConnection); + soundRecorder->startPlay(this->content); + } + else { + //啥也不做 + } + } +} + +void MessageContentLabel::updateUI(const QString& fileId, const QByteArray& fileContent) +{ + if (fileId != this->fileId) { + return; + } + this->content = fileContent; + this->loadContentDone = true; + //从服务器拿到文件正文之前,界面内容就应该已经绘制好了,拿到正文之后,也不需要做出调整 + //所以,👇没有也行 + this->update(); +} + +void MessageContentLabel::saveAsFile(const QByteArray& content) +{ + //弹出对话框,让让用户选择路径 + QString filePath = QFileDialog::getOpenFileName(this, "另存为", QDir::homePath(), "*"); + if (filePath.isEmpty()) { + LOG() << "用户取消了文件另存为"; + return; + } + writeByteArrayToFile(filePath, content); +} + +void MessageContentLabel::playDone() +{ + if (this->label->text() == "播放中...") { + this->label->setText("[语音]"); + } +} + +void MessageContentLabel::contextMenuEvent(QContextMenuEvent* event) +{ + //LOG() << "触发上下文菜单"; + (void)event; + if (messageType != SPEECH_TYPE) { + LOG() << "非语音消息暂不支持右键菜单"; + return; + } + + QMenu* menu = new QMenu(this); + menu->setStyleSheet("QMenu { color: rgb(0, 0, 0); }"); + QAction* action = menu->addAction("语音转文字"); + connect(action, &QAction::triggered, this, [=]() { + DataCenter* dataCenter = DataCenter::getInstance(); + connect(dataCenter, &DataCenter::speechConvertTextDone, this, &MessageContentLabel::speechConverTextDone, Qt::UniqueConnection); + dataCenter->speechConvertTextAsync(this->fileId, this->content); + }); + //类似于模态对话框 + menu->exec(event->globalPos()); + delete menu; +} + +void MessageContentLabel::speechConverTextDone(const QString& fileId, const QString& text) +{ + if (this->fileId != fileId) { + return; + } + //修改界面内容 + this->label->setText("[语音转文字] " + text); + this->update(); +} + +//////////////////////////////////////////// +/// 创建类表示“图片消息”部分 +//////////////////////////////////////////// +MessageImageLabel::MessageImageLabel(const QString& fileId, const QByteArray& content, bool isLeft) + :fileId(fileId), content(content), isLeft(isLeft) +{ + this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + imageBtn = new QPushButton(this); + imageBtn->setStyleSheet("QPushButton { border: none; border-radius: 10px; }"); + + if (content.isEmpty()) { + //此处,从服务器拿到图片消息 + //拿着fileId,去服务器获取图片内容 + DataCenter* dataCenter = DataCenter::getInstance(); + connect(dataCenter, &DataCenter::getSingleFileDone, this, &MessageImageLabel::updateUI); + dataCenter->getSingleFileAsync(fileId); + } +} + +void MessageImageLabel::updateUI(const QString& fileId, const QByteArray& content) +{ + //由于是一呼百应,要判断fileId是否是当前的fileId + if (this->fileId != fileId) { + return; + } + //对上了,就真正显示图片内容 + this->content = content; + + //进行绘制图片到界面上 + this->update(); +} + +//真正进行绘制图片到界面上 +void MessageImageLabel::paintEvent(QPaintEvent* event) +{ + (void)event; + QObject* object = this->parent(); + if (!object->isWidgetType()) { + return; + } + QWidget* parent = dynamic_cast(object); + int width = parent->width() * 0.4; + + //加载二进制数据为图片对象 + QImage image; + if (content.isEmpty()) { + //说明此时响应的数据还没有回来, + //那么先拿默认图片临时代替 + QByteArray tmpContent = loadFileToByteArray(":resource/image/image.png"); + image.loadFromData(tmpContent); + } + else { + image.loadFromData(content); + } + + //针对图片进行缩放 + int height = 0; + if (image.width() > width) { + //说明图片过宽,把图片放缩(等比) + height = ((double)image.height() / image.width()) * width; + } + else { + //没有过阈值,不用管 + width = image.width(); + height = image.height(); + } + + //QImage不能直接转换为QIcon,需要QPixmap中转一下 + QPixmap pixmap = QPixmap::fromImage(image); + imageBtn->setFixedSize(width, height); + imageBtn->setIconSize(QSize(width, height)); + imageBtn->setIcon(QIcon(pixmap)); + + //为了容纳下上方名字部分,同时留下一点冗余 + parent->setFixedHeight(height + 50); + + //确定是左侧还是右侧消息 + if (isLeft) { + imageBtn->setGeometry(10, 0, width, height); + } + else { + int leftPos = this->width() - width - 10; + imageBtn->setGeometry(leftPos, 0, width, height); + } + } diff --git a/messageshowarea.h b/messageshowarea.h index 8b6e55d..9ce1222 100644 --- a/messageshowarea.h +++ b/messageshowarea.h @@ -56,9 +56,9 @@ public: //添加工厂函数 static QWidget* makeTextMessageItem(bool isLeft, const QString& message); - static QWidget* makeImageMessageItem(); - static QWidget* makeFileMessageItem(); - static QWidget* makeSpeechMessageItem(); + static QWidget* makeImageMessageItem(bool isLeft, const QString& fileId, const QByteArray& content); + static QWidget* makeFileMessageItem(bool isLeft, const Message& message); + static QWidget* makeSpeechMessageItem(bool isLeft, const Message& message); private: bool isLeft; @@ -66,16 +66,55 @@ private: //////////////////////////////////////////// /// 创建类表示“文本消息”正文部分 +//也让这个类表示文件消息 //////////////////////////////////////////// class MessageContentLabel : public QWidget { public: - MessageContentLabel(const QString& text, bool isLeft); + MessageContentLabel(const QString& text, bool isLeft, model::MessageType messageType, const QString& fileId, + const QByteArray& content); void paintEvent(QPaintEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + + void updateUI(const QString& fileId, const QByteArray& fileContent); + void saveAsFile(const QByteArray& content); + + void playDone(); + + void contextMenuEvent(QContextMenuEvent* event) override; + void speechConverTextDone(const QString& fileId, const QString& text); private: QLabel* label; bool isLeft; + + model::MessageType messageType; + QString fileId; + QByteArray content; + QString fileName; + + bool loadContentDone = false; +}; + +//////////////////////////////////////////// +/// 创建类表示“图片消息”部分 +//////////////////////////////////////////// +class MessageImageLabel : public QWidget { + Q_OBJECT + +public: + MessageImageLabel(const QString& fileId, const QByteArray& content, bool isLeft); + + void updateUI(const QString& fileId, const QByteArray& content); + + void paintEvent(QPaintEvent* event) override; + +private: + QPushButton* imageBtn; + + QString fileId; //该图片在服务器对应的文件id + QByteArray content; //图片的二进制数据 + bool isLeft; }; #endif // MESSAGESHOWAREA_H diff --git a/model/data.h b/model/data.h index 27e1f50..c199637 100644 --- a/model/data.h +++ b/model/data.h @@ -170,30 +170,30 @@ void load(const bite_im::MessageInfo& messageInfo) { } if (messageInfo.message().imageMessage().hasFileId()) { this->fileId = messageInfo.message().imageMessage().fileId(); - }else if (type == bite_im::MessageTypeGadget::MessageType::FILE) { - this->messageType = FILE_TYPE; - if (messageInfo.message().fileMessage().hasFileContents()) { - this->content = messageInfo.message().fileMessage().fileContents(); - } - if (messageInfo.message().fileMessage().hasFileId()) { - this->fileId = messageInfo.message().fileMessage().fileId(); - } - this->fileName = messageInfo.message().fileMessage().fileName(); } - else if (type == bite_im::MessageTypeGadget::MessageType::SPEECH) { - this->messageType = SPEECH_TYPE; - if (messageInfo.message().speechMessage().hasFileContents()) { - this->content = messageInfo.message().speechMessage().fileContents(); - } - if (messageInfo.message().speechMessage().hasFileId()) { - this->fileId = messageInfo.message().speechMessage().fileId(); - } + } + else if (type == bite_im::MessageTypeGadget::MessageType::FILE) { + this->messageType = FILE_TYPE; + if (messageInfo.message().fileMessage().hasFileContents()) { + this->content = messageInfo.message().fileMessage().fileContents(); } - else { - // 错误的类型, 啥都不做了, 只是打印一个日志 - LOG() << "非法的消息类型! type=" << type; + if (messageInfo.message().fileMessage().hasFileId()) { + this->fileId = messageInfo.message().fileMessage().fileId(); } - + this->fileName = messageInfo.message().fileMessage().fileName(); + } + else if (type == bite_im::MessageTypeGadget::MessageType::SPEECH) { + this->messageType = SPEECH_TYPE; + if (messageInfo.message().speechMessage().hasFileContents()) { + this->content = messageInfo.message().speechMessage().fileContents(); + } + if (messageInfo.message().speechMessage().hasFileId()) { + this->fileId = messageInfo.message().speechMessage().fileId(); + } + } + else { + // 错误的类型, 啥都不做了, 只是打印一个日志 + LOG() << "非法的消息类型! type=" << type; } } @@ -288,7 +288,7 @@ public: QString userId = ""; //(单聊为对方的id,群聊为"") - void load(bite_im::ChatSessionInfo& chatSessionInfo) { + void load(const bite_im::ChatSessionInfo& chatSessionInfo) { this->chatSessionId = chatSessionInfo.chatSessionId(); this->chatSessionName = chatSessionInfo.chatSessionName(); if (chatSessionInfo.hasSingleChatFriendId()) { diff --git a/model/datacenter.cpp b/model/datacenter.cpp index 7fd75d3..24466d3 100644 --- a/model/datacenter.cpp +++ b/model/datacenter.cpp @@ -163,6 +163,11 @@ namespace model return (*unreadMessageCount)[chatSessionId]; } + void DataCenter::initWebsocket() + { + netClient.initWebSocket(); + } + void DataCenter::getMyselfAsync() { //DataCenter 只是负责“处理数据”, @@ -294,6 +299,21 @@ namespace model netClient.sendMessage(loginSessionId, chatSessionId, MessageType::TEXT_TYPE, content.toUtf8(), ""); } + void DataCenter::sendImageMessageAsync(const QString& chatSessionId, const QByteArray& content) + { + netClient.sendMessage(loginSessionId, chatSessionId, MessageType::IMAGE_TYPE, content, ""); + } + + void DataCenter::sendFileMessageAsync(const QString& chatSessionId, const QString& fileName, const QByteArray& content) + { + netClient.sendMessage(loginSessionId, chatSessionId, MessageType::FILE_TYPE, content, fileName); + } + + void DataCenter::sendSpeechMessageAsync(const QString& chatSessionId, const QByteArray& content) + { + netClient.sendMessage(loginSessionId, chatSessionId, MessageType::SPEECH_TYPE, content, ""); + } + void DataCenter::changeNicknameAsync(const QString& nickname) { netClient.changeNickname(loginSessionId, nickname); @@ -350,6 +370,195 @@ namespace model myself->phone = email; } + void DataCenter::deleteFriendAsync(const QString& userId) + { + netClient.deleteFriend(loginSessionId, userId); + } + + void DataCenter::removeFriend(const QString& userId) + { + //遍历friendList 找到要删除的匹配的好友元素 + if (friendList == nullptr || chatSessionList == nullptr) { + return; + } + friendList->removeIf([=](const UserInfo& userInfo) { + //说明就是这个 + return userInfo.userId == userId; + }); + + //考虑到会话列表 + //删除会话操作,客户端和服务器都会删除 + chatSessionList->removeIf([=](const ChatSessionInfo& chatSessionInfo) { + if (chatSessionInfo.userId == "") { + //群聊,不受影响 + return false; + } + if (chatSessionInfo.userId == userId) { + //此处如果删除的会话正是用户正在选中的会话,此时就需要把当前选中会话的内容都清空 + if (chatSessionInfo.chatSessionId == this->currentChatSessionId) { + emit this->clearCurrentSession(); + } + return true; + } + return false; + }); + } + + void DataCenter::addFriendApplyAsync(const QString& userId) + { + netClient.addFriendApply(loginSessionId, userId); + } + + void DataCenter::acceptFriendApplyAsync(const QString& userId) + { + netClient.acceptFriendApply(loginSessionId, userId); + } + + UserInfo DataCenter::removeFromApplyList(const QString& userId) + { + if (applyList == nullptr) { + return UserInfo(); + } + + for (auto it = applyList->begin(); it != applyList->end(); ++it) { + if (it->userId == userId) { + //复制要删除的对象,进行返回 + UserInfo toDelete = *it; + applyList->erase(it); + return toDelete; + } + } + return UserInfo(); + } + + void DataCenter::rejectFriendApplyAsync(const QString& userId) + { + netClient.rejectFriendApply(loginSessionId, userId); + } + + void DataCenter::createGroupChatSessionAsync(const QList& userIdList) + { + netClient.createGroupChatSession(loginSessionId, userIdList); + } + + void DataCenter::getMemberListAsync(const QString& chatSessionId) + { + netClient.getMemberList(loginSessionId, chatSessionId); + } + + QList* DataCenter::getMemberList(const QString& chatSessionId) + { + if (!this->memberList->contains(chatSessionId)) return nullptr; + return &(*this->memberList)[chatSessionId]; + } + + void DataCenter::resetMemberList(const QString& chatSessionId, const QList& memberList) + { + //根据chatSessionId,这个key,得到对应的value(QList) + QList& currentMemberList = (*this->memberList)[chatSessionId]; + currentMemberList.clear(); + + for (const auto& m : memberList) { + UserInfo userInfo; + userInfo.load(m); + currentMemberList.push_back(userInfo); + } + } + + void DataCenter::searchUserAsync(const QString& searchKey) + { + netClient.searchUser(loginSessionId, searchKey); + } + + QList* DataCenter::getSearchUserResult() + { + return searchUserResult; + } + + void DataCenter::resetSearchUserResult(const QList& userList) + { + if (searchUserResult == nullptr) { + searchUserResult = new QList(); + } + searchUserResult->clear(); + + for (const auto& u : userList) { + UserInfo userInfo; + userInfo.load(u); + searchUserResult->push_back(userInfo); + } + } + + void DataCenter::searchMessageAsync(const QString& searchKey) + { + //搜索历史消息,根据会话来组织 + netClient.searchMessage(loginSessionId, this->currentChatSessionId, searchKey); + } + + void DataCenter::searchMessageByTimeAsync(const QDateTime& begTime, const QDateTime& endTime) + { + netClient.searchMessageByTime(loginSessionId, currentChatSessionId, begTime, endTime); + } + + QList* DataCenter::getSearchMessageReuslt() + { + return searchMessageResult; + } + + void DataCenter::resetSearchMessageResult(const QList& msgList) + { + if (this->searchMessageResult == nullptr) { + this->searchMessageResult = new QList(); + } + this->searchMessageResult->clear(); + + for (const auto& m : msgList) { + Message message; + message.load(m); + searchMessageResult->push_back(message); + } + } + + void DataCenter::userLoginAsync(const QString& username, const QString& password) + { + //登录操作,没有loginSessionId + //登录成功之后,服务器才会返回loginSessionId + netClient.userLogin(username, password); + } + + void DataCenter::resetLoginSessionId(const QString& loginSessionId) + { + this->loginSessionId = loginSessionId; + + //一旦会话id更改,就保存到硬盘上 + saveDataFile(); + } + + void DataCenter::userRegisterAsync(const QString& username, const QString& password) + { + netClient.userRegister(username, password); + } + + void DataCenter::phoneLoginAsync(const QString& phone, const QString& verifyCode) + { + netClient.phoneLogin(phone, this->currentVerifyCodeId, verifyCode); + } + + void DataCenter::phoneRegisterAsync(const QString& phone, const QString& verifyCode) + { + netClient.phoneRegister(phone, this->currentVerifyCodeId, verifyCode); + } + + void DataCenter::getSingleFileAsync(const QString& fileId) + { + netClient.getSingleFile(loginSessionId, fileId); + } + + void DataCenter::speechConvertTextAsync(const QString& fileId, const QByteArray& content) + { + netClient.speechConvertText(loginSessionId, fileId, content); + } + void DataCenter::changeAvatarAsync(const QByteArray& imageBytes) { netClient.changeAvatar(loginSessionId, imageBytes); diff --git a/model/datacenter.h b/model/datacenter.h index 43f26eb..f4d21d0 100644 --- a/model/datacenter.h +++ b/model/datacenter.h @@ -4,6 +4,7 @@ #include #include #include + //#include #include "data.h" @@ -104,6 +105,9 @@ namespace model //验证网络的连通性 void ping() { netClient.ping(); } + //针对netclient中的websocket进行初始化 + void initWebsocket(); + //通过网络获取到用户的个人信息 void getMyselfAsync(); @@ -133,6 +137,9 @@ namespace model //发送消息给服务器 void sendTextMessageAsync(const QString& chatSessionId, const QString& content); + void sendImageMessageAsync(const QString& chatSessionId, const QByteArray& content); + void sendFileMessageAsync(const QString& chatSessionId, const QString& fileName, const QByteArray& content); + void sendSpeechMessageAsync(const QString& chatSessionId, const QByteArray& content); //修改用户昵称 void changeNicknameAsync(const QString& nickname); @@ -157,6 +164,50 @@ namespace model void changeAvatarAsync(const QByteArray& imageBytes); void resetAvatar(const QByteArray& avatar); + //删除好友 + void deleteFriendAsync(const QString& userId); + void removeFriend(const QString& userId); + void addFriendApplyAsync(const QString& userId); + + //发送同意好友申请操作 + void acceptFriendApplyAsync(const QString& userId); + UserInfo removeFromApplyList(const QString& userId); + + //拒绝好友申请操作 + void rejectFriendApplyAsync(const QString& userId); + + //创建群聊 + void createGroupChatSessionAsync(const QList& userIdList); + + //获取会话成员列表 + void getMemberListAsync(const QString& chatSessionId); + QList* getMemberList(const QString& chatSessionId); + void resetMemberList(const QString& chatSessionId, const QList& memberList); + + //搜索用户 + void searchUserAsync(const QString& searchKey); + QList* getSearchUserResult(); + void resetSearchUserResult(const QList& userList); + + //搜索历史消息 + void searchMessageAsync(const QString& searchKey); + void searchMessageByTimeAsync(const QDateTime& begTime, const QDateTime& endTime); + QList* getSearchMessageReuslt(); + void resetSearchMessageResult(const QList& msgList); + + //登录注册 + void userLoginAsync(const QString& username, const QString& password); + void resetLoginSessionId(const QString& loginSessionId); + void userRegisterAsync(const QString& username, const QString& password); + void phoneLoginAsync(const QString& phone, const QString& verifyCode); + void phoneRegisterAsync(const QString& phone, const QString& verifyCode); + + //获取单个文件 + void getSingleFileAsync(const QString& fileId); + + //语音转文字 + void speechConvertTextAsync(const QString& fileId, const QByteArray& content); + /////////////////////////////////////////////////////////////////////////////////// ///辅助函数 /////////////////////////////////////////////////////////////////////////////// @@ -189,8 +240,26 @@ namespace model void receiveMessageDone(const Message& lastMessage); void changeNicknameDone(); void changeDescriptionDone(); - void getVerifyCodeDone(); + void getVerifyCodeDone(bool ok); void changePhoneDone(); void changeAvatarDone(); + void deleteFriendDone(); + void clearCurrentSession(); + void addFriendApplyDone(); + void receiveFriendApplyDone(); + void acceptFriendApplyDone(); + void rejectFriendApplyDone(); + void receiveFriendProcessDone(const QString& nickname, bool agree); + void createGroupChatSessionDone(); + void receiveSessionCreateDone(); + void getMemberListDone(const QString& chatSessionId); + void searchUserDone(); + void searchMessageDone(); + void userLoginDone(bool ok, const QString& reason); + void userRegisterDone(bool ok, const QString& reason); + void phoneLoginDone(bool ok, const QString& reason); + void phoneRegisterDone(bool ok, const QString& reason); + void getSingleFileDone(const QString& fileId, const QByteArray& fileContent); + void speechConvertTextDone(const QString& fileId, const QString& text); }; } //end namespace model diff --git a/network/netclient.cpp b/network/netclient.cpp index 1347b54..714cdaa 100644 --- a/network/netclient.cpp +++ b/network/netclient.cpp @@ -6,7 +6,8 @@ namespace network { NetClient::NetClient(model::DataCenter* dataCenter) :dataCenter(dataCenter) { - initWebSocket(); + //不应该在此处初始化websocket + //initWebSocket(); } void NetClient::ping() @@ -70,16 +71,27 @@ namespace network { handleWsMessage(message); } else if (notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::CHAT_SESSION_CREATE_NOTIFY) { - + //创建新的会话通知 + ChatSessionInfo chatSessionInfo; + chatSessionInfo.load(notifyMessage.newChatSessionInfo().chatSessionInfo()); + handleWsSessionCreate(chatSessionInfo); } else if (notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::FRIEND_ADD_APPLY_NOTIFY) { - + //添加好友申请通知 + UserInfo userInfo; + userInfo.load(notifyMessage.friendAddApply().userInfo()); + handleWsAddfriendApply(userInfo); } else if (notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::FRIEND_ADD_PROCESS_NOTIFY) { - + //添加好友申请的处理结果通知 + UserInfo userInfo; + userInfo.load(notifyMessage.friendProcessResult().userInfo()); + bool agree = notifyMessage.friendProcessResult().agree(); + handleWsAddFriendProcess(userInfo, agree); } else if (notifyMessage.notifyType() == bite_im::NotifyTypeGadget::NotifyType::FRIEND_REMOVE_NOTIFY) { - + const QString& userId = notifyMessage.friendRemove().userId(); + handleWsRemoveFriend(userId); } } @@ -99,6 +111,63 @@ namespace network { } } + void NetClient::handleWsRemoveFriend(const QString& userId) + { + //删除数据 DataCenter 好友列表的数据 + dataCenter->removeFriend(userId); + //通知界面变化,更新好友列表/会话列表 + emit dataCenter->deleteFriendDone(); + } + + void NetClient::handleWsAddfriendApply(const model::UserInfo& userInfo) + { + //DataCenter中有一个好友申请列表,需要把这个数据添加到好友申请的列表中 + QList* applyList = dataCenter->getApplyList(); + if (applyList == nullptr) { + LOG() << "客户端没有加载到好友申请的列表"; + return; + } + //把新的元素放到列表前面 + applyList->push_front(userInfo); + + //通知界面进行更新 + emit dataCenter->receiveFriendApplyDone(); + } + + void NetClient::handleWsAddFriendProcess(const model::UserInfo& userInfo, bool agree) + { + if (agree) { + //对方同意了你的好友申请 + QList* friendList = dataCenter->getFriendList(); + if (friendList == nullptr) { + LOG() << "客户端没有加载好友列表"; + return; + } + friendList->push_front(userInfo); + + //通知更新一下界面 + emit dataCenter->receiveFriendProcessDone(userInfo.nickname, agree); + } + else { + //对方未同意你的好友申请 + emit dataCenter->receiveFriendProcessDone(userInfo.nickname, agree); + } + } + + void NetClient::handleWsSessionCreate(const model::ChatSessionInfo& chatSessionInfo) + { + //把这个ChatSessionInfo添加到会话列表中即可 + QList* chatSessionList = dataCenter->getChatSessionList(); + if (chatSessionList == nullptr) { + LOG() << "客户端没有加载会话列表"; + return; + } + //新的元素添加到列表头部 + chatSessionList->push_front(chatSessionInfo); + //发送一个信号,通知界面更新 + emit dataCenter->receiveSessionCreateDone(); + } + void NetClient::sendAuth() { bite_im::ClientAuthenticationReq req; @@ -107,7 +176,6 @@ namespace network { QByteArray body = req.serialize(&serializer); websocketClient.sendBinaryMessage(body); LOG() << "[WS身份认证] requestId= " << req.requestId() << ",loginSessionId= " << req.sessionId(); - } QString NetClient::makeRequestId() @@ -525,6 +593,8 @@ namespace network { bool ok = false; QString reason; auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + //发送信号给调用者 + emit dataCenter->getVerifyCodeDone(ok); //判定响应是否正确 if (!ok) { LOG() << "获取验证码->处理出错 reason= " << reason; @@ -534,9 +604,6 @@ namespace network { //把得到的结果写入到DataCenter中 dataCenter->resetVerifyCodeId(pbResp->verifyCodeId()); - //发送信号给调用者 - emit dataCenter->getVerifyCodeDone(); - //打印日志 LOG() << "获取验证码->响应处理完毕 requestId= " << pbResp->requestId() << "Id= " << dataCenter->getVerifyCodeId(); }); @@ -619,4 +686,592 @@ namespace network { LOG() << "修改用户头像->响应处理完成 requestId= " << pbResp->requestId(); }); } + + void NetClient::deleteFriend(const QString& loginSessionId, const QString& userId) + { + //构造请求body + bite_im::FriendRemoveReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setSessionId(loginSessionId); + pbReq.setPeerId(userId); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "删除好友->发送请求 requestId= " << pbReq.requestId() << ", loginSessionId= " << pbReq.sessionId() + << ", userId= " << pbReq.peerId(); + + //发送HTTP请求 + QNetworkReply* resp = this->sendHttpRequest("/service/friend/remove_friend", body); + + //处理HTTP响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + //解析响应 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + //判定响应结果是否正确 + if (!ok) { + LOG() << "删除好友->处理出错 reason= " << reason; + return; + } + + //把结果写入到DataCenter中,把该用户从好友列表中删除掉 + dataCenter->removeFriend(userId); + + //发送信号,通知调用者当前好友删除完毕 + emit dataCenter->deleteFriendDone(); + + //打印日志 + LOG() << "删除好友->响应完成 requestId= " << pbResp->requestId(); + }); + } + + void NetClient::addFriendApply(const QString& loginSessionId, const QString& userId) + { + //构造请求body + bite_im::FriendAddReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setSessionId(loginSessionId); + pbReq.setRespondentId(userId); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "添加好友申请->发送请求 requestId= " << pbReq.requestId() << ", loginSessionId= " << pbReq.sessionId() + << ", userId= " << userId; + + //发送HTTP请求 + QNetworkReply* resp = this->sendHttpRequest("/service/friend/add_friend_apply", body); + + //处理响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + //解析响应 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + //判定响应是否正确 + if (!ok) { + LOG() << "添加好友申请->响应失败 reason= " << reason; + return; + } + + //记录结果到DataCenter,此处不需要记录任何数据 + + //发送信号,通知调用者 + emit dataCenter->addFriendApplyDone(); + + //打印日志 + LOG() << "添加好友申请->响应完毕 requestId= " << pbResp->requestId(); + }); + } + + void NetClient::acceptFriendApply(const QString& loginSessionId, const QString& userId) + { + //构造请求body + bite_im::FriendAddProcessReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setSessionId(loginSessionId); + pbReq.setAgree(true); + pbReq.setApplyUserId(userId); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "同意好友申请->发送请求 requestId= " << pbReq.requestId() << ", loginSessionId= " << pbReq.sessionId() + << ", userId= " << pbReq.applyUserId(); + + //发送HTTP请求 + QNetworkReply* resp = this->sendHttpRequest("/service/friend/add_friend_process", body); + + //处理HTTP响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + //解析响应 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + //判定响应结果是否正确 + if (!ok) { + LOG() << "同意好友申请->处理出错 reason= " << reason; + return; + } + + //此处做一个好友列表的更新 + //把好友从申请列表中删除掉 + //并将该好友添加到好友列表中 + UserInfo applyUser = dataCenter->removeFromApplyList(userId); + QList* friendList = dataCenter->getFriendList(); + friendList->push_front(applyUser); + + //发送信号,通知界面进行更新 + emit dataCenter->acceptFriendApplyDone(); + + //打印日志 + LOG() << "同意好友申请->相应完成 requestId= " << pbResp->requestId(); + }); + } + + void NetClient::rejectFriendApply(const QString& loginSessionId, const QString& userId) + { + //构造请求body + bite_im::FriendAddProcessReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setSessionId(loginSessionId); + pbReq.setAgree(false); + pbReq.setApplyUserId(userId); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "拒绝好友申请->发送请求 requestId= " << pbReq.requestId() << ", loginSessionId= " << pbReq.sessionId() + << ", userId= " << pbReq.applyUserId(); + + //发送HTTP请求 + QNetworkReply* resp = this->sendHttpRequest("/service/friend/add_friend_process", body); + + //处理HTTP响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + //解析响应 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + //判定响应结果是否正确 + if (!ok) { + LOG() << "拒绝好友申请->处理出错 reason= " << reason; + return; + } + + //把好友从申请列表中删除掉 + dataCenter->removeFromApplyList(userId); + + //发送信号,通知界面进行更新 + emit dataCenter->rejectFriendApplyDone(); + + //打印日志 + LOG() << "拒绝好友申请->相应完成 requestId= " << pbResp->requestId(); + }); + } + + void NetClient::createGroupChatSession(const QString& loginSessionId, const QList& userIdList) + { + //构造请求body + bite_im::ChatSessionCreateReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setSessionId(loginSessionId); + pbReq.setChatSessionName("新的群聊"); + pbReq.setMemberIdList(userIdList); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "创建群聊会话->发送请求 requestId= " << pbReq.requestId() << ", loginSessionId= " << loginSessionId + << ", userIdList= " << userIdList; + + //发送HTTP请求 + QNetworkReply* resp = this->sendHttpRequest("/service/friend/create_chat_session", body); + + //处理响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + //解析响应 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + //判断结果是否正确 + if (!ok) { + LOG() << "创建群聊会话->响应失败 reason= " << reason; + return; + } + + //这里无需更新DataCenter, 后续通过websocket的逻辑来更新即可 + + //通知调用者 + emit dataCenter->createGroupChatSessionDone(); + + //打印日志 + LOG() << "创建群聊会话->响应完成 requestId= " << pbResp->requestId(); + }); + } + + void NetClient::getMemberList(const QString& loginSessionId, const QString& chatSessionId) + { + //构造请求body + bite_im::GetChatSessionMemberReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setSessionId(loginSessionId); + pbReq.setChatSessionId(chatSessionId); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "获取会话成员列表->发送请求 requestId= " << pbReq.requestId() << ", loginSessionId= " << pbReq.sessionId() + << ", chatSessionId= " << pbReq.chatSessionId(); + + //发送HTTP请求 + QNetworkReply* resp = this->sendHttpRequest("/service/friend/get_chat_session_member", body); + + //处理响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + //解析响应 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + //判定响应结果是否正确 + if (!ok) { + LOG() << "获取会话成员列表->响应失败 reason= " << reason; + return; + } + + //把结果记录到DataCenter + dataCenter->resetMemberList(chatSessionId, pbResp->memberInfoList()); + + //发送信号 + emit dataCenter->getMemberListDone(chatSessionId); + + //打印日志 + LOG() << "获取会话成员列表->响应完成 requestId= " << pbResp->requestId(); + }); + } + + void NetClient::searchUser(const QString& loginSessionId, const QString& searchKey) + { + // 1. 构造请求 body + bite_im::FriendSearchReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setSessionId(loginSessionId); + pbReq.setSearchKey(searchKey); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "搜索用户->发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << loginSessionId + << ", searchKey=" << searchKey; + + // 2. 发送 HTTP 请求 + QNetworkReply* resp = this->sendHttpRequest("/service/friend/search_friend", body); + + // 3. 处理响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + // a) 解析响应 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + // b) 判定响应成功 + if (!ok) { + LOG() << "搜索用户->响应失败 reason=" << reason; + return; + } + + // c) 把得到的结果, 记录到 DataCenter + dataCenter->resetSearchUserResult(pbResp->userInfo()); + + // d) 发送信号, 通知调用者 + emit dataCenter->searchUserDone(); + + // e) 打印日志 + LOG() << "搜索用户->响应完成 requestId=" << pbResp->requestId(); + }); + + } + + void NetClient::searchMessage(const QString& loginSessionId, const QString& chatSessionId, const QString& searchKey) + { + // 1. 构造请求 body + bite_im::MsgSearchReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setSessionId(loginSessionId); + pbReq.setChatSessionId(chatSessionId); + pbReq.setSearchKey(searchKey); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "按关键词搜索历史消息->发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << pbReq.sessionId() + << ", chatSessionId=" << pbReq.chatSessionId() << ", searchKey=" << searchKey; + + // 2. 发送 HTTP 请求 + QNetworkReply* resp = this->sendHttpRequest("/service/message_storage/search_history", body); + + // 3. 处理响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + // a) 解析响应 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + // b) 判定响应是否正确 + if (!ok) { + LOG() << "按关键词搜索历史消息->响应失败! reason=" << reason; + return; + } + + // c) 把响应结果写入到 DataCenter + dataCenter->resetSearchMessageResult(pbResp->msgList()); + + // d) 发送信号 + emit dataCenter->searchMessageDone(); + + // e) 打印日志 + LOG() << "按关键词搜索历史消息->响应完成 requestId=" << pbResp->requestId(); + }); + } + + void NetClient::searchMessageByTime(const QString& loginSessionId, const QString& chatSessionId, const QDateTime& begTime, const QDateTime& endTime) + { + // 1. 构造请求 body + bite_im::GetHistoryMsgReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setSessionId(loginSessionId); + pbReq.setChatSessionId(chatSessionId); + pbReq.setStartTime(begTime.toSecsSinceEpoch()); + pbReq.setOverTime(endTime.toSecsSinceEpoch()); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "按时间搜索历史消息->发送请求 requestId=" << pbReq.requestId() << ", loginSessionId=" << pbReq.sessionId() + << ", chatSessionId=" << pbReq.chatSessionId() << ", begTime= " << begTime << ", endTime= " << endTime; + + // 2. 发送 HTTP 请求 + QNetworkReply* resp = this->sendHttpRequest("/service/message_storage/get_history", body); + + // 3. 处理响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + // a) 解析响应 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + // b) 判定响应是否正确 + if (!ok) { + LOG() << "按时间搜索历史消息->响应失败! reason=" << reason; + return; + } + + // c) 把响应结果写入到 DataCenter + dataCenter->resetSearchMessageResult(pbResp->msgList()); + + // d) 发送信号 + emit dataCenter->searchMessageDone(); + + // e) 打印日志 + LOG() << "按时间搜索历史消息->响应完成 requestId=" << pbResp->requestId(); + }); + } + + void NetClient::userLogin(const QString& username, const QString& password) + { + // 1. 构造请求 body + bite_im::UserLoginReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setNickname(username); + pbReq.setPassword(password); + pbReq.setVerifyCodeId(""); + pbReq.setVerifyCode(""); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "用户名登录->发送请求 requestId=" << pbReq.requestId() << ", username=" << pbReq.nickname() << ", password=" << pbReq.password(); + + // 2. 发送 HTTP 请求 + QNetworkReply* resp = this->sendHttpRequest("/service/user/username_login", body); + + // 3. 处理响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + // a) 解析响应内容 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + // b) 判定响应结果是否正确 + if (!ok) { + LOG() << "用户名登录->处理失败 reason=" << reason; + emit dataCenter->userLoginDone(false, reason); + return; + } + + // c) 记录一下当前返回的数据 + dataCenter->resetLoginSessionId(pbResp->loginSessionId()); + + // d) 发送信号, 通知调用者, 处理完毕了. + emit dataCenter->userLoginDone(true, ""); + + // e) 打印日志 + LOG() << "用户名登录->处理响应 requestId=" << pbResp->requestId(); + }); + + } + + void NetClient::userRegister(const QString& username, const QString& password) + { + // 1. 构造请求 body + bite_im::UserRegisterReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setNickname(username); + pbReq.setPassword(password); + pbReq.setVerifyCodeId(""); + pbReq.setVerifyCode(""); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "用户名注册->发送请求 requestId=" << pbReq.requestId() << ", username=" << pbReq.nickname() << ", password=" << pbReq.password(); + + // 2. 发送 HTTP 请求 + QNetworkReply* resp = this->sendHttpRequest("/service/user/username_register", body); + + // 3. 处理响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + // a) 解析响应 body + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + // b) 判定响应结果是否正确 + if (!ok) { + LOG() << "用户名注册->响应失败! reason=" << reason; + emit dataCenter->userRegisterDone(false, reason); + return; + } + + // c) 把返回的数据保存到 DataCenter 中 + // 对于注册来说, 不需要保存任何信息, 直接跳过这个环节. + + // d) 通知调用者响应处理完成 + emit dataCenter->userRegisterDone(true, ""); + + // e) 打印日志 + LOG() << "用户名注册->响应完成 requestId=" << pbResp->requestId(); + }); + + } + + void NetClient::phoneLogin(const QString& phone, const QString& verifyCodeId, const QString& verifyCode) + { + // 1. 构造请求 body + bite_im::PhoneLoginReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setPhoneNumber(phone); + pbReq.setVerifyCodeId(verifyCodeId); + pbReq.setVerifyCode(verifyCode); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "手机号登录->发送请求 requestId=" << pbReq.requestId() << ", phone=" << pbReq.phoneNumber() + << ", verifyCodeId=" << pbReq.verifyCodeId() << ", verifyCode=" << pbReq.verifyCode(); + + // 2. 发送 HTTP 请求 + QNetworkReply* resp = this->sendHttpRequest("/service/user/phone_login", body); + + // 3. 处理响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + // a) 解析响应 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + // b) 判定响应是否成功 + if (!ok) { + LOG() << "手机号登录->响应出错! reason=" << reason; + emit dataCenter->phoneLoginDone(false, reason); + return; + } + + // c) 把响应结果记录到 DataCenter + dataCenter->resetLoginSessionId(pbResp->loginSessionId()); + + // d) 发送信号 + emit dataCenter->phoneLoginDone(true, ""); + + // e) 打印日志 + LOG() << "手机号登录->响应完毕 requestId=" << pbResp->requestId(); + }); + + } + + void NetClient::phoneRegister(const QString& phone, const QString& verifyCodeId, const QString& verifyCode) + { + // 1. 构造请求 body + bite_im::PhoneRegisterReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setPhoneNumber(phone); + pbReq.setVerifyCodeId(verifyCodeId); + pbReq.setVerifyCode(verifyCode); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "手机号注册->发送请求 requestId=" << pbReq.requestId() << ", phone=" << pbReq.phoneNumber() + << ", verifyCodeId=" << pbReq.verifyCodeId() << ", verifyCode=" << pbReq.verifyCode(); + + // 2. 发送 HTTP 请求 + QNetworkReply* resp = this->sendHttpRequest("/service/user/phone_register", body); + + // 3. 处理响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + // a) 解析响应 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + // b) 判定响应是否成功 + if (!ok) { + LOG() << "手机号注册->响应失败! reason=" << reason; + emit dataCenter->phoneRegisterDone(false, reason); + return; + } + + // c) 让 DataCenter 记录结果, 注册操作不需要记录 + + // d) 发送信号 + emit dataCenter->phoneRegisterDone(true, ""); + + // e) 打印日志 + LOG() << "手机号注册->响应完成 requestId=" << pbResp->requestId(); + }); + + } + + void NetClient::getSingleFile(const QString& loginSessionId, const QString& fileId) + { + //构造请求body + bite_im::GetSingleFileReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setSessionId(loginSessionId); + pbReq.setFileId(fileId); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "获取文件内容->发送请求 requestId= " << pbReq.requestId() << ", fileId= " << fileId; + + //发送HTTP请求 + QNetworkReply* resp = this->sendHttpRequest("/service/file/get_single_file", body); + + //处理响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + //解析响应 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + //判定响应结果是否正确 + if (!ok) { + LOG() << "获取文件内容->响应失败 reason= " << reason; + return; + } + + //这里涉及的文件可能会很多,不使用DataCenter保存 + //直接通过信号把文件数据,投送到调用者位置 + + //发送信号 + emit dataCenter->getSingleFileDone(fileId, pbResp->fileData().fileContent()); + + //打印日志 + LOG() << "获取文件内容->响应完成 requestId= " << pbResp->requestId(); + }); + } + + void NetClient::speechConvertText(const QString& loginSessionId, const QString& fileId, const QByteArray& content) + { + // 1. 构造请求 body + bite_im::SpeechRecognitionReq pbReq; + pbReq.setRequestId(makeRequestId()); + pbReq.setSessionId(loginSessionId); + pbReq.setSpeechContent(content); + QByteArray body = pbReq.serialize(&serializer); + LOG() << "[语音转文字] 发送请求 requestId=" << pbReq.requestId() << ", loginSessonId=" << pbReq.sessionId(); + + // 2. 发送 HTTP 请求 + QNetworkReply* resp = this->sendHttpRequest("/service/speech/recognition", body); + + // 3. 处理响应 + connect(resp, &QNetworkReply::finished, this, [=]() { + // a) 解析响应 + bool ok = false; + QString reason; + auto pbResp = this->handleHttpResponse(resp, &ok, &reason); + + // b) 判定响应结果 + if (!ok) { + LOG() << "[语音转文字] 响应错误! reason=" << reason; + return; + } + + // c) 把结果写入到 DataCenter 中. 此处不打算通过 DataCenter 表示这里的语音识别结果. 直接通过 信号 通知结果即可. + + // d) 发送信号, 通知调用者 + emit dataCenter->speechConvertTextDone(fileId, pbResp->recognitionResult()); + + // e) 打印日志 + LOG() << "[语音转文字] 响应完成 requestId=" << pbResp->requestId(); + }); + + } } //end namespace network \ No newline at end of file diff --git a/network/netclient.h b/network/netclient.h index 9861de7..5796971 100644 --- a/network/netclient.h +++ b/network/netclient.h @@ -36,8 +36,13 @@ namespace network { //初始化websocket void initWebSocket(); + //针对websocket的处理 void handleWsResponse(const bite_im::NotifyMessage& notifyMessage); void handleWsMessage(const model::Message& message); + void handleWsRemoveFriend(const QString& userId); + void handleWsAddfriendApply(const model::UserInfo& userInfo); + void handleWsAddFriendProcess(const model::UserInfo& userInfo, bool agree); + void handleWsSessionCreate(const model::ChatSessionInfo& chatSessionInfo); //发送身份认证请求 void sendAuth(); @@ -95,6 +100,21 @@ namespace network { void getVerifyCode(const QString& email); void changeEmail(const QString& loginSessionId, const QString& email, const QString& verifyCodeId, const QString& verifyCode); void changeAvatar(const QString& loginSessionId, const QByteArray& avatar); + void deleteFriend(const QString& loginSessionId, const QString& userId); + void addFriendApply(const QString& loginSessionId, const QString& userId); + void acceptFriendApply(const QString& loginSessionId, const QString& userId); + void rejectFriendApply(const QString& loginSessionId, const QString& userId); + void createGroupChatSession(const QString& loginSessionId, const QList& userIdList); + void getMemberList(const QString& loginSessionId, const QString& chatSessionId); + void searchUser(const QString& loginSessionId, const QString& searchKey); + void searchMessage(const QString& loginSessionId, const QString& chatSessionId, const QString& searchKey); + void searchMessageByTime(const QString& loginSessionId, const QString& chatSessionId, const QDateTime& begTime, const QDateTime& endTime); + void userLogin(const QString& username, const QString& password); + void userRegister(const QString& username, const QString& password); + void phoneLogin(const QString& phone, const QString& verifyCodeId, const QString& verifyCode); + void phoneRegister(const QString& phone, const QString& verifyCodeId, const QString& verifyCode); + void getSingleFile(const QString& loginSessionId, const QString& fileId); + void speechConvertText(const QString& loginSessionId, const QString& fileId, const QByteArray& content); private: model::DataCenter* dataCenter; diff --git a/phoneloginwidget.cpp b/phoneloginwidget.cpp index 68b568d..bae3094 100644 --- a/phoneloginwidget.cpp +++ b/phoneloginwidget.cpp @@ -1,5 +1,11 @@ #include "phoneloginwidget.h" +#include "mainwidget.h" +#include "model/datacenter.h" +#include "toast.h" + +using namespace model; + PhoneLoginWidget::PhoneLoginWidget(QWidget *parent) : QWidget(parent) { @@ -27,7 +33,7 @@ PhoneLoginWidget::PhoneLoginWidget(QWidget *parent) // 4. 创建手机号输入框 QString editStyle = "QLineEdit { border: none; background-color: rgb(240, 240, 240); font-size: 20px; border-radius: 10px; padding-left: 5px;}"; phoneEdit = new QLineEdit(); - phoneEdit->setPlaceholderText("输入手机号"); + phoneEdit->setPlaceholderText("输入邮箱号"); phoneEdit->setFixedHeight(40); phoneEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); phoneEdit->setStyleSheet(editStyle); @@ -81,24 +87,24 @@ PhoneLoginWidget::PhoneLoginWidget(QWidget *parent) layout->addWidget(switchModeBtn, 4, 4, 1, 1); // 11. 添加信号槽 - //connect(switchModeBtn, &QPushButton::clicked, this, &PhoneLoginWidget::switchMode); - connect(switchModeBtn, &QPushButton::clicked, this, [=]() { - if (isLoginMode) { - //切换到注册模式 - this->setWindowTitle("注册"); - titleLabel->setText("注册"); - submitBtn->setText("注册"); - switchModeBtn->setText("登录"); - } - else { - //切换到登录模式 - this->setWindowTitle("登录"); - titleLabel->setText("登录"); - submitBtn->setText("登录"); - switchModeBtn->setText("注册"); - } - isLoginMode = !isLoginMode; - }); + connect(switchModeBtn, &QPushButton::clicked, this, &PhoneLoginWidget::switchMode); + //connect(switchModeBtn, &QPushButton::clicked, this, [=]() { + // if (isLoginMode) { + // //切换到注册模式 + // this->setWindowTitle("注册"); + // titleLabel->setText("注册"); + // submitBtn->setText("注册"); + // switchModeBtn->setText("登录"); + // } + // else { + // //切换到登录模式 + // this->setWindowTitle("登录"); + // titleLabel->setText("登录"); + // submitBtn->setText("登录"); + // switchModeBtn->setText("注册"); + // } + // isLoginMode = !isLoginMode; + // }); connect(userModeBtn, &QPushButton::clicked, this, [=]() { LoginWidget* loginWidget = new LoginWidget(nullptr); @@ -106,11 +112,139 @@ PhoneLoginWidget::PhoneLoginWidget(QWidget *parent) this->close(); }); - //connect(sendVerifyCodeBtn, &QPushButton::clicked, this, &PhoneLoginWidget::sendVerifyCode); + connect(sendVerifyCodeBtn, &QPushButton::clicked, this, &PhoneLoginWidget::sendVerifyCode); - //timer = new QTimer(this); - //connect(timer, &QTimer::timeout, this, &PhoneLoginWidget::countDown); + timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &PhoneLoginWidget::countDown); - //connect(submitBtn, &QPushButton::clicked, this, &PhoneLoginWidget::clickSubmitBtn); + connect(submitBtn, &QPushButton::clicked, this, &PhoneLoginWidget::clickSubmitBtn); } + +void PhoneLoginWidget::sendVerifyCode() +{ + sendVerifyCodeBtn->setEnabled(false); + sendVerifyCodeBtn->setStyleSheet("QPushButton { color: rgb(200, 200, 200); }"); + //获取到手机验证码 + const QString& phone = this->phoneEdit->text(); + if (phone.isEmpty()) { + return; + } + + this->currentPhone = phone; + + //发送网络请求,获取验证吗 + DataCenter* dataCenter = DataCenter::getInstance(); + connect(dataCenter, &DataCenter::getVerifyCodeDone, this, &PhoneLoginWidget::sendVerifyCodeDone, Qt::AutoConnection); + dataCenter->getVerifyCodeAsync(phone); + + //开启定时器,开始倒计时 + timer->start(1000); +} + +void PhoneLoginWidget::sendVerifyCodeDone(bool ok) +{ + if (!ok) { + Toast::showMessage("邮箱格式错误"); + sendVerifyCodeBtn->setEnabled(true); + sendVerifyCodeBtn->setText("发送验证码"); + timer->stop(); + sendVerifyCodeBtn->setStyleSheet("QPushButton { color: rgb(0, 0, 0); }"); + } + else { + Toast::showMessage("验证码已发送"); + } +} + +void PhoneLoginWidget::clickSubmitBtn() +{ + //从输入框中拿到必要的内容 + const QString& phone = phoneEdit->text(); + const QString& verifyCode = verifyCodeEdit->text(); + if (phone.isEmpty() || verifyCode.isEmpty()) { + Toast::showMessage("电话或验证码不应该为空"); + return; + } + + //发送请求 + DataCenter* dataCenter = DataCenter::getInstance(); + if (isLoginMode) { + //登录 + connect(dataCenter, &DataCenter::phoneLoginDone, this, &PhoneLoginWidget::phoneLoginDone, Qt::AutoConnection); + dataCenter->phoneLoginAsync(phone, verifyCode); + } + else { + //注册 + connect(dataCenter, &DataCenter::phoneRegisterDone, this, &PhoneLoginWidget::phoneRegisterDone, Qt::UniqueConnection); + dataCenter->phoneRegisterAsync(phone, verifyCode); + } +} + +void PhoneLoginWidget::phoneLoginDone(bool ok, const QString& reason) +{ + if (!ok) { + Toast::showMessage("登录失败:" + reason); + return; + } + //跳转到主窗口 + MainWidget* mainWidget = MainWidget::getInstance(); + mainWidget->show(); + + //关闭自己 + this->close(); +} + +void PhoneLoginWidget::phoneRegisterDone(bool ok, const QString& reason) +{ + if (!ok) { + Toast::showMessage("注册失败:" + reason); + return; + } + Toast::showMessage("注册成功"); + + //跳转到登录页面 + switchMode(); + + //清空一下输入框 + verifyCodeEdit->clear(); + + //处理一下倒计时按钮 + leftTime = 1; +} + +void PhoneLoginWidget::countDown() +{ + if (leftTime <= 1) { + //时间到了,发送按钮设为可用,并停止定时器 + sendVerifyCodeBtn->setEnabled(true); + sendVerifyCodeBtn->setText("发送验证码"); + timer->stop(); + sendVerifyCodeBtn->setStyleSheet("QPushButton { color: rgb(0, 0, 0); }"); + return; + } + + leftTime -= 1; + sendVerifyCodeBtn->setText(QString::number(leftTime) + " s"); + if (sendVerifyCodeBtn->isEnabled()) { + sendVerifyCodeBtn->setEnabled(false); + } +} + +void PhoneLoginWidget::switchMode() +{ + if (isLoginMode) { + //切换到注册模式 + this->setWindowTitle("注册"); + titleLabel->setText("注册"); + submitBtn->setText("注册"); + switchModeBtn->setText("登录"); + } + else { + //切换到登录模式 + this->setWindowTitle("登录"); + titleLabel->setText("登录"); + submitBtn->setText("登录"); + switchModeBtn->setText("注册"); + } + isLoginMode = !isLoginMode; +} \ No newline at end of file diff --git a/phoneloginwidget.h b/phoneloginwidget.h index 04ff1c1..7cf2fab 100644 --- a/phoneloginwidget.h +++ b/phoneloginwidget.h @@ -15,15 +15,15 @@ class PhoneLoginWidget : public QWidget public: PhoneLoginWidget(QWidget *parent); - //void sendVerifyCode(); - //void sendVerifyCodeDone(); + void sendVerifyCode(); + void sendVerifyCodeDone(bool ok); - //void clickSubmitBtn(); - //void phoneLoginDone(bool ok, const QString& reason); - //void phoneRegisterDone(bool ok, const QString& reason); + void clickSubmitBtn(); + void phoneLoginDone(bool ok, const QString& reason); + void phoneRegisterDone(bool ok, const QString& reason); - //void countDown(); - //void switchMode(); + void countDown(); + void switchMode(); private: QLineEdit* phoneEdit; diff --git a/sessiondetailwidget.cpp b/sessiondetailwidget.cpp index 9451b2a..7db59af 100644 --- a/sessiondetailwidget.cpp +++ b/sessiondetailwidget.cpp @@ -1,5 +1,8 @@ #include "sessiondetailwidget.h" +#include "model/datacenter.h" + +using namespace model; //右上角详情的会话人员列表 AvatarItem::AvatarItem(const QIcon& avatar, const QString& name) @@ -48,9 +51,9 @@ AvatarItem::AvatarItem(const QIcon& avatar, const QString& name) } -SessionDetailWidget::SessionDetailWidget(QWidget *parent) - //:userInfo(userInfo), - : QDialog(parent) +SessionDetailWidget::SessionDetailWidget(QWidget *parent, const UserInfo& userInfo) + :userInfo(userInfo), + QDialog(parent) { // 1. 设置基本属性 this->setWindowTitle("会话详情"); @@ -74,8 +77,8 @@ SessionDetailWidget::SessionDetailWidget(QWidget *parent) AvatarItem* currentUser = new AvatarItem(QIcon(":/resource/image/defaultAvatar.png"), "张三123456"); layout->addWidget(currentUser, 0, 1); #endif - //AvatarItem* currentUser = new AvatarItem(userInfo.avatar, userInfo.nickname); - //layout->addWidget(currentUser, 0, 1); + AvatarItem* currentUser = new AvatarItem(userInfo.avatar, userInfo.nickname); + layout->addWidget(currentUser, 0, 1); // 5. 添加 "删除好友" 按钮 deleteFriendBtn = new QPushButton(); @@ -88,10 +91,36 @@ SessionDetailWidget::SessionDetailWidget(QWidget *parent) // 6. 添加信号槽, 处理点击 "创建群聊" 按钮 connect(createGroupBtn->getAvatar(), &QPushButton::clicked, this, [=]() { - ChooseFriendDialog* chooseFriendDialog = new ChooseFriendDialog(this); + ChooseFriendDialog* chooseFriendDialog = new ChooseFriendDialog(this, userInfo.userId); chooseFriendDialog->exec(); }); - //connect(deleteFriendBtn, &QPushButton::clicked, this, &SessionDetailWidget::clickDeleteFriendBtn); + connect(deleteFriendBtn, &QPushButton::clicked, this, &SessionDetailWidget::clickDeleteFriendBtn); +} + +void SessionDetailWidget::clickDeleteFriendBtn() { + //弹出一个对话框让用户确认是否真的删除 + //auto result = QMessageBox::warning(this, "确认删除", "确认删除该好友?", QMessageBox::Ok | QMessageBox::Cancel); + QMessageBox msgBox; + msgBox.setWindowTitle("确认删除"); + msgBox.setText("确认删除该好友?"); + msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + //msgBox.setStyleSheet("QPushButton { color: black; background-color: rgb(255, 255, 255); }"); + msgBox.setStyleSheet("QMessageBox { background-color: white; } " + "QLabel { color: black; } " + "QPushButton { color: black; background-color: #E0E0E0; }"); + int result = msgBox.exec(); + + if (result != QMessageBox::Ok) { + LOG() << "用户取消了好友删除"; + return; + } + + //发送好友删除请求 + DataCenter* dataCenter = DataCenter::getInstance(); + dataCenter->deleteFriendAsync(this->userInfo.userId); + + //关闭当前窗口 + this->close(); } \ No newline at end of file diff --git a/sessiondetailwidget.h b/sessiondetailwidget.h index a2f99b0..0fde2a6 100644 --- a/sessiondetailwidget.h +++ b/sessiondetailwidget.h @@ -40,10 +40,12 @@ class SessionDetailWidget : public QDialog Q_OBJECT public: - SessionDetailWidget(QWidget *parent); + SessionDetailWidget(QWidget *parent, const UserInfo& userInfo); + + void clickDeleteFriendBtn(); private: - /*UserInfo userInfo;*/ + UserInfo userInfo; QPushButton* deleteFriendBtn; }; diff --git a/sessionfriendarea.cpp b/sessionfriendarea.cpp index 329829a..7aca78b 100644 --- a/sessionfriendarea.cpp +++ b/sessionfriendarea.cpp @@ -126,7 +126,7 @@ SessionFriendItem::SessionFriendItem(QWidget* owner, const QIcon& avatar, const //添加到网格布局 layout->addWidget(avatarBtn, 0, 0, 2, 2); - layout->addWidget(nameLabel, 0, 2, 1, 25); + layout->addWidget(nameLabel, 0, 2, 1, 20); layout->addWidget(messageLabel, 1, 2, 1, 25); } @@ -390,16 +390,35 @@ ApplyItem::ApplyItem(QWidget *owner, const QString &userId, const QIcon &avatar, //创建两个按钮 QPushButton* acceptBtn = new QPushButton(); - acceptBtn->setStyleSheet("QPushButton {color: rgb(0, 0, 0); } "); + acceptBtn->setStyleSheet("QPushButton {color: rgb(255, 255, 255); background-color: rgb(7, 193, 96); border: none; border-radius: 5px; } "); acceptBtn->setText("同意"); + acceptBtn->setFixedSize(40, 20); QPushButton* rejectBtn = new QPushButton(); - rejectBtn->setStyleSheet("QPushButton {color: rgb(0, 0, 0); } "); + rejectBtn->setStyleSheet("QPushButton {color: rgb(250, 81, 81); background-color: rgb(190, 190, 190); border: none; border-radius: 5px; } "); rejectBtn->setText("拒绝"); + rejectBtn->setFixedSize(40, 20); //添加到布局管理器中 - layout->addWidget(acceptBtn, 1, 2, 1, 3); - layout->addWidget(rejectBtn, 1, 5, 1, 3); + layout->addWidget(acceptBtn, 1, 2, 1, 4); + layout->addWidget(rejectBtn, 1, 6, 1, 4); + //添加信号槽 + connect(acceptBtn, &QPushButton::clicked, this, &ApplyItem::acceptFriendApply); + connect(rejectBtn, &QPushButton::clicked, this, &ApplyItem::rejectFriendApply); +} + +void ApplyItem::acceptFriendApply() +{ + //发送网络请求,告知服务器,同意了好友的申请 + DataCenter* dataCenter = DataCenter::getInstance(); + //看同意了谁的好友申请 + dataCenter->acceptFriendApplyAsync(this->userId); +} + +void ApplyItem::rejectFriendApply() +{ + DataCenter* dataCenter = DataCenter::getInstance(); + dataCenter->rejectFriendApplyAsync(this->userId); } void ApplyItem::active() diff --git a/sessionfriendarea.h b/sessionfriendarea.h index ea9bbd1..ac59cc2 100644 --- a/sessionfriendarea.h +++ b/sessionfriendarea.h @@ -129,6 +129,9 @@ class ApplyItem : public SessionFriendItem { public: ApplyItem(QWidget* owner, const QString& userId, const QIcon& avatar, const QString& name); + void acceptFriendApply(); + void rejectFriendApply(); + void active() override; private: //好友申请Id diff --git a/soundrecorder.cpp b/soundrecorder.cpp new file mode 100644 index 0000000..ff38ebe --- /dev/null +++ b/soundrecorder.cpp @@ -0,0 +1,131 @@ +#include "soundrecorder.h" +#include +#include + +#include "model/data.h" +#include "toast.h" + +///////////////////////////////////////////// +/// 单例模式 +///////////////////////////////////////////// +SoundRecorder* SoundRecorder::instance = nullptr; + +SoundRecorder *SoundRecorder::getInstance() +{ + if (instance == nullptr) { + instance = new SoundRecorder(); + } + return instance; +} + +// 播放参考 https://www.cnblogs.com/tony-yang-flutter/p/16477212.html +// 录制参考 https://doc.qt.io/qt-6/qaudiosource.html +SoundRecorder::SoundRecorder(QObject *parent) + : QObject{parent} +{ + // 1. 创建目录 + QDir soundRootPath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); + soundRootPath.mkdir("sound"); + + // 2. 初始化录制模块 + soundFile.setFileName(RECORD_PATH); + + QAudioFormat inputFormat; + inputFormat.setSampleRate(16000); + inputFormat.setChannelCount(1); + inputFormat.setSampleFormat(QAudioFormat::Int16); + + QAudioDevice info = QMediaDevices::defaultAudioInput(); + if (!info.isFormatSupported(inputFormat)) { + //LOG() << "录制设备, 格式不支持!"; + LOG() << "指定格式不支持,尝试使用设备的最佳格式进行录音"; + inputFormat = info.preferredFormat(); + + QAudioFormat preferredFormat = inputFormat; + // 打印查看详细的格式信息 + //LOG() << "设备首选格式详情:"; + //LOG() << " 采样率 (SampleRate):" << preferredFormat.sampleRate() << "Hz";48000hz + //LOG() << " 声道数 (ChannelCount):" << preferredFormat.channelCount(); 2 + //LOG() << " 样本格式 (SampleFormat):" << preferredFormat.sampleFormat(); FLOAT + + // 确保至少是单声道 + /*if (inputFormat.channelCount() > 1) { + inputFormat.setChannelCount(1); + }*/ + //return; + } + + audioSource = new QAudioSource(inputFormat, this); + connect(audioSource, &QAudioSource::stateChanged, this, [=](QtAudio::State state) { + if (state == QtAudio::StoppedState) { + // 录制完毕 + if (audioSource->error() != QAudio::NoError) { + LOG() << audioSource->error(); + } + } + }); + + // 3. 初始化播放模块 + outputDevices = new QMediaDevices(this); + outputDevice = outputDevices->defaultAudioOutput(); + QAudioFormat outputFormat; + outputFormat.setSampleRate(16000); + outputFormat.setChannelCount(1); + outputFormat.setSampleFormat(QAudioFormat::Int16); + if (!outputDevice.isFormatSupported(outputFormat)) { + //LOG() << "播放设备, 格式不支持"; + LOG() << "指定格式不支持,尝试使用设备的最佳格式进行播放"; + outputFormat = outputDevice.preferredFormat(); + // 确保至少是单声道 + /*if (outputFormat.channelCount() > 1) { + outputFormat.setChannelCount(1); + }*/ + //return; + } + audioSink = new QAudioSink(outputDevice, outputFormat); + + connect(audioSink, &QAudioSink::stateChanged, this, [=](QtAudio::State state) { + if (state == QtAudio::IdleState) { + LOG() << "IdleState"; + this->stopPlay(); + emit this->soundPlayDone(); + } else if (state == QAudio::ActiveState) { + LOG() << "ActiveState"; + } else if (state == QAudio::StoppedState) { + LOG() << "StoppedState"; + if (audioSink->error() != QtAudio::NoError) { + LOG() << audioSink->error(); + } + } + }); +} + +void SoundRecorder::startRecord() { + soundFile.open( QIODevice::WriteOnly | QIODevice::Truncate ); + audioSource->start(&soundFile); +} + +void SoundRecorder::stopRecord() { + audioSource->stop(); + soundFile.close(); + emit this->soundRecordDone(RECORD_PATH); +} + +void SoundRecorder::startPlay(const QByteArray& content) { + if (content.isEmpty()) { + Toast::showMessage("数据加载中, 请稍后播放"); + return; + } + // 1. 把数据写入到临时文件 + model::writeByteArrayToFile(PLAY_PATH, content); + + // 2. 播放语音 + inputFile.setFileName(PLAY_PATH); + inputFile.open(QIODevice::ReadOnly); + audioSink->start(&inputFile); +} + +void SoundRecorder::stopPlay() { + audioSink->stop(); + inputFile.close(); +} diff --git a/soundrecorder.h b/soundrecorder.h new file mode 100644 index 0000000..f5ecc29 --- /dev/null +++ b/soundrecorder.h @@ -0,0 +1,59 @@ +#ifndef SOUNDRECORDER_H +#define SOUNDRECORDER_H + +#include +#include +#include +#include +#include +#include + +class SoundRecorder : public QObject +{ + Q_OBJECT +public: + const QString RECORD_PATH = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/sound/tmpRecord.pcm"; + const QString PLAY_PATH = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/sound/tmpPlay.pcm"; + +public: + static SoundRecorder* getInstance(); + + ///////////////////////////////////////////////// + /// 录制语音语音 + ///////////////////////////////////////////////// + // 开始录制 + void startRecord(); + // 停止录制 + void stopRecord(); + +private: + static SoundRecorder* instance; + explicit SoundRecorder(QObject *parent = nullptr); + + QFile soundFile; + QAudioSource* audioSource; + + ///////////////////////////////////////////////// + /// 播放语音 + ///////////////////////////////////////////////// +public: + // 开始播放 + void startPlay(const QByteArray& content); + // 停止播放 + void stopPlay(); + +private: + QAudioSink *audioSink; + QMediaDevices *outputDevices; + QAudioDevice outputDevice; + QFile inputFile; + +signals: + // 录制完毕后发送这个信号 + void soundRecordDone(const QString& path); + // 播放完毕发送这个信号 + void soundPlayDone(); + +}; + +#endif // SOUNDRECORDER_H diff --git a/userinfowidget.cpp b/userinfowidget.cpp index 0018e6d..d38d139 100644 --- a/userinfowidget.cpp +++ b/userinfowidget.cpp @@ -144,4 +144,36 @@ void UserInfoWidget::initSingleSlot() //把当前模态窗口关闭 this->close(); }); + + connect(deleteFriendBtn, &QPushButton::clicked, this, &UserInfoWidget::clickDeleteFriendBtn); + connect(applyBtn, &QPushButton::clicked, this, &UserInfoWidget::clickApplyBtn); +} + +void UserInfoWidget::clickDeleteFriendBtn() +{ + //弹出对话框,让用户确认是否删除好友 + auto result = QMessageBox::warning(this, "删除好友", "确认要删除好友吗?", QMessageBox::Ok | QMessageBox::Cancel); + if (result != QMessageBox::Ok) + { + LOG() << "删除好友取消"; + return; + } + + //发送网络请求,实现删除好友功能 + DataCenter* dataCenter = DataCenter::getInstance(); + //由于此处信号的处理,都是由主窗口这里进行的 + dataCenter->deleteFriendAsync(userInfo.userId); + + //关闭本窗口 + this->close(); +} + +void UserInfoWidget::clickApplyBtn() +{ + //发送好友申请 + DataCenter* dataCenter = DataCenter::getInstance(); + dataCenter->addFriendApplyAsync(userInfo.userId); + + //关闭窗口 + this->close(); } diff --git a/userinfowidget.h b/userinfowidget.h index 086d629..5781a56 100644 --- a/userinfowidget.h +++ b/userinfowidget.h @@ -22,6 +22,9 @@ public: UserInfoWidget(const UserInfo& userInfo, QWidget *parent); void initSingleSlot(); + void clickDeleteFriendBtn(); + void clickApplyBtn(); + private: ////保存对应的Message对象,暂时先放在这里 //Message message; diff --git a/verifycodewidget.cpp b/verifycodewidget.cpp new file mode 100644 index 0000000..a8ba50b --- /dev/null +++ b/verifycodewidget.cpp @@ -0,0 +1,124 @@ +#include "verifycodewidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "model/data.h" + +VerifyCodeWidget::VerifyCodeWidget(QWidget *parent) + : QWidget(parent), + randomGenerator(model::getTime()) +{ + verifyCode = generateVerifyCode(); +} + +QString VerifyCodeWidget::generateVerifyCode() +{ + QString code; + for (int i = 0; i < 4; i++) { + //每次循环生成一个字符 + int init = 'A'; + init += randomGenerator.generate() % 26; + code += static_cast(init); + } + return code; +} + +void VerifyCodeWidget::refreshVerifyCode() +{ + verifyCode = generateVerifyCode(); + // 通过 update 就可以起到 "刷新界面" , 本身就是触发 paintEvent + this->update(); +} + +bool VerifyCodeWidget::checkVerifyCode(const QString& verifyCode) +{ + //此处比较验证码需要忽略大小写 + return this->verifyCode.compare(verifyCode, Qt::CaseInsensitive) == 0; +} + +void VerifyCodeWidget::paintEvent(QPaintEvent* event) +{ + (void)event; + const int width = 180; + const int height = 80; + + QPainter painter(this); + QPen pen; + QFont font("楷体", 25, QFont::Bold, true); + painter.setFont(font); + + // 画点: 添加随机噪点 + for (int i = 0; i < 100; i++) + { + pen = QPen(QColor(randomGenerator.generate() % 256, randomGenerator.generate() % 256, randomGenerator.generate() % 256)); + painter.setPen(pen); + painter.drawPoint(randomGenerator.generate() % width, randomGenerator.generate() % height); + } + + // 画线: 添加随机干扰线 + for (int i = 0; i < 5; i++) + { + pen = QPen(QColor(randomGenerator.generate() % 256, randomGenerator.generate() % 256, randomGenerator.generate() % 256)); + painter.setPen(pen); + painter.drawLine(randomGenerator.generate() % width, randomGenerator.generate() % height, + randomGenerator.generate() % width, randomGenerator.generate() % height); + } + + // 绘制验证码 + for (int i = 0; i < verifyCode.size(); i++) + { + // 随机字体大小 + int fontSize = 13 + randomGenerator.generate() % 16; // 20~35 + QFont font("楷体", fontSize, QFont::Bold, true); + painter.setFont(font); + + // 随机旋转角度(-30~30度) + int angle = (randomGenerator.generate() % 61) - 30; + + // 随机颜色 + pen = QPen(QColor(randomGenerator.generate() % 255, randomGenerator.generate() % 255, randomGenerator.generate() % 255)); + painter.setPen(pen); + + // 保存当前状态 + painter.save(); + + // 计算字符中心点 + int x = 5 + 22 * i; + int y = 10; + int w = 30, h = 30; + QPoint center(x + w / 2, y + h / 2); + + // 平移到中心,旋转,再平移回去 + painter.translate(center); + painter.rotate(angle); + painter.translate(-center); + + // 绘制字符 + painter.drawText(x, y, w, h, Qt::AlignCenter, QString(verifyCode[i])); + + // 恢复状态 + painter.restore(); + } + + // 绘制验证码 + /*for (int i = 0; i < verifyCode.size(); i++) + { + pen = QPen(QColor(randomGenerator.generate() % 255, randomGenerator.generate() % 255, randomGenerator.generate() % 255)); + painter.setPen(pen); + painter.drawText(5 + 22 * i, randomGenerator.generate() % 10, 30, 30, Qt::AlignCenter, QString(verifyCode[i])); + }*/ + +} + +void VerifyCodeWidget::mousePressEvent(QMouseEvent* event) +{ + (void)event; + this->refreshVerifyCode(); +} diff --git a/verifycodewidget.h b/verifycodewidget.h new file mode 100644 index 0000000..eaf4077 --- /dev/null +++ b/verifycodewidget.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +class VerifyCodeWidget : public QWidget +{ + Q_OBJECT + +public: + explicit VerifyCodeWidget(QWidget *parent = nullptr); + + //通过这个函数生成随机的验证码字符串 + QString generateVerifyCode(); + + //刷新并把当前验证码显示到界面上 + void refreshVerifyCode(); + + //检验验证码是否匹配 + bool checkVerifyCode(const QString& verifyCode); + + void paintEvent(QPaintEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + +private: + //随机数生成器 + QRandomGenerator randomGenerator; + + //保存验证码的值 + QString verifyCode = ""; +signals: +};