Compare commits
15 Commits
51f0b4941f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| e7af9ad1d7 | |||
| 89ff4fbac0 | |||
| 83f3f4f74e | |||
| 01c4baf04d | |||
| 971ab64769 | |||
| c50f574eed | |||
| e4ec73b510 | |||
| 24536ca80f | |||
| 68cd53a80c | |||
| 030b5d6101 | |||
| 44a1452251 | |||
| 0811720e4d | |||
| 62460933aa | |||
| a814d43d45 | |||
| 8b3290b755 |
1
.gitignore
vendored
@ -5,6 +5,7 @@ CMakeLists.txt.user
|
|||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
.vs/
|
.vs/
|
||||||
|
Android/
|
||||||
*.iml
|
*.iml
|
||||||
*.suo
|
*.suo
|
||||||
*.vsidx
|
*.vsidx
|
||||||
|
|||||||
@ -1,65 +1,3 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.1.3)
|
||||||
|
project(ChatClient)
|
||||||
project(ClientChat VERSION 0.1 LANGUAGES CXX)
|
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/ChatClient)
|
||||||
|
|
||||||
set(CMAKE_AUTOUIC ON)
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
|
||||||
set(CMAKE_AUTORCC ON)
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
|
|
||||||
# 查找Qt版本
|
|
||||||
find_package(Qt6 COMPONENTS Widgets QUIET)
|
|
||||||
if(NOT Qt6_FOUND)
|
|
||||||
find_package(Qt5 COMPONENTS Widgets REQUIRED)
|
|
||||||
set(QT_VERSION_MAJOR 5)
|
|
||||||
else()
|
|
||||||
set(QT_VERSION_MAJOR 6)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(PROJECT_SOURCES
|
|
||||||
main.cpp
|
|
||||||
mainwidget.cpp
|
|
||||||
mainwidget.h
|
|
||||||
mainwidget.ui
|
|
||||||
model/data.h
|
|
||||||
resource.qrc
|
|
||||||
sessionfriendarea.h
|
|
||||||
sessionfriendarea.cpp
|
|
||||||
debug.h
|
|
||||||
messageshowarea.h
|
|
||||||
messageshowarea.cpp
|
|
||||||
messageeditarea.h
|
|
||||||
messageeditarea.cpp
|
|
||||||
SelfInfoWidget.h
|
|
||||||
SelfInfoWidget.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
|
||||||
qt_add_executable(ClientChat
|
|
||||||
MANUAL_FINALIZATION
|
|
||||||
${PROJECT_SOURCES}
|
|
||||||
)
|
|
||||||
else()
|
|
||||||
if(ANDROID)
|
|
||||||
add_library(ClientChat SHARED ${PROJECT_SOURCES})
|
|
||||||
else()
|
|
||||||
add_executable(ClientChat ${PROJECT_SOURCES})
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_link_libraries(ClientChat PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
|
|
||||||
|
|
||||||
# Bundle设置(略)
|
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
|
||||||
install(TARGETS ClientChat
|
|
||||||
BUNDLE DESTINATION .
|
|
||||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
||||||
)
|
|
||||||
|
|
||||||
if(QT_VERSION_MAJOR EQUAL 6)
|
|
||||||
qt_finalize_executable(ClientChat)
|
|
||||||
endif()
|
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"configurePresets": [
|
|
||||||
{
|
|
||||||
"hidden": true,
|
|
||||||
"name": "Qt",
|
|
||||||
"cacheVariables": {
|
|
||||||
"CMAKE_PREFIX_PATH": "$env{QTDIR}"
|
|
||||||
},
|
|
||||||
"vendor": {
|
|
||||||
"qt-project.org/Qt": {
|
|
||||||
"checksum": "wVa86FgEkvdCTVp1/nxvrkaemJc="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"vendor": {
|
|
||||||
"qt-project.org/Presets": {
|
|
||||||
"checksum": "67SmY24ZeVbebyKD0fGfIzb/bGI="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"configurePresets": [
|
|
||||||
{
|
|
||||||
"name": "Qt-Debug",
|
|
||||||
"inherits": "Qt-Default",
|
|
||||||
"binaryDir": "${sourceDir}/out/build/debug",
|
|
||||||
"cacheVariables": {
|
|
||||||
"CMAKE_BUILD_TYPE": "Debug",
|
|
||||||
"CMAKE_CXX_FLAGS": "-DQT_QML_DEBUG"
|
|
||||||
},
|
|
||||||
"environment": {
|
|
||||||
"QML_DEBUG_ARGS": "-qmljsdebugger=file:{7e8c2a2f-a445-4c9b-bb42-24fd265a3e1e},block"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Qt-Release",
|
|
||||||
"inherits": "Qt-Default",
|
|
||||||
"binaryDir": "${sourceDir}/out/build/release",
|
|
||||||
"cacheVariables": {
|
|
||||||
"CMAKE_BUILD_TYPE": "Release"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": true,
|
|
||||||
"name": "Qt-Default",
|
|
||||||
"inherits": "6.9.0",
|
|
||||||
"vendor": {
|
|
||||||
"qt-project.org/Default": {
|
|
||||||
"checksum": "ogzyvXATpX3FyMqBErb6IpyYPKI="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": true,
|
|
||||||
"name": "5.14.0",
|
|
||||||
"inherits": "Qt",
|
|
||||||
"environment": {
|
|
||||||
"QTDIR": "E:/Qt/Qt5.14.0/5.14.0/msvc2017_64"
|
|
||||||
},
|
|
||||||
"architecture": {
|
|
||||||
"strategy": "external",
|
|
||||||
"value": "x64"
|
|
||||||
},
|
|
||||||
"generator": "Ninja",
|
|
||||||
"vendor": {
|
|
||||||
"qt-project.org/Version": {
|
|
||||||
"checksum": "3cWOu5Lvdo5oEp6qU2AAXDl3CO8="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": true,
|
|
||||||
"name": "6.9.0",
|
|
||||||
"inherits": "Qt",
|
|
||||||
"environment": {
|
|
||||||
"QTDIR": "E:/Qt/Qt6.9.0/6.9.0/msvc2022_64"
|
|
||||||
},
|
|
||||||
"architecture": {
|
|
||||||
"strategy": "external",
|
|
||||||
"value": "x64"
|
|
||||||
},
|
|
||||||
"generator": "Ninja",
|
|
||||||
"vendor": {
|
|
||||||
"qt-project.org/Version": {
|
|
||||||
"checksum": "5GMO6/002JUUngppM/iaIHJADvk="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"vendor": {
|
|
||||||
"qt-project.org/Presets": {
|
|
||||||
"checksum": "0wzuyL8qCbVtFSbq9ZO9Fr8MNQU="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
141
ChatClient/CMakeLists.txt
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
project(ClientChat VERSION 0.1 LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 设置 UI 文件的搜索路径
|
||||||
|
set(CMAKE_AUTOUIC_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/ui)
|
||||||
|
|
||||||
|
# proto文件
|
||||||
|
file(GLOB PB_FILES
|
||||||
|
proto/*.proto
|
||||||
|
)
|
||||||
|
|
||||||
|
# 源文件
|
||||||
|
# file(GLOB PROJECT_SOURCES
|
||||||
|
# include/model/*.h src/model/*.cpp
|
||||||
|
# include/network/*.h src/network/*.cpp
|
||||||
|
# include/*.h src/*.cpp
|
||||||
|
# ui/*.ui
|
||||||
|
# resource/qrc/*.qrc
|
||||||
|
# )
|
||||||
|
|
||||||
|
# 显式列出源文件
|
||||||
|
set(PROJECT_SOURCES
|
||||||
|
src/model/datacenter.cpp
|
||||||
|
src/network/netclient.cpp
|
||||||
|
src/addfrienddialog.cpp
|
||||||
|
src/choosefrienddialog.cpp
|
||||||
|
src/groupsessiondetailwidget.cpp
|
||||||
|
src/historymessagewidget.cpp
|
||||||
|
src/loginwidget.cpp
|
||||||
|
src/main.cpp
|
||||||
|
src/mainwidget.cpp
|
||||||
|
src/messageeditarea.cpp
|
||||||
|
src/messageshowarea.cpp
|
||||||
|
src/phoneloginwidget.cpp
|
||||||
|
src/selfinfowidget.cpp
|
||||||
|
src/sessiondetailwidget.cpp
|
||||||
|
src/sessionfriendarea.cpp
|
||||||
|
src/soundrecorder.cpp
|
||||||
|
src/toast.cpp
|
||||||
|
src/userinfowidget.cpp
|
||||||
|
src/verifycodewidget.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# 显式列出头文件
|
||||||
|
set(PROJECT_HEADERS
|
||||||
|
include/model/data.h
|
||||||
|
include/model/datacenter.h
|
||||||
|
include/network/netclient.h
|
||||||
|
include/addfrienddialog.h
|
||||||
|
include/choosefrienddialog.h
|
||||||
|
include/debug.h
|
||||||
|
include/groupsessiondetailwidget.h
|
||||||
|
include/historymessagewidget.h
|
||||||
|
include/loginwidget.h
|
||||||
|
include/mainwidget.h
|
||||||
|
include/messageeditarea.h
|
||||||
|
include/messageshowarea.h
|
||||||
|
include/phoneloginwidget.h
|
||||||
|
include/selfinfowidget.h
|
||||||
|
include/sessiondetailwidget.h
|
||||||
|
include/sessionfriendarea.h
|
||||||
|
include/soundrecorder.h
|
||||||
|
include/toast.h
|
||||||
|
include/userinfowidget.h
|
||||||
|
include/verifycodewidget.h
|
||||||
|
)
|
||||||
|
|
||||||
|
# 显式列出 UI 文件
|
||||||
|
set(PROJECT_FORMS
|
||||||
|
ui/mainwidget.ui
|
||||||
|
)
|
||||||
|
|
||||||
|
# 显式列出资源文件
|
||||||
|
set(PROJECT_RESOURCES
|
||||||
|
resource.qrc
|
||||||
|
)
|
||||||
|
|
||||||
|
if(ANDROID)
|
||||||
|
# Android 平台:附加 Android 可部署资源
|
||||||
|
qt_add_executable(ClientChat
|
||||||
|
MANUAL_FINALIZATION
|
||||||
|
# ${PROJECT_SOURCES}
|
||||||
|
${PROJECT_SOURCES}
|
||||||
|
${PROJECT_HEADERS}
|
||||||
|
${PROJECT_FORMS}
|
||||||
|
${PROJECT_RESOURCES}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 指定自定义的 Android 部署目录
|
||||||
|
# set_property(TARGET ClientChat APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
|
||||||
|
else()
|
||||||
|
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||||
|
|
||||||
|
qt_add_executable(ClientChat
|
||||||
|
MANUAL_FINALIZATION
|
||||||
|
# ${PROJECT_SOURCES}
|
||||||
|
${PROJECT_SOURCES}
|
||||||
|
${PROJECT_HEADERS}
|
||||||
|
${PROJECT_FORMS}
|
||||||
|
${PROJECT_RESOURCES}
|
||||||
|
)
|
||||||
|
|
||||||
|
qt_add_protobuf(ClientChat PROTO_FILES ${PB_FILES})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# 添加包含目录
|
||||||
|
target_include_directories(ClientChat PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/model
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/network
|
||||||
|
)
|
||||||
|
|
||||||
|
# 链接动态库
|
||||||
|
target_link_libraries(ClientChat PRIVATE
|
||||||
|
Qt6::Core
|
||||||
|
Qt${QT_VERSION_MAJOR}::Widgets
|
||||||
|
Qt6::Network
|
||||||
|
Qt6::WebSockets
|
||||||
|
Qt6::Multimedia
|
||||||
|
)
|
||||||
66
ChatClient/include/addfrienddialog.h
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QScrollBar>
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
#include "model/data.h"
|
||||||
|
|
||||||
|
using model::UserInfo;
|
||||||
|
|
||||||
|
/////////////////////////////////////////
|
||||||
|
//表示一个好友搜索的结果
|
||||||
|
/////////////////////////////////////////
|
||||||
|
class FriendResultItem : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
FriendResultItem(const UserInfo& userInfo);
|
||||||
|
void clickAddBtn();
|
||||||
|
|
||||||
|
private:
|
||||||
|
const UserInfo& userInfo;
|
||||||
|
|
||||||
|
QPushButton* addBtn;
|
||||||
|
};
|
||||||
|
|
||||||
|
/////////////////////////////////////////
|
||||||
|
//整个搜索好友的窗口
|
||||||
|
/////////////////////////////////////////
|
||||||
|
class AddFriendDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
AddFriendDialog(QWidget *parent);
|
||||||
|
|
||||||
|
//初始化结果区域
|
||||||
|
void initResultArea();
|
||||||
|
|
||||||
|
//往窗口中新增一个好友搜索的结果
|
||||||
|
void addResult(const UserInfo& userInfo);
|
||||||
|
|
||||||
|
//清空界面上所有的好友搜索结果
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
//
|
||||||
|
void setSearchKey(const QString& searcheKey);
|
||||||
|
|
||||||
|
void clickSearchBtn();
|
||||||
|
void clickSearchDone();
|
||||||
|
|
||||||
|
private:
|
||||||
|
//整个窗口的网格布局
|
||||||
|
QGridLayout* layout;
|
||||||
|
|
||||||
|
//
|
||||||
|
QLineEdit* searchEdit;
|
||||||
|
|
||||||
|
//保存搜索好友的结果
|
||||||
|
QWidget* resultContainer;
|
||||||
|
};
|
||||||
72
ChatClient/include/choosefrienddialog.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QScrollBar>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QLabel>
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
class ChooseFriendDialog;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
/// 选择好友窗口中的一个 元素/好友项
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
class ChooseFriendItem : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ChooseFriendItem(ChooseFriendDialog* owner, const QString& userId, const QIcon& avatar, const QString& name, bool checked);
|
||||||
|
|
||||||
|
void paintEvent(QPaintEvent* event) override;
|
||||||
|
void enterEvent(QEnterEvent* event) override;
|
||||||
|
void leaveEvent(QEvent* event) override;
|
||||||
|
|
||||||
|
QString& getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
QCheckBox* getCheckBox() {
|
||||||
|
return checkBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool isHover = false;
|
||||||
|
|
||||||
|
QCheckBox* checkBox;
|
||||||
|
QPushButton* avatarBtn;
|
||||||
|
QLabel* nameLabel;
|
||||||
|
ChooseFriendDialog* owner; //记录了哪个QWidget持有这个Item,此处应该是ChooseFriendDialog
|
||||||
|
|
||||||
|
QString userId; //记录成员id用于删除唯一指定的成员Item
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChooseFriendDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
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<QString> generateMemberList();
|
||||||
|
private:
|
||||||
|
QWidget* totalContainer;
|
||||||
|
QWidget* selectedContainer;
|
||||||
|
|
||||||
|
//当前选择窗口是点击哪个用户弹出的
|
||||||
|
QString userId;
|
||||||
|
};
|
||||||
|
|
||||||
14
ChatClient/include/debug.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef DEBUG_H
|
||||||
|
#define DEBUG_H
|
||||||
|
|
||||||
|
#define TEST_UI 0
|
||||||
|
|
||||||
|
#define TEST_GROUP_SESSION_DETAIL 0
|
||||||
|
|
||||||
|
#define TEST_SKIP_LOGIN 0
|
||||||
|
|
||||||
|
#define TEST_NETWORK 0
|
||||||
|
|
||||||
|
#define DEPOLY 1
|
||||||
|
|
||||||
|
#endif // DEBUG_H
|
||||||
37
ChatClient/include/groupsessiondetailwidget.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QLabel>
|
||||||
|
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <qscrollarea.h>
|
||||||
|
#include <qscrollbar.h>
|
||||||
|
#include <qgridlayout.h>
|
||||||
|
#include <qpushbutton.h>
|
||||||
|
#include <qgridlayout.h>
|
||||||
|
#include "sessiondetailwidget.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
class AvatarItem;
|
||||||
|
|
||||||
|
class GroupSessionDetailWidget : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
GroupSessionDetailWidget(QWidget* parent);
|
||||||
|
|
||||||
|
void initData();
|
||||||
|
void initMembers(const QString& chatSessionId);
|
||||||
|
|
||||||
|
void addMember(AvatarItem* avatarItem);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QGridLayout* glayout;
|
||||||
|
QLabel* groupNameLabel;
|
||||||
|
|
||||||
|
//表示当前所要添加的AvatarItem处在的行和列
|
||||||
|
int curRow = 0;
|
||||||
|
int curCol = 1;
|
||||||
|
};
|
||||||
108
ChatClient/include/historymessagewidget.h
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QRadioButton>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QDateTimeEdit>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QScrollBar>
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
#include "model/data.h"
|
||||||
|
|
||||||
|
using model::Message;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
/// 表示一个历史消息元素
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
class HistoryItem : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
HistoryItem() {}
|
||||||
|
|
||||||
|
static HistoryItem* makeHistoryItem(const Message& message);
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
/// 展示历史消息窗口
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
class HistoryMessageWidget : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
HistoryMessageWidget(QWidget *parent);
|
||||||
|
|
||||||
|
//在窗口中添加一个历史消息
|
||||||
|
void addHistoryMessage(const Message& message);
|
||||||
|
|
||||||
|
//清空窗口中所有的历史消息
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
void clickSearchBtn();
|
||||||
|
void clickSearchBtnDone();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void initScrollArea(QGridLayout* layout);
|
||||||
|
|
||||||
|
QRadioButton* keyRadioBtn;
|
||||||
|
QRadioButton* timeRadioBtn;
|
||||||
|
QLineEdit* searchEdit;
|
||||||
|
|
||||||
|
QDateTimeEdit* begTimeEdit;
|
||||||
|
QDateTimeEdit* endTimeEdit;
|
||||||
|
|
||||||
|
//持有所有的历史消息结果的容器对象
|
||||||
|
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;
|
||||||
|
};
|
||||||
38
ChatClient/include/loginwidget.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
#include "phoneloginwidget.h"
|
||||||
|
#include "verifycodewidget.h"
|
||||||
|
#include "toast.h"
|
||||||
|
|
||||||
|
class LoginWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit LoginWidget(QWidget *parent);
|
||||||
|
|
||||||
|
void switchMode();
|
||||||
|
|
||||||
|
void clickSubmitBtn();
|
||||||
|
void userLoginDone(bool ok, const QString& reason);
|
||||||
|
void userRegisterDone(bool ok, const QString& reason);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool isLoginMode = true;
|
||||||
|
QLabel* titleLabel;
|
||||||
|
|
||||||
|
QLineEdit* usernameEdit;
|
||||||
|
QLineEdit* passwordEdit;
|
||||||
|
QLineEdit* verifyCodeEdit;
|
||||||
|
VerifyCodeWidget* verifyCodeWidget;
|
||||||
|
|
||||||
|
QPushButton* submitBtn;
|
||||||
|
QPushButton* phoneModeBtn;
|
||||||
|
QPushButton* switchModeBtn;
|
||||||
|
};
|
||||||
@ -4,11 +4,17 @@
|
|||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
|
#include <QMainWindow>
|
||||||
|
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "messageeditarea.h"
|
#include "messageeditarea.h"
|
||||||
#include "messageshowarea.h"
|
#include "messageshowarea.h"
|
||||||
#include "SelfInfoWidget.h"
|
#include "selfinfowidget.h"
|
||||||
|
#include "sessionfriendarea.h"
|
||||||
|
#include "groupsessiondetailwidget.h"
|
||||||
|
#include "addfrienddialog.h"
|
||||||
|
#include "model/datacenter.h"
|
||||||
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
@ -53,6 +59,15 @@ private:
|
|||||||
//添加好友按钮
|
//添加好友按钮
|
||||||
QPushButton* addFriendBtn;
|
QPushButton* addFriendBtn;
|
||||||
|
|
||||||
|
//
|
||||||
|
SessionFriendArea* sessionFriendArea;
|
||||||
|
|
||||||
|
//显示会话标题
|
||||||
|
QLabel* sessionTitleLabel;
|
||||||
|
|
||||||
|
//显示会话详情按钮
|
||||||
|
QPushButton* extraBtn;
|
||||||
|
|
||||||
//消息展示区
|
//消息展示区
|
||||||
MessageShowArea* messageShowArea;
|
MessageShowArea* messageShowArea;
|
||||||
|
|
||||||
@ -67,12 +82,14 @@ private:
|
|||||||
|
|
||||||
ActiveTab activeTab = SESSION_LIST;
|
ActiveTab activeTab = SESSION_LIST;
|
||||||
|
|
||||||
|
public:
|
||||||
void initMainWindow();
|
void initMainWindow();
|
||||||
void initLeftWindow();
|
void initLeftWindow();
|
||||||
void initMidWindow();
|
void initMidWindow();
|
||||||
void initRightWindow();
|
void initRightWindow();
|
||||||
|
|
||||||
void initSignalSlot();
|
void initSignalSlot();
|
||||||
|
void initWebSocket();
|
||||||
|
|
||||||
void switchTabToSession();
|
void switchTabToSession();
|
||||||
void switchTabToFriend();
|
void switchTabToFriend();
|
||||||
@ -81,5 +98,17 @@ private:
|
|||||||
void loadSessionList();
|
void loadSessionList();
|
||||||
void loadFriendList();
|
void loadFriendList();
|
||||||
void loadApplyList();
|
void loadApplyList();
|
||||||
|
|
||||||
|
void updateFriendList();
|
||||||
|
void updateChatSessionList();
|
||||||
|
void updateApplyList();
|
||||||
|
|
||||||
|
void loadRecentMessage(const QString& chatSessionId);
|
||||||
|
void updateRecentMessage(const QString& chatSessionId);
|
||||||
|
|
||||||
|
//点击好友项后,切换到会话列表的总函数,上方的switchTabToSession只是其中的一个环节
|
||||||
|
void switchSession(const QString& userId);
|
||||||
|
|
||||||
|
MessageShowArea* getMessageShowArea();
|
||||||
};
|
};
|
||||||
#endif // MAINWIDGET_H
|
#endif // MAINWIDGET_H
|
||||||
@ -11,6 +11,11 @@
|
|||||||
//#include <qpropertyanimation.h>
|
//#include <qpropertyanimation.h>
|
||||||
//#include <QEvent>
|
//#include <QEvent>
|
||||||
|
|
||||||
|
#include "historymessagewidget.h"
|
||||||
|
#include "model/data.h"
|
||||||
|
|
||||||
|
using namespace model;
|
||||||
|
|
||||||
//编辑消息的区域
|
//编辑消息的区域
|
||||||
class MessageEditArea : public QWidget
|
class MessageEditArea : public QWidget
|
||||||
{
|
{
|
||||||
@ -18,9 +23,20 @@ class MessageEditArea : public QWidget
|
|||||||
public:
|
public:
|
||||||
explicit MessageEditArea(QWidget *parent = nullptr);
|
explicit MessageEditArea(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void initSignalSlot();
|
||||||
|
void sendTextMessage();
|
||||||
|
void addSelfMessage(MessageType messageType, const QByteArray& content, const QString& extraInfo);
|
||||||
|
void addOtherMessage(const model::Message& message);
|
||||||
//花式按钮事件
|
//花式按钮事件
|
||||||
//bool eventFilter(QObject* obj, QEvent* event) override;
|
//bool eventFilter(QObject* obj, QEvent* event) override;
|
||||||
|
|
||||||
|
void clickSendImageBtn();
|
||||||
|
void clickSendFileBtn();
|
||||||
|
|
||||||
|
void soundRecordPressed();
|
||||||
|
void soundRecordReleased();
|
||||||
|
|
||||||
|
void soundSpeech(const QString& path);
|
||||||
private:
|
private:
|
||||||
QPushButton* sendImageBtn;
|
QPushButton* sendImageBtn;
|
||||||
QPushButton* sendFileBtn;
|
QPushButton* sendFileBtn;
|
||||||
@ -28,6 +44,7 @@ private:
|
|||||||
QPushButton* showHistoryBtn;
|
QPushButton* showHistoryBtn;
|
||||||
QPlainTextEdit* textEdit;
|
QPlainTextEdit* textEdit;
|
||||||
QPushButton* sendTextButton;
|
QPushButton* sendTextButton;
|
||||||
|
QLabel* tipLabel;
|
||||||
|
|
||||||
//花式按钮
|
//花式按钮
|
||||||
//QGraphicsDropShadowEffect* shadowEffect;
|
//QGraphicsDropShadowEffect* shadowEffect;
|
||||||
@ -11,6 +11,7 @@
|
|||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QPainterPath>
|
#include <QPainterPath>
|
||||||
|
|
||||||
|
|
||||||
#include "model/data.h"
|
#include "model/data.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
|
||||||
@ -34,6 +35,9 @@ public:
|
|||||||
//清空
|
//清空
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
//滚动到末尾
|
||||||
|
void scrollToEnd();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QWidget* container;
|
QWidget* container;
|
||||||
};
|
};
|
||||||
@ -52,9 +56,9 @@ public:
|
|||||||
|
|
||||||
//添加工厂函数
|
//添加工厂函数
|
||||||
static QWidget* makeTextMessageItem(bool isLeft, const QString& message);
|
static QWidget* makeTextMessageItem(bool isLeft, const QString& message);
|
||||||
static QWidget* makeImageMessageItem();
|
static QWidget* makeImageMessageItem(bool isLeft, const QString& fileId, const QByteArray& content);
|
||||||
static QWidget* makeFileMessageItem();
|
static QWidget* makeFileMessageItem(bool isLeft, const Message& message);
|
||||||
static QWidget* makeSpeechMessageItem();
|
static QWidget* makeSpeechMessageItem(bool isLeft, const Message& message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isLeft;
|
bool isLeft;
|
||||||
@ -62,16 +66,55 @@ private:
|
|||||||
|
|
||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
/// 创建类表示“文本消息”正文部分
|
/// 创建类表示“文本消息”正文部分
|
||||||
|
//也让这个类表示文件消息
|
||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
class MessageContentLabel : public QWidget {
|
class MessageContentLabel : public QWidget {
|
||||||
public:
|
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 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:
|
private:
|
||||||
QLabel* label;
|
QLabel* label;
|
||||||
bool isLeft;
|
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
|
#endif // MESSAGESHOWAREA_H
|
||||||
@ -8,6 +8,16 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
|
#include "base.qpb.h"
|
||||||
|
#include "gateway.qpb.h"
|
||||||
|
#include "user.qpb.h"
|
||||||
|
#include "friend.qpb.h"
|
||||||
|
#include "file.qpb.h"
|
||||||
|
#include "notify.qpb.h"
|
||||||
|
#include "speech_recognition.qpb.h"
|
||||||
|
#include "message_storage.qpb.h"
|
||||||
|
#include "message_transmit.qpb.h"
|
||||||
|
|
||||||
// 创建命名空间
|
// 创建命名空间
|
||||||
namespace model {
|
namespace model {
|
||||||
///////////////////////////
|
///////////////////////////
|
||||||
@ -87,6 +97,21 @@ public:
|
|||||||
QString description = ""; //用户签名
|
QString description = ""; //用户签名
|
||||||
QString phone = ""; //手机号码
|
QString phone = ""; //手机号码
|
||||||
QIcon avatar; //用户头像
|
QIcon avatar; //用户头像
|
||||||
|
|
||||||
|
//从protobuffer的UserInfo对象,转换为当前代码的UserInfo对象
|
||||||
|
void load(const bite_im::UserInfo& userInfo) {
|
||||||
|
this->userId = userInfo.userId();
|
||||||
|
this->nickname = userInfo.nickname();
|
||||||
|
this->phone = userInfo.phone();
|
||||||
|
this->description = userInfo.description();
|
||||||
|
if (userInfo.avatar().isEmpty()) {
|
||||||
|
//使用默认的头像即可
|
||||||
|
this->avatar = QIcon(":resource/image/defaultAvatar.png");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this->avatar = makeIcon(userInfo.avatar());
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
///////////////////////////
|
///////////////////////////
|
||||||
@ -126,6 +151,52 @@ static Message makeMessage(MessageType messageType, const QString& chatSessionId
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void load(const bite_im::MessageInfo& messageInfo) {
|
||||||
|
this->messageId = messageInfo.messageId();
|
||||||
|
this->chatSessionId = messageInfo.chatSessionId();
|
||||||
|
this->time = formatTime(messageInfo.timestamp());
|
||||||
|
this->sender.load(messageInfo.sender());
|
||||||
|
|
||||||
|
//设置消息的类型
|
||||||
|
auto type = messageInfo.message().messageType();
|
||||||
|
if (type == bite_im::MessageTypeGadget::MessageType::STRING) {
|
||||||
|
this->messageType = TEXT_TYPE;
|
||||||
|
this->content = messageInfo.message().stringMessage().content().toUtf8();
|
||||||
|
}
|
||||||
|
else if (type == bite_im::MessageTypeGadget::MessageType::IMAGE) {
|
||||||
|
this->messageType = IMAGE_TYPE;
|
||||||
|
if (messageInfo.message().imageMessage().hasImageContent()) {
|
||||||
|
this->content = messageInfo.message().imageMessage().imageContent();
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
// 错误的类型, 啥都不做了, 只是打印一个日志
|
||||||
|
LOG() << "非法的消息类型! type=" << type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static QString makeId() {
|
static QString makeId() {
|
||||||
@ -215,6 +286,33 @@ public:
|
|||||||
Message lastMessage; //表示会话的最新消息
|
Message lastMessage; //表示会话的最新消息
|
||||||
QIcon avatar; //会话的头像(单聊或群聊)
|
QIcon avatar; //会话的头像(单聊或群聊)
|
||||||
QString userId = ""; //(单聊为对方的id,群聊为"")
|
QString userId = ""; //(单聊为对方的id,群聊为"")
|
||||||
|
|
||||||
|
|
||||||
|
void load(const bite_im::ChatSessionInfo& chatSessionInfo) {
|
||||||
|
this->chatSessionId = chatSessionInfo.chatSessionId();
|
||||||
|
this->chatSessionName = chatSessionInfo.chatSessionName();
|
||||||
|
if (chatSessionInfo.hasSingleChatFriendId()) {
|
||||||
|
this->userId = chatSessionInfo.singleChatFriendId();
|
||||||
|
}
|
||||||
|
if (chatSessionInfo.hasPrevMessage()) {
|
||||||
|
lastMessage.load(chatSessionInfo.prevMessage());
|
||||||
|
}
|
||||||
|
if (chatSessionInfo.hasAvatar() && !chatSessionInfo.avatar().isEmpty()) {
|
||||||
|
//如果有头像,则设置这个头像
|
||||||
|
this->avatar = makeIcon(chatSessionInfo.avatar());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//如果没有,则会根据是单聊还是群聊,使用不同的默认头像
|
||||||
|
if (userId != "") {
|
||||||
|
//单聊
|
||||||
|
this->avatar = QIcon(":/resource/image/defaultAvatar.png");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//群聊
|
||||||
|
this->avatar = QIcon(":/resource/image/groupAvatar.png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} //end namespace model
|
} //end namespace model
|
||||||
265
ChatClient/include/model/datacenter.h
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <qstandardpaths.h>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
//#include <QList>
|
||||||
|
|
||||||
|
#include "data.h"
|
||||||
|
#include "../network/netclient.h"
|
||||||
|
|
||||||
|
namespace model
|
||||||
|
{
|
||||||
|
|
||||||
|
class DataCenter : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
static DataCenter* getInstance();
|
||||||
|
|
||||||
|
~DataCenter();
|
||||||
|
/// <summary>
|
||||||
|
/// 计算两个整数的和。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="a">第一个加数</param>
|
||||||
|
/// <param name="b">第二个加数</param>
|
||||||
|
/// <returns>返回 a + b 的结果</returns>
|
||||||
|
/*int add(int a, int b) {
|
||||||
|
return a + b;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
private:
|
||||||
|
DataCenter();
|
||||||
|
|
||||||
|
static DataCenter* instance;
|
||||||
|
|
||||||
|
//列出DataCenter中要组织管理的所有数据
|
||||||
|
|
||||||
|
//当前客户端登录到服务器对应的登录会话Id
|
||||||
|
QString loginSessionId;
|
||||||
|
|
||||||
|
//当前的用户信息
|
||||||
|
UserInfo* myself = nullptr;
|
||||||
|
|
||||||
|
//好友列表
|
||||||
|
QList<UserInfo>* friendList = nullptr;
|
||||||
|
|
||||||
|
//会话列表
|
||||||
|
QList<ChatSessionInfo>* chatSessionList = nullptr;
|
||||||
|
|
||||||
|
//记录当前选中的会话是哪个
|
||||||
|
QString currentChatSessionId = "";
|
||||||
|
|
||||||
|
//记录每个会话中,都有哪些成员
|
||||||
|
QHash<QString, QList<UserInfo>>* memberList = nullptr;//unordered_map
|
||||||
|
|
||||||
|
//待处理的好友申请列表
|
||||||
|
QList<UserInfo>* applyList = nullptr;
|
||||||
|
|
||||||
|
//每个会话最近消息的列表,key为chatSessionId,value为消息列表
|
||||||
|
QHash<QString, QList<Message>>* recentMessages = nullptr;
|
||||||
|
|
||||||
|
//存储每个会话,表示未读消息的数量
|
||||||
|
QHash<QString, int>* unreadMessageCount = nullptr;
|
||||||
|
|
||||||
|
// 用户的好友搜索结果
|
||||||
|
QList<UserInfo>* searchUserResult = nullptr;
|
||||||
|
|
||||||
|
//保存一个历史消息搜索结果
|
||||||
|
QList<Message>* searchMessageResult = nullptr;
|
||||||
|
|
||||||
|
//短信(邮箱)验证码的验证Id
|
||||||
|
QString currentVerifyCodeId = "";
|
||||||
|
|
||||||
|
//让dataCenter持有Netclient实例
|
||||||
|
network::NetClient netClient;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化数据文件
|
||||||
|
/// </summary>
|
||||||
|
void initDataFile();
|
||||||
|
|
||||||
|
//存储数据到文件中
|
||||||
|
void saveDataFile();
|
||||||
|
|
||||||
|
//从数据文件中加载数据到内存
|
||||||
|
void loadDataFile();
|
||||||
|
|
||||||
|
//清空未读消息数目
|
||||||
|
void clearUnread(const QString& chatSessionId);
|
||||||
|
|
||||||
|
//增加未读消息的数目
|
||||||
|
void addUnread(const QString& chatSessionId);
|
||||||
|
|
||||||
|
//获取未读消息的数目
|
||||||
|
int getUnread(const QString& chatSessionId);
|
||||||
|
|
||||||
|
//获取到当前的登录会话Id
|
||||||
|
const QString& getLoginSessionId() const{
|
||||||
|
return loginSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
//验证网络的连通性
|
||||||
|
void ping() { netClient.ping(); }
|
||||||
|
|
||||||
|
//针对netclient中的websocket进行初始化
|
||||||
|
void initWebsocket();
|
||||||
|
|
||||||
|
//通过网络获取到用户的个人信息
|
||||||
|
void getMyselfAsync();
|
||||||
|
|
||||||
|
UserInfo* getMyselfsync();
|
||||||
|
//
|
||||||
|
void resetMyself(std::shared_ptr<bite_im::GetUserInfoRsp> resp);
|
||||||
|
|
||||||
|
//通过网络获取好友列表
|
||||||
|
QList<UserInfo>* getFriendList();
|
||||||
|
void getFriendListAsync();
|
||||||
|
void resetFriendList(std::shared_ptr<bite_im::GetFriendListRsp> resp);
|
||||||
|
|
||||||
|
//获取会话列表
|
||||||
|
QList<ChatSessionInfo>* getChatSessionList();
|
||||||
|
void getChatSessionListAsync();
|
||||||
|
void resetChatSessionList(std::shared_ptr<bite_im::GetChatSessionListRsp> resp);
|
||||||
|
|
||||||
|
//获取好友申请列表
|
||||||
|
QList<UserInfo>* getApplyList();
|
||||||
|
void getApplyListAsync();
|
||||||
|
void resetApplyList(std::shared_ptr<bite_im::GetPendingFriendEventListRsp> resp);
|
||||||
|
|
||||||
|
//获取最近的消息列表
|
||||||
|
void getRecnetMessageListAsync(const QString& chatSessionId, bool updateUI);
|
||||||
|
QList<Message>* getRecentMessageList(const QString& chatSessionId);
|
||||||
|
void resetRecentMessageList(const QString& chatSessionId, std::shared_ptr<bite_im::GetRecentMsgRsp> resp);
|
||||||
|
|
||||||
|
//发送消息给服务器
|
||||||
|
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);
|
||||||
|
void resetNickname(const QString& nickname);
|
||||||
|
|
||||||
|
//修改用户签名
|
||||||
|
void changeDescriptionAsync(const QString& desc);
|
||||||
|
void resetDescription(const QString& desc);
|
||||||
|
|
||||||
|
//获取邮箱验证码
|
||||||
|
void getVerifyCodeAsync(const QString& email);
|
||||||
|
void resetVerifyCodeId(const QString& verifyCodeId);
|
||||||
|
|
||||||
|
//获取verifyCodeId
|
||||||
|
const QString& getVerifyCodeId() const;
|
||||||
|
|
||||||
|
//修改邮箱号码
|
||||||
|
void changePhoneAsync(const QString& email, const QString& verifyCodeId, const QString& verifyCode);
|
||||||
|
void resetPhone(const QString& email);
|
||||||
|
|
||||||
|
//修改头像
|
||||||
|
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<QString>& userIdList);
|
||||||
|
|
||||||
|
//获取会话成员列表
|
||||||
|
void getMemberListAsync(const QString& chatSessionId);
|
||||||
|
QList<UserInfo>* getMemberList(const QString& chatSessionId);
|
||||||
|
void resetMemberList(const QString& chatSessionId, const QList<bite_im::UserInfo>& memberList);
|
||||||
|
|
||||||
|
//搜索用户
|
||||||
|
void searchUserAsync(const QString& searchKey);
|
||||||
|
QList<UserInfo>* getSearchUserResult();
|
||||||
|
void resetSearchUserResult(const QList<bite_im::UserInfo>& userList);
|
||||||
|
|
||||||
|
//搜索历史消息
|
||||||
|
void searchMessageAsync(const QString& searchKey);
|
||||||
|
void searchMessageByTimeAsync(const QDateTime& begTime, const QDateTime& endTime);
|
||||||
|
QList<Message>* getSearchMessageReuslt();
|
||||||
|
void resetSearchMessageResult(const QList<bite_im::MessageInfo>& 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);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
///辅助函数
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//根据会话id查询会话信息
|
||||||
|
ChatSessionInfo* findChatSessionById(const QString& chatSessionId);
|
||||||
|
//根据用户ID查询会话信息
|
||||||
|
ChatSessionInfo* findChatSessionByUserId(const QString& userId);
|
||||||
|
//把指定的会话信息,放到列表头部
|
||||||
|
void topCurrentChatSessionId(const ChatSessionInfo& chatSessionInfo);
|
||||||
|
//根据用户id查询好友信息
|
||||||
|
UserInfo* findFriendById(const QString& userId);
|
||||||
|
|
||||||
|
//设置/获取当前选中的会话
|
||||||
|
void setCurrentChatSessionId(const QString& chatSessionId);
|
||||||
|
const QString& getCurrentSessionId();
|
||||||
|
|
||||||
|
//添加消息到DataCenter中
|
||||||
|
void addMessage(const Message& message);
|
||||||
|
signals:
|
||||||
|
//自定义信号
|
||||||
|
void getMyselfDone();
|
||||||
|
void getFriendListDone();
|
||||||
|
void getChatSessionListDone();
|
||||||
|
void getApplyListDone();
|
||||||
|
void getRecentMessageListDone(const QString& chatSessionId);
|
||||||
|
void getRecentMessageListDoneNoUI(const QString& chatSessionId);
|
||||||
|
void sendMessageDone(MessageType messageType, const QByteArray& content, const QString& extraInfo);
|
||||||
|
void updateLastMessage(const QString& chatSessionId);
|
||||||
|
void receiveMessageDone(const Message& lastMessage);
|
||||||
|
void changeNicknameDone();
|
||||||
|
void changeDescriptionDone();
|
||||||
|
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
|
||||||
132
ChatClient/include/network/netclient.h
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QWebSocket>
|
||||||
|
#include <QProtobufSerializer>
|
||||||
|
#include <qnetworkreply.h>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
|
#include "../model/data.h"
|
||||||
|
|
||||||
|
//此处为了避免“循环包含”的问题,就需要使用前置声明的方法
|
||||||
|
// 代替包含头文件
|
||||||
|
namespace model {
|
||||||
|
class DataCenter;
|
||||||
|
} //end namespace model
|
||||||
|
|
||||||
|
class model::DataCenter;
|
||||||
|
|
||||||
|
namespace network {
|
||||||
|
class NetClient : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
//定义重要的常量,ip暂时使用本地的回环ip,端口号暂定的8000和8001
|
||||||
|
const QString HTTP_URL = "http://127.0.0.1:8000";
|
||||||
|
const QString WEBSOCKET_URL = "ws://127.0.0.1:8001/ws";
|
||||||
|
|
||||||
|
public:
|
||||||
|
NetClient(model::DataCenter* dataCenter);
|
||||||
|
|
||||||
|
//验证网络的联通性
|
||||||
|
void ping();
|
||||||
|
|
||||||
|
//初始化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();
|
||||||
|
|
||||||
|
//生成请求的Id
|
||||||
|
static QString makeRequestId();
|
||||||
|
|
||||||
|
//封装发送请求的逻辑
|
||||||
|
QNetworkReply* sendHttpRequest(const QString& apiPath, const QByteArray& body);
|
||||||
|
|
||||||
|
//封装处理响应的逻辑(判定HTTP正确性,反序列化,判断业务的正确性)
|
||||||
|
//由于不同的api返回的pb对象结构不同,为了让一个函数能够处理多种不同的类型,需要使用模板
|
||||||
|
//后面两个是输出型参数,用于表示这次的操作是成功还是失败
|
||||||
|
template <typename T>
|
||||||
|
std::shared_ptr<T> handleHttpResponse(QNetworkReply* httpResp, bool* ok, QString* reason) {
|
||||||
|
//判定HTTP层面上
|
||||||
|
if (httpResp->error() != QNetworkReply::NoError) {
|
||||||
|
*ok = false;
|
||||||
|
*reason = httpResp->errorString();
|
||||||
|
httpResp->deleteLater();
|
||||||
|
return std::shared_ptr<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
//说明并没有出错, 那就获取到响应的body
|
||||||
|
QByteArray respBody = httpResp->readAll();
|
||||||
|
|
||||||
|
//针对body反序列化
|
||||||
|
std::shared_ptr<T> respObj = std::make_shared<T>();
|
||||||
|
respObj->deserialize(&serializer, respBody);
|
||||||
|
|
||||||
|
//判定业务的结构是否正确
|
||||||
|
if (!respObj->success()) {
|
||||||
|
*ok = false;
|
||||||
|
*reason = respObj->errmsg();
|
||||||
|
httpResp->deleteLater();
|
||||||
|
return std::shared_ptr<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
//释放httpResp对象
|
||||||
|
httpResp->deleteLater();
|
||||||
|
*ok = true;
|
||||||
|
return respObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void getMyself(const QString& loginSessionId);
|
||||||
|
void getFriendList(const QString& loginSessionId);
|
||||||
|
void getChatSessionList(const QString& loginSessionId);
|
||||||
|
void getApplyList(const QString& loginSessionId);
|
||||||
|
void getRecentMessageList(const QString& loginSessionId, const QString& chatSessionId, bool updateUI);
|
||||||
|
void sendMessage(const QString& loginSessionId, const QString& chatSessionId, model::MessageType messageType,
|
||||||
|
const QByteArray& content, const QString& extraInfo);
|
||||||
|
void receiveMessage(const QString& chatSessionId);
|
||||||
|
void changeNickname(const QString& loginSessionId, const QString& nickname);
|
||||||
|
void changeDescription(const QString& loginSessionId, const QString& desc);
|
||||||
|
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<QString>& 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;
|
||||||
|
|
||||||
|
//http客户端
|
||||||
|
QNetworkAccessManager httpClient;
|
||||||
|
|
||||||
|
//websocket客户端
|
||||||
|
QWebSocket websocketClient;
|
||||||
|
|
||||||
|
//序列化器
|
||||||
|
QProtobufSerializer serializer;
|
||||||
|
signals:
|
||||||
|
};
|
||||||
|
} //end namespace network
|
||||||
40
ChatClient/include/phoneloginwidget.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
#include "loginwidget.h"
|
||||||
|
|
||||||
|
class PhoneLoginWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
PhoneLoginWidget(QWidget *parent);
|
||||||
|
|
||||||
|
void sendVerifyCode();
|
||||||
|
void sendVerifyCodeDone(bool ok);
|
||||||
|
|
||||||
|
void clickSubmitBtn();
|
||||||
|
void phoneLoginDone(bool ok, const QString& reason);
|
||||||
|
void phoneRegisterDone(bool ok, const QString& reason);
|
||||||
|
|
||||||
|
void countDown();
|
||||||
|
void switchMode();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QLineEdit* phoneEdit;
|
||||||
|
QPushButton* sendVerifyCodeBtn;
|
||||||
|
QLineEdit* verifyCodeEdit;
|
||||||
|
QLabel* titleLabel;
|
||||||
|
QPushButton* submitBtn;
|
||||||
|
QPushButton* switchModeBtn;
|
||||||
|
|
||||||
|
bool isLoginMode = true;
|
||||||
|
QString currentPhone = ""; // 记录是使用哪个手机号发送的验证码
|
||||||
|
QTimer* timer;
|
||||||
|
int leftTime = 30;
|
||||||
|
};
|
||||||
@ -4,10 +4,13 @@
|
|||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <qlineedit.h>
|
#include <qlineedit.h>
|
||||||
|
#include <QPushButton>
|
||||||
#include <QCursor>
|
#include <QCursor>
|
||||||
#include <QGridLayout>
|
#include <QGridLayout>
|
||||||
#include <QPushButton>
|
#include <QTimer>
|
||||||
|
#include <QFileDialog>
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
#include "toast.h"
|
||||||
|
|
||||||
class SelfInfoWidget : public QDialog
|
class SelfInfoWidget : public QDialog
|
||||||
{
|
{
|
||||||
@ -17,7 +20,22 @@ public:
|
|||||||
SelfInfoWidget(QWidget *parent);
|
SelfInfoWidget(QWidget *parent);
|
||||||
//~SelfInfoWidget();
|
//~SelfInfoWidget();
|
||||||
|
|
||||||
|
void initSingnalSlots();
|
||||||
|
|
||||||
|
void clickNameSubmitBtn();
|
||||||
|
void clickNameSubmitBtnDone();
|
||||||
|
|
||||||
|
void clickDescSubmitBtn();
|
||||||
|
void clickDescSubmitBtnDone();
|
||||||
|
void clickGetVerifyCodeBtn();
|
||||||
|
void clickPhoneSubmitBtn();
|
||||||
|
void clickPhoneSubmitBtnDone();
|
||||||
|
void clickAvatarBtn();
|
||||||
|
void clickAvatarBtnDone();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QGridLayout* layout;
|
||||||
|
|
||||||
QPushButton* avatarBtn;
|
QPushButton* avatarBtn;
|
||||||
QLabel* idTag; //序号标签
|
QLabel* idTag; //序号标签
|
||||||
QLabel* idLabel; //序号
|
QLabel* idLabel; //序号
|
||||||
@ -43,4 +61,8 @@ private:
|
|||||||
QLabel* verifyCodeTag;//显示验证码
|
QLabel* verifyCodeTag;//显示验证码
|
||||||
QLineEdit* verifyCodeEdit;//输入验证码
|
QLineEdit* verifyCodeEdit;//输入验证码
|
||||||
QPushButton* getVerifyCodeBtn;//获取验证码按钮
|
QPushButton* getVerifyCodeBtn;//获取验证码按钮
|
||||||
|
|
||||||
|
//要修改新的手机号码
|
||||||
|
QString emailToChange;
|
||||||
|
int leftTime = 30;
|
||||||
};
|
};
|
||||||
51
ChatClient/include/sessiondetailwidget.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QFontMetrics>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
#include "model/data.h"
|
||||||
|
#include "choosefrienddialog.h"
|
||||||
|
|
||||||
|
using namespace model;
|
||||||
|
|
||||||
|
///////////////////////////////////////
|
||||||
|
///表示 一个头像 + 一个名字的组合控件
|
||||||
|
///////////////////////////////////////
|
||||||
|
class AvatarItem : public QWidget {
|
||||||
|
public:
|
||||||
|
AvatarItem(const QIcon& avatar, const QString& name);
|
||||||
|
|
||||||
|
QPushButton* getAvatar() {
|
||||||
|
return avatarBtn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPushButton* avatarBtn;
|
||||||
|
QLabel* nameLabel;
|
||||||
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////
|
||||||
|
///表示“单聊会话详情”窗口
|
||||||
|
///////////////////////////////////////
|
||||||
|
class SessionDetailWidget : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
SessionDetailWidget(QWidget *parent, const UserInfo& userInfo);
|
||||||
|
|
||||||
|
void clickDeleteFriendBtn();
|
||||||
|
|
||||||
|
private:
|
||||||
|
UserInfo userInfo;
|
||||||
|
|
||||||
|
QPushButton* deleteFriendBtn;
|
||||||
|
};
|
||||||
@ -98,10 +98,14 @@ public:
|
|||||||
const QString& name, const QString& lastmessage);
|
const QString& name, const QString& lastmessage);
|
||||||
|
|
||||||
void active() override;
|
void active() override;
|
||||||
|
|
||||||
|
void updateLastMessage(const QString& chatSessionId);
|
||||||
private:
|
private:
|
||||||
//当前会话Id
|
//当前会话Id
|
||||||
QString chatSessionId;
|
QString chatSessionId;
|
||||||
|
|
||||||
|
//最后一条消息的文本预览
|
||||||
|
QString text;
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
@ -125,6 +129,9 @@ class ApplyItem : public SessionFriendItem {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
ApplyItem(QWidget* owner, const QString& userId, const QIcon& avatar, const QString& name);
|
ApplyItem(QWidget* owner, const QString& userId, const QIcon& avatar, const QString& name);
|
||||||
|
void acceptFriendApply();
|
||||||
|
void rejectFriendApply();
|
||||||
|
|
||||||
void active() override;
|
void active() override;
|
||||||
private:
|
private:
|
||||||
//好友申请Id
|
//好友申请Id
|
||||||
59
ChatClient/include/soundrecorder.h
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#ifndef SOUNDRECORDER_H
|
||||||
|
#define SOUNDRECORDER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QAudioSource>
|
||||||
|
#include <QAudioSink>
|
||||||
|
#include <QMediaDevices>
|
||||||
|
|
||||||
|
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
|
||||||
22
ChatClient/include/toast.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
class Toast : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
//此处并不需要指定父窗口,全局通知的父窗口就是桌面
|
||||||
|
Toast(const QString& text);
|
||||||
|
|
||||||
|
//并不需要手动的来new这个对象,而是通过showMessage来弹出
|
||||||
|
static void showMessage(const QString& text);
|
||||||
|
|
||||||
|
};
|
||||||
46
ChatClient/include/userinfowidget.h
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include "mainwidget.h"
|
||||||
|
#include "model/data.h"
|
||||||
|
#include "model/datacenter.h"
|
||||||
|
|
||||||
|
//using model::Message;
|
||||||
|
using namespace model;
|
||||||
|
|
||||||
|
class UserInfoWidget : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
UserInfoWidget(const UserInfo& userInfo, QWidget *parent);
|
||||||
|
void initSingleSlot();
|
||||||
|
|
||||||
|
void clickDeleteFriendBtn();
|
||||||
|
void clickApplyBtn();
|
||||||
|
|
||||||
|
private:
|
||||||
|
////保存对应的Message对象,暂时先放在这里
|
||||||
|
//Message message;
|
||||||
|
const UserInfo& userInfo;
|
||||||
|
|
||||||
|
QPushButton* avatarBtn;
|
||||||
|
QLabel* idTag;
|
||||||
|
QLabel* idLabel;
|
||||||
|
|
||||||
|
QLabel* nameTag;
|
||||||
|
QLabel* nameLabel;
|
||||||
|
|
||||||
|
QLabel* phoneTag;
|
||||||
|
QLabel* phoneLabel;
|
||||||
|
|
||||||
|
QPushButton* applyBtn;
|
||||||
|
QPushButton* sendMessageBtn;
|
||||||
|
QPushButton* deleteFriendBtn;
|
||||||
|
};
|
||||||
32
ChatClient/include/verifycodewidget.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QRandomGenerator>
|
||||||
|
|
||||||
|
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:
|
||||||
|
};
|
||||||
80
ChatClient/proto/base.proto
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package bite_im;
|
||||||
|
|
||||||
|
option cc_generic_services = true;
|
||||||
|
|
||||||
|
//用户信息结构
|
||||||
|
message UserInfo {
|
||||||
|
string user_id = 1;//用户ID
|
||||||
|
string nickname = 2;//昵称
|
||||||
|
string description = 3;//个人签名/描述
|
||||||
|
string phone = 4; //绑定手机号
|
||||||
|
bytes avatar = 5;//头像照片,文件内容使用二进制
|
||||||
|
}
|
||||||
|
|
||||||
|
//聊天会话信息
|
||||||
|
message ChatSessionInfo {
|
||||||
|
optional string single_chat_friend_id = 1;//群聊会话不需要设置,单聊会话设置为对方ID
|
||||||
|
string chat_session_id = 2; //会话ID
|
||||||
|
string chat_session_name = 3;//会话名称git
|
||||||
|
optional MessageInfo prev_message = 4;//会话上一条消息,新建的会话没有最新消息
|
||||||
|
optional bytes avatar = 5;//会话头像 --群聊会话不需要,直接由前端固定渲染,单聊就是对方的头像
|
||||||
|
}
|
||||||
|
|
||||||
|
//消息类型
|
||||||
|
enum MessageType {
|
||||||
|
STRING = 0;
|
||||||
|
IMAGE = 1;
|
||||||
|
FILE = 2;
|
||||||
|
SPEECH = 3;
|
||||||
|
}
|
||||||
|
message StringMessageInfo {
|
||||||
|
string content = 1;//文字聊天内容
|
||||||
|
}
|
||||||
|
message ImageMessageInfo {
|
||||||
|
optional string file_id = 1;//图片文件id,客户端发送的时候不用设置,由transmit服务器进行设置后交给storage的时候设置
|
||||||
|
optional bytes image_content = 2;//图片数据,在ES中存储消息的时候只要id不要文件数据, 服务端转发的时候需要原样转发
|
||||||
|
}
|
||||||
|
message FileMessageInfo {
|
||||||
|
optional string file_id = 1;//文件id,客户端发送的时候不用设置
|
||||||
|
int64 file_size = 2;//文件大小
|
||||||
|
string file_name = 3;//文件名称
|
||||||
|
optional bytes file_contents = 4;//文件数据,在ES中存储消息的时候只要id和元信息,不要文件数据, 服务端转发的时候也不需要填充
|
||||||
|
}
|
||||||
|
message SpeechMessageInfo {
|
||||||
|
optional string file_id = 1;//语音文件id,客户端发送的时候不用设置
|
||||||
|
optional bytes file_contents = 2;//文件数据,在ES中存储消息的时候只要id不要文件数据, 服务端转发的时候也不需要填充
|
||||||
|
}
|
||||||
|
message MessageContent {
|
||||||
|
MessageType message_type = 1; //消息类型
|
||||||
|
oneof msg_content {
|
||||||
|
StringMessageInfo string_message = 2;//文字消息
|
||||||
|
FileMessageInfo file_message = 3;//文件消息
|
||||||
|
SpeechMessageInfo speech_message = 4;//语音消息
|
||||||
|
ImageMessageInfo image_message = 5;//图片消息
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//消息结构
|
||||||
|
message MessageInfo {
|
||||||
|
string message_id = 1;//消息ID
|
||||||
|
string chat_session_id = 2;//消息所属聊天会话ID
|
||||||
|
int64 timestamp = 3;//消息产生时间
|
||||||
|
UserInfo sender = 4;//消息发送者信息
|
||||||
|
MessageContent message = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Message {
|
||||||
|
string request_id = 1;
|
||||||
|
MessageInfo message = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FileDownloadData {
|
||||||
|
string file_id = 1;
|
||||||
|
bytes file_content = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FileUploadData {
|
||||||
|
string file_name = 1;
|
||||||
|
int64 file_size = 2;
|
||||||
|
bytes file_content = 3;
|
||||||
|
}
|
||||||
71
ChatClient/proto/file.proto
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
文件操作服务器的子服务注册信息: /service/file/instance_id
|
||||||
|
服务名称:/service/file
|
||||||
|
实例ID: instance_id 每个能够提供用户操作服务的子服务器唯一ID
|
||||||
|
当服务发现的时候,通过 /service/file 进行服务发现,就可以发现所有的能够提供用户操作的实例信息了
|
||||||
|
*/
|
||||||
|
syntax = "proto3";
|
||||||
|
package bite_im;
|
||||||
|
import "base.proto";
|
||||||
|
|
||||||
|
option cc_generic_services = true;
|
||||||
|
|
||||||
|
|
||||||
|
message GetSingleFileReq {
|
||||||
|
string request_id = 1;
|
||||||
|
string file_id = 2;
|
||||||
|
optional string user_id = 3;
|
||||||
|
optional string session_id = 4;
|
||||||
|
}
|
||||||
|
message GetSingleFileRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
FileDownloadData file_data = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetMultiFileReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string user_id = 2;
|
||||||
|
optional string session_id = 3;
|
||||||
|
repeated string file_id_list = 4;
|
||||||
|
}
|
||||||
|
message GetMultiFileRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
repeated FileDownloadData file_data = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PutSingleFileReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string user_id = 2;
|
||||||
|
optional string session_id = 3;
|
||||||
|
FileUploadData file_data = 4;
|
||||||
|
}
|
||||||
|
message PutSingleFileRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
FileMessageInfo file_info = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PutMultiFileReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string user_id = 2;
|
||||||
|
optional string session_id = 3;
|
||||||
|
repeated FileUploadData file_data = 4;
|
||||||
|
}
|
||||||
|
message PutMultiFileRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
repeated FileMessageInfo file_info = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
service FileService {
|
||||||
|
rpc GetSingleFile(GetSingleFileReq) returns (GetSingleFileRsp);
|
||||||
|
rpc GetMultiFile(GetMultiFileReq) returns (GetMultiFileRsp);
|
||||||
|
rpc PutSingleFile(PutSingleFileReq) returns (PutSingleFileRsp);
|
||||||
|
rpc PutMultiFile(PutMultiFileReq) returns (PutMultiFileRsp);
|
||||||
|
}
|
||||||
161
ChatClient/proto/friend.proto
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
好友操作服务器的子服务注册信息: /service/friend/instance_id
|
||||||
|
服务名称:/service/friend
|
||||||
|
实例ID: instance_id 每个能够提供用户操作服务的子服务器唯一ID
|
||||||
|
当服务发现的时候,通过 /service/friend 进行服务发现,就可以发现所有的能够提供用户操作的实例信息了
|
||||||
|
*/
|
||||||
|
syntax = "proto3";
|
||||||
|
package bite_im;
|
||||||
|
import "base.proto";
|
||||||
|
|
||||||
|
option cc_generic_services = true;
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------
|
||||||
|
//好友列表获取
|
||||||
|
message GetFriendListReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string user_id = 2;
|
||||||
|
optional string session_id = 3;
|
||||||
|
}
|
||||||
|
message GetFriendListRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
repeated UserInfo friend_list = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------
|
||||||
|
//好友删除
|
||||||
|
message FriendRemoveReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string user_id = 2;
|
||||||
|
optional string session_id = 3;
|
||||||
|
string peer_id = 4;
|
||||||
|
}
|
||||||
|
message FriendRemoveRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
}
|
||||||
|
//--------------------------------------
|
||||||
|
//添加好友--发送好友申请
|
||||||
|
message FriendAddReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string session_id = 2;
|
||||||
|
optional string user_id = 3;//申请人id
|
||||||
|
string respondent_id = 4;//被申请人id
|
||||||
|
}
|
||||||
|
message FriendAddRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
string notify_event_id = 4;//通知事件id
|
||||||
|
}
|
||||||
|
//--------------------------------------
|
||||||
|
//好友申请的处理
|
||||||
|
message FriendAddProcessReq {
|
||||||
|
string request_id = 1;
|
||||||
|
string notify_event_id = 2;//通知事件id
|
||||||
|
bool agree = 3;//是否同意好友申请
|
||||||
|
string apply_user_id = 4; //申请人的用户id
|
||||||
|
optional string session_id = 5;
|
||||||
|
optional string user_id = 6;
|
||||||
|
}
|
||||||
|
// +++++++++++++++++++++++++++++++++
|
||||||
|
message FriendAddProcessRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
optional string new_session_id = 4; // 同意后会创建会话,向网关返回会话信息,用于通知双方会话的建立,这个字段客户端不需要关注
|
||||||
|
}
|
||||||
|
//--------------------------------------
|
||||||
|
//获取待处理的,申请自己好友的信息列表
|
||||||
|
message GetPendingFriendEventListReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string session_id = 2;
|
||||||
|
optional string user_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FriendEvent {
|
||||||
|
string event_id = 1;
|
||||||
|
UserInfo sender = 3;
|
||||||
|
}
|
||||||
|
message GetPendingFriendEventListRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
repeated FriendEvent event = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------
|
||||||
|
//好友搜索
|
||||||
|
message FriendSearchReq {
|
||||||
|
string request_id = 1;
|
||||||
|
string search_key = 2;//就是名称模糊匹配关键字
|
||||||
|
optional string session_id = 3;
|
||||||
|
optional string user_id = 4;
|
||||||
|
}
|
||||||
|
message FriendSearchRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
repeated UserInfo user_info = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------
|
||||||
|
//会话列表获取
|
||||||
|
message GetChatSessionListReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string session_id = 2;
|
||||||
|
optional string user_id = 3;
|
||||||
|
}
|
||||||
|
message GetChatSessionListRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
repeated ChatSessionInfo chat_session_info_list = 4;
|
||||||
|
}
|
||||||
|
//--------------------------------------
|
||||||
|
//创建会话
|
||||||
|
message ChatSessionCreateReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string session_id = 2;
|
||||||
|
optional string user_id = 3;
|
||||||
|
string chat_session_name = 4;
|
||||||
|
//需要注意的是,这个列表中也必须包含创建者自己的用户ID
|
||||||
|
repeated string member_id_list = 5;
|
||||||
|
}
|
||||||
|
message ChatSessionCreateRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
//这个字段属于后台之间的数据,给前端回复的时候不需要这个字段,会话信息通过通知进行发送
|
||||||
|
optional ChatSessionInfo chat_session_info = 4;
|
||||||
|
}
|
||||||
|
//--------------------------------------
|
||||||
|
//获取会话成员列表
|
||||||
|
message GetChatSessionMemberReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string session_id = 2;
|
||||||
|
optional string user_id = 3;
|
||||||
|
string chat_session_id = 4;
|
||||||
|
}
|
||||||
|
message GetChatSessionMemberRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
repeated UserInfo member_info_list = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
service FriendService {
|
||||||
|
rpc GetFriendList(GetFriendListReq) returns (GetFriendListRsp);
|
||||||
|
rpc FriendRemove(FriendRemoveReq) returns (FriendRemoveRsp);
|
||||||
|
rpc FriendAdd(FriendAddReq) returns (FriendAddRsp);
|
||||||
|
rpc FriendAddProcess(FriendAddProcessReq) returns (FriendAddProcessRsp);
|
||||||
|
rpc FriendSearch(FriendSearchReq) returns (FriendSearchRsp);
|
||||||
|
rpc GetChatSessionList(GetChatSessionListReq) returns (GetChatSessionListRsp);
|
||||||
|
rpc ChatSessionCreate(ChatSessionCreateReq) returns (ChatSessionCreateRsp);
|
||||||
|
rpc GetChatSessionMember(GetChatSessionMemberReq) returns (GetChatSessionMemberRsp);
|
||||||
|
rpc GetPendingFriendEventList(GetPendingFriendEventListReq) returns (GetPendingFriendEventListRsp);
|
||||||
|
}
|
||||||
81
ChatClient/proto/gateway.proto
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package bite_im;
|
||||||
|
option cc_generic_services = true;
|
||||||
|
/*
|
||||||
|
消息推送使用websocket长连接进行
|
||||||
|
websocket长连接转换请求:ws://host:ip/ws
|
||||||
|
长连建立以后,需要客户端给服务器发送一个身份验证信息
|
||||||
|
*/
|
||||||
|
message ClientAuthenticationReq {
|
||||||
|
string request_id = 1;
|
||||||
|
string session_id = 2;
|
||||||
|
}
|
||||||
|
message ClientAuthenticationRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
//通信接口统一采用POST请求实现,正文采用protobuf协议进行组织
|
||||||
|
/*
|
||||||
|
HTTP HEADER:
|
||||||
|
POST /service/xxxxx
|
||||||
|
Content-Type: application/x-protobuf
|
||||||
|
Content-Length: 123
|
||||||
|
|
||||||
|
xxxxxx
|
||||||
|
|
||||||
|
-------------------------------------------------------
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/x-protobuf
|
||||||
|
Content-Length: 123
|
||||||
|
|
||||||
|
xxxxxxxxxx
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
//在客户端与网关服务器的通信中,使用HTTP协议进行通信
|
||||||
|
// 通信时采用POST请求作为请求方法
|
||||||
|
// 通信时,正文采用protobuf作为正文协议格式,具体内容字段以前边各个文件中定义的字段格式为准
|
||||||
|
/* 以下是HTTP请求的功能与接口路径对应关系:
|
||||||
|
SERVICE HTTP PATH:
|
||||||
|
{
|
||||||
|
获取随机验证码 /service/user/get_random_verify_code
|
||||||
|
获取短信验证码 /service/user/get_phone_verify_code
|
||||||
|
用户名密码注册 /service/user/username_register
|
||||||
|
用户名密码登录 /service/user/username_login
|
||||||
|
手机号码注册 /service/user/phone_register
|
||||||
|
手机号码登录 /service/user/phone_login
|
||||||
|
获取个人信息 /service/user/get_user_info
|
||||||
|
修改头像 /service/user/set_avatar
|
||||||
|
修改昵称 /service/user/set_nickname
|
||||||
|
修改签名 /service/user/set_description
|
||||||
|
修改绑定手机 /service/user/set_phone
|
||||||
|
|
||||||
|
获取好友列表 /service/friend/get_friend_list
|
||||||
|
获取好友信息 /service/friend/get_friend_info
|
||||||
|
发送好友申请 /service/friend/add_friend_apply
|
||||||
|
好友申请处理 /service/friend/add_friend_process
|
||||||
|
删除好友 /service/friend/remove_friend
|
||||||
|
搜索用户 /service/friend/search_friend
|
||||||
|
获取指定用户的消息会话列表 /service/friend/get_chat_session_list
|
||||||
|
创建消息会话 /service/friend/create_chat_session
|
||||||
|
获取消息会话成员列表 /service/friend/get_chat_session_member
|
||||||
|
获取待处理好友申请事件列表 /service/friend/get_pending_friend_events
|
||||||
|
|
||||||
|
获取历史消息/离线消息列表 /service/message_storage/get_history
|
||||||
|
获取最近N条消息列表 /service/message_storage/get_recent
|
||||||
|
搜索历史消息 /service/message_storage/search_history
|
||||||
|
|
||||||
|
发送消息 /service/message_transmit/new_message
|
||||||
|
|
||||||
|
获取单个文件数据 /service/file/get_single_file
|
||||||
|
获取多个文件数据 /service/file/get_multi_file
|
||||||
|
发送单个文件 /service/file/put_single_file
|
||||||
|
发送多个文件 /service/file/put_multi_file
|
||||||
|
|
||||||
|
语音转文字 /service/speech/recognition
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
62
ChatClient/proto/message_storage.proto
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
消息存储服务器的子服务注册信息: /service/message_storage/instance_id
|
||||||
|
服务名称:/service/message_storage
|
||||||
|
实例ID: instance_id 每个能够提供用户操作服务的子服务器唯一ID
|
||||||
|
当服务发现的时候,通过 /service/message_storage 进行服务发现,就可以发现所有的能够提供用户操作的实例信息了
|
||||||
|
*/
|
||||||
|
syntax = "proto3";
|
||||||
|
package bite_im;
|
||||||
|
import "base.proto";
|
||||||
|
|
||||||
|
option cc_generic_services = true;
|
||||||
|
|
||||||
|
message GetHistoryMsgReq {
|
||||||
|
string request_id = 1;
|
||||||
|
string chat_session_id = 2;
|
||||||
|
int64 start_time = 3;
|
||||||
|
int64 over_time = 4;
|
||||||
|
optional string user_id = 5;
|
||||||
|
optional string session_id = 6;
|
||||||
|
}
|
||||||
|
message GetHistoryMsgRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
repeated MessageInfo msg_list = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetRecentMsgReq {
|
||||||
|
string request_id = 1;
|
||||||
|
string chat_session_id = 2;
|
||||||
|
int64 msg_count = 3;
|
||||||
|
optional int64 cur_time = 4;//用于扩展获取指定时间前的n条消息
|
||||||
|
optional string user_id = 5;
|
||||||
|
optional string session_id = 6;
|
||||||
|
}
|
||||||
|
message GetRecentMsgRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
repeated MessageInfo msg_list = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MsgSearchReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string user_id = 2;
|
||||||
|
optional string session_id = 3;
|
||||||
|
string chat_session_id = 4;
|
||||||
|
string search_key = 5;
|
||||||
|
}
|
||||||
|
message MsgSearchRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
repeated MessageInfo msg_list = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
service MsgStorageService {
|
||||||
|
rpc GetHistoryMsg(GetHistoryMsgReq) returns (GetHistoryMsgRsp);
|
||||||
|
rpc GetRecentMsg(GetRecentMsgReq) returns (GetRecentMsgRsp);
|
||||||
|
rpc MsgSearch(MsgSearchReq) returns (MsgSearchRsp);
|
||||||
|
|
||||||
|
}
|
||||||
39
ChatClient/proto/message_transmit.proto
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
消息转发服务器的子服务注册信息: /service/message_transmit/instance_id
|
||||||
|
服务名称:/service/message_transmit
|
||||||
|
实例ID: instance_id 每个能够提供用户操作服务的子服务器唯一ID
|
||||||
|
当服务发现的时候,通过 /service/message_transmit 进行服务发现,就可以发现所有的能够提供用户操作的实例信息了
|
||||||
|
*/
|
||||||
|
//消息转发服务器接口
|
||||||
|
syntax = "proto3";
|
||||||
|
package bite_im;
|
||||||
|
import "base.proto";
|
||||||
|
|
||||||
|
option cc_generic_services = true;
|
||||||
|
|
||||||
|
//这个用于和网关进行通信
|
||||||
|
message NewMessageReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string user_id = 2;
|
||||||
|
optional string session_id = 3;
|
||||||
|
string chat_session_id = 4;
|
||||||
|
MessageContent message = 5;
|
||||||
|
}
|
||||||
|
message NewMessageRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
//这个用于内部的通信,生成完整的消息信息,并获取消息的转发人员列表
|
||||||
|
message GetTransmitTargetRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
MessageInfo message = 4;
|
||||||
|
repeated string target_id_list = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
service MsgTransmitService {
|
||||||
|
rpc GetTransmitTarget(NewMessageReq) returns (GetTransmitTargetRsp);
|
||||||
|
}
|
||||||
43
ChatClient/proto/notify.proto
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package bite_im;
|
||||||
|
import "base.proto";
|
||||||
|
|
||||||
|
option cc_generic_services = true;
|
||||||
|
|
||||||
|
enum NotifyType {
|
||||||
|
FRIEND_ADD_APPLY_NOTIFY = 0;
|
||||||
|
FRIEND_ADD_PROCESS_NOTIFY = 1;
|
||||||
|
CHAT_SESSION_CREATE_NOTIFY = 2;
|
||||||
|
CHAT_MESSAGE_NOTIFY = 3;
|
||||||
|
FRIEND_REMOVE_NOTIFY = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NotifyFriendAddApply {
|
||||||
|
UserInfo user_info = 1; //申请人信息
|
||||||
|
}
|
||||||
|
message NotifyFriendAddProcess {
|
||||||
|
bool agree = 1;
|
||||||
|
UserInfo user_info = 2; //处理人信息
|
||||||
|
}
|
||||||
|
message NotifyFriendRemove {
|
||||||
|
string user_id = 1; //删除自己的用户ID
|
||||||
|
}
|
||||||
|
message NotifyNewChatSession {
|
||||||
|
ChatSessionInfo chat_session_info = 1; //新建会话信息
|
||||||
|
}
|
||||||
|
message NotifyNewMessage {
|
||||||
|
MessageInfo message_info = 1; //新消息
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message NotifyMessage {
|
||||||
|
optional string notify_event_id = 1;//通知事件操作id(有则填无则忽略)
|
||||||
|
NotifyType notify_type = 2;//通知事件类型
|
||||||
|
oneof notify_remarks { //事件备注信息
|
||||||
|
NotifyFriendAddApply friend_add_apply = 3;
|
||||||
|
NotifyFriendAddProcess friend_process_result = 4;
|
||||||
|
NotifyFriendRemove friend_remove = 7;
|
||||||
|
NotifyNewChatSession new_chat_session_info = 5;//会话信息
|
||||||
|
NotifyNewMessage new_message_info = 6;//消息信息
|
||||||
|
}
|
||||||
|
}
|
||||||
28
ChatClient/proto/speech_recognition.proto
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
语音识别服务器的子服务注册信息: /service/speech/instance_id
|
||||||
|
服务名称:/service/speech
|
||||||
|
实例ID: instance_id 每个能够提供用户操作服务的子服务器唯一ID
|
||||||
|
当服务发现的时候,通过 /service/speech 进行服务发现,就可以发现所有的能够提供用户操作的实例信息了
|
||||||
|
*/
|
||||||
|
syntax = "proto3";
|
||||||
|
package bite_im;
|
||||||
|
|
||||||
|
option cc_generic_services = true;
|
||||||
|
|
||||||
|
message SpeechRecognitionReq {
|
||||||
|
string request_id = 1;
|
||||||
|
bytes speech_content = 2;
|
||||||
|
optional string user_id = 3;
|
||||||
|
optional string session_id = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SpeechRecognitionRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
string recognition_result = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
service SpeechService {
|
||||||
|
rpc SpeechRecognition(SpeechRecognitionReq) returns (SpeechRecognitionRsp);
|
||||||
|
}
|
||||||
161
ChatClient/proto/user.proto
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
用户操作服务器的子服务注册信息: /service/user/instance_id
|
||||||
|
服务名称:/service/user
|
||||||
|
实例ID: instance_id 每个能够提供用户操作服务的子服务器唯一ID
|
||||||
|
当服务发现的时候,通过 /service/user 进行服务发现,就可以发现所有的能够提供用户操作的实例信息了
|
||||||
|
*/
|
||||||
|
syntax = "proto3";
|
||||||
|
package bite_im;
|
||||||
|
import "base.proto";
|
||||||
|
|
||||||
|
option cc_generic_services = true;
|
||||||
|
|
||||||
|
//----------------------------
|
||||||
|
//用户名注册
|
||||||
|
message UserRegisterReq {
|
||||||
|
string request_id = 1;
|
||||||
|
string nickname = 2;
|
||||||
|
string password = 3;
|
||||||
|
string verify_code_id = 4;
|
||||||
|
string verify_code = 5;
|
||||||
|
}
|
||||||
|
message UserRegisterRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
}
|
||||||
|
//----------------------------
|
||||||
|
//用户名登录
|
||||||
|
message UserLoginReq {
|
||||||
|
string request_id = 1;
|
||||||
|
string nickname = 2;
|
||||||
|
string password = 3;
|
||||||
|
string verify_code_id = 4;
|
||||||
|
string verify_code = 5;
|
||||||
|
}
|
||||||
|
message UserLoginRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
string login_session_id = 4;
|
||||||
|
}
|
||||||
|
//----------------------------
|
||||||
|
//手机号验证码获取
|
||||||
|
message PhoneVerifyCodeReq {
|
||||||
|
string request_id = 1;
|
||||||
|
string phone_number = 2;
|
||||||
|
}
|
||||||
|
message PhoneVerifyCodeRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
string verify_code_id = 4;
|
||||||
|
}
|
||||||
|
//----------------------------
|
||||||
|
//手机号注册
|
||||||
|
message PhoneRegisterReq {
|
||||||
|
string request_id = 1;
|
||||||
|
string phone_number = 2;
|
||||||
|
string verify_code_id = 3;
|
||||||
|
string verify_code = 4;
|
||||||
|
}
|
||||||
|
message PhoneRegisterRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
}
|
||||||
|
//----------------------------
|
||||||
|
//手机号登录
|
||||||
|
message PhoneLoginReq {
|
||||||
|
string request_id = 1;
|
||||||
|
string phone_number = 2;
|
||||||
|
string verify_code_id = 3;
|
||||||
|
string verify_code = 4;
|
||||||
|
}
|
||||||
|
message PhoneLoginRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
string login_session_id = 4;
|
||||||
|
}
|
||||||
|
//个人信息获取-这个只用于获取当前登录用户的信息
|
||||||
|
// 客户端传递的时候只需要填充session_id即可
|
||||||
|
//其他个人/好友信息的获取在好友操作中完成
|
||||||
|
message GetUserInfoReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string user_id = 2;
|
||||||
|
optional string session_id = 3;
|
||||||
|
}
|
||||||
|
message GetUserInfoRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
UserInfo user_info = 4;
|
||||||
|
}
|
||||||
|
//----------------------------
|
||||||
|
//用户头像修改
|
||||||
|
message SetUserAvatarReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string user_id = 2;
|
||||||
|
optional string session_id = 3;
|
||||||
|
bytes avatar = 4;
|
||||||
|
}
|
||||||
|
message SetUserAvatarRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
}
|
||||||
|
//----------------------------
|
||||||
|
//用户昵称修改
|
||||||
|
message SetUserNicknameReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string user_id = 2;
|
||||||
|
optional string session_id = 3;
|
||||||
|
string nickname = 4;
|
||||||
|
}
|
||||||
|
message SetUserNicknameRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
}
|
||||||
|
//----------------------------
|
||||||
|
//用户签名修改
|
||||||
|
message SetUserDescriptionReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string user_id = 2;
|
||||||
|
optional string session_id = 3;
|
||||||
|
string description = 4;
|
||||||
|
}
|
||||||
|
message SetUserDescriptionRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
}
|
||||||
|
//----------------------------
|
||||||
|
//用户手机修改
|
||||||
|
message SetUserPhoneNumberReq {
|
||||||
|
string request_id = 1;
|
||||||
|
optional string user_id = 2;
|
||||||
|
optional string session_id = 3;
|
||||||
|
string phone_number = 4;
|
||||||
|
string phone_verify_code_id = 5;
|
||||||
|
string phone_verify_code = 6;
|
||||||
|
}
|
||||||
|
message SetUserPhoneNumberRsp {
|
||||||
|
string request_id = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string errmsg = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
service UserService {
|
||||||
|
rpc UserRegister(UserRegisterReq) returns (UserRegisterRsp);
|
||||||
|
rpc UserLogin(UserLoginReq) returns (UserLoginRsp);
|
||||||
|
rpc GetPhoneVerifyCode(PhoneVerifyCodeReq) returns (PhoneVerifyCodeRsp);
|
||||||
|
rpc PhoneRegister(PhoneRegisterReq) returns (PhoneRegisterRsp);
|
||||||
|
rpc PhoneLogin(PhoneLoginReq) returns (PhoneLoginRsp);
|
||||||
|
rpc GetUserInfo(GetUserInfoReq) returns (GetUserInfoRsp);
|
||||||
|
rpc SetUserAvatar(SetUserAvatarReq) returns (SetUserAvatarRsp);
|
||||||
|
rpc SetUserNickname(SetUserNicknameReq) returns (SetUserNicknameRsp);
|
||||||
|
rpc SetUserDescription(SetUserDescriptionReq) returns (SetUserDescriptionRsp);
|
||||||
|
rpc SetUserPhoneNumber(SetUserPhoneNumberReq) returns (SetUserPhoneNumberRsp);
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@
|
|||||||
<file>resource/image/apply_inactive.png</file>
|
<file>resource/image/apply_inactive.png</file>
|
||||||
<file>resource/image/checked.png</file>
|
<file>resource/image/checked.png</file>
|
||||||
<file>resource/image/cross.png</file>
|
<file>resource/image/cross.png</file>
|
||||||
|
<file>resource/image/defaultAv.png</file>
|
||||||
<file>resource/image/defaultAvatar.png</file>
|
<file>resource/image/defaultAvatar.png</file>
|
||||||
<file>resource/image/file.png</file>
|
<file>resource/image/file.png</file>
|
||||||
<file>resource/image/friend_active.png</file>
|
<file>resource/image/friend_active.png</file>
|
||||||
@ -21,6 +22,5 @@
|
|||||||
<file>resource/image/sound_active.png</file>
|
<file>resource/image/sound_active.png</file>
|
||||||
<file>resource/image/submit.png</file>
|
<file>resource/image/submit.png</file>
|
||||||
<file>resource/image/unchecked.png</file>
|
<file>resource/image/unchecked.png</file>
|
||||||
<file>resource/image/defaultAv.png</file>
|
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
217
ChatClient/src/addfrienddialog.cpp
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
#include "addfrienddialog.h"
|
||||||
|
|
||||||
|
#include "model/datacenter.h"
|
||||||
|
|
||||||
|
using namespace model;
|
||||||
|
|
||||||
|
/////////////////////////////////////////
|
||||||
|
//表示一个好友搜索的结果
|
||||||
|
/////////////////////////////////////////
|
||||||
|
FriendResultItem::FriendResultItem(const UserInfo& userInfo)
|
||||||
|
:userInfo(userInfo)
|
||||||
|
{
|
||||||
|
// 1. 设置基本属性
|
||||||
|
this->setFixedHeight(70);
|
||||||
|
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
// 2. 创建布局管理器
|
||||||
|
QGridLayout* layout = new QGridLayout();
|
||||||
|
layout->setVerticalSpacing(0);
|
||||||
|
layout->setHorizontalSpacing(10);
|
||||||
|
layout->setContentsMargins(0, 0, 20, 0);
|
||||||
|
this->setLayout(layout);
|
||||||
|
|
||||||
|
// 3. 创建头像
|
||||||
|
QPushButton* avatarBtn = new QPushButton();
|
||||||
|
avatarBtn->setFixedSize(50, 50);
|
||||||
|
avatarBtn->setIconSize(QSize(50, 50));
|
||||||
|
avatarBtn->setIcon(userInfo.avatar);
|
||||||
|
avatarBtn->setStyleSheet("QPushButton { background-color: transparent; }");
|
||||||
|
|
||||||
|
// 4. 创建昵称
|
||||||
|
QLabel* nameLabel = new QLabel();
|
||||||
|
nameLabel->setFixedHeight(35); // 整个 Item 高度是 70. 昵称和个性签名各自占一半.
|
||||||
|
nameLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
nameLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||||
|
nameLabel->setStyleSheet("QLabel { font-size: 16px; font-weight: 700;}");
|
||||||
|
nameLabel->setText(userInfo.nickname);
|
||||||
|
|
||||||
|
// 5. 创建个性签名
|
||||||
|
QLabel* descLabel = new QLabel();
|
||||||
|
descLabel->setFixedHeight(35);
|
||||||
|
descLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
descLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||||
|
descLabel->setStyleSheet("QLabel { font-size: 14px; }");
|
||||||
|
descLabel->setText(userInfo.description);
|
||||||
|
|
||||||
|
// 6. 创建添加好友按钮
|
||||||
|
addBtn = new QPushButton();
|
||||||
|
addBtn->setFixedSize(100, 40);
|
||||||
|
addBtn->setText("添加好友");
|
||||||
|
QString btnStyle = "QPushButton { border: none; background-color: rgb(137, 217, 97); color: rgb(255, 255, 255); border-radius: 10px;} ";
|
||||||
|
btnStyle += "QPushButton:pressed { background-color: rgb(200, 200, 200); }";
|
||||||
|
addBtn->setStyleSheet(btnStyle);
|
||||||
|
|
||||||
|
// 7. 把上述内容, 添加到布局管理器中
|
||||||
|
layout->addWidget(avatarBtn, 0, 0, 2, 1);
|
||||||
|
layout->addWidget(nameLabel, 0, 1);
|
||||||
|
layout->addWidget(descLabel, 1, 1);
|
||||||
|
layout->addWidget(addBtn, 0, 2, 2, 1);
|
||||||
|
|
||||||
|
// 8. 连接信号槽
|
||||||
|
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;}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////
|
||||||
|
//整个搜索好友的窗口
|
||||||
|
/////////////////////////////////////////
|
||||||
|
AddFriendDialog::AddFriendDialog(QWidget *parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
{
|
||||||
|
// 1. 设置基本属性
|
||||||
|
this->setFixedSize(500, 500);
|
||||||
|
this->setWindowTitle("添加好友");
|
||||||
|
this->setWindowIcon(QIcon(":/resource/image/logo.png"));
|
||||||
|
this->setStyleSheet("QDialog {background-color: rgb(255, 255, 255); }");
|
||||||
|
this->setAttribute(Qt::WA_DeleteOnClose); // 不要忘记这个属性!!!
|
||||||
|
|
||||||
|
// 2. 添加布局管理器
|
||||||
|
layout = new QGridLayout();
|
||||||
|
layout->setSpacing(10);
|
||||||
|
layout->setContentsMargins(20, 20, 20, 0);
|
||||||
|
this->setLayout(layout);
|
||||||
|
|
||||||
|
// 3. 创建搜索框
|
||||||
|
searchEdit = new QLineEdit();
|
||||||
|
searchEdit->setFixedHeight(50);
|
||||||
|
searchEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
QString style = "QLineEdit { border: none; border-radius: 10px; font-size: 16px; color: rgb(129, 129, 129); background-color: rgb(240, 240, 240); padding-left: 5px;}";
|
||||||
|
searchEdit->setStyleSheet(style);
|
||||||
|
searchEdit->setPlaceholderText("按手机号/用户序号/昵称搜索");
|
||||||
|
layout->addWidget(searchEdit, 0, 0, 1, 8);
|
||||||
|
|
||||||
|
// 4. 创建搜索按钮
|
||||||
|
QPushButton* searchBtn = new QPushButton();
|
||||||
|
searchBtn->setFixedSize(50, 50);
|
||||||
|
searchBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
searchBtn->setIconSize(QSize(30, 30));
|
||||||
|
searchBtn->setIcon(QIcon(":/resource/image/search.png"));
|
||||||
|
QString btnStyle = "QPushButton { border: none; background-color: rgb(240, 240, 240); border-radius: 10px; }";
|
||||||
|
btnStyle += "QPushButton:hover { background-color: rgb(220, 220, 220); } QPushButton:pressed { background-color: rgb(200, 200, 200); } ";
|
||||||
|
searchBtn->setStyleSheet(btnStyle);
|
||||||
|
layout->addWidget(searchBtn, 0, 8, 1, 1);
|
||||||
|
|
||||||
|
// 5. 添加滚动区域
|
||||||
|
initResultArea();
|
||||||
|
|
||||||
|
// 构造假的数据, 验证界面效果
|
||||||
|
#if TEST_UI
|
||||||
|
QIcon avatar(":/resource/image/defaultAvatar.png");
|
||||||
|
for (int i = 0; i < 20; ++i) {
|
||||||
|
// new 出来这个对象, 再往 addResult 中添加. FriendResultItem 中持有了 UserInfo 的 const 引用. 需要确保引用是有效的引用
|
||||||
|
UserInfo* userInfo = new UserInfo();
|
||||||
|
userInfo->userId = QString::number(1000 + i);
|
||||||
|
userInfo->nickname = "张三" + QString::number(i);
|
||||||
|
userInfo->description = "这是一段个性签名";
|
||||||
|
userInfo->avatar = avatar;
|
||||||
|
this->addResult(*userInfo);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
//
|
||||||
|
// 6. 连接信号槽
|
||||||
|
connect(searchBtn, &QPushButton::clicked, this, &AddFriendDialog::clickSearchBtn);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddFriendDialog::initResultArea()
|
||||||
|
{
|
||||||
|
// 1. 创建滚动区域对象
|
||||||
|
QScrollArea* scrollArea = new QScrollArea();
|
||||||
|
scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
scrollArea->setWidgetResizable(true);
|
||||||
|
scrollArea->horizontalScrollBar()->setStyleSheet("QScrollBar:horizontal {height: 0;} ");
|
||||||
|
scrollArea->verticalScrollBar()->setStyleSheet("QScrollBar:vertical {width: 2px; background-color: rgb(255, 255, 255);}");
|
||||||
|
scrollArea->setStyleSheet("QScrollArea { border: none; }");
|
||||||
|
layout->addWidget(scrollArea, 1, 0, 1, 9);
|
||||||
|
|
||||||
|
// 2. 创建 QWidget
|
||||||
|
resultContainer = new QWidget();
|
||||||
|
resultContainer->setObjectName("resultContainer");
|
||||||
|
resultContainer->setStyleSheet("#resultContainer { background-color: rgb(255, 255, 255); } ");
|
||||||
|
scrollArea->setWidget(resultContainer);
|
||||||
|
|
||||||
|
// 3. 给这个 QWidget 里面添加元素, 需要给它创建垂直的布局管理器
|
||||||
|
QVBoxLayout* vlayout = new QVBoxLayout();
|
||||||
|
vlayout->setSpacing(0);
|
||||||
|
vlayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
resultContainer->setLayout(vlayout);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddFriendDialog::addResult(const UserInfo& userInfo)
|
||||||
|
{
|
||||||
|
FriendResultItem* item = new FriendResultItem(userInfo);
|
||||||
|
resultContainer->layout()->addWidget(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddFriendDialog::clear()
|
||||||
|
{
|
||||||
|
// 从后向前遍历
|
||||||
|
QVBoxLayout* layout = dynamic_cast<QVBoxLayout*>(resultContainer->layout());
|
||||||
|
for (int i = layout->count() - 1; i >= 0; i--) {
|
||||||
|
QLayoutItem* layoutItem = layout->takeAt(i);
|
||||||
|
if (layoutItem == nullptr || layoutItem->widget() == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//删除这里面所持有的元素
|
||||||
|
delete layoutItem->widget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<UserInfo>* searchResult = dataCenter->getSearchUserResult();
|
||||||
|
if (searchResult == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->clear();
|
||||||
|
for (const auto& u : *searchResult) {
|
||||||
|
this->addResult(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
341
ChatClient/src/choosefrienddialog.cpp
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
#include "choosefrienddialog.h"
|
||||||
|
|
||||||
|
#include "model/datacenter.h"
|
||||||
|
#include "toast.h"
|
||||||
|
|
||||||
|
using namespace model;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
/// 选择好友窗口中的一个 元素/好友项
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
ChooseFriendItem::ChooseFriendItem(ChooseFriendDialog* owner, const QString& userId, const QIcon& avatar, const QString& name, bool checked)
|
||||||
|
:userId(userId)
|
||||||
|
{
|
||||||
|
// 1. 设置控件的基本属性
|
||||||
|
this->setFixedHeight(50);
|
||||||
|
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
// 2. 设置布局管理器
|
||||||
|
QHBoxLayout* layout = new QHBoxLayout();
|
||||||
|
layout->setSpacing(10);
|
||||||
|
layout->setContentsMargins(20, 0, 20, 0);
|
||||||
|
this->setLayout(layout);
|
||||||
|
|
||||||
|
// 3. 创建复选框
|
||||||
|
checkBox = new QCheckBox();
|
||||||
|
checkBox->setChecked(checked);
|
||||||
|
checkBox->setFixedSize(25, 25);
|
||||||
|
QString style = "QCheckBox { background-color: transparent; } QCheckBox::indicator { width: 20px; height: 20px; image: url(:/resource/image/unchecked.png);}";
|
||||||
|
style += "QCheckBox::indicator:checked { image: url(:/resource/image/checked.png);}";
|
||||||
|
checkBox->setStyleSheet(style);
|
||||||
|
|
||||||
|
// 4. 创建头像
|
||||||
|
avatarBtn = new QPushButton();
|
||||||
|
avatarBtn->setFixedSize(40, 40);
|
||||||
|
avatarBtn->setIconSize(QSize(40, 40));
|
||||||
|
avatarBtn->setIcon(avatar);
|
||||||
|
|
||||||
|
// 5. 创建名字
|
||||||
|
nameLabel = new QLabel();
|
||||||
|
nameLabel->setText(name);
|
||||||
|
nameLabel->setStyleSheet("QLabel {background-color: transparent;}");
|
||||||
|
|
||||||
|
// 6. 添加上述内容到布局管理器中
|
||||||
|
layout->addWidget(checkBox);
|
||||||
|
layout->addWidget(avatarBtn);
|
||||||
|
layout->addWidget(nameLabel);
|
||||||
|
|
||||||
|
// 7. 连接信号槽
|
||||||
|
connect(checkBox, &QCheckBox::toggled, this, [=](bool checked) {
|
||||||
|
if (checked) {
|
||||||
|
// 勾选了复选框, 把当前这个 Item, 添加到右侧的已选择区域
|
||||||
|
owner->addSelectedFriend(userId, avatar, name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 取消勾选
|
||||||
|
owner->deleteSelectedFriend(userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChooseFriendItem::paintEvent(QPaintEvent* event)
|
||||||
|
{
|
||||||
|
//根据鼠标进入的状态,来决定绘制成不同的颜色
|
||||||
|
QPainter painter(this);
|
||||||
|
if (isHover) {
|
||||||
|
//绘制成深色
|
||||||
|
painter.fillRect(this->rect(), QColor(230, 230, 230));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//绘制成浅色
|
||||||
|
painter.fillRect(this->rect(), QColor(255, 255, 255));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChooseFriendItem::enterEvent(QEnterEvent* event)
|
||||||
|
{
|
||||||
|
(void)event;
|
||||||
|
isHover = true;
|
||||||
|
//相当于界面更新
|
||||||
|
this->update();
|
||||||
|
//this->repaint(); //也可以
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChooseFriendItem::leaveEvent(QEvent* event)
|
||||||
|
{
|
||||||
|
(void)event;
|
||||||
|
isHover = false;
|
||||||
|
this->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
ChooseFriendDialog::ChooseFriendDialog(QWidget* parent, const QString& userId)
|
||||||
|
: QDialog(parent),
|
||||||
|
userId(userId)
|
||||||
|
{
|
||||||
|
// 1. 设置窗口的基本属性
|
||||||
|
this->setWindowTitle("选择好友");
|
||||||
|
this->setWindowIcon(QIcon(":/resource/image/logo.png"));
|
||||||
|
this->setFixedSize(750, 550);
|
||||||
|
this->setStyleSheet("QDialog { background-color: rgb(255, 255, 255);}");
|
||||||
|
this->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
// 2. 创建布局管理器
|
||||||
|
QHBoxLayout* layout = new QHBoxLayout();
|
||||||
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
layout->setSpacing(0);
|
||||||
|
this->setLayout(layout);
|
||||||
|
|
||||||
|
// 3. 针对左侧窗口进行初始化
|
||||||
|
initLeft(layout);
|
||||||
|
|
||||||
|
// 4. 针对右侧窗口进行初始化
|
||||||
|
initRight(layout);
|
||||||
|
|
||||||
|
//加载数据到窗口
|
||||||
|
initData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChooseFriendDialog::initLeft(QHBoxLayout* layout)
|
||||||
|
{
|
||||||
|
// 1. 创建滚动区域
|
||||||
|
QScrollArea* scrollArea = new QScrollArea();
|
||||||
|
scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
scrollArea->setWidgetResizable(true);
|
||||||
|
scrollArea->horizontalScrollBar()->setStyleSheet("QScrollBar:horizontal { height: 0px;}");
|
||||||
|
scrollArea->verticalScrollBar()->setStyleSheet("QScrollBar:vertical { width: 2px; background-color: rgb(255, 255, 255) }");
|
||||||
|
scrollArea->setStyleSheet("QScrollArea { border:none; }");
|
||||||
|
layout->addWidget(scrollArea, 1);
|
||||||
|
|
||||||
|
// 2. 创建 QWidget 设置到滚动区域中.
|
||||||
|
totalContainer = new QWidget();
|
||||||
|
totalContainer->setObjectName("totalContainer");
|
||||||
|
totalContainer->setStyleSheet("#totalContainer { background-color: rgb(255, 255, 255); }");
|
||||||
|
scrollArea->setWidget(totalContainer);
|
||||||
|
|
||||||
|
// 3. 创建左侧子窗口内部的 垂直布局管理器
|
||||||
|
QVBoxLayout* vlayout = new QVBoxLayout();
|
||||||
|
vlayout->setSpacing(0);
|
||||||
|
vlayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
vlayout->setAlignment(Qt::AlignTop);
|
||||||
|
totalContainer->setLayout(vlayout);
|
||||||
|
|
||||||
|
// 还需要进一步的添加 vlayout 内部的元素, 才能看到效果!
|
||||||
|
// 此处也是先构造测试数据, 后续接入服务器之后, 从服务器拿到真实的好友列表, 再添加真实的数据
|
||||||
|
#if TEST_UI
|
||||||
|
QIcon defaultAvatar(":/resource/image/defaultAvatar.png");
|
||||||
|
for (int i = 0; i < 15; ++i) {
|
||||||
|
this->addFriend(QString::number(1000 + i), defaultAvatar, "张三" + QString::number(i), false);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChooseFriendDialog::initRight(QHBoxLayout* layout)
|
||||||
|
{
|
||||||
|
// 1. 创建右侧的布局管理器
|
||||||
|
QGridLayout* gridLayout = new QGridLayout();
|
||||||
|
gridLayout->setContentsMargins(20, 0, 20, 20);
|
||||||
|
gridLayout->setSpacing(10);
|
||||||
|
layout->addLayout(gridLayout, 1);
|
||||||
|
|
||||||
|
// 2. 创建 "提示" label
|
||||||
|
QLabel* tipLabel = new QLabel();
|
||||||
|
tipLabel->setText("选择联系人");
|
||||||
|
tipLabel->setFixedHeight(30);
|
||||||
|
tipLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
tipLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||||
|
tipLabel->setStyleSheet("QLabel { font-size: 16px; font-weight: 700}");
|
||||||
|
|
||||||
|
// 3. 创建滚动区域
|
||||||
|
QScrollArea* scrollArea = new QScrollArea();
|
||||||
|
scrollArea->setWidgetResizable(true);
|
||||||
|
scrollArea->verticalScrollBar()->setStyleSheet("QScrollBar:vertical { width: 2px; background-color: rgb(255, 255, 255);}");
|
||||||
|
scrollArea->horizontalScrollBar()->setStyleSheet("QScrollBar:horizontal {height: 0px;}");
|
||||||
|
scrollArea->setStyleSheet("QScrollArea {border: none;}");
|
||||||
|
scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
|
||||||
|
// 4. 创建滚动区域中的 QWidget
|
||||||
|
selectedContainer = new QWidget();
|
||||||
|
selectedContainer->setObjectName("selectedContainer");
|
||||||
|
selectedContainer->setStyleSheet("#selectedContainer { background-color: rgb(255, 255, 255); }");
|
||||||
|
scrollArea->setWidget(selectedContainer);
|
||||||
|
|
||||||
|
// 5. 创建 selectedContainer 中的 "垂直布局"
|
||||||
|
QVBoxLayout* vlayout = new QVBoxLayout();
|
||||||
|
vlayout->setSpacing(0);
|
||||||
|
vlayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
vlayout->setAlignment(Qt::AlignTop);
|
||||||
|
selectedContainer->setLayout(vlayout);
|
||||||
|
|
||||||
|
// 6. 创建底部按钮
|
||||||
|
QString style = "QPushButton { color: rgb(7, 191, 96); background-color: rgb(240, 240, 240); border: none; border-radius: 5px;}";
|
||||||
|
style += "QPushButton:hover { background-color: rgb(220, 220, 220); } QPushButton:pressed { background-color: rgb(200, 200, 200); }";
|
||||||
|
|
||||||
|
QPushButton* okBtn = new QPushButton();
|
||||||
|
okBtn->setFixedHeight(40);
|
||||||
|
okBtn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
okBtn->setText("完成");
|
||||||
|
okBtn->setStyleSheet(style);
|
||||||
|
|
||||||
|
QPushButton* cancelBtn = new QPushButton();
|
||||||
|
cancelBtn->setFixedHeight(40);
|
||||||
|
cancelBtn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
cancelBtn->setText("取消");
|
||||||
|
cancelBtn->setStyleSheet(style);
|
||||||
|
|
||||||
|
// 7. 把上述控件添加到布局中
|
||||||
|
gridLayout->addWidget(tipLabel, 0, 0, 1, 9);
|
||||||
|
gridLayout->addWidget(scrollArea, 1, 0, 1, 9);
|
||||||
|
gridLayout->addWidget(okBtn, 2, 1, 1, 3);
|
||||||
|
gridLayout->addWidget(cancelBtn, 2, 5, 1, 3);
|
||||||
|
|
||||||
|
// 构造一些数据用来进行测试界面
|
||||||
|
#if 0
|
||||||
|
// 此处的数据通过勾选左侧列表来生成.
|
||||||
|
QIcon defaultAvatar(":/resource/image/defaultAvatar.png");
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
this->addSelectedFriend(QString::number(1000 + i), defaultAvatar, "张三" + QString::number(i));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 8. 添加信号槽, 处理 ok 和 cancel 的点击
|
||||||
|
connect(okBtn, &QPushButton::clicked, this, &ChooseFriendDialog::clickOkBtn);
|
||||||
|
connect(cancelBtn, &QPushButton::clicked, this, [=]() {
|
||||||
|
this->close();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChooseFriendDialog::clickOkBtn()
|
||||||
|
{
|
||||||
|
//根据选中好友列表中的元素,得到所有的要创建群聊会话的用户id列表
|
||||||
|
QList<QString> userIdList = generateMemberList();
|
||||||
|
if (userIdList.size() < 3) {
|
||||||
|
Toast::showMessage("群聊中的成员不足三个,无法创建群聊");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//发送网络请求,创建群聊
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
dataCenter->createGroupChatSessionAsync(userIdList);
|
||||||
|
|
||||||
|
//关闭当前窗口
|
||||||
|
this->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QString> ChooseFriendDialog::generateMemberList()
|
||||||
|
{
|
||||||
|
QList<QString> result;
|
||||||
|
|
||||||
|
//把自己添加到结果中
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
if (dataCenter->getMyselfsync() == nullptr) {
|
||||||
|
LOG() << "个人信息尚未加载...";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.push_back(dataCenter->getMyselfsync()->userId);
|
||||||
|
|
||||||
|
//遍历选中的列表
|
||||||
|
QVBoxLayout* layout = dynamic_cast<QVBoxLayout*>(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<ChooseFriendItem*>(item->widget());
|
||||||
|
result.push_back(chooseFriendItem->getUserId());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ChooseFriendDialog::initData()
|
||||||
|
{
|
||||||
|
//遍历好友列表,把好友列表中的元素添加到这个窗口界面上
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
QList<UserInfo>* 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);
|
||||||
|
totalContainer->layout()->addWidget(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChooseFriendDialog::addSelectedFriend(const QString& userId, const QIcon& avatar, const QString& name)
|
||||||
|
{
|
||||||
|
ChooseFriendItem* item = new ChooseFriendItem(this, userId, avatar, name, true);
|
||||||
|
selectedContainer->layout()->addWidget(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChooseFriendDialog::deleteSelectedFriend(const QString& userId)
|
||||||
|
{
|
||||||
|
//遍历selectedContainer中的所有的Item,并对比其userId
|
||||||
|
QVBoxLayout* vlayout = dynamic_cast<QVBoxLayout*>(selectedContainer->layout());
|
||||||
|
//由于是要遍历加删除所以,要从后向前进行
|
||||||
|
for (int i = vlayout->count() - 1; i >= 0; --i) {
|
||||||
|
auto* item = vlayout->itemAt(i);
|
||||||
|
if (item == nullptr || item->widget() == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ChooseFriendItem* chooseFriendItem = dynamic_cast<ChooseFriendItem*>(item->widget());
|
||||||
|
//判定当前的Item的userId是否是要删除的userId
|
||||||
|
if (chooseFriendItem->getUserId() != userId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
vlayout->removeWidget(chooseFriendItem);
|
||||||
|
//会报错!!!
|
||||||
|
// 要释放对象,不是直接delete,而是告诉qt让qt在信号槽这一轮
|
||||||
|
// 执行完成后,自行负责释放
|
||||||
|
//delete chooseFriendItem;
|
||||||
|
chooseFriendItem->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
//再遍历一下左侧的列表,把左侧对应的item的checkBox的勾选状态给取消
|
||||||
|
QVBoxLayout* vlayoutLeft = dynamic_cast<QVBoxLayout*>(totalContainer->layout());
|
||||||
|
for(int i = 0; i < vlayoutLeft->count(); i++) {
|
||||||
|
auto* item = vlayoutLeft->itemAt(i);
|
||||||
|
if (item == nullptr || item->widget() == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ChooseFriendItem* chooseFriendItem = dynamic_cast<ChooseFriendItem*>(item->widget());
|
||||||
|
if (chooseFriendItem->getUserId() != userId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//已找到,取消勾选状态
|
||||||
|
chooseFriendItem->getCheckBox()->setChecked(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
147
ChatClient/src/groupsessiondetailwidget.cpp
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
#include "groupsessiondetailwidget.h"
|
||||||
|
#include "model/datacenter.h"
|
||||||
|
|
||||||
|
using namespace model;
|
||||||
|
|
||||||
|
GroupSessionDetailWidget::GroupSessionDetailWidget(QWidget *parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
{
|
||||||
|
//设置窗口的基本属性
|
||||||
|
this->setFixedSize(410, 600);
|
||||||
|
this->setWindowTitle("群组详情");
|
||||||
|
this->setWindowIcon(QIcon(":resource/image/logo.png"));
|
||||||
|
this->setStyleSheet("QDialog { background-color: rgb(255, 255, 255); }");
|
||||||
|
this->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
//创建界面布局管理器
|
||||||
|
QVBoxLayout* vlayout = new QVBoxLayout();
|
||||||
|
vlayout->setSpacing(10);
|
||||||
|
vlayout->setContentsMargins(50, 20, 50, 50);
|
||||||
|
vlayout->setAlignment(Qt::AlignTop);
|
||||||
|
this->setLayout(vlayout);
|
||||||
|
|
||||||
|
//创建滚动区域
|
||||||
|
//创建qscrollarea的对象
|
||||||
|
QScrollArea* scrollArea = new QScrollArea();
|
||||||
|
scrollArea->setWidgetResizable(true);
|
||||||
|
scrollArea->verticalScrollBar()->setStyleSheet("QScrollBar:vertical { width: 2px; background-color: rgb(255, 255, 255); }");
|
||||||
|
scrollArea->horizontalScrollBar()->setStyleSheet("QScrollBar:horizontal { height: 0; }");
|
||||||
|
scrollArea->setFixedSize(310, 350);
|
||||||
|
scrollArea->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
scrollArea->setStyleSheet("QWidget { background-color: transparent; border: none; }");
|
||||||
|
|
||||||
|
//创建一个qscrollarea的内部的qwidget
|
||||||
|
QWidget* container = new QWidget();
|
||||||
|
scrollArea->setWidget(container);
|
||||||
|
|
||||||
|
//给container添加一个网格布局
|
||||||
|
glayout = new QGridLayout();
|
||||||
|
glayout->setSpacing(0);
|
||||||
|
glayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
glayout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
||||||
|
container->setLayout(glayout);
|
||||||
|
|
||||||
|
//把滚动区域, 添加到布局管理器中
|
||||||
|
vlayout->addWidget(scrollArea);
|
||||||
|
|
||||||
|
//添加按钮
|
||||||
|
AvatarItem* addBtn = new AvatarItem(QIcon(":/resource/image/cross.png"), "添加");
|
||||||
|
glayout->addWidget(addBtn, 0, 0);
|
||||||
|
|
||||||
|
//添加群聊名称
|
||||||
|
QLabel* groupNameTag = new QLabel();
|
||||||
|
groupNameTag->setText("群聊名称");
|
||||||
|
groupNameTag->setStyleSheet("QLabel {font-weight: 700; font-size: 16px;}");
|
||||||
|
groupNameTag->setFixedHeight(50);
|
||||||
|
groupNameTag->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
|
||||||
|
// 设置文字在 QLabel 内部的对齐方式.
|
||||||
|
groupNameTag->setAlignment(Qt::AlignBottom);
|
||||||
|
// 这里设置的 QLabel 在布局管理器中的对齐方式.
|
||||||
|
vlayout->addWidget(groupNameTag);
|
||||||
|
|
||||||
|
//添加群聊的真实名字和修改按钮
|
||||||
|
//创建水平布局
|
||||||
|
QHBoxLayout* hlayout = new QHBoxLayout();
|
||||||
|
hlayout->setSpacing(0);
|
||||||
|
hlayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
vlayout->addLayout(hlayout);
|
||||||
|
|
||||||
|
//创建群聊名字的label
|
||||||
|
groupNameLabel = new QLabel();
|
||||||
|
groupNameLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
|
||||||
|
groupNameLabel->setFixedHeight(50);
|
||||||
|
groupNameLabel->setStyleSheet("QLabel { font-size: 18px; }");
|
||||||
|
hlayout->addWidget(groupNameLabel);
|
||||||
|
|
||||||
|
//创建修改按钮
|
||||||
|
QPushButton* modifyBtn = new QPushButton();
|
||||||
|
modifyBtn->setFixedSize(30, 30);
|
||||||
|
modifyBtn->setIconSize(QSize(20, 20));
|
||||||
|
modifyBtn->setIcon(QIcon(":/resource/image/modify.png"));
|
||||||
|
modifyBtn->setIcon(QIcon(":/resource/image/modify.png"));
|
||||||
|
modifyBtn->setStyleSheet("QPushButton { border: none; background-color: transparent; } QPushButton:pressed { background-color: rgb(230, 230, 230); }");
|
||||||
|
hlayout->addWidget(modifyBtn, 0, Qt::AlignRight | Qt::AlignVCenter);
|
||||||
|
|
||||||
|
// 退出群聊按钮
|
||||||
|
QPushButton* exitGroupBtn = new QPushButton();
|
||||||
|
exitGroupBtn->setText("退出群聊");
|
||||||
|
exitGroupBtn->setFixedHeight(50);
|
||||||
|
exitGroupBtn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
QString btnStyle = "QPushButton { border: 1px solid rgb(90, 90, 90); border-radius: 5px; background-color: transparent; color: rgb(0, 0, 0); }";
|
||||||
|
btnStyle += "QPushButton:pressed { background-color: rgb(230, 230, 230); }";
|
||||||
|
exitGroupBtn->setStyleSheet(btnStyle);
|
||||||
|
vlayout->addWidget(exitGroupBtn);
|
||||||
|
|
||||||
|
|
||||||
|
#if TEST_UI
|
||||||
|
groupNameLabel->setText("人类吃喝行为研究小组");
|
||||||
|
QIcon avatar(":/resource/image/defaultAvatar.png");
|
||||||
|
for (int i = 0; i < 20; ++i) {
|
||||||
|
AvatarItem* item = new AvatarItem(avatar, "张三" + QString::number(i));
|
||||||
|
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<UserInfo>* 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)
|
||||||
|
{
|
||||||
|
//拿到滚动区域的布局管理器
|
||||||
|
const int MAX_COL = 4;
|
||||||
|
if (curCol >= MAX_COL) {
|
||||||
|
// 换行操作
|
||||||
|
++curRow;
|
||||||
|
curCol = 0;
|
||||||
|
}
|
||||||
|
glayout->addWidget(avatarItem, curRow, curCol);
|
||||||
|
++curCol;
|
||||||
|
|
||||||
|
}
|
||||||
416
ChatClient/src/historymessagewidget.cpp
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
#include "historymessagewidget.h"
|
||||||
|
|
||||||
|
#include "model/datacenter.h"
|
||||||
|
#include "toast.h"
|
||||||
|
#include "soundrecorder.h"
|
||||||
|
|
||||||
|
using namespace model;
|
||||||
|
|
||||||
|
//工厂函数
|
||||||
|
HistoryItem* HistoryItem::makeHistoryItem(const Message& message)
|
||||||
|
{
|
||||||
|
// 1. 创建出对象
|
||||||
|
HistoryItem* item = new HistoryItem();
|
||||||
|
item->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||||
|
|
||||||
|
// 2. 创建布局
|
||||||
|
QGridLayout* layout = new QGridLayout();
|
||||||
|
layout->setVerticalSpacing(0);
|
||||||
|
layout->setHorizontalSpacing(10);
|
||||||
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
item->setLayout(layout);
|
||||||
|
|
||||||
|
// 3. 创建头像
|
||||||
|
QPushButton* avatarBtn = new QPushButton();
|
||||||
|
avatarBtn->setFixedSize(40, 40);
|
||||||
|
avatarBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
avatarBtn->setIconSize(QSize(40, 40));
|
||||||
|
// 当前消息发送者的头像
|
||||||
|
avatarBtn->setIcon(message.sender.avatar);
|
||||||
|
avatarBtn->setStyleSheet("QPushButton { border: none; }");
|
||||||
|
|
||||||
|
// 4. 创建昵称和时间
|
||||||
|
QLabel* nameLabel = new QLabel();
|
||||||
|
nameLabel->setText(message.sender.nickname + " | " + message.time);
|
||||||
|
nameLabel->setFixedHeight(20); // 高度设置为头像高度的一半
|
||||||
|
nameLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
// 5. 消息内容部分
|
||||||
|
QWidget* contentWidget = nullptr;
|
||||||
|
if (message.messageType == model::MessageType::TEXT_TYPE) {
|
||||||
|
// 文本消息
|
||||||
|
QLabel* label = new QLabel();
|
||||||
|
label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
label->setWordWrap(true);
|
||||||
|
label->setText(QString(message.content));
|
||||||
|
label->adjustSize(); // 设置让 label 能够自动调整大小
|
||||||
|
contentWidget = label;
|
||||||
|
}
|
||||||
|
else if (message.messageType == model::MessageType::IMAGE_TYPE) {
|
||||||
|
// 图片消息
|
||||||
|
contentWidget = new ImageButton(message.fileId, message.content);
|
||||||
|
}
|
||||||
|
else if (message.messageType == model::MessageType::FILE_TYPE) {
|
||||||
|
// 文件消息
|
||||||
|
contentWidget = new FileLabel(message.fileId, message.fileName);
|
||||||
|
}
|
||||||
|
else if (message.messageType == model::MessageType::SPEECH_TYPE) {
|
||||||
|
// 语音消息
|
||||||
|
contentWidget = new SpeechLabel(message.fileId);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG() << "错误的消息类型! messageType=" << message.messageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 把上述控件添加到布局中
|
||||||
|
layout->addWidget(avatarBtn, 0, 0, 2, 1);
|
||||||
|
layout->addWidget(nameLabel, 0, 1, 1, 1);
|
||||||
|
layout->addWidget(contentWidget, 1, 1, 5, 1);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoryMessageWidget::HistoryMessageWidget(QWidget *parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
{
|
||||||
|
// 1. 设置窗口本身属性
|
||||||
|
this->setFixedSize(600, 600);
|
||||||
|
this->setWindowTitle("历史消息");
|
||||||
|
this->setWindowIcon(QIcon(":/resource/image/logo.png"));
|
||||||
|
this->setStyleSheet("QWidget { background-color: rgb(255, 255, 255); }");
|
||||||
|
this->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
// 2. 创建布局管理器.
|
||||||
|
QGridLayout* layout = new QGridLayout();
|
||||||
|
layout->setSpacing(10);
|
||||||
|
layout->setContentsMargins(30, 30, 30, 0);
|
||||||
|
this->setLayout(layout);
|
||||||
|
|
||||||
|
// 3. 创建单选按钮
|
||||||
|
keyRadioBtn = new QRadioButton();
|
||||||
|
timeRadioBtn = new QRadioButton();
|
||||||
|
keyRadioBtn->setText("按关键词查询");
|
||||||
|
timeRadioBtn->setText("按时间查询");
|
||||||
|
// 默认按照关键词查询
|
||||||
|
keyRadioBtn->setChecked(true);
|
||||||
|
layout->addWidget(keyRadioBtn, 0, 0, 1, 2);
|
||||||
|
layout->addWidget(timeRadioBtn, 0, 2, 1, 2);
|
||||||
|
|
||||||
|
// 4. 创建搜索框
|
||||||
|
searchEdit = new QLineEdit();
|
||||||
|
searchEdit->setFixedHeight(50);
|
||||||
|
searchEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
searchEdit->setPlaceholderText("要搜索的关键词");
|
||||||
|
searchEdit->setStyleSheet("QLineEdit { border: none; border-radius: 10px; color: rgb(129, 129, 129); background-color: rgb(240, 240, 240); font-size: 16px; padding-left: 10px; }");
|
||||||
|
layout->addWidget(searchEdit, 1, 0, 1, 8);
|
||||||
|
|
||||||
|
// 5. 创建搜索按钮
|
||||||
|
QPushButton* searchBtn = new QPushButton();
|
||||||
|
searchBtn->setFixedSize(50, 50);
|
||||||
|
searchBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
searchBtn->setIconSize(QSize(30, 30));
|
||||||
|
searchBtn->setIcon(QIcon(":/resource/image/search.png"));
|
||||||
|
QString btnStyle = "QPushButton { border: none; background-color: rgb(240, 240, 240); border-radius: 10px; }";
|
||||||
|
btnStyle += "QPushButton:pressed { background-color: rgb(220, 220, 220); }";
|
||||||
|
searchBtn->setStyleSheet(btnStyle);
|
||||||
|
layout->addWidget(searchBtn, 1, 8, 1, 1);
|
||||||
|
|
||||||
|
// 6. 创建时间相关的部分控件, 初始情况下要隐藏
|
||||||
|
QLabel* begTag = new QLabel();
|
||||||
|
begTag->setText("开始时间");
|
||||||
|
QLabel* endTag = new QLabel();
|
||||||
|
endTag->setText("结束时间");
|
||||||
|
begTimeEdit = new QDateTimeEdit();
|
||||||
|
endTimeEdit = new QDateTimeEdit();
|
||||||
|
|
||||||
|
begTimeEdit->setStyleSheet("QDateTimeEdit { color: rgb(0, 0, 0); } ");
|
||||||
|
endTimeEdit->setStyleSheet("QDateTimeEdit { color: rgb(0, 0, 0); } ");
|
||||||
|
// [联调新增]
|
||||||
|
begTimeEdit->setDisplayFormat("yyyy-MM-dd hh:mm");
|
||||||
|
endTimeEdit->setDisplayFormat("yyyy-MM-dd hh:mm");
|
||||||
|
begTimeEdit->setFixedHeight(40);
|
||||||
|
endTimeEdit->setFixedHeight(40);
|
||||||
|
|
||||||
|
begTag->hide();
|
||||||
|
endTag->hide();
|
||||||
|
begTimeEdit->hide();
|
||||||
|
endTimeEdit->hide();
|
||||||
|
|
||||||
|
// 7. 创建滚动区域
|
||||||
|
initScrollArea(layout);
|
||||||
|
|
||||||
|
// 8. 设置槽函数
|
||||||
|
connect(keyRadioBtn, &QRadioButton::clicked, this, [=]() {
|
||||||
|
// 把时间相关的控件, 隐藏起来
|
||||||
|
layout->removeWidget(begTag);
|
||||||
|
layout->removeWidget(begTimeEdit);
|
||||||
|
layout->removeWidget(endTag);
|
||||||
|
layout->removeWidget(endTimeEdit);
|
||||||
|
begTag->hide();
|
||||||
|
begTimeEdit->hide();
|
||||||
|
endTag->hide();
|
||||||
|
endTimeEdit->hide();
|
||||||
|
|
||||||
|
// 把关键词搜索框显示加入布局
|
||||||
|
layout->addWidget(searchEdit, 1, 0, 1, 8);
|
||||||
|
searchEdit->show();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(timeRadioBtn, &QRadioButton::clicked, this, [=]() {
|
||||||
|
// 关键词搜索框隐藏并从布局中删除掉.
|
||||||
|
layout->removeWidget(searchEdit);
|
||||||
|
searchEdit->hide();
|
||||||
|
|
||||||
|
// 把时间相关的控件, 添加到布局中, 并且进行显示.
|
||||||
|
layout->addWidget(begTag, 1, 0, 1, 1);
|
||||||
|
layout->addWidget(begTimeEdit, 1, 1, 1, 3);
|
||||||
|
layout->addWidget(endTag, 1, 4, 1, 1);
|
||||||
|
layout->addWidget(endTimeEdit, 1, 5, 1, 3);
|
||||||
|
begTag->show();
|
||||||
|
begTimeEdit->show();
|
||||||
|
endTag->show();
|
||||||
|
endTimeEdit->show();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(searchBtn, &QPushButton::clicked, this, &HistoryMessageWidget::clickSearchBtn);
|
||||||
|
|
||||||
|
// 构造测试数据
|
||||||
|
#if TEST_UI
|
||||||
|
for (int i = 0; i < 30; ++i) {
|
||||||
|
// 注意此处代码和前面的差别.
|
||||||
|
// 前面有个代码, UserInfo 必须要 new 出来才能构造. 当时 Item 对象里, 持有了 const UserInfo& , 不是 new 的话
|
||||||
|
// 就可能使引用指向的对象失效的.
|
||||||
|
// 此处后续的代码, 都是按照传值的方式来使用 message 的内容, 不 new 也行.
|
||||||
|
model::UserInfo sender;
|
||||||
|
sender.userId = "";
|
||||||
|
sender.nickname = "张三" + QString::number(i);
|
||||||
|
sender.avatar = QIcon(":/resource/image/defaultAvatar.png");
|
||||||
|
sender.description = "";
|
||||||
|
sender.phone = "18612345678";
|
||||||
|
Message message = Message::makeMessage(model::MessageType::TEXT_TYPE, "", sender, QString("消息内容" + QString::number(i)).toUtf8(), "");
|
||||||
|
this->addHistoryMessage(message);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryMessageWidget::addHistoryMessage(const Message& message)
|
||||||
|
{
|
||||||
|
HistoryItem* item = HistoryItem::makeHistoryItem(message);
|
||||||
|
container->layout()->addWidget(item);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryMessageWidget::clear()
|
||||||
|
{
|
||||||
|
QVBoxLayout* layout = dynamic_cast<QVBoxLayout*>(container->layout());
|
||||||
|
for (int i = layout->count() - 1; i >= 0; i--) {
|
||||||
|
//之前使用的是takeat,效果和这个是一样的,
|
||||||
|
QWidget* w = layout->itemAt(i)->widget();
|
||||||
|
if (w == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
layout->removeWidget(w);
|
||||||
|
w->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Message>* messageResult = dataCenter->getSearchMessageReuslt();
|
||||||
|
if (messageResult == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//把结果列表的数据,显示到界面上
|
||||||
|
this->clear();
|
||||||
|
for (const Message& m : *messageResult) {
|
||||||
|
this->addHistoryMessage(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryMessageWidget::initScrollArea(QGridLayout* layout)
|
||||||
|
{
|
||||||
|
// 1. 创建滚动区域对象
|
||||||
|
QScrollArea* scrollArea = new QScrollArea();
|
||||||
|
scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
scrollArea->setWidgetResizable(true);
|
||||||
|
scrollArea->verticalScrollBar()->setStyleSheet("QScrollBar:vertical { width: 2px; background-color: rgb(255, 255, 255); }");
|
||||||
|
scrollArea->horizontalScrollBar()->setStyleSheet("QScrollBar:horizontal { height: 0; }");
|
||||||
|
scrollArea->setStyleSheet("QScrollArea { border: none; }");
|
||||||
|
|
||||||
|
// 2. 创建 QWidget, 持有要加入的新的内容
|
||||||
|
container = new QWidget();
|
||||||
|
scrollArea->setWidget(container);
|
||||||
|
|
||||||
|
// 3. 创建 container 中的布局管理器.
|
||||||
|
QVBoxLayout* vlayout = new QVBoxLayout();
|
||||||
|
vlayout->setSpacing(10);
|
||||||
|
vlayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
vlayout->setAlignment(Qt::AlignTop);
|
||||||
|
container->setLayout(vlayout);
|
||||||
|
|
||||||
|
// 4. 把滚动区加入到整个 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);
|
||||||
|
}
|
||||||
219
ChatClient/src/loginwidget.cpp
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
#include "loginwidget.h"
|
||||||
|
|
||||||
|
#include "mainwidget.h"
|
||||||
|
#include "model/datacenter.h"
|
||||||
|
|
||||||
|
using namespace model;
|
||||||
|
|
||||||
|
LoginWidget::LoginWidget(QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
{
|
||||||
|
//设置本窗口的基本属性
|
||||||
|
this->setFixedSize(400, 350);
|
||||||
|
this->setWindowTitle("登录");
|
||||||
|
this->setWindowIcon(QIcon(":/resource/image/logo.png"));
|
||||||
|
this->setStyleSheet("QWidget { background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); }");
|
||||||
|
this->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
//创建布局管理器
|
||||||
|
QGridLayout* layout = new QGridLayout();
|
||||||
|
layout->setSpacing(0);
|
||||||
|
layout->setContentsMargins(50, 0, 50, 0);
|
||||||
|
this->setLayout(layout);
|
||||||
|
|
||||||
|
// 创建标题
|
||||||
|
titleLabel = new QLabel();
|
||||||
|
titleLabel->setText("登录");
|
||||||
|
titleLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
titleLabel->setFixedHeight(50);
|
||||||
|
titleLabel->setStyleSheet("QLabel { font-size: 40px; font-weight: 600; }");
|
||||||
|
|
||||||
|
// 创建用户名输入框
|
||||||
|
QString editStyle = "QLineEdit { border: none; border-radius: 10px; font-size: 20px; background-color: rgb(240, 240, 240); padding-left:5px; }";
|
||||||
|
usernameEdit = new QLineEdit();
|
||||||
|
usernameEdit->setFixedHeight(40);
|
||||||
|
usernameEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
usernameEdit->setPlaceholderText("输入用户名");
|
||||||
|
usernameEdit->setStyleSheet(editStyle);
|
||||||
|
|
||||||
|
// 创建密码输入框
|
||||||
|
passwordEdit = new QLineEdit();
|
||||||
|
passwordEdit->setFixedHeight(40);
|
||||||
|
passwordEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
passwordEdit->setPlaceholderText("输入密码");
|
||||||
|
passwordEdit->setStyleSheet(editStyle);
|
||||||
|
passwordEdit->setEchoMode(QLineEdit::Password);
|
||||||
|
|
||||||
|
// 创建验证码输入框
|
||||||
|
verifyCodeEdit = new QLineEdit();
|
||||||
|
verifyCodeEdit->setFixedHeight(40);
|
||||||
|
verifyCodeEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
verifyCodeEdit->setPlaceholderText("输入验证码");
|
||||||
|
verifyCodeEdit->setStyleSheet(editStyle);
|
||||||
|
|
||||||
|
// 创建显示验证码图片的控件 (此处先用 QPushButton 来表示一下, 后续进一步编写这里的逻辑)
|
||||||
|
// 后续会自定义 QWidget, 通过画图 api 来实现这里的验证码功能.
|
||||||
|
//* verifyCodeWidget = new QPushButton();
|
||||||
|
//idget->setText("验证码");
|
||||||
|
//idget->setStyleSheet("QWidget { border: none; }");
|
||||||
|
verifyCodeWidget = new VerifyCodeWidget(this);
|
||||||
|
|
||||||
|
// 创建登录按钮
|
||||||
|
submitBtn = new QPushButton();
|
||||||
|
submitBtn->setText("登录");
|
||||||
|
submitBtn->setFixedHeight(40);
|
||||||
|
submitBtn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
QString btnGreenStyle = "QPushButton { border: none; border-radius: 10px; background-color: rgb(44, 182, 61); color: rgb(255, 255, 255); }";
|
||||||
|
btnGreenStyle += "QPushButton:pressed { background-color: rgb(240, 240, 240); }";
|
||||||
|
submitBtn->setStyleSheet(btnGreenStyle);
|
||||||
|
|
||||||
|
// 创建切换到手机号登录按钮
|
||||||
|
phoneModeBtn = new QPushButton();
|
||||||
|
phoneModeBtn->setFixedSize(100, 40);
|
||||||
|
phoneModeBtn->setText("手机号登录");
|
||||||
|
QString btnWhiteStyle = "QPushButton { border: none; border-radius: 10px; background-color: transparent; }";
|
||||||
|
btnWhiteStyle += "QPushButton:pressed { background-color: rgb(240, 240, 240); }";
|
||||||
|
phoneModeBtn->setStyleSheet(btnWhiteStyle);
|
||||||
|
|
||||||
|
// 创建切换模式(登录和注册)按钮
|
||||||
|
switchModeBtn = new QPushButton();
|
||||||
|
switchModeBtn->setFixedSize(100, 40);
|
||||||
|
switchModeBtn->setText("注册");
|
||||||
|
switchModeBtn->setStyleSheet(btnWhiteStyle);
|
||||||
|
|
||||||
|
// 添加到布局管理器中
|
||||||
|
layout->addWidget(titleLabel, 0, 0, 1, 5);
|
||||||
|
layout->addWidget(usernameEdit, 1, 0, 1, 5);
|
||||||
|
layout->addWidget(passwordEdit, 2, 0, 1, 5);
|
||||||
|
layout->addWidget(verifyCodeEdit, 3, 0, 1, 4);
|
||||||
|
layout->addWidget(verifyCodeWidget, 3, 4, 1, 1);
|
||||||
|
layout->addWidget(submitBtn, 4, 0, 1, 5);
|
||||||
|
layout->addWidget(phoneModeBtn, 5, 0, 1, 1);
|
||||||
|
layout->addWidget(switchModeBtn, 5, 4, 1, 1);
|
||||||
|
|
||||||
|
|
||||||
|
// 处理信号槽
|
||||||
|
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 这个值传到新的窗口中, 让新的窗口决定自己是登录状态还是注册状态. 大家自行尝试实现.
|
||||||
|
PhoneLoginWidget* phoneLoginWidget = new PhoneLoginWidget(nullptr);
|
||||||
|
phoneLoginWidget->show();
|
||||||
|
|
||||||
|
// 关闭当前窗口
|
||||||
|
this->close();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(submitBtn, &QPushButton::clicked, this, &LoginWidget::clickSubmitBtn);
|
||||||
|
/*connect(submitBtn, &QPushButton::clicked, this, [=]() {
|
||||||
|
Toast::showMessage("Just a test message...");
|
||||||
|
});*/
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
66
ChatClient/src/main.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#include "mainwidget.h"
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QStyleFactory>
|
||||||
|
|
||||||
|
#include "model/data.h"
|
||||||
|
#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";
|
||||||
|
|
||||||
|
//测试代码
|
||||||
|
//DataCenter* dataCenter = model::DataCenter::getInstance();
|
||||||
|
//dataCenter->initDataFile();
|
||||||
|
//dataCenter->saveDataFile();
|
||||||
|
|
||||||
|
#if TEST_NETWORK
|
||||||
|
//开始执行network
|
||||||
|
LOG() << "start network";
|
||||||
|
/*network::NetClient netClient(nullptr);
|
||||||
|
netClient.ping();*/
|
||||||
|
|
||||||
|
model::DataCenter* dataCenter = model::DataCenter::getInstance();
|
||||||
|
dataCenter->ping();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if TEST_SKIP_LOGIN
|
||||||
|
|
||||||
|
QPalette palette;
|
||||||
|
palette.setColor(QPalette::WindowText, Qt::black);// 窗口文字颜色
|
||||||
|
QApplication::setPalette(palette);
|
||||||
|
|
||||||
|
//mychat的主窗口
|
||||||
|
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
|
||||||
|
|
||||||
|
return a.exec();
|
||||||
|
}
|
||||||
669
ChatClient/src/mainwidget.cpp
Normal file
@ -0,0 +1,669 @@
|
|||||||
|
#include "mainwidget.h"
|
||||||
|
#include "./ui_mainwidget.h"
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include "sessiondetailwidget.h"
|
||||||
|
|
||||||
|
MainWidget *MainWidget::instance = nullptr;
|
||||||
|
|
||||||
|
MainWidget *MainWidget::getInstance()
|
||||||
|
{
|
||||||
|
if(instance == nullptr) {
|
||||||
|
//此处不传入参数,以桌面为父窗口
|
||||||
|
//
|
||||||
|
instance = new MainWidget();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWidget::MainWidget(QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, ui(new Ui::MainWidget)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
// this->setStyleSheet("MainWidget {min-height: 6000px; }");
|
||||||
|
this->resize(QSize(1280, 800));
|
||||||
|
|
||||||
|
this->setWindowTitle("MyChat");
|
||||||
|
this->setWindowIcon(QIcon(":/resource/image/logo.png"));
|
||||||
|
|
||||||
|
//初始化主窗口的样式布局
|
||||||
|
initMainWindow();
|
||||||
|
//初始化左侧窗口的样式布局
|
||||||
|
initLeftWindow();
|
||||||
|
//初始化中间窗口的样式布局
|
||||||
|
initMidWindow();
|
||||||
|
//初始化右侧窗口的样式布局
|
||||||
|
initRightWindow();
|
||||||
|
//初始化信号槽
|
||||||
|
initSignalSlot();
|
||||||
|
//初始化websocket
|
||||||
|
initWebSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWidget::~MainWidget()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::initMainWindow()
|
||||||
|
{
|
||||||
|
QHBoxLayout* layout = new QHBoxLayout();
|
||||||
|
//layout内部的元素间隔设置为0
|
||||||
|
layout->setSpacing(0);
|
||||||
|
//layout内部元素四个方向边界的距离设置
|
||||||
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
this->setLayout(layout);
|
||||||
|
|
||||||
|
windowLeft = new QWidget();
|
||||||
|
windowMid = new QWidget();
|
||||||
|
windowRight = new QWidget();
|
||||||
|
|
||||||
|
|
||||||
|
windowLeft->setFixedWidth(70);
|
||||||
|
windowMid->setFixedWidth(320);
|
||||||
|
windowRight->setMinimumWidth(600);
|
||||||
|
|
||||||
|
windowLeft->setStyleSheet("QWidget { background-color: rgb(46, 46, 46); }");
|
||||||
|
windowMid->setStyleSheet("QWidget { background-color: rgb(247, 247, 247); }");
|
||||||
|
windowRight->setStyleSheet("QWidget { background-color: rgb(245, 245, 245); }");
|
||||||
|
|
||||||
|
layout->addWidget(windowLeft);
|
||||||
|
layout->addWidget(windowMid);
|
||||||
|
layout->addWidget(windowRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::initLeftWindow()
|
||||||
|
{
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout();
|
||||||
|
|
||||||
|
layout->setSpacing(20);
|
||||||
|
layout->setContentsMargins(0, 50, 0, 0);
|
||||||
|
|
||||||
|
windowLeft->setLayout(layout);
|
||||||
|
|
||||||
|
//添加用户头像
|
||||||
|
userAvatar = new QPushButton();
|
||||||
|
userAvatar->setFixedSize(45, 45);
|
||||||
|
userAvatar->setIconSize(QSize(45, 45));
|
||||||
|
//由于要从网络获取头像信息,所以这里就不能再设置默认的头像了,;避免头像意外变化
|
||||||
|
//userAvatar->setIcon(QIcon(":/resource/image/defaultAv.png"));
|
||||||
|
userAvatar->setStyleSheet("QPushButton {background-color: transparent; }");
|
||||||
|
layout->addWidget(userAvatar, 1, Qt::AlignTop | Qt::AlignCenter);
|
||||||
|
|
||||||
|
//添加会话标签页按钮
|
||||||
|
sessionTabBtn = new QPushButton();
|
||||||
|
sessionTabBtn->setFixedSize(45, 45);
|
||||||
|
sessionTabBtn->setIconSize(QSize(35, 35));
|
||||||
|
sessionTabBtn->setIcon(QIcon(":/resource/image/session_active.png"));
|
||||||
|
sessionTabBtn->setStyleSheet("QPushButton {background-color: transparent; border: none; }");
|
||||||
|
layout->addWidget(sessionTabBtn, 1, Qt::AlignTop | Qt::AlignCenter);
|
||||||
|
|
||||||
|
//添加好友标签页按钮
|
||||||
|
friendTabBtn = new QPushButton();
|
||||||
|
friendTabBtn->setFixedSize(45, 45);
|
||||||
|
friendTabBtn->setIconSize(QSize(35, 35));
|
||||||
|
friendTabBtn->setIcon(QIcon(":/resource/image/friend_inactive.png"));
|
||||||
|
friendTabBtn->setStyleSheet("QPushButton {background-color: transparent; border: none; }");
|
||||||
|
layout->addWidget(friendTabBtn, 1, Qt::AlignTop | Qt::AlignCenter);
|
||||||
|
|
||||||
|
//添加好友申请标签页按钮
|
||||||
|
applyTabBtn = new QPushButton();
|
||||||
|
applyTabBtn->setFixedSize(45, 45);
|
||||||
|
applyTabBtn->setIconSize(QSize(35, 35));
|
||||||
|
applyTabBtn->setIcon(QIcon(":/resource/image/apply_inactive.png"));
|
||||||
|
applyTabBtn->setStyleSheet("QPushButton {background-color: transparent; border: none; }");
|
||||||
|
layout->addWidget(applyTabBtn, 1, Qt::AlignTop | Qt::AlignCenter);
|
||||||
|
|
||||||
|
layout->addStretch(20);
|
||||||
|
|
||||||
|
}
|
||||||
|
void MainWidget::initMidWindow()
|
||||||
|
{
|
||||||
|
QGridLayout* layout = new QGridLayout();
|
||||||
|
//距离上方有20px,另外的三个方向都不要边距
|
||||||
|
layout->setContentsMargins(0, 20, 0, 0);
|
||||||
|
layout->setSpacing(0);
|
||||||
|
windowMid->setLayout(layout);
|
||||||
|
|
||||||
|
searchEdit = new QLineEdit();
|
||||||
|
searchEdit->setFixedHeight(30);
|
||||||
|
searchEdit->setPlaceholderText("搜索");
|
||||||
|
searchEdit->setStyleSheet("QLineEdit { border-radius: 5px; color: rgb(129, 129, 129); background-color: rgb(226, 226, 226); padding-left: 5px; }");
|
||||||
|
|
||||||
|
addFriendBtn = new QPushButton();
|
||||||
|
addFriendBtn->setFixedSize(30, 30);
|
||||||
|
addFriendBtn->setIcon(QIcon(":/resource/image/cross.png"));
|
||||||
|
QString style = R"(QPushButton { border-radius: 5px; background-color: rgb(226, 226, 226); }
|
||||||
|
QPushButton::pressed { background-color: rgb(240, 240, 240); })";
|
||||||
|
addFriendBtn->setStyleSheet(style);
|
||||||
|
|
||||||
|
sessionFriendArea = new SessionFriendArea();
|
||||||
|
|
||||||
|
//为了更加灵活的控制边距,只影响搜索框和按钮的这一行,
|
||||||
|
//创建空白的widget填充到布局管理器上
|
||||||
|
QWidget* spacer1 = new QWidget();
|
||||||
|
spacer1->setFixedWidth(10);
|
||||||
|
QWidget* spacer2 = new QWidget();
|
||||||
|
spacer2->setFixedWidth(10);
|
||||||
|
QWidget* spacer3 = new QWidget();
|
||||||
|
spacer3->setFixedWidth(10);
|
||||||
|
|
||||||
|
QWidget* spacer4 = new QWidget();
|
||||||
|
spacer4->setFixedHeight(10);
|
||||||
|
|
||||||
|
layout->addWidget(spacer1, 0, 0);
|
||||||
|
layout->addWidget(searchEdit, 0, 1);
|
||||||
|
layout->addWidget(spacer2, 0, 2);
|
||||||
|
layout->addWidget(addFriendBtn, 0 ,3);
|
||||||
|
layout->addWidget(spacer3, 0, 4);
|
||||||
|
|
||||||
|
layout->addWidget(spacer4, 1, 0, 1, 5);
|
||||||
|
layout->addWidget(sessionFriendArea, 2, 0, 1, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::initRightWindow()
|
||||||
|
{
|
||||||
|
//创建右侧窗口的布局管理器
|
||||||
|
QVBoxLayout* vlayout = new QVBoxLayout();
|
||||||
|
vlayout->setSpacing(0);
|
||||||
|
vlayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
vlayout->setAlignment(Qt::AlignTop);
|
||||||
|
windowRight->setLayout(vlayout);
|
||||||
|
|
||||||
|
//创建上方的标题栏
|
||||||
|
QWidget* titleWidget = new QWidget();
|
||||||
|
titleWidget->setFixedHeight(62);
|
||||||
|
titleWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
titleWidget->setObjectName("titleWidget");
|
||||||
|
titleWidget->setStyleSheet(R"(#titleWidget { border-bottom: 2px solid rgb(231, 231, 231);
|
||||||
|
border-left: 1px solid rgb(231, 231, 231); }
|
||||||
|
)");
|
||||||
|
vlayout->addWidget(titleWidget);
|
||||||
|
|
||||||
|
//给标题栏添加 label 和 button
|
||||||
|
QHBoxLayout* hlayout = new QHBoxLayout();
|
||||||
|
hlayout->setSpacing(0);
|
||||||
|
hlayout->setContentsMargins(10, 0, 10, 0);
|
||||||
|
titleWidget->setLayout(hlayout);
|
||||||
|
|
||||||
|
sessionTitleLabel = new QLabel();
|
||||||
|
sessionTitleLabel->setStyleSheet("QLabel { font-size: 22px; border-bottom: 1px solid rgb(230, 230, 230); }");
|
||||||
|
|
||||||
|
#if TEST_UI
|
||||||
|
//为了测试仅填充从服务器获取的数据
|
||||||
|
sessionTitleLabel->setText("TEST TITLE");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
hlayout->addWidget(sessionTitleLabel);
|
||||||
|
|
||||||
|
extraBtn = new QPushButton();
|
||||||
|
extraBtn->setFixedSize(30, 30);
|
||||||
|
extraBtn->setIconSize(QSize(30, 30));
|
||||||
|
extraBtn->setIcon(QIcon(":/resource/image/more.png"));
|
||||||
|
extraBtn->setStyleSheet(R"(QPushButton { border: none; background-color: transparent; }
|
||||||
|
QPushButton:pressed { background-color: rgb(210, 210 ,210); }
|
||||||
|
)");
|
||||||
|
hlayout->addWidget(extraBtn);
|
||||||
|
|
||||||
|
|
||||||
|
//添加消息展示区
|
||||||
|
messageShowArea = new MessageShowArea();
|
||||||
|
vlayout->addWidget(messageShowArea);
|
||||||
|
|
||||||
|
//添加消息编辑区
|
||||||
|
messageEditArea = new MessageEditArea();
|
||||||
|
//确保消息编辑区处于窗口正下方
|
||||||
|
vlayout->addWidget(messageEditArea, 0, Qt::AlignBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::initSignalSlot()
|
||||||
|
{
|
||||||
|
model::DataCenter* dataCenter = model::DataCenter::getInstance();
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
//连接信号槽,处理标签页切换
|
||||||
|
/////////////////////////////////////
|
||||||
|
connect(sessionTabBtn, &QPushButton::clicked, this, &MainWidget::switchTabToSession);
|
||||||
|
connect(friendTabBtn, &QPushButton::clicked, this, &MainWidget::switchTabToFriend);
|
||||||
|
connect(applyTabBtn, &QPushButton::clicked, this, &MainWidget::switchTabToApply);
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// 点击自己的头像,弹出对话框显示个人的主页
|
||||||
|
/////////////////////////////////////
|
||||||
|
connect(userAvatar, &QPushButton::clicked, this, [=]()->void {
|
||||||
|
SelfInfoWidget* selfInfoWidget = new SelfInfoWidget(this);
|
||||||
|
selfInfoWidget->setStyleSheet("QDialog { background-color: rgb(245, 245, 245); }");
|
||||||
|
selfInfoWidget->exec(); //弹出模态对话框
|
||||||
|
//selfInfoWidget->show(); //弹出非模态对加框
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// 点击右上角的更多按钮,扩展会话的详细信息
|
||||||
|
/////////////////////////////////////
|
||||||
|
connect(extraBtn, &QPushButton::clicked, 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 {
|
||||||
|
//说明是群聊
|
||||||
|
GroupSessionDetailWidget* groupSessionDetailWidget = new GroupSessionDetailWidget(this);
|
||||||
|
groupSessionDetailWidget->exec();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// 点击添加好友按钮,弹出添加好友的窗口
|
||||||
|
/////////////////////////////////////
|
||||||
|
connect(addFriendBtn, &QPushButton::clicked, this, [=]() {
|
||||||
|
AddFriendDialog* addFriendDialog = new AddFriendDialog(this);
|
||||||
|
addFriendDialog->exec();
|
||||||
|
});
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// 修改搜索框内容,设置到新弹出的输入框里面
|
||||||
|
/////////////////////////////////////
|
||||||
|
connect(searchEdit, &QLineEdit::textEdited, this, [=]() {
|
||||||
|
const QString& searchKey = searchEdit->text();
|
||||||
|
AddFriendDialog* addFriendDialog = new AddFriendDialog(this);
|
||||||
|
addFriendDialog->setSearchKey(searchKey);
|
||||||
|
searchEdit->setText("");
|
||||||
|
addFriendDialog->exec();
|
||||||
|
});
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// 获取个人的信息
|
||||||
|
/////////////////////////////////////
|
||||||
|
//提供一个具体的方法,来获取到网络的数据
|
||||||
|
connect(dataCenter, &DataCenter::getMyselfDone, this, [=]() {
|
||||||
|
//从DataCenter中拿到响应的结果myself,把里面的头像取出来,显示到界面上
|
||||||
|
//由于响应的逻辑已经执行完毕,说明从网络拿到了信息,则直接同步获取即可
|
||||||
|
auto myself = dataCenter->getMyselfsync();
|
||||||
|
userAvatar->setIcon(myself->avatar);
|
||||||
|
});
|
||||||
|
dataCenter->getMyselfAsync();
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// 获取好友列表
|
||||||
|
/////////////////////////////////////
|
||||||
|
loadFriendList();
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// 获取会话列表
|
||||||
|
/////////////////////////////////////
|
||||||
|
loadSessionList();
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// 获取好友申请的列表
|
||||||
|
/////////////////////////////////////
|
||||||
|
loadApplyList();
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// 处理修改头像
|
||||||
|
/////////////////////////////////////
|
||||||
|
connect(dataCenter, &DataCenter::changeAvatarDone, this, [=]() {
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
//记录当前切换到了哪个标签页
|
||||||
|
activeTab = SESSION_LIST;
|
||||||
|
//调整图标显示情况
|
||||||
|
sessionTabBtn->setIcon(QIcon(":/resource/image/session_active.png"));
|
||||||
|
friendTabBtn->setIcon(QIcon(":/resource/image/friend_inactive.png"));
|
||||||
|
applyTabBtn->setIcon(QIcon(":/resource/image/apply_inactive.png"));
|
||||||
|
// 在主窗口的中间部分,加载出会话列表数据
|
||||||
|
this->loadSessionList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::switchTabToFriend()
|
||||||
|
{
|
||||||
|
//记录当前切换到了哪个标签页
|
||||||
|
activeTab = FRIEND_LIST;
|
||||||
|
//调整图标显示情况
|
||||||
|
sessionTabBtn->setIcon(QIcon(":/resource/image/session_inactive.png"));
|
||||||
|
friendTabBtn->setIcon(QIcon(":/resource/image/friend_active.png"));
|
||||||
|
applyTabBtn->setIcon(QIcon(":/resource/image/apply_inactive.png"));
|
||||||
|
// 在主窗口的中间部分,加载出会话列表数据
|
||||||
|
this->loadFriendList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::switchTabToApply()
|
||||||
|
{
|
||||||
|
//记录当前切换到了哪个标签页
|
||||||
|
activeTab = APPLY_LIST;
|
||||||
|
//调整图标显示情况
|
||||||
|
sessionTabBtn->setIcon(QIcon(":/resource/image/session_inactive.png"));
|
||||||
|
friendTabBtn->setIcon(QIcon(":/resource/image/friend_inactive.png"));
|
||||||
|
applyTabBtn->setIcon(QIcon(":/resource/image/apply_active.png"));
|
||||||
|
// 在主窗口的中间部分,加载出会话列表数据
|
||||||
|
this->loadApplyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
//加载会话列表
|
||||||
|
void MainWidget::loadSessionList()
|
||||||
|
{
|
||||||
|
//会判定会话是否存在本地的DataCenter中,若存在,则直接构造界面
|
||||||
|
//若不存在则从服务器获取数据
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
if (dataCenter->getChatSessionList() != nullptr) {
|
||||||
|
//本地内存中获取数据
|
||||||
|
LOG() << "本地已存在会话列表数据,更新加载本地会话";
|
||||||
|
updateChatSessionList();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 从网路中加载数据
|
||||||
|
LOG() << "本地会话列表数据不存在,正在从网络获取...";
|
||||||
|
connect(dataCenter, &DataCenter::getChatSessionListDone, this, &MainWidget::updateChatSessionList, Qt::UniqueConnection);
|
||||||
|
dataCenter->getChatSessionListAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//加载好友列表
|
||||||
|
void MainWidget::loadFriendList()
|
||||||
|
{
|
||||||
|
//好友列表数据是在DataCenter中存储的
|
||||||
|
//首先需要判定DataCenter中是否已经有数据了,如果有,则直接加载本地数据
|
||||||
|
//如果没有,则从服务器读取
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
if (dataCenter->getFriendList() != nullptr) {
|
||||||
|
//从内存中加载数据
|
||||||
|
LOG() << "本地已存在好友列表数据,更新加载本地好友";
|
||||||
|
updateFriendList();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//通过网络加载数据 通过这个参数确保信号槽不会别重复绑定
|
||||||
|
LOG() << "本地好友列表数据不存在,正在从网络获取...";
|
||||||
|
connect(dataCenter, &DataCenter::getFriendListDone, this, &MainWidget::updateFriendList, Qt::UniqueConnection);
|
||||||
|
dataCenter->getFriendListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//加载申请列表
|
||||||
|
void MainWidget::loadApplyList()
|
||||||
|
{
|
||||||
|
//好友申请列表在DataCenter中存储
|
||||||
|
//同理首先需要判定DataCenter中是否已经有数据了,如果有,则直接加载本地数据
|
||||||
|
//如果没有,则从服务器读取
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
if (dataCenter->getApplyList() != nullptr) {
|
||||||
|
//从内存中加载数据
|
||||||
|
LOG() << "本地已存在用户申请列表数据,更新加载申请列表";
|
||||||
|
updateApplyList();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//通过网络加载数据
|
||||||
|
LOG() << "用户申请列表数据不存在,正在从网络获取...";
|
||||||
|
connect(dataCenter, &DataCenter::getApplyListDone, this, &MainWidget::updateApplyList, Qt::UniqueConnection);
|
||||||
|
dataCenter->getApplyListAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::updateFriendList()
|
||||||
|
{
|
||||||
|
if (activeTab != FRIEND_LIST) {
|
||||||
|
//当前的标签页不是好友列表,就不渲染任何数据到界面上
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
QList<UserInfo>* friendList = dataCenter->getFriendList();
|
||||||
|
|
||||||
|
//清空一下之前的界面的数据
|
||||||
|
sessionFriendArea->clear();
|
||||||
|
|
||||||
|
//遍历好友列表,添加到界面上
|
||||||
|
for (const auto& f : *friendList) {
|
||||||
|
sessionFriendArea->addItem(FriendItemType, f.userId, f.avatar, f.nickname, f.description);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::updateChatSessionList()
|
||||||
|
{
|
||||||
|
if (activeTab != SESSION_LIST) {
|
||||||
|
//当前的标签页不是会话列表,不渲染任何数据到界面上
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
QList<ChatSessionInfo>* chatSessionList = dataCenter->getChatSessionList();
|
||||||
|
|
||||||
|
sessionFriendArea->clear();
|
||||||
|
|
||||||
|
for (const auto& c : *chatSessionList) {
|
||||||
|
switch (c.lastMessage.messageType)
|
||||||
|
{
|
||||||
|
case TEXT_TYPE:
|
||||||
|
sessionFriendArea->addItem(SessionItemType, c.chatSessionId, c.avatar, c.chatSessionName, c.lastMessage.content);
|
||||||
|
break;
|
||||||
|
case IMAGE_TYPE:
|
||||||
|
sessionFriendArea->addItem(SessionItemType, c.chatSessionId, c.avatar, c.chatSessionName, "[图片]");
|
||||||
|
break;
|
||||||
|
case FILE_TYPE:
|
||||||
|
sessionFriendArea->addItem(SessionItemType, c.chatSessionId, c.avatar, c.chatSessionName, "[文件]");
|
||||||
|
break;
|
||||||
|
case SPEECH_TYPE:
|
||||||
|
sessionFriendArea->addItem(SessionItemType, c.chatSessionId, c.avatar, c.chatSessionName, "[语音]");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG() << "updateSessionList 收到了错误的“最后一条消息的类型”";
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::updateApplyList()
|
||||||
|
{
|
||||||
|
if (activeTab != APPLY_LIST) {
|
||||||
|
//当前的标签页不是“好友申请列表”
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
QList<UserInfo>* applyList = dataCenter->getApplyList();
|
||||||
|
|
||||||
|
sessionFriendArea->clear();
|
||||||
|
|
||||||
|
for (const auto& u : *applyList) {
|
||||||
|
//直接把这个UserInfo对象添加到界面之上,不涉及PB和自己结构的转换
|
||||||
|
//此处的UserInfo的desc不需要填写进来,好友的申请列表中, 不显示用户的标签
|
||||||
|
sessionFriendArea->addItem(ApplyItemType, u.userId, u.avatar, u.nickname, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::loadRecentMessage(const QString& chatSessionId)
|
||||||
|
{
|
||||||
|
//也是先判定,本地内存中是否已经存在对应的消息列表数据
|
||||||
|
//有的话直接显示到界面之上,没有的话从网络中获取
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
|
||||||
|
if (dataCenter->getRecentMessageList(chatSessionId) != nullptr) {
|
||||||
|
//直接拿着本地数据更新界面
|
||||||
|
LOG() << "本地已存在好友聊天数据,更新加载好友聊天数据";
|
||||||
|
updateRecentMessage(chatSessionId);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//本地没有数据,从网络加载
|
||||||
|
LOG() << "好友聊天数据不存在,正在从网络获取...";
|
||||||
|
connect(dataCenter, &DataCenter::getRecentMessageListDone, this, &MainWidget::updateRecentMessage, Qt::UniqueConnection);
|
||||||
|
dataCenter->getRecnetMessageListAsync(chatSessionId, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::updateRecentMessage(const QString& chatSessionId)
|
||||||
|
{
|
||||||
|
//拿到该会话最近的消息列表
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
auto* recentMessageList = dataCenter->getRecentMessageList(chatSessionId);
|
||||||
|
|
||||||
|
//清空原有界面上的消息列表
|
||||||
|
messageShowArea->clear();
|
||||||
|
|
||||||
|
//根据当前拿到的消息列表,显示到界面上
|
||||||
|
//此处把数据显示到界面上,可以使用头插也可以尾插
|
||||||
|
//对于消息列表来说,用户最先看到的应该是最近的消息,也就是末尾的消息
|
||||||
|
for (int i = recentMessageList->size() - 1; i >= 0; i--) {
|
||||||
|
const Message& message = recentMessageList->at(i);
|
||||||
|
bool isLeft = message.sender.userId != dataCenter->getMyselfsync()->userId;
|
||||||
|
messageShowArea->addFrontMessage(isLeft, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置会话标题
|
||||||
|
ChatSessionInfo* chatSessionInfo = dataCenter->findChatSessionById(chatSessionId);
|
||||||
|
if (chatSessionInfo != nullptr) {
|
||||||
|
//把会话名称显示到界面之上
|
||||||
|
sessionTitleLabel->setText(chatSessionInfo->chatSessionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
//保存当前选中的会话是哪个
|
||||||
|
dataCenter->setCurrentChatSessionId(chatSessionId);
|
||||||
|
|
||||||
|
//自动把滚动条滚动到末尾
|
||||||
|
messageShowArea->scrollToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::switchSession(const QString& userId)
|
||||||
|
{
|
||||||
|
//找到对应的会话元素
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
ChatSessionInfo* chatSessionInfo = dataCenter->findChatSessionByUserId(userId);
|
||||||
|
if (chatSessionInfo == nullptr) {
|
||||||
|
//每个好友都会有一个对应的会话(即便没有说过话)
|
||||||
|
//添加好友的时候,就创建出来的会话
|
||||||
|
LOG() << "严重错误->当前选中的好友对应的会话不存在";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//把选中的会话置顶
|
||||||
|
dataCenter->topCurrentChatSessionId(*chatSessionInfo);
|
||||||
|
|
||||||
|
//切换到会话列表标签页
|
||||||
|
switchTabToSession();
|
||||||
|
|
||||||
|
// 加载这个会话对应的历史消息
|
||||||
|
sessionFriendArea->clickItem(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageShowArea* MainWidget::getMessageShowArea()
|
||||||
|
{
|
||||||
|
return messageShowArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
424
ChatClient/src/messageeditarea.cpp
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
#include "messageeditarea.h"
|
||||||
|
|
||||||
|
#include "mainwidget.h"
|
||||||
|
#include "soundrecorder.h"
|
||||||
|
#include "model/datacenter.h"
|
||||||
|
#include "toast.h"
|
||||||
|
using namespace model;
|
||||||
|
|
||||||
|
MessageEditArea::MessageEditArea(QWidget *parent)
|
||||||
|
: QWidget{parent}
|
||||||
|
{
|
||||||
|
//设置必要的属性
|
||||||
|
this->setFixedHeight(250);
|
||||||
|
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
//创建垂直方向的布局管理器
|
||||||
|
QVBoxLayout* vlayout = new QVBoxLayout();
|
||||||
|
vlayout->setSpacing(0);
|
||||||
|
vlayout->setContentsMargins(0, 0, 30, 10);
|
||||||
|
this->setLayout(vlayout);
|
||||||
|
|
||||||
|
//创建水平方向的布局管理器
|
||||||
|
QHBoxLayout* hlayout = new QHBoxLayout();
|
||||||
|
hlayout->setSpacing(0);
|
||||||
|
hlayout->setContentsMargins(10, 0, 0, 0);
|
||||||
|
hlayout->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||||
|
vlayout->addLayout(hlayout);
|
||||||
|
|
||||||
|
//将上方的四个按钮,创建好并添加到layout中
|
||||||
|
QString btnStyle = R"(QPushButton { background-color: rgb(245, 245, 245); border: none; }
|
||||||
|
QPushButton:pressed {background-color: rgb(255, 255, 255); })";
|
||||||
|
QSize btnSize(35, 35);
|
||||||
|
QSize iconSize(25, 25);
|
||||||
|
|
||||||
|
sendImageBtn = new QPushButton();
|
||||||
|
sendImageBtn->setFixedSize(btnSize);
|
||||||
|
sendImageBtn->setIconSize(iconSize);
|
||||||
|
sendImageBtn->setIcon(QIcon(":/resource/image/image.png"));
|
||||||
|
sendImageBtn->setStyleSheet(btnStyle);
|
||||||
|
hlayout->addWidget(sendImageBtn);
|
||||||
|
|
||||||
|
sendFileBtn = new QPushButton();
|
||||||
|
sendFileBtn->setFixedSize(btnSize);
|
||||||
|
sendFileBtn->setIconSize(iconSize);
|
||||||
|
sendFileBtn->setIcon(QIcon(":/resource/image/file.png"));
|
||||||
|
sendFileBtn->setStyleSheet(btnStyle);
|
||||||
|
hlayout->addWidget(sendFileBtn);
|
||||||
|
|
||||||
|
sendSpeechBtn = new QPushButton();
|
||||||
|
sendSpeechBtn->setFixedSize(btnSize);
|
||||||
|
sendSpeechBtn->setIconSize(iconSize);
|
||||||
|
sendSpeechBtn->setIcon(QIcon(":/resource/image/sound.png"));
|
||||||
|
sendSpeechBtn->setStyleSheet(btnStyle);
|
||||||
|
hlayout->addWidget(sendSpeechBtn);
|
||||||
|
|
||||||
|
showHistoryBtn = new QPushButton();
|
||||||
|
showHistoryBtn->setFixedSize(btnSize);
|
||||||
|
showHistoryBtn->setIconSize(iconSize);
|
||||||
|
showHistoryBtn->setIcon(QIcon(":/resource/image/history.png"));
|
||||||
|
showHistoryBtn->setStyleSheet(btnStyle);
|
||||||
|
hlayout->addWidget(showHistoryBtn);
|
||||||
|
|
||||||
|
//添加多行编辑框
|
||||||
|
textEdit = new QPlainTextEdit();
|
||||||
|
textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
textEdit->setStyleSheet(R"( QPlainTextEdit {
|
||||||
|
color: black;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 10px; }
|
||||||
|
)");
|
||||||
|
textEdit->verticalScrollBar()->setStyleSheet(R"(
|
||||||
|
QScrollBar:vertical {
|
||||||
|
background-color: rgb(228, 228, 228); /* 滚动条背景透明 */
|
||||||
|
width: 6px; /* 默认宽度 */
|
||||||
|
margin: 0px 0px 0px 0px; /* 边距清零 */
|
||||||
|
}
|
||||||
|
QScrollBar::handle:vertical {
|
||||||
|
background: #CCCCCC; /* 滑块颜色(浅灰色) */
|
||||||
|
min-height: 30px; /* 滑块最小高度 */
|
||||||
|
border-radius: 3px; /* 圆角 */
|
||||||
|
}
|
||||||
|
QScrollBar::handle:vertical:hover {
|
||||||
|
background: #999999; /* 悬停时滑块颜色加深 */
|
||||||
|
width: 10px; /* 悬停时宽度增大 */
|
||||||
|
}
|
||||||
|
QScrollBar::handle:vertical:pressed {
|
||||||
|
background: #666666; /* 按下时颜色更深 */
|
||||||
|
}
|
||||||
|
QScrollBar::add-line:vertical,
|
||||||
|
QScrollBar::sub-line:vertical {
|
||||||
|
height: 0px; /* 隐藏上下箭头按钮 */
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
QScrollBar::add-page:vertical,
|
||||||
|
QScrollBar::sub-page:vertical {
|
||||||
|
background: none; /* 滚动条背景区域透明 */
|
||||||
|
}
|
||||||
|
|
||||||
|
)");
|
||||||
|
|
||||||
|
vlayout->addWidget(textEdit);
|
||||||
|
|
||||||
|
|
||||||
|
//添加发送消息文本的按钮
|
||||||
|
sendTextButton = new QPushButton();
|
||||||
|
sendTextButton->setText("发送");
|
||||||
|
sendTextButton->setFixedSize(120, 40);
|
||||||
|
//仿Wechat的标准样式
|
||||||
|
sendTextButton->setStyleSheet(R"(QPushButton {
|
||||||
|
font-size: 16px;
|
||||||
|
color: rgb(7, 193, 96);
|
||||||
|
border: none;
|
||||||
|
background-color: rgb(233, 233, 233);
|
||||||
|
border-radius: 5px; }
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: rgb(210, 210, 210); }
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: rgb(190, 190, 190); }
|
||||||
|
)");
|
||||||
|
|
||||||
|
|
||||||
|
//// 创建阴影效果
|
||||||
|
// shadowEffect = new QGraphicsDropShadowEffect;
|
||||||
|
// shadowEffect->setBlurRadius(12);
|
||||||
|
// shadowEffect->setOffset(0, 5);
|
||||||
|
// shadowEffect->setColor(QColor(0, 0, 0, 150));
|
||||||
|
// sendTextButton->setGraphicsEffect(shadowEffect);
|
||||||
|
//
|
||||||
|
//// 设置按钮样式表
|
||||||
|
// sendTextButton->setStyleSheet(R"(
|
||||||
|
// QPushButton {
|
||||||
|
// background: qlineargradient(spread:pad,
|
||||||
|
// x1:0, y1:0, x2:1, y2:0,
|
||||||
|
// stop:0 #9b59b6,
|
||||||
|
// stop:1 #8e44ad);
|
||||||
|
// border-radius: 10px;
|
||||||
|
// color: white;
|
||||||
|
// font: bold 14px;
|
||||||
|
// border: none;
|
||||||
|
// margin: 5px;
|
||||||
|
// box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||||||
|
// }
|
||||||
|
// QPushButton:hover {
|
||||||
|
// background: qlineargradient(spread:pad,
|
||||||
|
// x1:0, y1:0, x2:1, y2:0,
|
||||||
|
// stop:0 #e67e22,
|
||||||
|
// stop:1 #d35400);
|
||||||
|
// box-shadow: 0 7px 20px rgba(0,0,0,0.4);
|
||||||
|
// }
|
||||||
|
// QPushButton:pressed {
|
||||||
|
// background: qlineargradient(spread:pad,
|
||||||
|
// x1:0, y1:0, x2:1, y2:0,
|
||||||
|
// stop:0 #e74c3c,
|
||||||
|
// stop:1 #c0392b);
|
||||||
|
// }
|
||||||
|
//)");
|
||||||
|
//
|
||||||
|
// // 连接按钮的按下和释放事件
|
||||||
|
// connect(sendTextButton, &QPushButton::pressed, [=]() {
|
||||||
|
// QPropertyAnimation* anim = new QPropertyAnimation(shadowEffect, "offset");
|
||||||
|
// anim->setDuration(100);
|
||||||
|
// anim->setStartValue(shadowEffect->offset());
|
||||||
|
// anim->setEndValue(QPointF(0, 1));
|
||||||
|
// anim->start(QAbstractAnimation::DeleteWhenStopped);
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// connect(sendTextButton, &QPushButton::released, [=]() {
|
||||||
|
// QPropertyAnimation* anim = new QPropertyAnimation(shadowEffect, "offset");
|
||||||
|
// anim->setDuration(150);
|
||||||
|
// anim->setStartValue(shadowEffect->offset());
|
||||||
|
// anim->setEndValue(QPointF(0, sendTextButton->underMouse() ? 3 : 5)); // 根据是否悬停决定最终位置
|
||||||
|
// anim->setEasingCurve(QEasingCurve::OutBack);
|
||||||
|
// anim->start(QAbstractAnimation::DeleteWhenStopped);
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// // 安装事件过滤器
|
||||||
|
// sendTextButton->installEventFilter(this);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditArea::initSignalSlot()
|
||||||
|
{
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
|
||||||
|
//关联“显示历史消息窗口”信号槽
|
||||||
|
connect(showHistoryBtn, &QPushButton::clicked, this, [=]() {
|
||||||
|
if (dataCenter->getCurrentSessionId().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HistoryMessageWidget* historyMessageWidget = new HistoryMessageWidget(this);
|
||||||
|
historyMessageWidget->exec();
|
||||||
|
});
|
||||||
|
|
||||||
|
//关联“发送文本消息”信号槽
|
||||||
|
connect(sendTextButton, &QPushButton::clicked, this, &MessageEditArea::sendTextMessage);
|
||||||
|
connect(dataCenter, &DataCenter::sendMessageDone, this, &MessageEditArea::addSelfMessage);
|
||||||
|
|
||||||
|
//关联收到消息的信号槽
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
//先确认当前是否有会话被选中
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
if (dataCenter->getCurrentSessionId().isEmpty()) {
|
||||||
|
LOG() << "当前未选中任何会话,不发送任何消息";
|
||||||
|
//上述日志只是在开发阶段能看到,程序发布出去,就无法看到了
|
||||||
|
//因此需要让普通用户也能看到提示
|
||||||
|
Toast::showMessage("当前未选中会话,不发送任何消息");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取到输入框的内容,没输入,则不做任何操作
|
||||||
|
const QString& content = textEdit->toPlainText().trimmed();
|
||||||
|
if (content.isEmpty()) {
|
||||||
|
LOG() << "输入框为空";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//清空输入框已有的内容
|
||||||
|
textEdit->setPlainText("");
|
||||||
|
|
||||||
|
//通过网络发送数据给服务器
|
||||||
|
dataCenter->sendTextMessageAsync(dataCenter->getCurrentSessionId(), content);
|
||||||
|
}
|
||||||
|
|
||||||
|
//针对自己发送消息的操作,做处理,把自己发送的消息显示到界面上
|
||||||
|
void MessageEditArea::addSelfMessage(MessageType messageType, const QByteArray& content, const QString& extraInfo)
|
||||||
|
{
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
const QString& currentChatSessionId = dataCenter->getCurrentSessionId();
|
||||||
|
//构造出一个消息对象
|
||||||
|
Message message = Message::makeMessage(messageType, currentChatSessionId, *dataCenter->getMyselfsync(), content, extraInfo);
|
||||||
|
dataCenter->addMessage(message);
|
||||||
|
|
||||||
|
//把这个新的消息,显示到消息展示区
|
||||||
|
MainWidget* mainWidget = MainWidget::getInstance();
|
||||||
|
MessageShowArea* messageShowArea = mainWidget->getMessageShowArea();
|
||||||
|
messageShowArea->addMessage(false, message);
|
||||||
|
|
||||||
|
//控制消息显示区,滚动条,滚动到末尾
|
||||||
|
messageShowArea->scrollToEnd();
|
||||||
|
|
||||||
|
//发送信号,通知会话列表,更新最后一条消息
|
||||||
|
emit dataCenter->updateLastMessage(currentChatSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditArea::addOtherMessage(const model::Message& message)
|
||||||
|
{
|
||||||
|
//通过主界面,拿到消息展示区
|
||||||
|
MainWidget* mainWidget = MainWidget::getInstance();
|
||||||
|
MessageShowArea* messageShowArea = mainWidget->getMessageShowArea();
|
||||||
|
|
||||||
|
//把收到的新消息,添加到消息展示区
|
||||||
|
messageShowArea->addMessage(true, message);
|
||||||
|
|
||||||
|
//控制消息展示区的滚动条,把窗口滚动到末尾
|
||||||
|
messageShowArea->scrollToEnd();
|
||||||
|
|
||||||
|
//提示一个收到消息
|
||||||
|
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)
|
||||||
|
//{
|
||||||
|
// if (obj == sendTextButton) {
|
||||||
|
// if (event->type() == QEvent::Enter) {
|
||||||
|
// // 鼠标进入事件
|
||||||
|
// QPropertyAnimation* hoverAnim = new QPropertyAnimation(shadowEffect, "offset");
|
||||||
|
// hoverAnim->setDuration(200);
|
||||||
|
// hoverAnim->setStartValue(shadowEffect->offset());
|
||||||
|
// hoverAnim->setEndValue(QPointF(0, 3));
|
||||||
|
// hoverAnim->start(QAbstractAnimation::DeleteWhenStopped);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// else if (event->type() == QEvent::Leave) {
|
||||||
|
// // 鼠标离开事件
|
||||||
|
// QPropertyAnimation* hoverAnim = new QPropertyAnimation(shadowEffect, "offset");
|
||||||
|
// hoverAnim->setDuration(200);
|
||||||
|
// hoverAnim->setStartValue(shadowEffect->offset());
|
||||||
|
// hoverAnim->setEndValue(QPointF(0, 5));
|
||||||
|
// hoverAnim->start(QAbstractAnimation::DeleteWhenStopped);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return QObject::eventFilter(obj, event);
|
||||||
|
//}
|
||||||
512
ChatClient/src/messageshowarea.cpp
Normal file
@ -0,0 +1,512 @@
|
|||||||
|
#include "messageshowarea.h"
|
||||||
|
|
||||||
|
#include <QMenu>
|
||||||
|
#include <qtimer.h>
|
||||||
|
#include "mainwidget.h"
|
||||||
|
#include "QFileDialog"
|
||||||
|
#include "toast.h"
|
||||||
|
#include "userinfowidget.h"
|
||||||
|
#include "model/datacenter.h"
|
||||||
|
#include "soundrecorder.h"
|
||||||
|
|
||||||
|
MessageShowArea::MessageShowArea() {
|
||||||
|
//初始化基本属性
|
||||||
|
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
this->setWidgetResizable(true);
|
||||||
|
|
||||||
|
//设置滚动条样式
|
||||||
|
this->verticalScrollBar()->setStyleSheet("QScrollBar:vertical { width: 2px; background-color: rgb(240, 240, 240); }");
|
||||||
|
this->horizontalScrollBar()->setStyleSheet("QScrollBar:horizontal { height: 0; }");
|
||||||
|
this->setStyleSheet("QScrollArea { border: none; }");
|
||||||
|
|
||||||
|
//创建container这样的widget,作为包含内部元素的容器
|
||||||
|
container = new QWidget();
|
||||||
|
this->setWidget(container);
|
||||||
|
|
||||||
|
//给container添加界面布局管理器
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout();
|
||||||
|
layout->setSpacing(0);
|
||||||
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
container->setLayout(layout);
|
||||||
|
|
||||||
|
// 添加测试数据
|
||||||
|
#if TEST_UI
|
||||||
|
|
||||||
|
//测试长消息
|
||||||
|
model::UserInfo userInfo;
|
||||||
|
userInfo.nickname = "???";
|
||||||
|
userInfo.userId = "4862";
|
||||||
|
userInfo.phone = "00000000000";
|
||||||
|
userInfo.description = "A test description.";
|
||||||
|
const QString s = R"(The starry sky is just a few years ago. And the past may no longer exist,The only thing that remains is light years away, it's just an ethereal phantom.)";
|
||||||
|
|
||||||
|
userInfo.avatar = QIcon(":/resource/image/defaultAvatar.png");
|
||||||
|
Message message = Message::makeMessage(model::TEXT_TYPE, "", userInfo, s.toUtf8(), "");
|
||||||
|
this->addMessage(false, message);
|
||||||
|
|
||||||
|
bool k = true;
|
||||||
|
for(int i = 0; i < 8; i++) {
|
||||||
|
model::UserInfo userInfo;
|
||||||
|
userInfo.userId = QString::number(i);
|
||||||
|
userInfo.phone = "12345678900";
|
||||||
|
userInfo.description = "A test description.";
|
||||||
|
userInfo.nickname = "xyz" + QString::number(i);
|
||||||
|
userInfo.avatar = QIcon(":/resource/image/defaultAvatar.png");
|
||||||
|
Message message = Message::makeMessage(model::TEXT_TYPE, "", userInfo, (QString("this is a test message...") + QString::number(i)).toUtf8(), "");
|
||||||
|
k = !k;
|
||||||
|
this->addMessage(k, message);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageShowArea::addFrontMessage(bool isLeft, const Message &message)
|
||||||
|
{
|
||||||
|
MessageItem* messageItem = MessageItem::makeMessageItem(isLeft, message);
|
||||||
|
QVBoxLayout* layout = dynamic_cast<QVBoxLayout*>(container->layout());
|
||||||
|
layout->insertWidget(0, messageItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageShowArea::addMessage(bool isLeft, const Message &message)
|
||||||
|
{
|
||||||
|
MessageItem* messageItem = MessageItem::makeMessageItem(isLeft, message);
|
||||||
|
container->layout()->addWidget(messageItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageShowArea::clear()
|
||||||
|
{
|
||||||
|
QLayout* layout = container->layout();
|
||||||
|
//要遍历布局管理器,删除里面的元素
|
||||||
|
for(int i = layout->count() - 1; i >= 0; i--) {
|
||||||
|
QLayoutItem* item = layout->takeAt(i);
|
||||||
|
if(item != nullptr && item->widget() != nullptr) {
|
||||||
|
delete item->widget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageShowArea::scrollToEnd()
|
||||||
|
{
|
||||||
|
//拿到滚动区域的滚动条
|
||||||
|
//获取到滚动条的最大值并设置
|
||||||
|
//为了使滚动到正确的位置,即能够在界面绘制好后进行滚动设置
|
||||||
|
//这里选择给滚动条加个延时
|
||||||
|
QTimer* timer = new QTimer();
|
||||||
|
connect(timer, &QTimer::timeout, this, [=]() {
|
||||||
|
//获取到垂直滚动条的最大值
|
||||||
|
int maxValue = this->verticalScrollBar()->maximum();
|
||||||
|
//设置滚动条滚动的位置
|
||||||
|
this->verticalScrollBar()->setValue(maxValue);
|
||||||
|
|
||||||
|
timer->stop();
|
||||||
|
timer->deleteLater();
|
||||||
|
});
|
||||||
|
timer->start(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////
|
||||||
|
/// 表示一个消息元素
|
||||||
|
////////////////////////////////////////////
|
||||||
|
MessageItem::MessageItem(bool isLeft)
|
||||||
|
:isLeft(isLeft)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageItem *MessageItem::makeMessageItem(bool isLeft, const Message &message)
|
||||||
|
{
|
||||||
|
//创建对象和布局管理器
|
||||||
|
MessageItem* messageItem = new MessageItem(isLeft);
|
||||||
|
QGridLayout* layout = new QGridLayout();
|
||||||
|
layout->setSpacing(10);
|
||||||
|
layout->setContentsMargins(30, 10, 40, 0);
|
||||||
|
|
||||||
|
//这个message最低不能低于100
|
||||||
|
messageItem->setMinimumHeight(100);
|
||||||
|
messageItem->setLayout(layout);
|
||||||
|
|
||||||
|
//创建头像
|
||||||
|
QPushButton* avatarBtn = new QPushButton();
|
||||||
|
avatarBtn->setFixedSize(40, 40);
|
||||||
|
avatarBtn->setIconSize(QSize(40, 40));
|
||||||
|
avatarBtn->setIcon(message.sender.avatar);
|
||||||
|
avatarBtn->setStyleSheet("QPushButton { border: none; }");
|
||||||
|
if(isLeft) {
|
||||||
|
layout->addWidget(avatarBtn, 0, 0, 2, 1, Qt::AlignCenter | Qt::AlignLeft);
|
||||||
|
} else {
|
||||||
|
layout->addWidget(avatarBtn, 0, 1, 2, 1, Qt::AlignCenter | Qt::AlignRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
//创建名字和时间
|
||||||
|
QLabel* nameLabel = new QLabel();
|
||||||
|
nameLabel->setText(message.sender.nickname + " " + message.time);
|
||||||
|
nameLabel->setAlignment(Qt::AlignBottom);
|
||||||
|
nameLabel->setStyleSheet("QLabel { font-size: 12px; color: rgb(178, 178, 178); }");
|
||||||
|
if(isLeft) {
|
||||||
|
layout->addWidget(nameLabel, 0, 1);
|
||||||
|
} else {
|
||||||
|
layout->addWidget(nameLabel, 0, 0, Qt::AlignRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
//创建消息体
|
||||||
|
QWidget* contentWidget = nullptr;
|
||||||
|
switch(message.messageType) {
|
||||||
|
case model::TEXT_TYPE:
|
||||||
|
contentWidget = makeTextMessageItem(isLeft, message.content);
|
||||||
|
break;
|
||||||
|
case model::IMAGE_TYPE:
|
||||||
|
contentWidget = makeImageMessageItem(isLeft, message.fileId, message.content);
|
||||||
|
break;
|
||||||
|
case model::FILE_TYPE:
|
||||||
|
contentWidget = makeFileMessageItem(isLeft, message);
|
||||||
|
break;
|
||||||
|
case model::SPEECH_TYPE:
|
||||||
|
contentWidget = makeSpeechMessageItem(isLeft, message);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG() << "error messageType: " << message.messageType;
|
||||||
|
}
|
||||||
|
if(isLeft) {
|
||||||
|
layout->addWidget(contentWidget, 1, 1);
|
||||||
|
} else {
|
||||||
|
layout->addWidget(contentWidget, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//连接信号槽,处理用户点击头像的操作
|
||||||
|
connect(avatarBtn, &QPushButton::clicked, messageItem, [=]() {
|
||||||
|
MainWidget* mainWidget = MainWidget::getInstance();
|
||||||
|
UserInfoWidget* userInfoWidget = new UserInfoWidget(message.sender, mainWidget);
|
||||||
|
userInfoWidget->setStyleSheet("QDialog { background-color: rgb(245, 245, 245); }");
|
||||||
|
userInfoWidget->exec();
|
||||||
|
});
|
||||||
|
|
||||||
|
//当用户修改了昵称的时候,更新名字的显示
|
||||||
|
if (!isLeft) {
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
connect(dataCenter, &DataCenter::changeNicknameDone, messageItem, [=]() {
|
||||||
|
nameLabel->setText(dataCenter->getMyselfsync()->nickname + " " + message.time);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(dataCenter, &DataCenter::changeAvatarDone, messageItem, [=]() {
|
||||||
|
UserInfo* myself = dataCenter->getMyselfsync();
|
||||||
|
avatarBtn->setIcon(myself->avatar);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return messageItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget *MessageItem::makeTextMessageItem(bool isLeft, const QString &text)
|
||||||
|
{
|
||||||
|
MessageContentLabel* messageContentLabel = new MessageContentLabel(text, isLeft, TEXT_TYPE, "", QByteArray());
|
||||||
|
return messageContentLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget *MessageItem::makeImageMessageItem(bool isLeft, const QString& fileId, const QByteArray& content)
|
||||||
|
{
|
||||||
|
MessageImageLabel* messageImageLabel = new MessageImageLabel(fileId, content, isLeft);
|
||||||
|
return messageImageLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget *MessageItem::makeFileMessageItem(bool isLeft, const Message& message)
|
||||||
|
{
|
||||||
|
MessageContentLabel* messageContentLabel = new MessageContentLabel("[文件] " + message.fileName, isLeft, message.messageType,
|
||||||
|
message.fileId, message.content);
|
||||||
|
return messageContentLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget *MessageItem::makeSpeechMessageItem(bool isLeft, const Message& message)
|
||||||
|
{
|
||||||
|
MessageContentLabel* messageContentLabel = new MessageContentLabel("[语音]", isLeft, message.messageType, message.fileId, message.content);
|
||||||
|
return messageContentLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////
|
||||||
|
/// 创建类表示“文本消息”正文部分
|
||||||
|
//这个类也表示文件消息
|
||||||
|
////////////////////////////////////////////
|
||||||
|
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);
|
||||||
|
|
||||||
|
QFont font;
|
||||||
|
font.setFamily("微软雅黑");
|
||||||
|
font.setPixelSize(16);
|
||||||
|
|
||||||
|
this->label = new QLabel(this);
|
||||||
|
this->label->setText(text);
|
||||||
|
this->label->setFont(font);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//这个函数会在该控件被显示时,自动的调用到
|
||||||
|
void MessageContentLabel::paintEvent(QPaintEvent *event)
|
||||||
|
{
|
||||||
|
(void) event;
|
||||||
|
|
||||||
|
//获取到父元素的宽度
|
||||||
|
QObject* object = this->parent();
|
||||||
|
if(!object->isWidgetType()) {
|
||||||
|
//说明当前的对象不是QWidget,则不需要进行任何后续的绘制操作
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QWidget* parent = dynamic_cast<QWidget*>(object);
|
||||||
|
int width = parent->width() * 0.6;
|
||||||
|
|
||||||
|
//计算当前文本,如果单行防止放置需要多宽
|
||||||
|
QFontMetrics metrics(this->label->font());
|
||||||
|
int totalWidth = metrics.horizontalAdvance(this->label->text());
|
||||||
|
|
||||||
|
//计算出此处的行数是多少
|
||||||
|
int rows = (totalWidth / (width - 40)) + 1;
|
||||||
|
if(rows == 1) {
|
||||||
|
//若此时得到的行数只有一行
|
||||||
|
width = totalWidth + 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
//根据行数来确定高度
|
||||||
|
//行数 × 行高(字体高度的 1.2 倍) + 上下内边距(各 10px)
|
||||||
|
int height = rows * (this->label->font().pixelSize() * 1.2 ) + 20;
|
||||||
|
|
||||||
|
//绘制圆角矩形和箭头
|
||||||
|
QPainter painter(this);
|
||||||
|
QPainterPath path;
|
||||||
|
//设置抗锯齿
|
||||||
|
painter.setRenderHint(QPainter::Antialiasing);
|
||||||
|
if(isLeft) {
|
||||||
|
painter.setPen(QPen(QColor(255, 255, 255)));
|
||||||
|
painter.setBrush(QColor(255, 255, 255)); //白色填充
|
||||||
|
|
||||||
|
//绘制圆角矩形
|
||||||
|
painter.drawRoundedRect(10, 0, width, height, 10 ,10);
|
||||||
|
//绘制箭头
|
||||||
|
path.moveTo(10, 15);
|
||||||
|
path.lineTo(0, 20);
|
||||||
|
path.lineTo(10, 25);
|
||||||
|
path.closeSubpath(); //绘制的线形成闭合的多边形,才能进行使用Brush填充颜色
|
||||||
|
painter.drawPath(path);//设置好,调用画笔进行绘制操作
|
||||||
|
|
||||||
|
this->label->setGeometry(10, 0, width, height);
|
||||||
|
} else {
|
||||||
|
painter.setPen(QPen(QColor(137, 217, 97)));
|
||||||
|
painter.setBrush(QColor(137, 217, 97));
|
||||||
|
|
||||||
|
//圆角矩形左侧边的横坐标位置
|
||||||
|
int leftPos = this->width() - width - 10; //10 用来容纳箭头的宽度
|
||||||
|
//圆角矩形右侧边的横坐标位置
|
||||||
|
int rightPos = this->width() - 10;
|
||||||
|
|
||||||
|
//绘制圆角矩形
|
||||||
|
painter.drawRoundedRect(leftPos, 0, width, height, 10, 10);
|
||||||
|
//绘制箭头
|
||||||
|
path.moveTo(rightPos, 15);
|
||||||
|
path.lineTo(rightPos + 10, 20);
|
||||||
|
path.lineTo(rightPos, 25);
|
||||||
|
path.closeSubpath(); //绘制的线形成闭合的多边形,才能进行使用Brush填充颜色
|
||||||
|
painter.drawPath(path);//设置好,调用画笔进行绘制操作
|
||||||
|
|
||||||
|
this->label->setGeometry(leftPos, 0, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
//重新设置父元素的高度,保证父元素足够的高,能够容纳下上述绘制消息的显示区域
|
||||||
|
//注意高度要涵盖之前的名字和时间的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<QWidget*>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
657
ChatClient/src/model/datacenter.cpp
Normal file
@ -0,0 +1,657 @@
|
|||||||
|
#include "datacenter.h"
|
||||||
|
|
||||||
|
namespace model
|
||||||
|
{
|
||||||
|
DataCenter* DataCenter::instance = nullptr;
|
||||||
|
|
||||||
|
DataCenter* DataCenter::getInstance()
|
||||||
|
{
|
||||||
|
if (instance == nullptr) {
|
||||||
|
instance = new DataCenter();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataCenter::~DataCenter()
|
||||||
|
{
|
||||||
|
//释放所有的成员
|
||||||
|
//此处就不必判断nullptr,直接delete即可
|
||||||
|
//c++标准中明确规定了,对nullptr进行delete是合法行为,不会有任何副作用
|
||||||
|
delete myself;
|
||||||
|
delete friendList;
|
||||||
|
delete chatSessionList;
|
||||||
|
delete memberList;
|
||||||
|
delete applyList;
|
||||||
|
delete recentMessages;
|
||||||
|
delete unreadMessageCount;
|
||||||
|
delete searchUserResult;
|
||||||
|
delete searchMessageResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataCenter::DataCenter() : netClient(this)
|
||||||
|
{
|
||||||
|
//此处只是把这几个hash类型的属性进行new出实例,其他的QList都暂时不实例化
|
||||||
|
//主要是为了使用nullptr表示非法状态
|
||||||
|
//对于hash来说,不关心整个的QHash是否是nullptr,而是关心某个key对应的value是否存在
|
||||||
|
//通过key是否存在,也能表示该值是否有效
|
||||||
|
recentMessages = new QHash<QString, QList<Message>>();
|
||||||
|
memberList = new QHash<QString, QList<UserInfo>>();
|
||||||
|
unreadMessageCount = new QHash<QString, int>();
|
||||||
|
|
||||||
|
//加载数据
|
||||||
|
loadDataFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::initDataFile()
|
||||||
|
{
|
||||||
|
//构造出文件的路径,使用appData存储文件
|
||||||
|
QString basePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
|
QString filePath = basePath + "/ChatClient.json";
|
||||||
|
|
||||||
|
QDir dir;
|
||||||
|
if (!dir.exists(basePath)) {
|
||||||
|
//没有则进行目录的创建
|
||||||
|
dir.mkpath(basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
//构造好文件的路径之后,把文件创建出来
|
||||||
|
//写的方式打开,并且写入初始内容
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
|
LOG() << "initDataFile open file failed!!!" << file.errorString();
|
||||||
|
file.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//打开成功,写入初始内容
|
||||||
|
QString data = "{\n\n}";
|
||||||
|
file.write(data.toUtf8());
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::saveDataFile()
|
||||||
|
{
|
||||||
|
QString basePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
|
QString filePath = basePath + "/ChatClient.json";
|
||||||
|
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
|
LOG() << "saveDataFile open file failed!!!" << file.errorString();
|
||||||
|
file.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//按照json格式进行数据的写入
|
||||||
|
//这个对象可以当作map一样来使用
|
||||||
|
QJsonObject jsonObj;
|
||||||
|
jsonObj["loginSessionId"] = loginSessionId;
|
||||||
|
|
||||||
|
QJsonObject jsonUnread;
|
||||||
|
for (auto it = unreadMessageCount->begin(); it != unreadMessageCount->end(); ++it) {
|
||||||
|
//注意此处的qt迭代器使用和stl有区别(不是使用first和second)
|
||||||
|
jsonUnread[it.key()] = it.value();
|
||||||
|
}
|
||||||
|
jsonObj["unread"] = jsonUnread;
|
||||||
|
|
||||||
|
//把json写入文件
|
||||||
|
QJsonDocument jsonDoc(jsonObj);
|
||||||
|
QString s = jsonDoc.toJson();
|
||||||
|
file.write(s.toUtf8());
|
||||||
|
|
||||||
|
//关闭文件
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
//加载文件,在DataCenter实例化就应该执行的
|
||||||
|
void DataCenter::loadDataFile()
|
||||||
|
{
|
||||||
|
//确保加载之前就针对文件进行初始化操作
|
||||||
|
QString basePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
|
QString filePath = basePath + "/ChatClient.json";
|
||||||
|
|
||||||
|
//判定文件是否存在,不存在则初始化,并创建出空空白的json文件
|
||||||
|
QFileInfo fileInfo(filePath);
|
||||||
|
if (!fileInfo.exists()) {
|
||||||
|
initDataFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
//按读方式打开文件
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
LOG() << "loadDataFile open file failed!!!" << file.errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//读取文件内容,解析为 JSON对象
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(file.readAll());
|
||||||
|
if (jsonDoc.isNull()) {
|
||||||
|
LOG() << "loadDataFile parsing the json file failed!!!";
|
||||||
|
file.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject jsonObj = jsonDoc.object();
|
||||||
|
this->loginSessionId = jsonObj["loginSessionId"].toString();
|
||||||
|
|
||||||
|
|
||||||
|
this->unreadMessageCount->clear();
|
||||||
|
QJsonObject jsonUnread = jsonObj["unread"].toObject();
|
||||||
|
for (auto it = jsonUnread.begin(); it != jsonUnread.end(); ++it) {
|
||||||
|
this->unreadMessageCount->insert(it.key(), it.value().toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::clearUnread(const QString& chatSessionId)
|
||||||
|
{
|
||||||
|
(*unreadMessageCount)[chatSessionId] = 0;
|
||||||
|
|
||||||
|
//手动保存一下结果到文件中
|
||||||
|
saveDataFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::addUnread(const QString& chatSessionId)
|
||||||
|
{
|
||||||
|
++(*unreadMessageCount)[chatSessionId];
|
||||||
|
|
||||||
|
//手动保存一下结果到文件
|
||||||
|
saveDataFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
int DataCenter::getUnread(const QString& chatSessionId)
|
||||||
|
{
|
||||||
|
return (*unreadMessageCount)[chatSessionId];
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::initWebsocket()
|
||||||
|
{
|
||||||
|
netClient.initWebSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::getMyselfAsync()
|
||||||
|
{
|
||||||
|
//DataCenter 只是负责“处理数据”,
|
||||||
|
//而真正负责网络进行通信,需要通过NetClient
|
||||||
|
netClient.getMyself(loginSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserInfo* DataCenter::getMyselfsync()
|
||||||
|
{
|
||||||
|
return myself;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::resetMyself(std::shared_ptr<bite_im::GetUserInfoRsp> resp)
|
||||||
|
{
|
||||||
|
if (myself == nullptr) {
|
||||||
|
myself = new UserInfo();
|
||||||
|
}
|
||||||
|
const bite_im::UserInfo& userInfo = resp->userInfo();
|
||||||
|
myself->load(userInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<UserInfo>* DataCenter::getFriendList()
|
||||||
|
{
|
||||||
|
return friendList;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::getFriendListAsync()
|
||||||
|
{
|
||||||
|
netClient.getFriendList(loginSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::resetFriendList(std::shared_ptr<bite_im::GetFriendListRsp> resp)
|
||||||
|
{
|
||||||
|
if (friendList == nullptr) {
|
||||||
|
friendList = new QList<UserInfo>();
|
||||||
|
}
|
||||||
|
friendList->clear();
|
||||||
|
|
||||||
|
QList<bite_im::UserInfo> friendListPB = resp->friendList();
|
||||||
|
for (auto& f : friendListPB) {
|
||||||
|
UserInfo userInfo;
|
||||||
|
userInfo.load(f);
|
||||||
|
friendList->push_back(userInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ChatSessionInfo>* DataCenter::getChatSessionList()
|
||||||
|
{
|
||||||
|
return chatSessionList;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::getChatSessionListAsync()
|
||||||
|
{
|
||||||
|
netClient.getChatSessionList(loginSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::resetChatSessionList(std::shared_ptr<bite_im::GetChatSessionListRsp> resp)
|
||||||
|
{
|
||||||
|
if (chatSessionList == nullptr) {
|
||||||
|
chatSessionList = new QList<ChatSessionInfo>();
|
||||||
|
}
|
||||||
|
chatSessionList->clear();
|
||||||
|
|
||||||
|
auto& chatSessionListPB = resp->chatSessionInfoList();
|
||||||
|
for (auto& c : chatSessionListPB) {
|
||||||
|
ChatSessionInfo chatSessionInfo;
|
||||||
|
chatSessionInfo.load(const_cast<bite_im::ChatSessionInfo&>(c));
|
||||||
|
chatSessionList->push_back(chatSessionInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<UserInfo>* DataCenter::getApplyList()
|
||||||
|
{
|
||||||
|
return applyList;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::getApplyListAsync()
|
||||||
|
{
|
||||||
|
netClient.getApplyList(loginSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::resetApplyList(std::shared_ptr<bite_im::GetPendingFriendEventListRsp> resp)
|
||||||
|
{
|
||||||
|
if (applyList == nullptr) {
|
||||||
|
applyList = new QList<UserInfo>();
|
||||||
|
}
|
||||||
|
applyList->clear();
|
||||||
|
|
||||||
|
auto& eventList = resp->event();
|
||||||
|
for (auto& event : eventList) {
|
||||||
|
UserInfo userInfo;
|
||||||
|
userInfo.load(event.sender());
|
||||||
|
applyList->push_back(userInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::getRecnetMessageListAsync(const QString& chatSessionId, bool updateUI)
|
||||||
|
{
|
||||||
|
netClient.getRecentMessageList(loginSessionId, chatSessionId, updateUI);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<Message>* DataCenter::getRecentMessageList(const QString& chatSessionId)
|
||||||
|
{
|
||||||
|
if (!recentMessages->contains(chatSessionId)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &(*recentMessages)[chatSessionId];
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::resetRecentMessageList(const QString& chatSessionId, std::shared_ptr<bite_im::GetRecentMsgRsp> resp)
|
||||||
|
{
|
||||||
|
//拿到chatSessionId 对应的消息列表,并清空
|
||||||
|
//注意此处务必使用的是引用类型,才是修改哈希表内部的内容
|
||||||
|
QList<Message>& messageList = (*recentMessages)[chatSessionId];
|
||||||
|
messageList.clear();
|
||||||
|
|
||||||
|
//遍历响应结果列表
|
||||||
|
for (auto& m : resp->msgList()) {
|
||||||
|
Message message;
|
||||||
|
message.load(m);
|
||||||
|
|
||||||
|
messageList.push_back(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::sendTextMessageAsync(const QString& chatSessionId, const QString& content)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::resetNickname(const QString& nickname)
|
||||||
|
{
|
||||||
|
if (myself == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
myself->nickname = nickname;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::changeDescriptionAsync(const QString& desc)
|
||||||
|
{
|
||||||
|
netClient.changeDescription(loginSessionId, desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::resetDescription(const QString& desc)
|
||||||
|
{
|
||||||
|
if (myself == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
myself->description = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::getVerifyCodeAsync(const QString& email)
|
||||||
|
{
|
||||||
|
//这个操作,不需要loginSessionId
|
||||||
|
//
|
||||||
|
netClient.getVerifyCode(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::resetVerifyCodeId(const QString& verifyCodeId)
|
||||||
|
{
|
||||||
|
this->currentVerifyCodeId = verifyCodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString& DataCenter::getVerifyCodeId() const
|
||||||
|
{
|
||||||
|
return currentVerifyCodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::changePhoneAsync(const QString& email, const QString& verifyCodeId, const QString& verifyCode)
|
||||||
|
{
|
||||||
|
netClient.changeEmail(loginSessionId, email, verifyCodeId, verifyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::resetPhone(const QString& email)
|
||||||
|
{
|
||||||
|
if (myself == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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<QString>& userIdList)
|
||||||
|
{
|
||||||
|
netClient.createGroupChatSession(loginSessionId, userIdList);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::getMemberListAsync(const QString& chatSessionId)
|
||||||
|
{
|
||||||
|
netClient.getMemberList(loginSessionId, chatSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<UserInfo>* DataCenter::getMemberList(const QString& chatSessionId)
|
||||||
|
{
|
||||||
|
if (!this->memberList->contains(chatSessionId)) return nullptr;
|
||||||
|
return &(*this->memberList)[chatSessionId];
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::resetMemberList(const QString& chatSessionId, const QList<bite_im::UserInfo>& memberList)
|
||||||
|
{
|
||||||
|
//根据chatSessionId,这个key,得到对应的value(QList)
|
||||||
|
QList<UserInfo>& 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<UserInfo>* DataCenter::getSearchUserResult()
|
||||||
|
{
|
||||||
|
return searchUserResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::resetSearchUserResult(const QList<bite_im::UserInfo>& userList)
|
||||||
|
{
|
||||||
|
if (searchUserResult == nullptr) {
|
||||||
|
searchUserResult = new QList<UserInfo>();
|
||||||
|
}
|
||||||
|
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<Message>* DataCenter::getSearchMessageReuslt()
|
||||||
|
{
|
||||||
|
return searchMessageResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::resetSearchMessageResult(const QList<bite_im::MessageInfo>& msgList)
|
||||||
|
{
|
||||||
|
if (this->searchMessageResult == nullptr) {
|
||||||
|
this->searchMessageResult = new QList<Message>();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::resetAvatar(const QByteArray& avatar)
|
||||||
|
{
|
||||||
|
if (myself == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
myself->avatar = makeIcon(avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatSessionInfo* DataCenter::findChatSessionById(const QString& chatSessionId)
|
||||||
|
{
|
||||||
|
if (chatSessionList == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
for (auto& info : *chatSessionList) {
|
||||||
|
if (info.chatSessionId == chatSessionId) {
|
||||||
|
return &info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatSessionInfo* DataCenter::findChatSessionByUserId(const QString& userId)
|
||||||
|
{
|
||||||
|
if (chatSessionList == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
for (auto& info : *chatSessionList) {
|
||||||
|
if (info.userId == userId) {
|
||||||
|
return &info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::topCurrentChatSessionId(const ChatSessionInfo& chatSessionInfo)
|
||||||
|
{
|
||||||
|
if (chatSessionList == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//把这个元素从列表中找到
|
||||||
|
auto it = chatSessionList->begin();
|
||||||
|
for (; it != chatSessionList->end(); ++it) {
|
||||||
|
if (it->chatSessionId == chatSessionInfo.chatSessionId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it == chatSessionList->end()) {
|
||||||
|
//上面的循环没有找到匹配的元素,直接返回,正常来说,不会走这个逻辑
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除并重新插入头部
|
||||||
|
ChatSessionInfo backup = chatSessionInfo;
|
||||||
|
chatSessionList->erase(it);
|
||||||
|
|
||||||
|
//把备份的元素插入到头部
|
||||||
|
chatSessionList->push_front(backup);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserInfo* DataCenter::findFriendById(const QString& userId)
|
||||||
|
{
|
||||||
|
if (this->friendList == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
for (auto& f : *friendList) {
|
||||||
|
if (f.userId == userId) {
|
||||||
|
return &f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::setCurrentChatSessionId(const QString& chatSessionId)
|
||||||
|
{
|
||||||
|
this->currentChatSessionId = chatSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString& DataCenter::getCurrentSessionId()
|
||||||
|
{
|
||||||
|
return currentChatSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::addMessage(const Message& message)
|
||||||
|
{
|
||||||
|
QList<Message>& messageList = (*recentMessages)[message.chatSessionId];
|
||||||
|
messageList.push_back(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
} //end namespace model
|
||||||
1277
ChatClient/src/network/netclient.cpp
Normal file
250
ChatClient/src/phoneloginwidget.cpp
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
#include "phoneloginwidget.h"
|
||||||
|
|
||||||
|
#include "mainwidget.h"
|
||||||
|
#include "model/datacenter.h"
|
||||||
|
#include "toast.h"
|
||||||
|
|
||||||
|
using namespace model;
|
||||||
|
|
||||||
|
PhoneLoginWidget::PhoneLoginWidget(QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
{
|
||||||
|
// 1. 设置窗口的基本属性
|
||||||
|
this->setFixedSize(400, 350);
|
||||||
|
this->setWindowTitle("登录");
|
||||||
|
this->setWindowIcon(QIcon(":/resource/image/logo.png"));
|
||||||
|
this->setStyleSheet("QWidget { background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); }");
|
||||||
|
this->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
// 2. 创建核心布局管理器
|
||||||
|
QGridLayout* layout = new QGridLayout();
|
||||||
|
layout->setSpacing(10);
|
||||||
|
layout->setContentsMargins(50, 0, 50, 0);
|
||||||
|
this->setLayout(layout);
|
||||||
|
|
||||||
|
// 3. 创建标题
|
||||||
|
titleLabel = new QLabel();
|
||||||
|
titleLabel->setText("登录");
|
||||||
|
titleLabel->setFixedHeight(50);
|
||||||
|
titleLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
titleLabel->setStyleSheet("QLabel { font-size: 40px; font-weight: 600; }");
|
||||||
|
titleLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
|
||||||
|
// 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->setFixedHeight(40);
|
||||||
|
phoneEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
phoneEdit->setStyleSheet(editStyle);
|
||||||
|
|
||||||
|
// 5. 创建验证码输入框
|
||||||
|
verifyCodeEdit = new QLineEdit();
|
||||||
|
verifyCodeEdit->setPlaceholderText("输入验证码");
|
||||||
|
verifyCodeEdit->setFixedHeight(40);
|
||||||
|
verifyCodeEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
verifyCodeEdit->setStyleSheet(editStyle);
|
||||||
|
|
||||||
|
// 6. 创建发送验证码按钮
|
||||||
|
QString btnWhiteStyle = "QPushButton { border: none; border-radius: 10px; background-color: transparent; }";
|
||||||
|
btnWhiteStyle += "QPushButton:pressed { background-color: rgb(240, 240, 240); }";
|
||||||
|
sendVerifyCodeBtn = new QPushButton();
|
||||||
|
sendVerifyCodeBtn->setFixedSize(100, 40);
|
||||||
|
sendVerifyCodeBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
sendVerifyCodeBtn->setText("发送验证码");
|
||||||
|
sendVerifyCodeBtn->setStyleSheet(btnWhiteStyle);
|
||||||
|
|
||||||
|
// 7. 创建提交按钮
|
||||||
|
submitBtn = new QPushButton();
|
||||||
|
submitBtn->setFixedHeight(40);
|
||||||
|
submitBtn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
submitBtn->setText("登录");
|
||||||
|
QString btnGreenStyle = "QPushButton { border: none; border-radius: 10px; background-color: rgb(44, 182, 61); color: rgb(255, 255, 255); }";
|
||||||
|
btnGreenStyle += "QPushButton:pressed { background-color: rgb(240, 240, 240); }";
|
||||||
|
submitBtn->setStyleSheet(btnGreenStyle);
|
||||||
|
|
||||||
|
// 8. 创建 "切换到用户名" 模式按钮
|
||||||
|
QPushButton* userModeBtn = new QPushButton();
|
||||||
|
userModeBtn->setFixedSize(100, 40);
|
||||||
|
userModeBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
userModeBtn->setText("切换到用户名");
|
||||||
|
userModeBtn->setStyleSheet(btnWhiteStyle);
|
||||||
|
|
||||||
|
// 9. 切换登录注册模式
|
||||||
|
switchModeBtn = new QPushButton();
|
||||||
|
switchModeBtn->setFixedSize(100, 40);
|
||||||
|
switchModeBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
switchModeBtn->setText("注册");
|
||||||
|
switchModeBtn->setStyleSheet(btnWhiteStyle);
|
||||||
|
|
||||||
|
// 10. 添加到布局管理器
|
||||||
|
layout->addWidget(titleLabel, 0, 0, 1, 5);
|
||||||
|
layout->addWidget(phoneEdit, 1, 0, 1, 5);
|
||||||
|
layout->addWidget(verifyCodeEdit, 2, 0, 1, 4);
|
||||||
|
layout->addWidget(sendVerifyCodeBtn, 2, 4, 1, 1);
|
||||||
|
layout->addWidget(submitBtn, 3, 0, 1, 5);
|
||||||
|
layout->addWidget(userModeBtn, 4, 0, 1, 1);
|
||||||
|
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(userModeBtn, &QPushButton::clicked, this, [=]() {
|
||||||
|
LoginWidget* loginWidget = new LoginWidget(nullptr);
|
||||||
|
loginWidget->show();
|
||||||
|
this->close();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(sendVerifyCodeBtn, &QPushButton::clicked, this, &PhoneLoginWidget::sendVerifyCode);
|
||||||
|
|
||||||
|
timer = new QTimer(this);
|
||||||
|
connect(timer, &QTimer::timeout, this, &PhoneLoginWidget::countDown);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
484
ChatClient/src/selfinfowidget.cpp
Normal file
@ -0,0 +1,484 @@
|
|||||||
|
#include "selfinfowidget.h"
|
||||||
|
|
||||||
|
#include "model/datacenter.h"
|
||||||
|
|
||||||
|
using namespace model;
|
||||||
|
|
||||||
|
SelfInfoWidget::SelfInfoWidget(QWidget *parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
{
|
||||||
|
//设置整个窗口的属性
|
||||||
|
this->setFixedSize(500, 200);
|
||||||
|
this->setWindowTitle("个人信息");
|
||||||
|
this->setWindowIcon(QIcon(":/resource/image/logo.png"));
|
||||||
|
//窗口被关闭时,自动销毁这个对话框对象
|
||||||
|
this->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
//把窗口移动到鼠标当前的位置
|
||||||
|
this->move(QCursor::pos());
|
||||||
|
|
||||||
|
//创建布局管理器
|
||||||
|
layout = new QGridLayout();
|
||||||
|
//layout->setSpacing(0);
|
||||||
|
layout->setHorizontalSpacing(10);
|
||||||
|
layout->setVerticalSpacing(3);
|
||||||
|
layout->setContentsMargins(20, 20, 20, 0);
|
||||||
|
layout->setAlignment(Qt::AlignTop);
|
||||||
|
this->setLayout(layout);
|
||||||
|
|
||||||
|
//创建头像
|
||||||
|
avatarBtn = new QPushButton();
|
||||||
|
avatarBtn->setFixedSize(75, 75);
|
||||||
|
avatarBtn->setIconSize(QSize(75, 75));
|
||||||
|
avatarBtn->setStyleSheet("QPushButton { border: none; background-color: transparent; }");
|
||||||
|
|
||||||
|
QString labelStyle = "QLabel { font-size: 14px; font-weight: 800; }";
|
||||||
|
QString btnStyle = "QPushButton {border: none; background-color: transparent; }";
|
||||||
|
btnStyle += "QPushButton:pressed { background-color: rgb(210, 210, 210); }";
|
||||||
|
QString editStyle = "QLineEdit { border: none; background-color: rgb(250 ,250, 250); color: rgb(0, 0, 0); border-radius: 5px; padding-left: 2px; }";
|
||||||
|
|
||||||
|
int height = 30;
|
||||||
|
|
||||||
|
//添加用户的ID的显示
|
||||||
|
idTag = new QLabel();
|
||||||
|
idTag->setFixedSize(50, height);
|
||||||
|
idTag->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
idTag->setText("ID");
|
||||||
|
idTag->setStyleSheet(labelStyle);
|
||||||
|
|
||||||
|
idLabel = new QLabel();
|
||||||
|
idLabel->setFixedHeight(height);
|
||||||
|
idLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
//添加新的用户的名字的显示
|
||||||
|
nameTag = new QLabel();
|
||||||
|
nameTag->setFixedSize(50, height);
|
||||||
|
nameTag->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
nameTag->setText("昵称");
|
||||||
|
nameTag->setStyleSheet(labelStyle);
|
||||||
|
|
||||||
|
nameLabel = new QLabel();
|
||||||
|
nameLabel->setFixedHeight(height);
|
||||||
|
nameLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
nameModifyBtn = new QPushButton();
|
||||||
|
nameModifyBtn->setFixedSize(70, 25);
|
||||||
|
nameModifyBtn->setIconSize(QSize(20, 20));
|
||||||
|
nameModifyBtn->setIcon(QIcon(":/resource/image/modify.png"));
|
||||||
|
nameModifyBtn->setStyleSheet(btnStyle);
|
||||||
|
|
||||||
|
nameEdit = new QLineEdit();
|
||||||
|
nameEdit->setFixedHeight(height);
|
||||||
|
nameEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
nameEdit->setStyleSheet(btnStyle);
|
||||||
|
nameEdit->hide();
|
||||||
|
|
||||||
|
nameSubmitBtn = new QPushButton();
|
||||||
|
nameSubmitBtn->setFixedSize(70, 25);
|
||||||
|
nameSubmitBtn->setIconSize(QSize(20, 20));
|
||||||
|
nameSubmitBtn->setIcon(QIcon(":/resource/image/submit.png"));
|
||||||
|
nameSubmitBtn->setStyleSheet(btnStyle);
|
||||||
|
nameSubmitBtn->hide();
|
||||||
|
|
||||||
|
//添加个性签名
|
||||||
|
descTag = new QLabel();
|
||||||
|
descTag->setFixedSize(50, height);
|
||||||
|
descTag->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
descTag->setText("签名");
|
||||||
|
descTag->setStyleSheet(labelStyle);
|
||||||
|
|
||||||
|
descLabel = new QLabel();
|
||||||
|
descLabel->setFixedHeight(height);
|
||||||
|
descLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
|
||||||
|
descModifyBtn = new QPushButton();
|
||||||
|
descModifyBtn->setFixedSize(70, 25);
|
||||||
|
descModifyBtn->setIconSize(QSize(20, 20));
|
||||||
|
descModifyBtn->setIcon(QIcon(":/resource/image/modify.png"));
|
||||||
|
descModifyBtn->setStyleSheet(btnStyle);
|
||||||
|
|
||||||
|
descEdit = new QLineEdit();
|
||||||
|
descEdit->setFixedHeight(height);
|
||||||
|
descEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
descEdit->setStyleSheet(btnStyle);
|
||||||
|
descEdit->hide();
|
||||||
|
|
||||||
|
descSubmitBtn = new QPushButton();
|
||||||
|
descSubmitBtn->setFixedSize(70, 25);
|
||||||
|
descSubmitBtn->setIconSize(QSize(20, 20));
|
||||||
|
descSubmitBtn->setIcon(QIcon(":/resource/image/submit.png"));
|
||||||
|
descSubmitBtn->setStyleSheet(btnStyle);
|
||||||
|
descSubmitBtn->hide();
|
||||||
|
|
||||||
|
// 7. 添加电话
|
||||||
|
phoneTag = new QLabel();
|
||||||
|
phoneTag->setFixedSize(50, height);
|
||||||
|
phoneTag->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
phoneTag->setText("邮箱");
|
||||||
|
phoneTag->setStyleSheet(labelStyle);
|
||||||
|
|
||||||
|
phoneLabel = new QLabel();
|
||||||
|
phoneLabel->setFixedHeight(height);
|
||||||
|
phoneLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
//phoneLabel->setStyleSheet("QLabel {background-color: rgb(255, 255, 255); }");
|
||||||
|
|
||||||
|
phoneModifyBtn = new QPushButton();
|
||||||
|
phoneModifyBtn->setFixedSize(70, 25);
|
||||||
|
phoneModifyBtn->setIconSize(QSize(20, 20));
|
||||||
|
phoneModifyBtn->setIcon(QIcon(":/resource/image/modify.png"));
|
||||||
|
phoneModifyBtn->setStyleSheet(btnStyle);
|
||||||
|
|
||||||
|
phoneEdit = new QLineEdit();
|
||||||
|
phoneEdit->setFixedHeight(height);
|
||||||
|
phoneEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
phoneEdit->setStyleSheet(editStyle);
|
||||||
|
//phoneEdit->setStyleSheet("QLineEdit {background-color: rgb(245, 245, 245); }");
|
||||||
|
phoneEdit->hide();
|
||||||
|
|
||||||
|
phoneSubmitBtn = new QPushButton();
|
||||||
|
phoneSubmitBtn->setFixedSize(70, 25);
|
||||||
|
phoneSubmitBtn->setIconSize(QSize(20, 20));
|
||||||
|
phoneSubmitBtn->setIcon(QIcon(":/resource/image/submit.png"));
|
||||||
|
phoneSubmitBtn->setStyleSheet(btnStyle);
|
||||||
|
phoneSubmitBtn->hide();
|
||||||
|
|
||||||
|
|
||||||
|
// 8. 添加验证码
|
||||||
|
verifyCodeTag = new QLabel();
|
||||||
|
verifyCodeTag->setFixedSize(50, height);
|
||||||
|
verifyCodeTag->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
verifyCodeTag->setText("验证码:");
|
||||||
|
verifyCodeTag->setStyleSheet(labelStyle);
|
||||||
|
verifyCodeTag->hide();
|
||||||
|
|
||||||
|
verifyCodeEdit = new QLineEdit();
|
||||||
|
verifyCodeEdit->setFixedHeight(height);
|
||||||
|
verifyCodeEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
verifyCodeEdit->setStyleSheet(editStyle);
|
||||||
|
verifyCodeEdit->hide();
|
||||||
|
|
||||||
|
getVerifyCodeBtn = new QPushButton();
|
||||||
|
getVerifyCodeBtn->setText("获取验证码");
|
||||||
|
getVerifyCodeBtn->setFixedSize(70, height);
|
||||||
|
getVerifyCodeBtn->setStyleSheet(R"(QPushButton {
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
color: rgb(0, 0, 0); }
|
||||||
|
QPushButton:pressed { background-color: rgb(231, 231, 231); }
|
||||||
|
)");
|
||||||
|
getVerifyCodeBtn->hide();
|
||||||
|
|
||||||
|
|
||||||
|
layout->addWidget(avatarBtn, 0, 0, 3, 1);
|
||||||
|
layout->addWidget(idTag, 0, 1);
|
||||||
|
layout->addWidget(idLabel, 0, 2);
|
||||||
|
|
||||||
|
layout->addWidget(nameTag, 1, 1);
|
||||||
|
layout->addWidget(nameLabel, 1, 2);
|
||||||
|
layout->addWidget(nameModifyBtn, 1, 3);
|
||||||
|
|
||||||
|
layout->addWidget(descTag, 2, 1);
|
||||||
|
layout->addWidget(descLabel, 2, 2);
|
||||||
|
layout->addWidget(descModifyBtn, 2, 3);
|
||||||
|
|
||||||
|
layout->addWidget(phoneTag, 3, 1);
|
||||||
|
layout->addWidget(phoneLabel, 3, 2);
|
||||||
|
layout->addWidget(phoneModifyBtn, 3, 3);
|
||||||
|
|
||||||
|
/*layout->addWidget(verifyCodeTag, 4, 1);
|
||||||
|
layout->addWidget(verifyCodeEdit, 4, 2);
|
||||||
|
layout->addWidget(getVerifyCodeBtn, 4, 3);*/
|
||||||
|
|
||||||
|
#if TEST_UI
|
||||||
|
idLabel->setText("12345");
|
||||||
|
nameLabel->setText("xyz");
|
||||||
|
descLabel->setText("It didn't matter that i lived another day.");
|
||||||
|
phoneLabel->setText("12345678900");
|
||||||
|
//此处只做测试,不要让界面显示默认头像
|
||||||
|
avatarBtn->setIcon(QIcon(":/resource/image/defaultAvatar.png"));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
initSingnalSlots();
|
||||||
|
|
||||||
|
//加载数据到界面上
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
UserInfo* myself = dataCenter->getMyselfsync();
|
||||||
|
if (myself != nullptr) {
|
||||||
|
//把个人信息加载到界面上
|
||||||
|
avatarBtn->setIcon(myself->avatar);
|
||||||
|
idLabel->setText(myself->userId);
|
||||||
|
nameLabel->setText(myself->nickname);
|
||||||
|
descLabel->setText(myself->description);
|
||||||
|
phoneLabel->setText(myself->phone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*connect(nameSubmitBtn, &QPushButton::clicked, this, &SelfInfoWidget::clickNameSubmitBtn);
|
||||||
|
connect(descSubmitBtn, &QPushButton::clicked, this, &SelfInfoWidget::clickDescSubmitBtn);
|
||||||
|
connect(getVerifyCodeBtn, &QPushButton::clicked, this, &SelfInfoWidget::clickGetVerifyCodeBtn);
|
||||||
|
connect(phoneSubmitBtn, &QPushButton::clicked, this, &SelfInfoWidget::clickPhoneSubmitBtn);
|
||||||
|
connect(avatarBtn, &QPushButton::clicked, this, &SelfInfoWidget::clickAvatarBtn);*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelfInfoWidget::initSingnalSlots()
|
||||||
|
{
|
||||||
|
//添加连接的槽函数
|
||||||
|
connect(nameModifyBtn, &QPushButton::clicked, this, [=]() {
|
||||||
|
//把当前的nameLabel和nameModifyBtn隐藏起来
|
||||||
|
nameLabel->hide();
|
||||||
|
nameModifyBtn->hide();
|
||||||
|
layout->removeWidget(nameLabel);
|
||||||
|
layout->removeWidget(nameModifyBtn);
|
||||||
|
//把nameEdit和nameSubmitBtn显示出来
|
||||||
|
nameEdit->show();
|
||||||
|
nameSubmitBtn->show();
|
||||||
|
layout->addWidget(nameEdit, 1, 2);
|
||||||
|
layout->addWidget(nameSubmitBtn, 1, 3);
|
||||||
|
//把输入框的内容进行设置
|
||||||
|
nameEdit->setText(nameLabel->text());
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(descModifyBtn, &QPushButton::clicked, this, [=]() {
|
||||||
|
descLabel->hide();
|
||||||
|
descModifyBtn->hide();
|
||||||
|
layout->removeWidget(descLabel);
|
||||||
|
layout->removeWidget(descModifyBtn);
|
||||||
|
|
||||||
|
descEdit->show();
|
||||||
|
descSubmitBtn->show();
|
||||||
|
layout->addWidget(descEdit, 2, 2);
|
||||||
|
layout->addWidget(descSubmitBtn, 2, 3);
|
||||||
|
|
||||||
|
descEdit->setText(descLabel->text());
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(phoneModifyBtn, &QPushButton::clicked, this, [=]() {
|
||||||
|
phoneLabel->hide();
|
||||||
|
phoneModifyBtn->hide();
|
||||||
|
layout->removeWidget(phoneLabel);
|
||||||
|
layout->removeWidget(phoneModifyBtn);
|
||||||
|
|
||||||
|
phoneEdit->show();
|
||||||
|
phoneSubmitBtn->show();
|
||||||
|
layout->addWidget(phoneEdit, 3, 2);
|
||||||
|
layout->addWidget(phoneSubmitBtn, 3, 3);
|
||||||
|
|
||||||
|
verifyCodeTag->show();
|
||||||
|
verifyCodeEdit->show();
|
||||||
|
getVerifyCodeBtn->show();
|
||||||
|
layout->addWidget(verifyCodeTag, 4, 1);
|
||||||
|
layout->addWidget(verifyCodeEdit, 4, 2);
|
||||||
|
layout->addWidget(getVerifyCodeBtn, 4, 3);
|
||||||
|
|
||||||
|
phoneEdit->setText(phoneLabel->text());
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(nameSubmitBtn, &QPushButton::clicked, this, &SelfInfoWidget::clickNameSubmitBtn);
|
||||||
|
connect(descSubmitBtn, &QPushButton::clicked, this, &SelfInfoWidget::clickDescSubmitBtn);
|
||||||
|
connect(getVerifyCodeBtn, &QPushButton::clicked, this, &SelfInfoWidget::clickGetVerifyCodeBtn);
|
||||||
|
connect(phoneSubmitBtn, &QPushButton::clicked, this, &SelfInfoWidget::clickPhoneSubmitBtn);
|
||||||
|
connect(avatarBtn, &QPushButton::clicked, this, &SelfInfoWidget::clickAvatarBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelfInfoWidget::clickNameSubmitBtn()
|
||||||
|
{
|
||||||
|
//从输入框中拿到修改后的呢称
|
||||||
|
const QString& nickName = nameEdit->text();
|
||||||
|
if (nickName.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//发送网络请求
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
connect(dataCenter, &DataCenter::changeNicknameDone, this, &SelfInfoWidget::clickNameSubmitBtnDone, Qt::UniqueConnection);
|
||||||
|
dataCenter->changeNicknameAsync(nickName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelfInfoWidget::clickNameSubmitBtnDone()
|
||||||
|
{
|
||||||
|
//对界面控件进行切换, 把输入框切换为label, 把提交按钮切换为编辑按钮
|
||||||
|
//同时要把输入框的内容设置为修改后的呢称
|
||||||
|
layout->removeWidget(nameEdit);
|
||||||
|
nameEdit->hide();
|
||||||
|
layout->addWidget(nameLabel, 1, 2);
|
||||||
|
nameLabel->show();
|
||||||
|
nameLabel->setText(nameEdit->text());
|
||||||
|
|
||||||
|
layout->removeWidget(nameSubmitBtn);
|
||||||
|
nameSubmitBtn->hide();
|
||||||
|
layout->addWidget(nameModifyBtn, 1, 3);
|
||||||
|
nameModifyBtn->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelfInfoWidget::clickDescSubmitBtn()
|
||||||
|
{
|
||||||
|
//从输入框拿到输入的签名内容
|
||||||
|
const QString& desc = descEdit->text();
|
||||||
|
if (desc.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//发送网络请求
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
connect(dataCenter, &DataCenter::changeDescriptionDone, this, &SelfInfoWidget::clickDescSubmitBtnDone, Qt::UniqueConnection);
|
||||||
|
dataCenter->changeDescriptionAsync(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelfInfoWidget::clickDescSubmitBtnDone()
|
||||||
|
{
|
||||||
|
//切换界面
|
||||||
|
//把label替换为输入框,把提交按钮替换为编辑按钮
|
||||||
|
layout->removeWidget(descEdit);
|
||||||
|
descEdit->hide();
|
||||||
|
|
||||||
|
layout->addWidget(descLabel, 2, 2);
|
||||||
|
descLabel->show();
|
||||||
|
descLabel->setText(descEdit->text());
|
||||||
|
|
||||||
|
layout->removeWidget(descSubmitBtn);
|
||||||
|
descSubmitBtn->hide();
|
||||||
|
layout->addWidget(descModifyBtn, 2, 3);
|
||||||
|
descModifyBtn->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelfInfoWidget::clickGetVerifyCodeBtn()
|
||||||
|
{
|
||||||
|
//获取到输入框的邮箱号
|
||||||
|
const QString& email = phoneEdit->text();
|
||||||
|
if (email.isEmpty()) {
|
||||||
|
|
||||||
|
QTimer* timer = new QTimer(this);
|
||||||
|
int* leftTime = new int(2);
|
||||||
|
phoneEdit->setPlaceholderText("请输入邮箱");
|
||||||
|
phoneEdit->setStyleSheet("QLineEdit { border: 1px solid red; background-color: rgb(255, 220, 220); color: rgb(0, 0, 0); border-radius: 5px; padding-left: 2px; }");
|
||||||
|
connect(timer, &QTimer::timeout, this, [=]() {
|
||||||
|
if (*leftTime <= 0) {
|
||||||
|
phoneEdit->setPlaceholderText("");
|
||||||
|
phoneEdit->setStyleSheet("QLineEdit { border: none; background-color: rgb(250 ,250, 250); color: rgb(0, 0, 0); border-radius: 5px; padding-left: 2px; }");
|
||||||
|
timer->stop();
|
||||||
|
timer->deleteLater();
|
||||||
|
delete timer;
|
||||||
|
delete leftTime;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*leftTime)--;
|
||||||
|
});
|
||||||
|
timer->start(1000); // 1秒后清除提示
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//给服务器发送请求
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
connect(dataCenter, &DataCenter::getVerifyCodeDone, this, [=](bool ok) {
|
||||||
|
//不需要做其他的处理,只需要提示一下,验证码已经发送出去了
|
||||||
|
if (!ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG() << "ok: " << ok;
|
||||||
|
Toast::showMessage("验证码已发送,请注意查收");
|
||||||
|
});
|
||||||
|
dataCenter->getVerifyCodeAsync(email);
|
||||||
|
|
||||||
|
//把刚才发送请求的邮箱号码保存起来
|
||||||
|
this->emailToChange = email;
|
||||||
|
|
||||||
|
//禁用发送验证码的按钮,给出倒计时
|
||||||
|
this->getVerifyCodeBtn->setEnabled(false);
|
||||||
|
|
||||||
|
leftTime = 30; // 倒计时30秒
|
||||||
|
QTimer* timer = new QTimer(this);
|
||||||
|
connect(timer, &QTimer::timeout, this, [=]() {
|
||||||
|
if (leftTime <= 0) {
|
||||||
|
//倒计时结束
|
||||||
|
getVerifyCodeBtn->setEnabled(true);
|
||||||
|
getVerifyCodeBtn->setText("获取验证码");
|
||||||
|
timer->stop();
|
||||||
|
timer->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(leftTime)--;
|
||||||
|
getVerifyCodeBtn->setText(QString::number(leftTime) + "s");
|
||||||
|
});
|
||||||
|
timer->start(1000); // 每秒更新一次
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelfInfoWidget::clickPhoneSubmitBtn()
|
||||||
|
{
|
||||||
|
//判定,当前的验证码是否已经收到
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
QString verifyCodeId = dataCenter->getVerifyCodeId();
|
||||||
|
if (verifyCodeId.isEmpty()) {
|
||||||
|
//服务器还没有返回验证码响应
|
||||||
|
Toast::showMessage("服务器未返回响应,请稍后重试");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//如果当前已经拿到了verifyCodeId,就可以清空DataCenter中存储的值了,确保下次点击提交按钮的时候,不会影响当次的逻辑
|
||||||
|
dataCenter->resetVerifyCodeId("");
|
||||||
|
|
||||||
|
//获取到用户输入的验证码
|
||||||
|
QString verifyCode = verifyCodeEdit->text();
|
||||||
|
if (verifyCode.isEmpty()) {
|
||||||
|
Toast::showMessage("验证码不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
verifyCodeEdit->setText("");
|
||||||
|
|
||||||
|
//发送请求,把当前验证码信息,发送给服务器
|
||||||
|
connect(dataCenter, &DataCenter::changePhoneDone, this, &SelfInfoWidget::clickPhoneSubmitBtnDone, Qt::UniqueConnection);
|
||||||
|
dataCenter->changePhoneAsync(this->emailToChange, verifyCodeId, verifyCode);
|
||||||
|
|
||||||
|
leftTime = 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelfInfoWidget::clickPhoneSubmitBtnDone()
|
||||||
|
{
|
||||||
|
layout->removeWidget(verifyCodeTag);
|
||||||
|
layout->removeWidget(verifyCodeEdit);
|
||||||
|
layout->removeWidget(getVerifyCodeBtn);
|
||||||
|
layout->removeWidget(phoneEdit);
|
||||||
|
layout->removeWidget(phoneSubmitBtn);
|
||||||
|
verifyCodeTag->hide();
|
||||||
|
verifyCodeEdit->hide();
|
||||||
|
getVerifyCodeBtn->hide();
|
||||||
|
phoneEdit->hide();
|
||||||
|
phoneSubmitBtn->hide();
|
||||||
|
|
||||||
|
layout->addWidget(phoneLabel, 3, 2);
|
||||||
|
phoneLabel->show();
|
||||||
|
layout->addWidget(phoneModifyBtn, 3, 3);
|
||||||
|
phoneModifyBtn->show();
|
||||||
|
|
||||||
|
phoneLabel->setText(this->emailToChange);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelfInfoWidget::clickAvatarBtn()
|
||||||
|
{
|
||||||
|
//弹出对话框,选择文件
|
||||||
|
QString filter = "Image Files (*.png *.jpg *.jpeg)";
|
||||||
|
QString imagePath = QFileDialog::getOpenFileName(this, "选择头像", QDir::homePath(), filter);
|
||||||
|
if (imagePath.isEmpty()) {
|
||||||
|
//用户取消了
|
||||||
|
LOG() << "用户没有选择任何头像文件";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//根据路径,读取到图片的内容
|
||||||
|
QByteArray imageBytes = loadFileToByteArray(imagePath);
|
||||||
|
|
||||||
|
//发送请求,修改头像
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
connect(dataCenter, &DataCenter::changeAvatarDone, this, &SelfInfoWidget::clickAvatarBtnDone, Qt::UniqueConnection);
|
||||||
|
dataCenter->changeAvatarAsync(imageBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelfInfoWidget::clickAvatarBtnDone()
|
||||||
|
{
|
||||||
|
//设置头像,更新到界面上
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
avatarBtn->setIcon(dataCenter->getMyselfsync()->avatar);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
126
ChatClient/src/sessiondetailwidget.cpp
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
#include "sessiondetailwidget.h"
|
||||||
|
|
||||||
|
#include "model/datacenter.h"
|
||||||
|
|
||||||
|
using namespace model;
|
||||||
|
|
||||||
|
//右上角详情的会话人员列表
|
||||||
|
AvatarItem::AvatarItem(const QIcon& avatar, const QString& name)
|
||||||
|
{
|
||||||
|
// 设置自身的基本属性
|
||||||
|
this->setFixedSize(70, 80);
|
||||||
|
|
||||||
|
// 创建布局管理器
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout();
|
||||||
|
layout->setSpacing(0);
|
||||||
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
layout->setAlignment(Qt::AlignHCenter);
|
||||||
|
this->setLayout(layout);
|
||||||
|
|
||||||
|
// 创建头像
|
||||||
|
avatarBtn = new QPushButton();
|
||||||
|
avatarBtn->setFixedSize(45, 45);
|
||||||
|
avatarBtn->setIconSize(QSize(45, 45));
|
||||||
|
avatarBtn->setIcon(avatar);
|
||||||
|
avatarBtn->setStyleSheet("QPushButton { border: none; }");
|
||||||
|
|
||||||
|
// 创建名字
|
||||||
|
nameLabel = new QLabel();
|
||||||
|
nameLabel->setText(name);
|
||||||
|
QFont font("微软雅黑", 12);
|
||||||
|
nameLabel->setFont(font);
|
||||||
|
nameLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
|
||||||
|
// 对名字做 "截断操作"
|
||||||
|
const int MAX_WIDTH = 65;
|
||||||
|
QFontMetrics metrics(font);
|
||||||
|
int totalWidth = metrics.horizontalAdvance(name);//测量水平的宽度
|
||||||
|
if (totalWidth >= MAX_WIDTH) {
|
||||||
|
// 说明需要截断
|
||||||
|
QString tail = "...";
|
||||||
|
int tailWidth = metrics.horizontalAdvance(tail);
|
||||||
|
int availableWidth = MAX_WIDTH - tailWidth;
|
||||||
|
int availableSize = name.size() * ((double)availableWidth / totalWidth);
|
||||||
|
QString newName = name.left(availableSize);
|
||||||
|
nameLabel->setText(newName + tail);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加
|
||||||
|
layout->addWidget(avatarBtn);
|
||||||
|
layout->addWidget(nameLabel);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionDetailWidget::SessionDetailWidget(QWidget *parent, const UserInfo& userInfo)
|
||||||
|
:userInfo(userInfo),
|
||||||
|
QDialog(parent)
|
||||||
|
{
|
||||||
|
// 1. 设置基本属性
|
||||||
|
this->setWindowTitle("会话详情");
|
||||||
|
this->setWindowIcon(QIcon(":/resource/image/logo.png"));
|
||||||
|
this->setFixedSize(300, 300);
|
||||||
|
this->setStyleSheet("QWidget { background-color: rgb(255, 255, 255); }");
|
||||||
|
this->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
// 2. 创建布局管理器
|
||||||
|
QGridLayout* layout = new QGridLayout();
|
||||||
|
layout->setSpacing(10);
|
||||||
|
layout->setContentsMargins(50, 0, 50, 0);
|
||||||
|
this->setLayout(layout);
|
||||||
|
|
||||||
|
// 3. 添加 "创建群聊" 按钮
|
||||||
|
AvatarItem* createGroupBtn = new AvatarItem(QIcon(":/resource/image/cross.png"), "添加");
|
||||||
|
layout->addWidget(createGroupBtn, 0, 0);
|
||||||
|
|
||||||
|
// 4. 添加当前用户的信息 (临时构造的假数据)
|
||||||
|
#if TEST_UI
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 5. 添加 "删除好友" 按钮
|
||||||
|
deleteFriendBtn = new QPushButton();
|
||||||
|
deleteFriendBtn->setFixedHeight(50);
|
||||||
|
deleteFriendBtn->setText("删除好友");
|
||||||
|
QString style = "QPushButton { border: 1px solid rgb(90, 90, 90); border-radius: 5px; color: rgb(0, 0, 0); } ";
|
||||||
|
style += "QPushButton:pressed { background-color: rgb(235, 235, 235); }";
|
||||||
|
deleteFriendBtn->setStyleSheet(style);
|
||||||
|
layout->addWidget(deleteFriendBtn, 1, 0, 1, 3);
|
||||||
|
|
||||||
|
// 6. 添加信号槽, 处理点击 "创建群聊" 按钮
|
||||||
|
connect(createGroupBtn->getAvatar(), &QPushButton::clicked, this, [=]() {
|
||||||
|
ChooseFriendDialog* chooseFriendDialog = new ChooseFriendDialog(this, userInfo.userId);
|
||||||
|
chooseFriendDialog->exec();
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
@ -3,11 +3,15 @@
|
|||||||
|
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "model/data.h"
|
#include "model/data.h"
|
||||||
|
#include "model/datacenter.h"
|
||||||
|
#include "mainwidget.h"
|
||||||
|
|
||||||
|
using namespace model;
|
||||||
|
|
||||||
SessionFriendArea::SessionFriendArea(QWidget *parent)
|
SessionFriendArea::SessionFriendArea(QWidget *parent)
|
||||||
: QScrollArea {parent}
|
: QScrollArea {parent}
|
||||||
{
|
{
|
||||||
//设置必要属性
|
//设置必要属性
|
||||||
//设置这个才能有滚动效果
|
//设置这个才能有滚动效果
|
||||||
this->setWidgetResizable(true);
|
this->setWidgetResizable(true);
|
||||||
//设置滚动条相关样式
|
//设置滚动条相关样式
|
||||||
@ -113,16 +117,16 @@ SessionFriendItem::SessionFriendItem(QWidget* owner, const QIcon& avatar, const
|
|||||||
nameLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
nameLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
|
||||||
//创建消息预览的label
|
//创建消息预览的label
|
||||||
QLabel* messageLabel = new QLabel();
|
messageLabel = new QLabel();
|
||||||
messageLabel->setText(text);
|
messageLabel->setText(text);
|
||||||
messageLabel->setStyleSheet("QLabel { font-size: 18px; font-weight: 600; }");
|
messageLabel->setStyleSheet("QLabel { font-size: 12px; font-weight: 600; color: rgb(153, 153, 153); }");
|
||||||
messageLabel->setFixedHeight(35);
|
messageLabel->setFixedHeight(35);
|
||||||
messageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
messageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
|
||||||
//添加到网格布局
|
//添加到网格布局
|
||||||
layout->addWidget(avatarBtn, 0, 0, 2, 2);
|
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);
|
layout->addWidget(messageLabel, 1, 2, 1, 25);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,21 +258,102 @@ void SessionFriendArea::clickItem(int index)
|
|||||||
item->select();
|
item->select();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
/// 会话Item的实现
|
/// 会话Item的实现
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
SessionItem::SessionItem(QWidget *owner, const QString &chatSessionId, const QIcon &avatar,
|
SessionItem::SessionItem(QWidget *owner, const QString &chatSessionId, const QIcon &avatar,
|
||||||
const QString &name, const QString &lastmessage)
|
const QString &name, const QString &lastmessage)
|
||||||
:SessionFriendItem(owner, avatar, name, lastmessage), chatSessionId(chatSessionId)
|
:SessionFriendItem(owner, avatar, name, lastmessage), chatSessionId(chatSessionId), text(lastmessage)
|
||||||
{
|
{
|
||||||
|
//处理更新最后一个消息的信号
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
connect(dataCenter, &DataCenter::updateLastMessage, this, &SessionItem::updateLastMessage);
|
||||||
|
|
||||||
|
//需要显示出未读消息的数目,需持久化,否则重启即丢失
|
||||||
|
int unread = dataCenter->getUnread(chatSessionId);
|
||||||
|
if (unread > 0) {
|
||||||
|
//说明存在未读消息
|
||||||
|
//this->messageLabel->setText(QString("[未读%1条]").arg(unread) + lastmessage);
|
||||||
|
QString coloredPart = QString("[未读%1条]").arg(unread).toHtmlEscaped();
|
||||||
|
QString escapedText = lastmessage.toHtmlEscaped();
|
||||||
|
messageLabel->setText(QString(
|
||||||
|
"<span style=\"color: rgb(221, 43, 43);\">%1</span>"
|
||||||
|
"<span style=\"color: rgb(153, 153, 153);\">%2</span>"
|
||||||
|
).arg(coloredPart, escapedText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionItem::updateLastMessage(const QString& chatSessionId)
|
||||||
|
{
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
//先判定,信号中的会话id得和当前元素自身持有放到会话id一致,才真正处理
|
||||||
|
if (this->chatSessionId != chatSessionId) {
|
||||||
|
//当前SessionItem不是正在发消息的SessionItem
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//chatSession匹配,真正更新最后一条消息
|
||||||
|
|
||||||
|
//把最后一条消息获取到
|
||||||
|
QList<Message>* messageList = dataCenter->getRecentMessageList(chatSessionId);
|
||||||
|
if (messageList == nullptr || messageList->size() == 0) {
|
||||||
|
//当前会话没有任何消息,无需更新
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Message& lastMessage = messageList->back();
|
||||||
|
|
||||||
|
//明确显示的文本内容
|
||||||
|
if (lastMessage.messageType == TEXT_TYPE) {
|
||||||
|
text = lastMessage.content;
|
||||||
|
}
|
||||||
|
else if (lastMessage.messageType == IMAGE_TYPE) {
|
||||||
|
text = "[图片]";
|
||||||
|
}
|
||||||
|
else if (lastMessage.messageType == FILE_TYPE) {
|
||||||
|
text = "[文件]";
|
||||||
|
}
|
||||||
|
else if (lastMessage.messageType == SPEECH_TYPE) {
|
||||||
|
text = "[语音]";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG() << "错误的消息类型";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//把这个内容显示到界面上
|
||||||
|
//后续还要考虑到“未读消息”情况,
|
||||||
|
if (chatSessionId == dataCenter->getCurrentSessionId()) {
|
||||||
|
this->messageLabel->setText(text);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int unread = dataCenter->getUnread(chatSessionId);
|
||||||
|
if (unread > 0) {
|
||||||
|
//说明存在未读消息
|
||||||
|
//this->messageLabel->setText(QString("[未读%1条]").arg(unread) + text);
|
||||||
|
QString coloredPart = QString("[未读%1条]").arg(unread).toHtmlEscaped();
|
||||||
|
QString escapedText = text.toHtmlEscaped();
|
||||||
|
messageLabel->setText(QString(
|
||||||
|
"<span style=\"color: rgb(221, 43, 43);\">%1</span>"
|
||||||
|
"<span style=\"color: rgb(153, 153, 153);\">%2</span>"
|
||||||
|
).arg(coloredPart, escapedText));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionItem::active()
|
void SessionItem::active()
|
||||||
{
|
{
|
||||||
//点击之后,要加载会话历史消息的列表
|
//点击之后,要加载会话历史消息的列表
|
||||||
LOG() << "click SessionItem... chatSessionId: " << chatSessionId;
|
LOG() << "click SessionItem... chatSessionId: " << chatSessionId;
|
||||||
|
|
||||||
|
//加载会话历史消息,会涉及到当前内存数据的操作,又会涉及到网络通信,以及UI界面的变更
|
||||||
|
MainWidget* mainWidget = MainWidget::getInstance();
|
||||||
|
mainWidget->loadRecentMessage(chatSessionId);
|
||||||
|
|
||||||
|
//清空未读消息数据,并更新显示
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
dataCenter->clearUnread(chatSessionId);
|
||||||
|
|
||||||
|
//更新界面的显示->把未读x条干掉
|
||||||
|
this->messageLabel->setText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
@ -285,6 +370,9 @@ void FriendItem::active()
|
|||||||
{
|
{
|
||||||
//点击之后,要激活对应的会话列元素
|
//点击之后,要激活对应的会话列元素
|
||||||
LOG() << "click FriendItem... userId: " << userId;
|
LOG() << "click FriendItem... userId: " << userId;
|
||||||
|
|
||||||
|
MainWidget* mainWidget = MainWidget::getInstance();
|
||||||
|
mainWidget->switchSession(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
@ -298,18 +386,39 @@ ApplyItem::ApplyItem(QWidget *owner, const QString &userId, const QIcon &avatar,
|
|||||||
QGridLayout* layout = dynamic_cast<QGridLayout*>(this->layout());
|
QGridLayout* layout = dynamic_cast<QGridLayout*>(this->layout());
|
||||||
layout->removeWidget(messageLabel);
|
layout->removeWidget(messageLabel);
|
||||||
//要记得释放内存,否则会内存泄露
|
//要记得释放内存,否则会内存泄露
|
||||||
// delete messageLabel;
|
delete messageLabel;
|
||||||
|
|
||||||
//创建两个按钮
|
//创建两个按钮
|
||||||
QPushButton* acceptBtn = new QPushButton();
|
QPushButton* acceptBtn = new QPushButton();
|
||||||
|
acceptBtn->setStyleSheet("QPushButton {color: rgb(255, 255, 255); background-color: rgb(7, 193, 96); border: none; border-radius: 5px; } ");
|
||||||
acceptBtn->setText("同意");
|
acceptBtn->setText("同意");
|
||||||
|
acceptBtn->setFixedSize(40, 20);
|
||||||
QPushButton* rejectBtn = new QPushButton();
|
QPushButton* rejectBtn = new QPushButton();
|
||||||
|
rejectBtn->setStyleSheet("QPushButton {color: rgb(250, 81, 81); background-color: rgb(190, 190, 190); border: none; border-radius: 5px; } ");
|
||||||
rejectBtn->setText("拒绝");
|
rejectBtn->setText("拒绝");
|
||||||
|
rejectBtn->setFixedSize(40, 20);
|
||||||
|
|
||||||
//添加到布局管理器中
|
//添加到布局管理器中
|
||||||
layout->addWidget(acceptBtn, 1, 2, 1, 1);
|
layout->addWidget(acceptBtn, 1, 2, 1, 4);
|
||||||
layout->addWidget(rejectBtn, 1, 3, 1, 1);
|
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()
|
void ApplyItem::active()
|
||||||
131
ChatClient/src/soundrecorder.cpp
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
#include "soundrecorder.h"
|
||||||
|
#include <QDir>
|
||||||
|
#include <QMediaDevices>
|
||||||
|
|
||||||
|
#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();
|
||||||
|
}
|
||||||
52
ChatClient/src/toast.cpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#include "toast.h"
|
||||||
|
|
||||||
|
Toast::Toast(const QString& text)
|
||||||
|
{
|
||||||
|
// 1. 设置窗口的基本参数
|
||||||
|
this->setFixedSize(600, 120);
|
||||||
|
this->setWindowTitle("消息通知");
|
||||||
|
this->setWindowIcon(QIcon(":/resource/image/logo.png"));
|
||||||
|
this->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
this->setStyleSheet("QDialog { background-color: rgb(255, 255, 255); }");
|
||||||
|
// 去掉窗口的标题栏
|
||||||
|
this->setWindowFlags(Qt::FramelessWindowHint);
|
||||||
|
|
||||||
|
// 2. 先考虑一下窗口的位置.
|
||||||
|
// 获取到整个屏幕的尺寸, 通过 primaryScreen 来获取.
|
||||||
|
QScreen* screen = QApplication::primaryScreen();
|
||||||
|
int width = screen->size().width();
|
||||||
|
int height = screen->size().height();
|
||||||
|
int x = (width - this->width()) / 2;
|
||||||
|
int y = height - this->height() - 100; // 此处的 100 是窗口底边距离屏幕底边的间隔
|
||||||
|
this->move(x, y);
|
||||||
|
|
||||||
|
// 3. 添加一个布局管理器
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout();
|
||||||
|
layout->setSpacing(0);
|
||||||
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
this->setLayout(layout);
|
||||||
|
|
||||||
|
// 4. 创建显示文本的 Label
|
||||||
|
QLabel* label = new QLabel();
|
||||||
|
label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
label->setAlignment(Qt::AlignCenter);
|
||||||
|
label->setStyleSheet("QLabel { font-size: 25px; color: rgb(0, 0, 0); }");
|
||||||
|
label->setText(text);
|
||||||
|
layout->addWidget(label);
|
||||||
|
|
||||||
|
// 5. 实现 2s 之后自动关闭.
|
||||||
|
QTimer* timer = new QTimer(this);
|
||||||
|
connect(timer, &QTimer::timeout, this, [=]() {
|
||||||
|
timer->stop();
|
||||||
|
// 核心代码, 关闭当前窗口
|
||||||
|
this->close();
|
||||||
|
});
|
||||||
|
timer->start(2000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Toast::showMessage(const QString& text)
|
||||||
|
{
|
||||||
|
Toast* toast = new Toast(text);
|
||||||
|
toast->show();
|
||||||
|
}
|
||||||
179
ChatClient/src/userinfowidget.cpp
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
#include "userinfowidget.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
UserInfoWidget::UserInfoWidget(const UserInfo& userInfo, QWidget *parent)
|
||||||
|
:userInfo(userInfo),
|
||||||
|
QDialog(parent)
|
||||||
|
{
|
||||||
|
// 设置基本的属性
|
||||||
|
this->setFixedSize(400, 200);
|
||||||
|
this->setWindowTitle("用户详情");
|
||||||
|
this->setWindowIcon(QIcon(":/resource/image/logo.png"));
|
||||||
|
this->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
this->move(QCursor::pos());
|
||||||
|
|
||||||
|
// 创建布局管理器
|
||||||
|
QGridLayout* layout = new QGridLayout();
|
||||||
|
layout->setVerticalSpacing(10);
|
||||||
|
layout->setHorizontalSpacing(20);
|
||||||
|
layout->setContentsMargins(50, 20, 0, 0);
|
||||||
|
layout->setAlignment(Qt::AlignTop);
|
||||||
|
this->setLayout(layout);
|
||||||
|
|
||||||
|
// 添加头像
|
||||||
|
avatarBtn = new QPushButton();
|
||||||
|
avatarBtn->setFixedSize(75, 75);
|
||||||
|
avatarBtn->setIconSize(QSize(75, 75));
|
||||||
|
avatarBtn->setIcon(userInfo.avatar);
|
||||||
|
avatarBtn->setStyleSheet("QPushButton { border: none; background-color: transparent; }");
|
||||||
|
|
||||||
|
QString labelStyle = "QLabel { font-weight: 800; padding-left: 20px;}";
|
||||||
|
QString btnStyle = "QPushButton { border: 1px solid rgb(100, 100, 100); border-radius: 5px; background-color: rgb(240, 240, 240); color: rgb(0, 0, 0);}";
|
||||||
|
//QString btnStyle = "";
|
||||||
|
btnStyle += "QPushButton:pressed { background-color: rgb(205, 205, 205); }";
|
||||||
|
|
||||||
|
int width = 80;
|
||||||
|
int height = 30;
|
||||||
|
|
||||||
|
// 添加用户序号
|
||||||
|
idTag = new QLabel();
|
||||||
|
idTag->setText("序号");
|
||||||
|
idTag->setStyleSheet(labelStyle);
|
||||||
|
idTag->setFixedSize(width, height);
|
||||||
|
idTag->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
idLabel = new QLabel();
|
||||||
|
idLabel->setText(userInfo.userId);
|
||||||
|
idLabel->setFixedSize(width, height);
|
||||||
|
idLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
// 添加用户昵称
|
||||||
|
nameTag = new QLabel();
|
||||||
|
nameTag->setText("昵称");
|
||||||
|
nameTag->setStyleSheet(labelStyle);
|
||||||
|
nameTag->setFixedSize(width, height);
|
||||||
|
nameTag->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
nameLabel = new QLabel();
|
||||||
|
nameLabel->setText(userInfo.nickname);
|
||||||
|
nameLabel->setFixedSize(width, height);
|
||||||
|
nameLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
// 设置电话
|
||||||
|
phoneTag = new QLabel();
|
||||||
|
phoneTag->setText("电话");
|
||||||
|
phoneTag->setStyleSheet(labelStyle);
|
||||||
|
phoneTag->setFixedSize(width, height);
|
||||||
|
phoneTag->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
phoneLabel = new QLabel();
|
||||||
|
phoneLabel->setText(userInfo.phone);
|
||||||
|
phoneLabel->setFixedSize(width, height);
|
||||||
|
phoneLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
// 添加功能按钮
|
||||||
|
applyBtn = new QPushButton();
|
||||||
|
applyBtn->setText("申请好友");
|
||||||
|
applyBtn->setFixedSize(80, 30);
|
||||||
|
applyBtn->setStyleSheet(btnStyle);
|
||||||
|
applyBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
sendMessageBtn = new QPushButton();
|
||||||
|
sendMessageBtn->setText("发送消息");
|
||||||
|
sendMessageBtn->setFixedSize(80, 30);
|
||||||
|
sendMessageBtn->setStyleSheet(btnStyle);
|
||||||
|
sendMessageBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
deleteFriendBtn = new QPushButton();
|
||||||
|
deleteFriendBtn->setText("删除好友");
|
||||||
|
deleteFriendBtn->setFixedSize(80, 30);
|
||||||
|
deleteFriendBtn->setStyleSheet(btnStyle);
|
||||||
|
deleteFriendBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
// 添加上述内容到布局管理器中
|
||||||
|
layout->addWidget(avatarBtn, 0, 0, 3, 1);
|
||||||
|
|
||||||
|
layout->addWidget(idTag, 0, 1);
|
||||||
|
layout->addWidget(idLabel, 0, 2);
|
||||||
|
|
||||||
|
layout->addWidget(nameTag, 1, 1);
|
||||||
|
layout->addWidget(nameLabel, 1, 2);
|
||||||
|
|
||||||
|
layout->addWidget(phoneTag, 2, 1);
|
||||||
|
layout->addWidget(phoneLabel, 2, 2);
|
||||||
|
|
||||||
|
layout->addWidget(applyBtn, 3, 0);
|
||||||
|
layout->addWidget(sendMessageBtn, 3, 1);
|
||||||
|
layout->addWidget(deleteFriendBtn, 3, 2);
|
||||||
|
|
||||||
|
// 9. 初始化按钮的禁用关系
|
||||||
|
// 判定依据就是拿着当前用户的 userId, 在 DataCenter 的好友列表中, 查询即可.
|
||||||
|
DataCenter* dataCenter = DataCenter::getInstance();
|
||||||
|
auto* myFriend = dataCenter->findFriendById(this->userInfo.userId);
|
||||||
|
if (myFriend == nullptr) {
|
||||||
|
// 不是好友
|
||||||
|
sendMessageBtn->setEnabled(false);
|
||||||
|
deleteFriendBtn->setEnabled(false);
|
||||||
|
|
||||||
|
/*sendMessageBtn->setObjectName("sendMessageBtn");
|
||||||
|
deleteFriendBtn->setObjectName("deleteFriendBtn");*/
|
||||||
|
|
||||||
|
sendMessageBtn->setStyleSheet("QPushButton { border: 1px solid rgb(200, 200, 200); border-radius: 5px; background-color: rgb(240, 240, 240); color: rgb(150, 150, 150); }");
|
||||||
|
deleteFriendBtn->setStyleSheet("QPushButton { border: 1px solid rgb(200, 200, 200); border-radius: 5px; background-color: rgb(240, 240, 240); color: rgb(150, 150, 150); }");
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 是好友
|
||||||
|
applyBtn->setEnabled(false);
|
||||||
|
applyBtn->setStyleSheet("QPushButton { border: 1px solid rgb(200, 200, 200); border-radius: 5px; background-color: rgb(240, 240, 240); color: rgb(150, 150, 150); }");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//初始化信号槽
|
||||||
|
initSingleSlot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserInfoWidget::initSingleSlot()
|
||||||
|
{
|
||||||
|
connect(sendMessageBtn, &QPushButton::clicked, this, [=]() {
|
||||||
|
//拿到主窗口的指针,通过主窗口,前面实现的切换到会话这样的功能,直接调用即可
|
||||||
|
MainWidget* mainWidget = MainWidget::getInstance();
|
||||||
|
mainWidget->switchSession(userInfo.userId);
|
||||||
|
|
||||||
|
//把当前模态窗口关闭
|
||||||
|
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();
|
||||||
|
}
|
||||||
124
ChatClient/src/verifycodewidget.cpp
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
#include "verifycodewidget.h"
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPen>
|
||||||
|
#include <QFont>
|
||||||
|
#include <QChar>
|
||||||
|
#include <QPaintEvent>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QColor>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#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<QChar>(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();
|
||||||
|
}
|
||||||
14
README.md
@ -4,32 +4,20 @@
|
|||||||
Microservices instant messaging platform.
|
Microservices instant messaging platform.
|
||||||
|
|
||||||
#### 软件架构
|
#### 软件架构
|
||||||
软件架构说明
|
|
||||||
|
|
||||||
|
|
||||||
#### 安装教程
|
#### 安装教程
|
||||||
|
|
||||||
1. Win + R 输入cmd打开终端
|
1. Win + R 输入cmd打开终端
|
||||||
2. cmake -B build && cd build
|
2. cmake -B build && cd build
|
||||||
3. make && make install
|
3. cmake build .
|
||||||
|
|
||||||
#### 使用说明
|
#### 使用说明
|
||||||
|
|
||||||
Freedom to explore.
|
|
||||||
|
|
||||||
#### 参与贡献
|
#### 参与贡献
|
||||||
|
|
||||||
1. Fork 本仓库
|
|
||||||
2. 新建 Feat_xxx 分支
|
|
||||||
3. 提交代码
|
|
||||||
4. 新建 Pull Request
|
|
||||||
|
|
||||||
|
|
||||||
#### 特技
|
#### 特技
|
||||||
|
|
||||||
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
|
||||||
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
|
||||||
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
|
||||||
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
|
||||||
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
|
||||||
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
|
||||||
|
|||||||
@ -1,176 +0,0 @@
|
|||||||
#include "SelfInfoWidget.h"
|
|
||||||
|
|
||||||
SelfInfoWidget::SelfInfoWidget(QWidget *parent)
|
|
||||||
: QDialog(parent)
|
|
||||||
{
|
|
||||||
//设置整个窗口的属性
|
|
||||||
this->setFixedSize(450, 250);
|
|
||||||
this->setWindowTitle("个人信息");
|
|
||||||
this->setWindowIcon(QIcon(":/resource/image/logo.png"));
|
|
||||||
//窗口被关闭时,自动销毁这个对话框对象
|
|
||||||
this->setAttribute(Qt::WA_DeleteOnClose);
|
|
||||||
//把窗口移动到鼠标当前的位置
|
|
||||||
this->move(QCursor::pos());
|
|
||||||
|
|
||||||
//创建布局管理器
|
|
||||||
QGridLayout* layout = new QGridLayout();
|
|
||||||
layout->setSpacing(0);
|
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
layout->setAlignment(Qt::AlignTop);
|
|
||||||
this->setLayout(layout);
|
|
||||||
|
|
||||||
//创建头像
|
|
||||||
avatarBtn = new QPushButton();
|
|
||||||
avatarBtn->setFixedSize(75, 75);
|
|
||||||
avatarBtn->setIconSize(QSize(75, 75));
|
|
||||||
avatarBtn->setIcon(QIcon(":/resource/image/defaultAvatar.png"));
|
|
||||||
avatarBtn->setStyleSheet("QPushButton { border: none; background-color: transparent; }");
|
|
||||||
|
|
||||||
QString labelStyle = "QLabel { font-size: 14px; font-weight: 800; }";
|
|
||||||
QString btnStyle = "QPushButton {border: none; background-color: transparent; }";
|
|
||||||
btnStyle += "QPushButton:pressed { background-color: rgb(210, 210, 210); }";
|
|
||||||
|
|
||||||
int height = 30;
|
|
||||||
|
|
||||||
//添加用户的ID的显示
|
|
||||||
idTag = new QLabel();
|
|
||||||
idTag->setFixedSize(50, height);
|
|
||||||
idTag->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
idTag->setText("序号");
|
|
||||||
idTag->setStyleSheet(labelStyle);
|
|
||||||
|
|
||||||
idLabel = new QLabel();
|
|
||||||
idLabel->setFixedHeight(height);
|
|
||||||
idLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
||||||
|
|
||||||
//添加新的用户的名字的显示
|
|
||||||
nameTag = new QLabel();
|
|
||||||
nameTag->setFixedSize(50, height);
|
|
||||||
nameTag->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
nameTag->setText("昵称");
|
|
||||||
nameTag->setStyleSheet(labelStyle);
|
|
||||||
|
|
||||||
nameLabel = new QLabel();
|
|
||||||
nameLabel->setFixedHeight(height);
|
|
||||||
nameLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
||||||
|
|
||||||
nameModifyBtn = new QPushButton();
|
|
||||||
nameModifyBtn->setFixedSize(25, 25);
|
|
||||||
nameModifyBtn->setIconSize(QSize(20, 20));
|
|
||||||
nameModifyBtn->setIcon(QIcon(":/resource/image/modify.png"));
|
|
||||||
nameModifyBtn->setStyleSheet(btnStyle);
|
|
||||||
|
|
||||||
nameEdit = new QLineEdit();
|
|
||||||
nameEdit->setFixedHeight(height);
|
|
||||||
nameEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
||||||
nameEdit->hide();
|
|
||||||
|
|
||||||
nameSubmitBtn = new QPushButton();
|
|
||||||
nameSubmitBtn->setFixedSize(25, 25);
|
|
||||||
nameSubmitBtn->setIconSize(QSize(20, 20));
|
|
||||||
nameSubmitBtn->setIcon(QIcon(":/resource/image/submit.png"));
|
|
||||||
nameSubmitBtn->setStyleSheet(btnStyle);
|
|
||||||
nameSubmitBtn->hide();
|
|
||||||
|
|
||||||
//添加个性签名
|
|
||||||
descTag = new QLabel();
|
|
||||||
descTag->setFixedSize(50, height);
|
|
||||||
descTag->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
descTag->setText("签名");
|
|
||||||
descTag->setStyleSheet(labelStyle);
|
|
||||||
|
|
||||||
descLabel = new QLabel();
|
|
||||||
descLabel->setFixedHeight(height);
|
|
||||||
descLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
||||||
|
|
||||||
descModifyBtn = new QPushButton();
|
|
||||||
descModifyBtn->setFixedSize(25, 25);
|
|
||||||
descModifyBtn->setIconSize(QSize(20, 20));
|
|
||||||
descModifyBtn->setIcon(QIcon(":/resource/image/modify.png"));
|
|
||||||
descModifyBtn->setStyleSheet(btnStyle);
|
|
||||||
|
|
||||||
descEdit = new QLineEdit();
|
|
||||||
descEdit->setFixedHeight(height);
|
|
||||||
descEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
||||||
descEdit->hide();
|
|
||||||
|
|
||||||
descSubmitBtn = new QPushButton();
|
|
||||||
descSubmitBtn->setFixedSize(25, 25);
|
|
||||||
descSubmitBtn->setIconSize(QSize(20, 20));
|
|
||||||
descSubmitBtn->setIcon(QIcon(":/resource/image/modify.png"));
|
|
||||||
descSubmitBtn->setStyleSheet(btnStyle);
|
|
||||||
descSubmitBtn->hide();
|
|
||||||
|
|
||||||
// 7. 添加电话
|
|
||||||
phoneTag = new QLabel();
|
|
||||||
phoneTag->setFixedSize(50, height);
|
|
||||||
phoneTag->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
phoneTag->setText("电话");
|
|
||||||
phoneTag->setStyleSheet(labelStyle);
|
|
||||||
|
|
||||||
phoneLabel = new QLabel();
|
|
||||||
phoneLabel->setFixedHeight(height);
|
|
||||||
phoneLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
||||||
|
|
||||||
phoneModifyBtn = new QPushButton();
|
|
||||||
phoneModifyBtn->setFixedSize(25, 25);
|
|
||||||
phoneModifyBtn->setIconSize(QSize(20, 20));
|
|
||||||
phoneModifyBtn->setIcon(QIcon(":/resource/image/modify.png"));
|
|
||||||
phoneModifyBtn->setStyleSheet(btnStyle);
|
|
||||||
|
|
||||||
phoneEdit = new QLineEdit();
|
|
||||||
phoneEdit->setFixedHeight(height);
|
|
||||||
phoneEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
||||||
phoneEdit->hide();
|
|
||||||
|
|
||||||
phoneSubmitBtn = new QPushButton();
|
|
||||||
phoneSubmitBtn->setFixedSize(25, 25);
|
|
||||||
phoneSubmitBtn->setIconSize(QSize(20, 20));
|
|
||||||
phoneSubmitBtn->setIcon(QIcon(":/resource/image/submit.png"));
|
|
||||||
phoneSubmitBtn->setStyleSheet(btnStyle);
|
|
||||||
phoneSubmitBtn->hide();
|
|
||||||
|
|
||||||
|
|
||||||
// 8. 添加验证码
|
|
||||||
verifyCodeTag = new QLabel();
|
|
||||||
verifyCodeTag->setFixedSize(50, height);
|
|
||||||
verifyCodeTag->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
verifyCodeTag->setText("输入验证码:");
|
|
||||||
verifyCodeTag->setStyleSheet(labelStyle);
|
|
||||||
|
|
||||||
verifyCodeEdit = new QLineEdit();
|
|
||||||
verifyCodeEdit->setFixedHeight(height);
|
|
||||||
verifyCodeEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
||||||
//verifyCodeEdit->hide();
|
|
||||||
|
|
||||||
getVerifyCodeBtn = new QPushButton();
|
|
||||||
getVerifyCodeBtn->setText("获取验证码");
|
|
||||||
|
|
||||||
|
|
||||||
layout->addWidget(avatarBtn, 0, 0, 3, 1);
|
|
||||||
layout->addWidget(idTag, 0, 1);
|
|
||||||
layout->addWidget(idLabel, 0, 2);
|
|
||||||
|
|
||||||
layout->addWidget(nameTag, 1, 1);
|
|
||||||
layout->addWidget(nameLabel, 1, 2);
|
|
||||||
layout->addWidget(nameModifyBtn, 1, 3);
|
|
||||||
|
|
||||||
layout->addWidget(descTag, 2, 1);
|
|
||||||
layout->addWidget(descLabel, 2, 2);
|
|
||||||
layout->addWidget(descModifyBtn, 2, 3);
|
|
||||||
|
|
||||||
layout->addWidget(phoneTag, 3, 1);
|
|
||||||
layout->addWidget(phoneLabel, 3, 2);
|
|
||||||
layout->addWidget(phoneModifyBtn, 3, 3);
|
|
||||||
|
|
||||||
layout->addWidget(verifyCodeTag, 4, 1);
|
|
||||||
layout->addWidget(verifyCodeEdit, 4, 2);
|
|
||||||
layout->addWidget(getVerifyCodeBtn, 4, 3);
|
|
||||||
|
|
||||||
#if TEST_UI
|
|
||||||
idLabel->setText("12345");
|
|
||||||
nameLabel->setText("xyz");
|
|
||||||
descLabel->setText("It didn't matter that i lived another day.");
|
|
||||||
phoneLabel->setText("12345678900");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
6
debug.h
@ -1,6 +0,0 @@
|
|||||||
#ifndef DEBUG_H
|
|
||||||
#define DEBUG_H
|
|
||||||
|
|
||||||
#define TEST_UI 1
|
|
||||||
|
|
||||||
#endif // DEBUG_H
|
|
||||||
BIN
docs/C++ 聊天室 - 客户端代码开发.pdf
Normal file
20
main.cpp
@ -1,20 +0,0 @@
|
|||||||
#include "mainwidget.h"
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QStyleFactory>
|
|
||||||
|
|
||||||
#include "model/data.h"
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
QApplication a(argc, argv);
|
|
||||||
|
|
||||||
LOG() << "Hello";
|
|
||||||
|
|
||||||
QPalette palette;
|
|
||||||
palette.setColor(QPalette::WindowText, Qt::black);// 窗口文字颜色
|
|
||||||
QApplication::setPalette(palette);
|
|
||||||
|
|
||||||
MainWidget* w = MainWidget::getInstance();
|
|
||||||
w->show();
|
|
||||||
return a.exec();
|
|
||||||
}
|
|
||||||
295
mainwidget.cpp
@ -1,295 +0,0 @@
|
|||||||
#include "mainwidget.h"
|
|
||||||
#include "./ui_mainwidget.h"
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
|
|
||||||
#include "sessionfriendarea.h"
|
|
||||||
|
|
||||||
MainWidget *MainWidget::instance = nullptr;
|
|
||||||
|
|
||||||
MainWidget *MainWidget::getInstance()
|
|
||||||
{
|
|
||||||
if(instance == nullptr) {
|
|
||||||
//此处不传入参数,以桌面为父窗口
|
|
||||||
//
|
|
||||||
instance = new MainWidget();
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
MainWidget::MainWidget(QWidget *parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
, ui(new Ui::MainWidget)
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
|
||||||
|
|
||||||
// this->setStyleSheet("MainWidget {min-height: 6000px; }");
|
|
||||||
this->resize(QSize(1280, 800));
|
|
||||||
|
|
||||||
this->setWindowTitle("MyChat");
|
|
||||||
this->setWindowIcon(QIcon(":/resource/image/logo.png"));
|
|
||||||
|
|
||||||
//初始化主窗口的样式布局
|
|
||||||
initMainWindow();
|
|
||||||
//初始化左侧窗口的样式布局
|
|
||||||
initLeftWindow();
|
|
||||||
//初始化中间窗口的样式布局
|
|
||||||
initMidWindow();
|
|
||||||
//初始化右侧窗口的样式布局
|
|
||||||
initRightWindow();
|
|
||||||
//初始化信号槽
|
|
||||||
initSignalSlot();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MainWidget::~MainWidget()
|
|
||||||
{
|
|
||||||
delete ui;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWidget::initMainWindow()
|
|
||||||
{
|
|
||||||
QHBoxLayout* layout = new QHBoxLayout();
|
|
||||||
//layout内部的元素间隔设置为0
|
|
||||||
layout->setSpacing(0);
|
|
||||||
//layout内部元素四个方向边界的距离设置
|
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
this->setLayout(layout);
|
|
||||||
|
|
||||||
windowLeft = new QWidget();
|
|
||||||
windowMid = new QWidget();
|
|
||||||
windowRight = new QWidget();
|
|
||||||
|
|
||||||
|
|
||||||
windowLeft->setFixedWidth(70);
|
|
||||||
windowMid->setFixedWidth(320);
|
|
||||||
windowRight->setMinimumWidth(900);
|
|
||||||
|
|
||||||
windowLeft->setStyleSheet("QWidget { background-color: rgb(46, 46, 46); }");
|
|
||||||
windowMid->setStyleSheet("QWidget { background-color: rgb(247, 247, 247); }");
|
|
||||||
windowRight->setStyleSheet("QWidget { background-color: rgb(245, 245, 245); }");
|
|
||||||
|
|
||||||
layout->addWidget(windowLeft);
|
|
||||||
layout->addWidget(windowMid);
|
|
||||||
layout->addWidget(windowRight);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWidget::initLeftWindow()
|
|
||||||
{
|
|
||||||
QVBoxLayout* layout = new QVBoxLayout();
|
|
||||||
|
|
||||||
layout->setSpacing(20);
|
|
||||||
layout->setContentsMargins(0, 50, 0, 0);
|
|
||||||
|
|
||||||
windowLeft->setLayout(layout);
|
|
||||||
|
|
||||||
//添加用户头像
|
|
||||||
userAvatar = new QPushButton();
|
|
||||||
userAvatar->setFixedSize(45, 45);
|
|
||||||
userAvatar->setIconSize(QSize(45, 45));
|
|
||||||
userAvatar->setIcon(QIcon(":/resource/image/defaultAv.png"));
|
|
||||||
userAvatar->setStyleSheet("QPushButton {background-color: transparent; }");
|
|
||||||
layout->addWidget(userAvatar, 1, Qt::AlignTop | Qt::AlignCenter);
|
|
||||||
|
|
||||||
//添加会话标签页按钮
|
|
||||||
sessionTabBtn = new QPushButton();
|
|
||||||
sessionTabBtn->setFixedSize(45, 45);
|
|
||||||
sessionTabBtn->setIconSize(QSize(35, 35));
|
|
||||||
sessionTabBtn->setIcon(QIcon(":/resource/image/session_active.png"));
|
|
||||||
sessionTabBtn->setStyleSheet("QPushButton {background-color: transparent; }");
|
|
||||||
layout->addWidget(sessionTabBtn, 1, Qt::AlignTop | Qt::AlignCenter);
|
|
||||||
|
|
||||||
//添加好友标签页按钮
|
|
||||||
friendTabBtn = new QPushButton();
|
|
||||||
friendTabBtn->setFixedSize(45, 45);
|
|
||||||
friendTabBtn->setIconSize(QSize(35, 35));
|
|
||||||
friendTabBtn->setIcon(QIcon(":/resource/image/friend_inactive.png"));
|
|
||||||
friendTabBtn->setStyleSheet("QPushButton {background-color: transparent; }");
|
|
||||||
layout->addWidget(friendTabBtn, 1, Qt::AlignTop | Qt::AlignCenter);
|
|
||||||
|
|
||||||
//添加好友申请标签页按钮
|
|
||||||
applyTabBtn = new QPushButton();
|
|
||||||
applyTabBtn->setFixedSize(45, 45);
|
|
||||||
applyTabBtn->setIconSize(QSize(35, 35));
|
|
||||||
applyTabBtn->setIcon(QIcon(":/resource/image/apply_inactive.png"));
|
|
||||||
applyTabBtn->setStyleSheet("QPushButton {background-color: transparent; }");
|
|
||||||
layout->addWidget(applyTabBtn, 1, Qt::AlignTop | Qt::AlignCenter);
|
|
||||||
|
|
||||||
layout->addStretch(20);
|
|
||||||
|
|
||||||
}
|
|
||||||
void MainWidget::initMidWindow()
|
|
||||||
{
|
|
||||||
QGridLayout* layout = new QGridLayout();
|
|
||||||
//距离上方有20px,另外的三个方向都不要边距
|
|
||||||
layout->setContentsMargins(0, 20, 0, 0);
|
|
||||||
layout->setSpacing(0);
|
|
||||||
windowMid->setLayout(layout);
|
|
||||||
|
|
||||||
searchEdit = new QLineEdit();
|
|
||||||
searchEdit->setFixedHeight(30);
|
|
||||||
searchEdit->setPlaceholderText("搜索");
|
|
||||||
searchEdit->setStyleSheet("QLineEdit { border-radius: 5px; background-color: rgb(226, 226, 226); padding-left: 5px; }");
|
|
||||||
|
|
||||||
addFriendBtn = new QPushButton();
|
|
||||||
addFriendBtn->setFixedSize(30, 30);
|
|
||||||
addFriendBtn->setIcon(QIcon(":/resource/image/cross.png"));
|
|
||||||
QString style = R"(QPushButton { border-radius: 5px; background-color: rgb(226, 226, 226); }
|
|
||||||
QPushButton::pressed { background-color: rgb(240, 240, 240); })";
|
|
||||||
addFriendBtn->setStyleSheet(style);
|
|
||||||
|
|
||||||
SessionFriendArea* sessionFriendArea = new SessionFriendArea();
|
|
||||||
|
|
||||||
//为了更加灵活的控制边距,只影响搜索框和按钮的这一行,
|
|
||||||
//创建空白的widget填充到布局管理器上
|
|
||||||
QWidget* spacer1 = new QWidget();
|
|
||||||
spacer1->setFixedWidth(10);
|
|
||||||
QWidget* spacer2 = new QWidget();
|
|
||||||
spacer2->setFixedWidth(10);
|
|
||||||
QWidget* spacer3 = new QWidget();
|
|
||||||
spacer3->setFixedWidth(10);
|
|
||||||
|
|
||||||
QWidget* spacer4 = new QWidget();
|
|
||||||
spacer4->setFixedHeight(10);
|
|
||||||
|
|
||||||
layout->addWidget(spacer1, 0, 0);
|
|
||||||
layout->addWidget(searchEdit, 0, 1);
|
|
||||||
layout->addWidget(spacer2, 0, 2);
|
|
||||||
layout->addWidget(addFriendBtn, 0 ,3);
|
|
||||||
layout->addWidget(spacer3, 0, 4);
|
|
||||||
|
|
||||||
layout->addWidget(spacer4, 1, 0, 1, 5);
|
|
||||||
layout->addWidget(sessionFriendArea, 2, 0, 1, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWidget::initRightWindow()
|
|
||||||
{
|
|
||||||
//创建右侧窗口的布局管理器
|
|
||||||
QVBoxLayout* vlayout = new QVBoxLayout();
|
|
||||||
vlayout->setSpacing(0);
|
|
||||||
vlayout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
vlayout->setAlignment(Qt::AlignTop);
|
|
||||||
windowRight->setLayout(vlayout);
|
|
||||||
|
|
||||||
//创建上方的标题栏
|
|
||||||
QWidget* titleWidget = new QWidget();
|
|
||||||
titleWidget->setFixedHeight(62);
|
|
||||||
titleWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
||||||
titleWidget->setObjectName("titleWidget");
|
|
||||||
titleWidget->setStyleSheet(R"(#titleWidget { border-bottom: 2px solid rgb(231, 231, 231);
|
|
||||||
border-left: 1px solid rgb(231, 231, 231); }
|
|
||||||
)");
|
|
||||||
vlayout->addWidget(titleWidget);
|
|
||||||
|
|
||||||
//给标题栏添加 label 和 button
|
|
||||||
QHBoxLayout* hlayout = new QHBoxLayout();
|
|
||||||
hlayout->setSpacing(0);
|
|
||||||
hlayout->setContentsMargins(10, 0, 10, 0);
|
|
||||||
titleWidget->setLayout(hlayout);
|
|
||||||
|
|
||||||
QLabel* sessionTitleLabel = new QLabel();
|
|
||||||
sessionTitleLabel->setStyleSheet("QLabel { font-size: 22px; border-bottom: 1px solid rgb(230, 230, 230); }");
|
|
||||||
|
|
||||||
#if TEST_UI
|
|
||||||
//为了测试仅填充从服务器获取的数据
|
|
||||||
sessionTitleLabel->setText("TEST TITLE");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
hlayout->addWidget(sessionTitleLabel);
|
|
||||||
|
|
||||||
QPushButton* extraBtn = new QPushButton();
|
|
||||||
extraBtn->setFixedSize(30, 30);
|
|
||||||
extraBtn->setIconSize(QSize(30, 30));
|
|
||||||
extraBtn->setIcon(QIcon(":/resource/image/more.png"));
|
|
||||||
extraBtn->setStyleSheet(R"(QPushButton { border: none; background-color: transparent; }
|
|
||||||
QPushButton:pressed { background-color: rgb(210, 210 ,210); }
|
|
||||||
)");
|
|
||||||
hlayout->addWidget(extraBtn);
|
|
||||||
|
|
||||||
|
|
||||||
//添加消息展示区
|
|
||||||
messageShowArea = new MessageShowArea();
|
|
||||||
vlayout->addWidget(messageShowArea);
|
|
||||||
|
|
||||||
//添加消息编辑区
|
|
||||||
messageEditArea = new MessageEditArea();
|
|
||||||
//确保消息编辑区处于窗口正下方
|
|
||||||
vlayout->addWidget(messageEditArea, 0, Qt::AlignBottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWidget::initSignalSlot()
|
|
||||||
{
|
|
||||||
/////////////////////////////////////
|
|
||||||
//连接信号槽,处理标签页切换
|
|
||||||
/////////////////////////////////////
|
|
||||||
connect(sessionTabBtn, &QPushButton::clicked, this, &MainWidget::switchTabToSession);
|
|
||||||
connect(friendTabBtn, &QPushButton::clicked, this, &MainWidget::switchTabToFriend);
|
|
||||||
connect(applyTabBtn, &QPushButton::clicked, this, &MainWidget::switchTabToApply);
|
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////
|
|
||||||
// 点击自己的头像,弹出对话框显示个人的主页
|
|
||||||
/////////////////////////////////////
|
|
||||||
connect(userAvatar, &QPushButton::clicked, this, [=]()->void {
|
|
||||||
SelfInfoWidget* selfInfoWidget = new SelfInfoWidget(this);
|
|
||||||
selfInfoWidget->setStyleSheet("QDialog { background-color: rgb(255, 255, 255); }");
|
|
||||||
selfInfoWidget->exec(); //弹出模态对话框
|
|
||||||
//selfInfoWidget->show(); //弹出非模态对加框
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWidget::switchTabToSession()
|
|
||||||
{
|
|
||||||
//记录当前切换到了哪个标签页
|
|
||||||
activeTab = SESSION_LIST;
|
|
||||||
//调整图标显示情况
|
|
||||||
sessionTabBtn->setIcon(QIcon(":/resource/image/session_active.png"));
|
|
||||||
friendTabBtn->setIcon(QIcon(":/resource/image/friend_inactive.png"));
|
|
||||||
applyTabBtn->setIcon(QIcon(":/resource/image/apply_inactive.png"));
|
|
||||||
// 在主窗口的中间部分,加载出会话列表数据
|
|
||||||
this->loadSessionList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWidget::switchTabToFriend()
|
|
||||||
{
|
|
||||||
//记录当前切换到了哪个标签页
|
|
||||||
activeTab = FRIEND_LIST;
|
|
||||||
//调整图标显示情况
|
|
||||||
sessionTabBtn->setIcon(QIcon(":/resource/image/session_inactive.png"));
|
|
||||||
friendTabBtn->setIcon(QIcon(":/resource/image/friend_active.png"));
|
|
||||||
applyTabBtn->setIcon(QIcon(":/resource/image/apply_inactive.png"));
|
|
||||||
// 在主窗口的中间部分,加载出会话列表数据
|
|
||||||
this->loadFriendList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWidget::switchTabToApply()
|
|
||||||
{
|
|
||||||
//记录当前切换到了哪个标签页
|
|
||||||
activeTab = APPLY_LIST;
|
|
||||||
//调整图标显示情况
|
|
||||||
sessionTabBtn->setIcon(QIcon(":/resource/image/session_inactive.png"));
|
|
||||||
friendTabBtn->setIcon(QIcon(":/resource/image/friend_inactive.png"));
|
|
||||||
applyTabBtn->setIcon(QIcon(":/resource/image/apply_active.png"));
|
|
||||||
// 在主窗口的中间部分,加载出会话列表数据
|
|
||||||
this->loadApplyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
//加载会话列表
|
|
||||||
void MainWidget::loadSessionList()
|
|
||||||
{
|
|
||||||
//TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
//加载好友列表
|
|
||||||
void MainWidget::loadFriendList()
|
|
||||||
{
|
|
||||||
//TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
//加载申请列表
|
|
||||||
void MainWidget::loadApplyList()
|
|
||||||
{
|
|
||||||
//TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,206 +0,0 @@
|
|||||||
#include "messageeditarea.h"
|
|
||||||
|
|
||||||
|
|
||||||
MessageEditArea::MessageEditArea(QWidget *parent)
|
|
||||||
: QWidget{parent}
|
|
||||||
{
|
|
||||||
//设置必要的属性
|
|
||||||
this->setFixedHeight(250);
|
|
||||||
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
||||||
|
|
||||||
//创建垂直方向的布局管理器
|
|
||||||
QVBoxLayout* vlayout = new QVBoxLayout();
|
|
||||||
vlayout->setSpacing(0);
|
|
||||||
vlayout->setContentsMargins(0, 0, 30, 10);
|
|
||||||
this->setLayout(vlayout);
|
|
||||||
|
|
||||||
//创建水平方向的布局管理器
|
|
||||||
QHBoxLayout* hlayout = new QHBoxLayout();
|
|
||||||
hlayout->setSpacing(0);
|
|
||||||
hlayout->setContentsMargins(10, 0, 0, 0);
|
|
||||||
hlayout->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
|
||||||
vlayout->addLayout(hlayout);
|
|
||||||
|
|
||||||
//将上方的四个按钮,创建好并添加到layout中
|
|
||||||
QString btnStyle = R"(QPushButton { background-color: rgb(245, 245, 245); border: none; }
|
|
||||||
QPushButton:pressed {background-color: rgb(255, 255, 255); })";
|
|
||||||
QSize btnSize(35, 35);
|
|
||||||
QSize iconSize(25, 25);
|
|
||||||
|
|
||||||
sendImageBtn = new QPushButton();
|
|
||||||
sendImageBtn->setFixedSize(btnSize);
|
|
||||||
sendImageBtn->setIconSize(iconSize);
|
|
||||||
sendImageBtn->setIcon(QIcon(":/resource/image/image.png"));
|
|
||||||
sendImageBtn->setStyleSheet(btnStyle);
|
|
||||||
hlayout->addWidget(sendImageBtn);
|
|
||||||
|
|
||||||
sendFileBtn = new QPushButton();
|
|
||||||
sendFileBtn->setFixedSize(btnSize);
|
|
||||||
sendFileBtn->setIconSize(iconSize);
|
|
||||||
sendFileBtn->setIcon(QIcon(":/resource/image/file.png"));
|
|
||||||
sendFileBtn->setStyleSheet(btnStyle);
|
|
||||||
hlayout->addWidget(sendFileBtn);
|
|
||||||
|
|
||||||
sendSpeechBtn = new QPushButton();
|
|
||||||
sendSpeechBtn->setFixedSize(btnSize);
|
|
||||||
sendSpeechBtn->setIconSize(iconSize);
|
|
||||||
sendSpeechBtn->setIcon(QIcon(":/resource/image/sound.png"));
|
|
||||||
sendSpeechBtn->setStyleSheet(btnStyle);
|
|
||||||
hlayout->addWidget(sendSpeechBtn);
|
|
||||||
|
|
||||||
showHistoryBtn = new QPushButton();
|
|
||||||
showHistoryBtn->setFixedSize(btnSize);
|
|
||||||
showHistoryBtn->setIconSize(iconSize);
|
|
||||||
showHistoryBtn->setIcon(QIcon(":/resource/image/history.png"));
|
|
||||||
showHistoryBtn->setStyleSheet(btnStyle);
|
|
||||||
hlayout->addWidget(showHistoryBtn);
|
|
||||||
|
|
||||||
//添加多行编辑框
|
|
||||||
textEdit = new QPlainTextEdit();
|
|
||||||
textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
||||||
textEdit->setStyleSheet(R"( QPlainTextEdit {
|
|
||||||
color: black;
|
|
||||||
border: none;
|
|
||||||
background-color: transparent;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 10px; }
|
|
||||||
)");
|
|
||||||
textEdit->verticalScrollBar()->setStyleSheet(R"(
|
|
||||||
QScrollBar:vertical {
|
|
||||||
background-color: rgb(228, 228, 228); /* 滚动条背景透明 */
|
|
||||||
width: 6px; /* 默认宽度 */
|
|
||||||
margin: 0px 0px 0px 0px; /* 边距清零 */
|
|
||||||
}
|
|
||||||
QScrollBar::handle:vertical {
|
|
||||||
background: #CCCCCC; /* 滑块颜色(浅灰色) */
|
|
||||||
min-height: 30px; /* 滑块最小高度 */
|
|
||||||
border-radius: 3px; /* 圆角 */
|
|
||||||
}
|
|
||||||
QScrollBar::handle:vertical:hover {
|
|
||||||
background: #999999; /* 悬停时滑块颜色加深 */
|
|
||||||
width: 10px; /* 悬停时宽度增大 */
|
|
||||||
}
|
|
||||||
QScrollBar::handle:vertical:pressed {
|
|
||||||
background: #666666; /* 按下时颜色更深 */
|
|
||||||
}
|
|
||||||
QScrollBar::add-line:vertical,
|
|
||||||
QScrollBar::sub-line:vertical {
|
|
||||||
height: 0px; /* 隐藏上下箭头按钮 */
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
QScrollBar::add-page:vertical,
|
|
||||||
QScrollBar::sub-page:vertical {
|
|
||||||
background: none; /* 滚动条背景区域透明 */
|
|
||||||
}
|
|
||||||
|
|
||||||
)");
|
|
||||||
|
|
||||||
vlayout->addWidget(textEdit);
|
|
||||||
|
|
||||||
|
|
||||||
//添加发送消息文本的按钮
|
|
||||||
sendTextButton = new QPushButton();
|
|
||||||
sendTextButton->setText("发送");
|
|
||||||
sendTextButton->setFixedSize(120, 40);
|
|
||||||
//仿Wechat的标准样式
|
|
||||||
sendTextButton->setStyleSheet(R"(QPushButton {
|
|
||||||
font-size: 16px;
|
|
||||||
color: rgb(7, 193, 96);
|
|
||||||
border: none;
|
|
||||||
background-color: rgb(233, 233, 233);
|
|
||||||
border-radius: 5px; }
|
|
||||||
QPushButton:hover {
|
|
||||||
background-color: rgb(210, 210, 210); }
|
|
||||||
QPushButton:pressed {
|
|
||||||
background-color: rgb(190, 190, 190); }
|
|
||||||
)");
|
|
||||||
|
|
||||||
|
|
||||||
//// 创建阴影效果
|
|
||||||
// shadowEffect = new QGraphicsDropShadowEffect;
|
|
||||||
// shadowEffect->setBlurRadius(12);
|
|
||||||
// shadowEffect->setOffset(0, 5);
|
|
||||||
// shadowEffect->setColor(QColor(0, 0, 0, 150));
|
|
||||||
// sendTextButton->setGraphicsEffect(shadowEffect);
|
|
||||||
//
|
|
||||||
//// 设置按钮样式表
|
|
||||||
// sendTextButton->setStyleSheet(R"(
|
|
||||||
// QPushButton {
|
|
||||||
// background: qlineargradient(spread:pad,
|
|
||||||
// x1:0, y1:0, x2:1, y2:0,
|
|
||||||
// stop:0 #9b59b6,
|
|
||||||
// stop:1 #8e44ad);
|
|
||||||
// border-radius: 10px;
|
|
||||||
// color: white;
|
|
||||||
// font: bold 14px;
|
|
||||||
// border: none;
|
|
||||||
// margin: 5px;
|
|
||||||
// box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
|
||||||
// }
|
|
||||||
// QPushButton:hover {
|
|
||||||
// background: qlineargradient(spread:pad,
|
|
||||||
// x1:0, y1:0, x2:1, y2:0,
|
|
||||||
// stop:0 #e67e22,
|
|
||||||
// stop:1 #d35400);
|
|
||||||
// box-shadow: 0 7px 20px rgba(0,0,0,0.4);
|
|
||||||
// }
|
|
||||||
// QPushButton:pressed {
|
|
||||||
// background: qlineargradient(spread:pad,
|
|
||||||
// x1:0, y1:0, x2:1, y2:0,
|
|
||||||
// stop:0 #e74c3c,
|
|
||||||
// stop:1 #c0392b);
|
|
||||||
// }
|
|
||||||
//)");
|
|
||||||
//
|
|
||||||
// // 连接按钮的按下和释放事件
|
|
||||||
// connect(sendTextButton, &QPushButton::pressed, [=]() {
|
|
||||||
// QPropertyAnimation* anim = new QPropertyAnimation(shadowEffect, "offset");
|
|
||||||
// anim->setDuration(100);
|
|
||||||
// anim->setStartValue(shadowEffect->offset());
|
|
||||||
// anim->setEndValue(QPointF(0, 1));
|
|
||||||
// anim->start(QAbstractAnimation::DeleteWhenStopped);
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// connect(sendTextButton, &QPushButton::released, [=]() {
|
|
||||||
// QPropertyAnimation* anim = new QPropertyAnimation(shadowEffect, "offset");
|
|
||||||
// anim->setDuration(150);
|
|
||||||
// anim->setStartValue(shadowEffect->offset());
|
|
||||||
// anim->setEndValue(QPointF(0, sendTextButton->underMouse() ? 3 : 5)); // 根据是否悬停决定最终位置
|
|
||||||
// anim->setEasingCurve(QEasingCurve::OutBack);
|
|
||||||
// anim->start(QAbstractAnimation::DeleteWhenStopped);
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// // 安装事件过滤器
|
|
||||||
// sendTextButton->installEventFilter(this);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
vlayout->addWidget(sendTextButton, 0, Qt::AlignRight | Qt::AlignVCenter);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//bool MessageEditArea::eventFilter(QObject* obj, QEvent* event)
|
|
||||||
//{
|
|
||||||
// if (obj == sendTextButton) {
|
|
||||||
// if (event->type() == QEvent::Enter) {
|
|
||||||
// // 鼠标进入事件
|
|
||||||
// QPropertyAnimation* hoverAnim = new QPropertyAnimation(shadowEffect, "offset");
|
|
||||||
// hoverAnim->setDuration(200);
|
|
||||||
// hoverAnim->setStartValue(shadowEffect->offset());
|
|
||||||
// hoverAnim->setEndValue(QPointF(0, 3));
|
|
||||||
// hoverAnim->start(QAbstractAnimation::DeleteWhenStopped);
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// else if (event->type() == QEvent::Leave) {
|
|
||||||
// // 鼠标离开事件
|
|
||||||
// QPropertyAnimation* hoverAnim = new QPropertyAnimation(shadowEffect, "offset");
|
|
||||||
// hoverAnim->setDuration(200);
|
|
||||||
// hoverAnim->setStartValue(shadowEffect->offset());
|
|
||||||
// hoverAnim->setEndValue(QPointF(0, 5));
|
|
||||||
// hoverAnim->start(QAbstractAnimation::DeleteWhenStopped);
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return QObject::eventFilter(obj, event);
|
|
||||||
//}
|
|
||||||
@ -1,258 +0,0 @@
|
|||||||
#include "messageshowarea.h"
|
|
||||||
|
|
||||||
MessageShowArea::MessageShowArea() {
|
|
||||||
//初始化基本属性
|
|
||||||
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
||||||
this->setWidgetResizable(true);
|
|
||||||
|
|
||||||
//设置滚动条样式
|
|
||||||
this->verticalScrollBar()->setStyleSheet("QScrollBar:vertical { width: 2px; background-color: rgb(240, 240, 240); }");
|
|
||||||
this->horizontalScrollBar()->setStyleSheet("QScrollBar:horizontal { height: 0; }");
|
|
||||||
this->setStyleSheet("QScrollArea { border: none; }");
|
|
||||||
|
|
||||||
//创建container这样的widget,作为包含内部元素的容器
|
|
||||||
container = new QWidget();
|
|
||||||
this->setWidget(container);
|
|
||||||
|
|
||||||
//给container添加界面布局管理器
|
|
||||||
QVBoxLayout* layout = new QVBoxLayout();
|
|
||||||
layout->setSpacing(0);
|
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
container->setLayout(layout);
|
|
||||||
|
|
||||||
// 添加测试数据
|
|
||||||
#if TEST_UI
|
|
||||||
bool k = true;
|
|
||||||
for(int i = 0; i < 8; i++) {
|
|
||||||
model::UserInfo userInfo;
|
|
||||||
userInfo.nickname = "xyz" + QString::number(i);
|
|
||||||
userInfo.avatar = QIcon(":/resource/image/defaultAvatar.png");
|
|
||||||
Message message = Message::makeMessage(model::TEXT_TYPE, "", userInfo, (QString("this is a test message...") + QString::number(i)).toUtf8(), "");
|
|
||||||
k = !k;
|
|
||||||
this->addMessage(k, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
//测试长消息
|
|
||||||
model::UserInfo userInfo;
|
|
||||||
userInfo.nickname = "???";
|
|
||||||
|
|
||||||
const QString s = R"(The starry sky is just a few years ago. And the past may no longer exist,The only thing that remains is light years away, it's just an ethereal phantom.)";
|
|
||||||
|
|
||||||
userInfo.avatar = QIcon(":/resource/image/defaultAvatar.png");
|
|
||||||
Message message = Message::makeMessage(model::TEXT_TYPE, "", userInfo, s.toUtf8(), "");
|
|
||||||
this->addMessage(false, message);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageShowArea::addFrontMessage(bool isLeft, const Message &message)
|
|
||||||
{
|
|
||||||
MessageItem* messageItem = MessageItem::makeMessageItem(isLeft, message);
|
|
||||||
QVBoxLayout* layout = dynamic_cast<QVBoxLayout*>(container->layout());
|
|
||||||
layout->insertWidget(0, messageItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageShowArea::addMessage(bool isLeft, const Message &message)
|
|
||||||
{
|
|
||||||
MessageItem* messageItem = MessageItem::makeMessageItem(isLeft, message);
|
|
||||||
container->layout()->addWidget(messageItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageShowArea::clear()
|
|
||||||
{
|
|
||||||
QLayout* layout = container->layout();
|
|
||||||
//要遍历布局管理器,删除里面的元素
|
|
||||||
for(int i = layout->count() - 1; i >= 0; i--) {
|
|
||||||
QLayoutItem* item = layout->takeAt(i);
|
|
||||||
if(item != nullptr && item->widget() != nullptr) {
|
|
||||||
delete item->widget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////
|
|
||||||
/// 表示一个消息元素
|
|
||||||
////////////////////////////////////////////
|
|
||||||
MessageItem::MessageItem(bool isLeft)
|
|
||||||
:isLeft(isLeft)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageItem *MessageItem::makeMessageItem(bool isLeft, const Message &message)
|
|
||||||
{
|
|
||||||
//创建对象和布局管理器
|
|
||||||
MessageItem* messageItem = new MessageItem(isLeft);
|
|
||||||
QGridLayout* layout = new QGridLayout();
|
|
||||||
layout->setSpacing(10);
|
|
||||||
layout->setContentsMargins(30, 10, 40, 0);
|
|
||||||
|
|
||||||
//这个message最低不能低于100
|
|
||||||
messageItem->setMinimumHeight(100);
|
|
||||||
messageItem->setLayout(layout);
|
|
||||||
|
|
||||||
//创建头像
|
|
||||||
QPushButton* avatarBtn = new QPushButton();
|
|
||||||
avatarBtn->setFixedSize(40, 40);
|
|
||||||
avatarBtn->setIconSize(QSize(40, 40));
|
|
||||||
avatarBtn->setIcon(message.sender.avatar);
|
|
||||||
if(isLeft) {
|
|
||||||
layout->addWidget(avatarBtn, 0, 0, 2, 1, Qt::AlignCenter | Qt::AlignLeft);
|
|
||||||
} else {
|
|
||||||
layout->addWidget(avatarBtn, 0, 1, 2, 1, Qt::AlignCenter | Qt::AlignRight);
|
|
||||||
}
|
|
||||||
|
|
||||||
//创建名字和时间
|
|
||||||
QLabel* nameLabel = new QLabel();
|
|
||||||
nameLabel->setText(message.sender.nickname + " | " + message.time);
|
|
||||||
nameLabel->setAlignment(Qt::AlignBottom);
|
|
||||||
nameLabel->setStyleSheet("QLabel { font-size: 12px; color: rgb(178, 178, 178); }");
|
|
||||||
if(isLeft) {
|
|
||||||
layout->addWidget(nameLabel, 0, 1);
|
|
||||||
} else {
|
|
||||||
layout->addWidget(nameLabel, 0, 0, Qt::AlignRight);
|
|
||||||
}
|
|
||||||
|
|
||||||
//创建消息体
|
|
||||||
QWidget* contentWidget = nullptr;
|
|
||||||
switch(message.messageType) {
|
|
||||||
case model::TEXT_TYPE:
|
|
||||||
contentWidget = makeTextMessageItem(isLeft, message.content);
|
|
||||||
break;
|
|
||||||
case model::IMAGE_TYPE:
|
|
||||||
contentWidget = makeImageMessageItem();
|
|
||||||
break;
|
|
||||||
case model::FILE_TYPE:
|
|
||||||
contentWidget = makeFileMessageItem();
|
|
||||||
break;
|
|
||||||
case model::SPEECH_TYPE:
|
|
||||||
contentWidget = makeSpeechMessageItem();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LOG() << "error messageType: " << message.messageType;
|
|
||||||
}
|
|
||||||
if(isLeft) {
|
|
||||||
layout->addWidget(contentWidget, 1, 1);
|
|
||||||
} else {
|
|
||||||
layout->addWidget(contentWidget, 1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return messageItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
QWidget *MessageItem::makeTextMessageItem(bool isLeft, const QString &text)
|
|
||||||
{
|
|
||||||
MessageContentLabel* messageContentLabel = new MessageContentLabel(text, isLeft);
|
|
||||||
return messageContentLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
QWidget *MessageItem::makeImageMessageItem()
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
QWidget *MessageItem::makeFileMessageItem()
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
QWidget *MessageItem::makeSpeechMessageItem()
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////
|
|
||||||
/// 创建类表示“文本消息”正文部分
|
|
||||||
////////////////////////////////////////////
|
|
||||||
MessageContentLabel::MessageContentLabel(const QString &text, bool isLeft)
|
|
||||||
:isLeft(isLeft)
|
|
||||||
{
|
|
||||||
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
||||||
|
|
||||||
QFont font;
|
|
||||||
font.setFamily("微软雅黑");
|
|
||||||
font.setPixelSize(16);
|
|
||||||
|
|
||||||
this->label = new QLabel(this);
|
|
||||||
this->label->setText(text);
|
|
||||||
this->label->setFont(font);
|
|
||||||
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; }");
|
|
||||||
}
|
|
||||||
|
|
||||||
//这个函数会在该控件被显示时,自动的调用到
|
|
||||||
void MessageContentLabel::paintEvent(QPaintEvent *event)
|
|
||||||
{
|
|
||||||
(void) event;
|
|
||||||
|
|
||||||
//获取到父元素的宽度
|
|
||||||
QObject* object = this->parent();
|
|
||||||
if(!object->isWidgetType()) {
|
|
||||||
//说明当前的对象不是QWidget,则不需要进行任何后续的绘制操作
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QWidget* parent = dynamic_cast<QWidget*>(object);
|
|
||||||
int width = parent->width() * 0.6;
|
|
||||||
|
|
||||||
//计算当前文本,如果单行防止放置需要多宽
|
|
||||||
QFontMetrics metrics(this->label->font());
|
|
||||||
int totalWidth = metrics.horizontalAdvance(this->label->text());
|
|
||||||
|
|
||||||
//计算出此处的行数是多少
|
|
||||||
int rows = (totalWidth / (width - 40)) + 1;
|
|
||||||
if(rows == 1) {
|
|
||||||
//若此时得到的行数只有一行
|
|
||||||
width = totalWidth + 40;
|
|
||||||
}
|
|
||||||
|
|
||||||
//根据行数来确定高度
|
|
||||||
//行数 × 行高(字体高度的 1.2 倍) + 上下内边距(各 10px)
|
|
||||||
int height = rows * (this->label->font().pixelSize() * 1.2 ) + 20;
|
|
||||||
|
|
||||||
//绘制圆角矩形和箭头
|
|
||||||
QPainter painter(this);
|
|
||||||
QPainterPath path;
|
|
||||||
//设置抗锯齿
|
|
||||||
painter.setRenderHint(QPainter::Antialiasing);
|
|
||||||
if(isLeft) {
|
|
||||||
painter.setPen(QPen(QColor(255, 255, 255)));
|
|
||||||
painter.setBrush(QColor(255, 255, 255)); //白色填充
|
|
||||||
|
|
||||||
//绘制圆角矩形
|
|
||||||
painter.drawRoundedRect(10, 0, width, height, 10 ,10);
|
|
||||||
//绘制箭头
|
|
||||||
path.moveTo(10, 15);
|
|
||||||
path.lineTo(0, 20);
|
|
||||||
path.lineTo(10, 25);
|
|
||||||
path.closeSubpath(); //绘制的线形成闭合的多边形,才能进行使用Brush填充颜色
|
|
||||||
painter.drawPath(path);//设置好,调用画笔进行绘制操作
|
|
||||||
|
|
||||||
this->label->setGeometry(10, 0, width, height);
|
|
||||||
} else {
|
|
||||||
painter.setPen(QPen(QColor(137, 217, 97)));
|
|
||||||
painter.setBrush(QColor(137, 217, 97));
|
|
||||||
|
|
||||||
//圆角矩形左侧边的横坐标位置
|
|
||||||
int leftPos = this->width() - width - 10; //10 用来容纳箭头的宽度
|
|
||||||
//圆角矩形右侧边的横坐标位置
|
|
||||||
int rightPos = this->width() - 10;
|
|
||||||
|
|
||||||
//绘制圆角矩形
|
|
||||||
painter.drawRoundedRect(leftPos, 0, width, height, 10, 10);
|
|
||||||
//绘制箭头
|
|
||||||
path.moveTo(rightPos, 15);
|
|
||||||
path.lineTo(rightPos + 10, 20);
|
|
||||||
path.lineTo(rightPos, 25);
|
|
||||||
path.closeSubpath(); //绘制的线形成闭合的多边形,才能进行使用Brush填充颜色
|
|
||||||
painter.drawPath(path);//设置好,调用画笔进行绘制操作
|
|
||||||
|
|
||||||
this->label->setGeometry(leftPos, 0, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
//重新设置父元素的高度,保证父元素足够的高,能够容纳下上述绘制消息的显示区域
|
|
||||||
//注意高度要涵盖之前的名字和时间的label的高度,以及留一点的冗余的空间
|
|
||||||
parent->setFixedHeight(height + 50);
|
|
||||||
}
|
|
||||||