提交之GitHub
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
package client.view;
|
||||
|
||||
import client.Client;
|
||||
import client.service.ChatSender;
|
||||
import client.service.LocalData;
|
||||
import client.view.login.*;
|
||||
import client.view.util.DesignToken;
|
||||
import server.serveice.Wrapper;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
|
||||
/**
|
||||
* 登录页面,用于完成用户账户登录的功能,作为一个独立的页面,登录完成之后将自动关闭,并开启主界面
|
||||
*/
|
||||
public class LoginPage extends JFrame {
|
||||
private static volatile LoginPage INSTANCE;
|
||||
|
||||
// 获取登录页面实例,使用单例模式确保全局唯一性
|
||||
public static LoginPage get() {
|
||||
if (INSTANCE == null) {
|
||||
synchronized (LoginPage.class) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new LoginPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
// 交替展示两个窗口,分别用于进行注册和登录操作。
|
||||
private SignInView signInView;
|
||||
private SignUpView signUpView;
|
||||
|
||||
private LoginPage() {
|
||||
setTitle("欢迎来到本地网聊天室!");
|
||||
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
setSize(DesignToken.LOGIN_WIDTH, DesignToken.LOGIN_HEIGHT);
|
||||
setLocationRelativeTo(null);
|
||||
|
||||
addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
// 如果链接上了,发出退出信息
|
||||
if (Client.isConnected()) {
|
||||
ChatSender.addMsg(Wrapper.logoutRequest(LocalData.get().getId()));
|
||||
}
|
||||
super.windowClosing(e);
|
||||
// 如果未登录,则直接退出。
|
||||
if (LocalData.get().getId().length() == 0) {
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
signInView = new SignInView(this);
|
||||
signUpView = new SignUpView(this);
|
||||
|
||||
// 默认为登录
|
||||
exchangeToSignInView();
|
||||
}
|
||||
|
||||
// 更换到注册界面
|
||||
public void exchangeToSignUpView() {
|
||||
this.remove(signInView);
|
||||
this.add(signUpView, BorderLayout.CENTER);
|
||||
this.validate();
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
// 更换到登录界面
|
||||
public void exchangeToSignInView() {
|
||||
this.remove(signUpView);
|
||||
this.add(signInView, BorderLayout.CENTER);
|
||||
this.validate();
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
// 打开主界面,并关闭本界面
|
||||
public void openMainPage() {
|
||||
// 创建新窗口
|
||||
MainPage.get().setVisible(true);
|
||||
|
||||
// 关闭当前窗口
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
public void showMsgDialog(String text) {
|
||||
JDialog inviteDialog = new JDialog(this, "信息", true);
|
||||
inviteDialog.setSize(300, 200);
|
||||
inviteDialog.setLocationRelativeTo(this);
|
||||
|
||||
// 设置对话框内容
|
||||
String htmlText = "<html><body style='width: 210px; padding: 10px;'>" + text + "</body></html>";
|
||||
JLabel label = new JLabel(htmlText, SwingConstants.CENTER);
|
||||
JButton closeBtn = new JButton("确定");
|
||||
|
||||
closeBtn.addActionListener(e -> inviteDialog.dispose());
|
||||
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
JPanel centerPanel = new JPanel(new FlowLayout());
|
||||
centerPanel.add(label);
|
||||
|
||||
panel.add(centerPanel, BorderLayout.CENTER);
|
||||
panel.add(closeBtn, BorderLayout.SOUTH);
|
||||
inviteDialog.add(panel);
|
||||
|
||||
// 显示对话框(会阻塞主窗口交互)
|
||||
inviteDialog.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,503 @@
|
||||
package client.view;
|
||||
|
||||
import client.Client;
|
||||
import client.service.ChatSender;
|
||||
import client.service.LocalData;
|
||||
import client.view.main.*;
|
||||
import global.global;
|
||||
import server.serveice.Wrapper;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
|
||||
import static client.view.util.DesignToken.*;
|
||||
|
||||
/**
|
||||
* 主界面。
|
||||
*/
|
||||
public class MainPage extends JFrame {
|
||||
private volatile static MainPage instance;
|
||||
|
||||
// 获取主界面
|
||||
public static MainPage get() {
|
||||
if (instance == null) {
|
||||
synchronized (LoginPage.class) {
|
||||
if (instance == null) {
|
||||
instance = new MainPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
// 可以左右的移动大小的分割界面
|
||||
private final JSplitPane splitPane;
|
||||
private final SideOptionView sideOptionView;
|
||||
private final SecondaryOptionView secondaryOptionView;
|
||||
private final ContentView contentView;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* 初始化主界面组件,包括侧边栏、二级选项栏、详细内容区域等。
|
||||
* 设置主界面的标题、关闭操作、大小、位置等属性。
|
||||
* 同时添加窗口关闭监听器,在窗口关闭时发送登出请求。
|
||||
*/
|
||||
private MainPage() {
|
||||
Dimension size = new Dimension(WINDOW_ORI_WIDTH, WINDOW_ORI_HEIGHT);
|
||||
|
||||
setTitle("本地网络聊天室");
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setSize(size);
|
||||
setLocationRelativeTo(null);
|
||||
|
||||
// 侧边栏
|
||||
sideOptionView = new SideOptionView();
|
||||
sideOptionView.setMinimumSize(new Dimension(SIDE_PANEL_WIDTH, WINDOW_ORI_HEIGHT));
|
||||
sideOptionView.setMaximumSize(new Dimension(SIDE_PANEL_WIDTH, Integer.MAX_VALUE));
|
||||
sideOptionView.setPreferredSize(new Dimension(SIDE_PANEL_WIDTH, WINDOW_ORI_HEIGHT));
|
||||
|
||||
// 二级选项栏
|
||||
secondaryOptionView = SecondaryOptionView.get();
|
||||
// 详细内容
|
||||
contentView = new ContentView();
|
||||
|
||||
splitPane = new JSplitPane(
|
||||
JSplitPane.HORIZONTAL_SPLIT,
|
||||
secondaryOptionView,
|
||||
contentView);
|
||||
|
||||
splitPane.setDividerSize(2);
|
||||
splitPane.setOneTouchExpandable(true);
|
||||
splitPane.setResizeWeight(0.5);
|
||||
splitPane.setContinuousLayout(true);
|
||||
splitPane.setMinimumSize(new Dimension(SECONDARY_PANEL_WIDTH_MIN + CONTENT_PANEL_WIDTH_MIN, size.height));
|
||||
splitPane.setPreferredSize(new Dimension(SECONDARY_PANEL_WIDTH_MIN + CONTENT_PANEL_WIDTH_MIN, size.height));
|
||||
|
||||
addDividerConstraintListener();
|
||||
|
||||
this.add(sideOptionView, BorderLayout.WEST);
|
||||
this.add(splitPane, BorderLayout.CENTER);
|
||||
|
||||
addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
// 如果没有连接上,则直接退出
|
||||
if (Client.isConnected()) {
|
||||
ChatSender.addMsg(Wrapper.logoutRequest(LocalData.get().getId()));
|
||||
}
|
||||
// 否则,窗口关闭的时候发送登出信息
|
||||
super.windowClosing(e);
|
||||
}
|
||||
});
|
||||
|
||||
// 发送初始化请求
|
||||
ChatSender.addMsg(Wrapper.initRequest(LocalData.get().getId(), global.OPT_INIT_USER));
|
||||
ChatSender.addMsg(Wrapper.initRequest(LocalData.get().getId(), global.OPT_INIT_GROUP));
|
||||
ChatSender.addMsg(Wrapper.initRequest(LocalData.get().getId(), global.OPT_INIT_CHAT));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加分隔条约束监听器
|
||||
* 监听分隔条位置变化事件,确保分隔条不会超出最小和最大允许位置范围。
|
||||
* 当窗口大小改变或分隔条位置改变时,调用constrainDividerLocation方法重新限制分隔条位置。
|
||||
*/
|
||||
private void addDividerConstraintListener() {
|
||||
splitPane.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
// 窗口大小改变时重新计算限制
|
||||
constrainDividerLocation();
|
||||
}
|
||||
});
|
||||
|
||||
splitPane.getLeftComponent().addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
constrainDividerLocation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制分隔条位置
|
||||
* 确保分隔条不会超出最小和最大允许位置范围。
|
||||
* 如果当前位置小于最小位置,将分隔条位置设置为最小位置。
|
||||
* 如果当前位置大于最大位置,将分隔条位置设置为最大位置。
|
||||
*/
|
||||
private void constrainDividerLocation() {
|
||||
int totalWidth = splitPane.getWidth();
|
||||
int dividerSize = splitPane.getDividerSize();
|
||||
int currentLocation = splitPane.getDividerLocation();
|
||||
|
||||
// 计算有效位置范围
|
||||
int minLocation = SECONDARY_PANEL_WIDTH_MIN;
|
||||
int maxLocation = totalWidth - dividerSize - SECONDARY_PANEL_WIDTH_MIN;
|
||||
|
||||
// 限制分隔条位置
|
||||
if (currentLocation < minLocation) {
|
||||
splitPane.setDividerLocation(minLocation);
|
||||
} else if (currentLocation > maxLocation) {
|
||||
splitPane.setDividerLocation(maxLocation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到设置界面,当前还没做具体实现
|
||||
*/
|
||||
public void exchangeToSettingPage() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示消息对话框
|
||||
* 创建一个对话框,显示text内容。
|
||||
* 对话框标题为"信息",大小为300x200,居中显示在主窗口上。
|
||||
* 对话框内容为text,居中对齐,宽度为210px, padding为10px。
|
||||
* 对话框包含一个确定按钮,点击后关闭对话框。
|
||||
*/
|
||||
public void showMsgDialog(String text) {
|
||||
JDialog inviteDialog = new JDialog(MainPage.get(), "信息", true);
|
||||
inviteDialog.setSize(300, 200);
|
||||
inviteDialog.setLocationRelativeTo(MainPage.get());
|
||||
|
||||
// 设置对话框内容
|
||||
String htmlText = "<html><body style='width: 210px; padding: 10px;'>" + text + "</body></html>";
|
||||
JLabel label = new JLabel(htmlText, SwingConstants.CENTER);
|
||||
JButton closeBtn = new JButton("确定");
|
||||
|
||||
closeBtn.addActionListener(e -> inviteDialog.dispose());
|
||||
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
JPanel centerPanel = new JPanel(new FlowLayout());
|
||||
centerPanel.add(label);
|
||||
|
||||
panel.add(centerPanel, BorderLayout.CENTER);
|
||||
panel.add(closeBtn, BorderLayout.SOUTH);
|
||||
inviteDialog.add(panel);
|
||||
|
||||
// 显示对话框(会阻塞主窗口交互)
|
||||
inviteDialog.setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到登录界面
|
||||
* 隐藏当前主窗口,显示登录界面。
|
||||
*/
|
||||
public void openLogInPage() {
|
||||
LoginPage.get().setVisible(true);
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到空白内容界面
|
||||
* 清空当前内容区域,显示一个空白界面。
|
||||
*/
|
||||
public void exchangeToBlankContent() {
|
||||
contentView.exchangeToBlank();
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到聊天房间界面
|
||||
* 清空当前内容区域,显示聊天房间界面。
|
||||
* 聊天房间界面显示与groupId相关的聊天内容。
|
||||
*/
|
||||
public void exchangeToChatRoom(String groupId) {
|
||||
contentView.exchangeToChatRoom(groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到好友个人信息界面
|
||||
* 清空当前内容区域,显示好友个人信息界面。
|
||||
* 好友个人信息界面显示与userId相关的好友信息。
|
||||
*/
|
||||
public void exchangeToFriendProfile(String userId, String userName) {
|
||||
contentView.exchangeToFriendProfile(userId, userName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到设置界面
|
||||
* 清空当前内容区域,显示设置界面。
|
||||
* 设置界面根据type显示不同的设置选项。
|
||||
*/
|
||||
public void exchangeToSettings(String type) {
|
||||
contentView.exchangeToSettings(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到群聊邀请请求界面
|
||||
* 清空当前内容区域,显示群聊邀请请求界面。
|
||||
* 群聊邀请请求界面显示与inviterId相关的群聊邀请请求,包括邀请者姓名、群聊名称等。
|
||||
*/
|
||||
public void showGroupInviteRequestDialog(String inviterId, String inviterName, String groupName, String groupId) {
|
||||
JDialog inviteDialog = new JDialog(MainPage.get(), "群聊邀请", true);
|
||||
inviteDialog.setSize(300, 200);
|
||||
inviteDialog.setLocationRelativeTo(MainPage.get());
|
||||
|
||||
// 设置对话框内容
|
||||
JLabel label = new JLabel(
|
||||
"用户" + inviterName + "邀请你加入:" + groupName,
|
||||
SwingConstants.CENTER);
|
||||
JButton confirmBtn = new JButton("接受");
|
||||
JButton closeBtn = new JButton("拒绝");
|
||||
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
JPanel centerPanel = new JPanel(new FlowLayout());
|
||||
centerPanel.add(label);
|
||||
|
||||
JPanel bottomPanel = new JPanel();
|
||||
bottomPanel.add(confirmBtn);
|
||||
bottomPanel.add(closeBtn);
|
||||
|
||||
panel.add(centerPanel, BorderLayout.CENTER);
|
||||
panel.add(bottomPanel, BorderLayout.SOUTH);
|
||||
inviteDialog.add(panel);
|
||||
|
||||
confirmBtn.addActionListener(
|
||||
e -> {
|
||||
ChatSender.addMsg(
|
||||
new Wrapper(inviterId, LocalData.get().getId(), groupId, global.OPT_GROUP_INVITE_AGREE));
|
||||
inviteDialog.dispose();
|
||||
});
|
||||
|
||||
closeBtn.addActionListener(
|
||||
e -> {
|
||||
ChatSender.addMsg(
|
||||
new Wrapper(inviterId, LocalData.get().getId(), groupId, global.OPT_GROUP_INVITE_REFUSE));
|
||||
inviteDialog.dispose();
|
||||
});
|
||||
|
||||
// 显示对话框(会阻塞主窗口交互)
|
||||
inviteDialog.setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到邀请好友界面
|
||||
* 清空当前内容区域,显示邀请好友界面。
|
||||
* 邀请好友界面允许用户输入好友ID,邀请好友加入当前聊天房间。
|
||||
*/
|
||||
public void showGroupInviteDialog() {
|
||||
JDialog inviteDialog = new JDialog(MainPage.get(), "邀请好友", true);
|
||||
inviteDialog.setSize(300, 200);
|
||||
inviteDialog.setLocationRelativeTo(MainPage.get());
|
||||
|
||||
// 创建主面板
|
||||
JPanel mainPanel = new JPanel();
|
||||
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
|
||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
||||
|
||||
// 创建ID面板
|
||||
JPanel idPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
idPanel.add(new JLabel("好友ID :"));
|
||||
JTextField idField = new JTextField(15);
|
||||
idPanel.add(idField);
|
||||
mainPanel.add(idPanel);
|
||||
|
||||
// 添加间隔
|
||||
mainPanel.add(Box.createVerticalStrut(20));
|
||||
|
||||
// 添加按钮面板
|
||||
JPanel buttonPanel = new JPanel();
|
||||
JButton confirmButton = new JButton("确定");
|
||||
|
||||
confirmButton.addActionListener(e -> {
|
||||
String userId = idField.getText().trim();
|
||||
|
||||
if (userId.isEmpty()) {
|
||||
JOptionPane.showMessageDialog(inviteDialog, "好友不能为空", "警告", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
// 这里添加创建群聊的逻辑
|
||||
ChatSender.addMsg(Wrapper.groupInviteRequest(
|
||||
userId,
|
||||
LocalData.get().getId(),
|
||||
LocalData.get().getCurrentChatId()));
|
||||
inviteDialog.dispose();
|
||||
});
|
||||
|
||||
buttonPanel.add(confirmButton);
|
||||
mainPanel.add(buttonPanel);
|
||||
|
||||
inviteDialog.add(mainPanel);
|
||||
inviteDialog.getRootPane().setDefaultButton(confirmButton);
|
||||
inviteDialog.setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到加入群聊界面
|
||||
* 清空当前内容区域,显示加入群聊界面。
|
||||
* 加入群聊界面允许用户输入群聊ID,申请加入群聊。
|
||||
*/
|
||||
public void showJoinGroupDialog() {
|
||||
JDialog dialog = new JDialog(MainPage.get(), "加入群聊", true);
|
||||
dialog.setSize(300, 200);
|
||||
dialog.setLocationRelativeTo(MainPage.get());
|
||||
|
||||
// 创建主面板
|
||||
JPanel mainPanel = new JPanel();
|
||||
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
|
||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
||||
|
||||
// 创建ID面板
|
||||
JPanel idPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
idPanel.add(new JLabel("群聊ID:"));
|
||||
JTextField idField = new JTextField(15);
|
||||
idPanel.add(idField);
|
||||
mainPanel.add(idPanel);
|
||||
|
||||
// 添加间隔
|
||||
mainPanel.add(Box.createVerticalStrut(20));
|
||||
|
||||
// 添加按钮面板
|
||||
JPanel buttonPanel = new JPanel();
|
||||
JButton confirmButton = new JButton("确定");
|
||||
|
||||
confirmButton.addActionListener(e -> {
|
||||
String groupId = idField.getText().trim();
|
||||
|
||||
if (groupId.isEmpty()) {
|
||||
JOptionPane.showMessageDialog(dialog, "群聊ID不能为空", "警告", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送加入群聊请求
|
||||
ChatSender.addMsg(new Wrapper(null, LocalData.get().getId(), groupId, global.OPT_GROUP_JOIN));
|
||||
dialog.dispose();
|
||||
});
|
||||
|
||||
buttonPanel.add(confirmButton);
|
||||
mainPanel.add(buttonPanel);
|
||||
|
||||
dialog.add(mainPanel);
|
||||
dialog.getRootPane().setDefaultButton(confirmButton);
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到添加好友界面
|
||||
* 清空当前内容区域,显示添加好友界面。
|
||||
* 添加好友界面允许用户输入好友ID,申请添加好友。
|
||||
*/
|
||||
public void showAddFriendDialog() {
|
||||
JDialog dialog = new JDialog(MainPage.get(), "添加好友", true);
|
||||
dialog.setSize(300, 200);
|
||||
dialog.setLocationRelativeTo(MainPage.get());
|
||||
|
||||
// 创建主面板
|
||||
JPanel mainPanel = new JPanel();
|
||||
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
|
||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
||||
|
||||
// 创建ID面板
|
||||
JPanel idPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
idPanel.add(new JLabel("好友ID:"));
|
||||
JTextField idField = new JTextField(15);
|
||||
idPanel.add(idField);
|
||||
mainPanel.add(idPanel);
|
||||
|
||||
// 添加间隔
|
||||
mainPanel.add(Box.createVerticalStrut(20));
|
||||
|
||||
// 添加按钮面板
|
||||
JPanel buttonPanel = new JPanel();
|
||||
JButton confirmButton = new JButton("确定");
|
||||
|
||||
confirmButton.addActionListener(e -> {
|
||||
String friendId = idField.getText().trim();
|
||||
|
||||
if (friendId.isEmpty()) {
|
||||
JOptionPane.showMessageDialog(dialog, "好友ID不能为空", "警告", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (friendId.equals(LocalData.get().getId())) {
|
||||
JOptionPane.showMessageDialog(dialog, "不能添加自己为好友", "警告", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送添加好友请求
|
||||
ChatSender.addMsg(new Wrapper(friendId, LocalData.get().getId(), null, global.OPT_FRIEND_ADD));
|
||||
dialog.dispose();
|
||||
});
|
||||
|
||||
buttonPanel.add(confirmButton);
|
||||
mainPanel.add(buttonPanel);
|
||||
|
||||
dialog.add(mainPanel);
|
||||
dialog.getRootPane().setDefaultButton(confirmButton);
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到创建群聊界面
|
||||
* 清空当前内容区域,显示创建群聊界面。
|
||||
* 创建群聊界面允许用户输入群聊ID和名称,创建一个新的群聊房间。
|
||||
*/
|
||||
public void showGroupCreateDialog() {
|
||||
JDialog inviteDialog = new JDialog(MainPage.get(), "创建群聊", true);
|
||||
inviteDialog.setSize(300, 200);
|
||||
inviteDialog.setLocationRelativeTo(MainPage.get());
|
||||
|
||||
// 创建主面板
|
||||
JPanel mainPanel = new JPanel();
|
||||
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
|
||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
||||
|
||||
// 创建ID面板
|
||||
JPanel idPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
idPanel.add(new JLabel("群聊ID :"));
|
||||
JTextField idField = new JTextField(15);
|
||||
idPanel.add(idField);
|
||||
mainPanel.add(idPanel);
|
||||
|
||||
// 创建名称面板
|
||||
JPanel namePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
namePanel.add(new JLabel("群聊名称:"));
|
||||
JTextField nameField = new JTextField(15);
|
||||
namePanel.add(nameField);
|
||||
mainPanel.add(namePanel);
|
||||
|
||||
// 添加间隔
|
||||
mainPanel.add(Box.createVerticalStrut(20));
|
||||
|
||||
// 添加按钮面板
|
||||
JPanel buttonPanel = new JPanel();
|
||||
JButton confirmButton = new JButton("确定");
|
||||
|
||||
confirmButton.addActionListener(e -> {
|
||||
String groupId = idField.getText().trim();
|
||||
String groupName = nameField.getText().trim();
|
||||
|
||||
if (groupId.isEmpty() || groupName.isEmpty()) {
|
||||
JOptionPane.showMessageDialog(inviteDialog, "群聊ID和名称不能为空", "警告", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (groupId.length() < 6 || groupId.length() > 10 || !groupId.matches("[a-zA-Z0-9_]+")) {
|
||||
JOptionPane.showMessageDialog(inviteDialog, "群聊ID只能包含字母大小写和数字下划线,且长度不得小于6,大于10", "警告",
|
||||
JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (groupName.contains(" ")) {
|
||||
JOptionPane.showMessageDialog(inviteDialog, "群聊名称不能包含空格", "警告", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加创建群聊的逻辑
|
||||
ChatSender.addMsg(Wrapper.createGroupRequest(LocalData.get().getId(), groupName, groupId));
|
||||
inviteDialog.dispose();
|
||||
});
|
||||
|
||||
buttonPanel.add(confirmButton);
|
||||
mainPanel.add(buttonPanel);
|
||||
|
||||
inviteDialog.add(mainPanel);
|
||||
inviteDialog.getRootPane().setDefaultButton(confirmButton);
|
||||
inviteDialog.setVisible(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package client.view.login;
|
||||
|
||||
|
||||
import client.Client;
|
||||
import client.service.ChatSender;
|
||||
import client.service.LocalData;
|
||||
import client.view.LoginPage;
|
||||
import client.view.util.DesignToken;
|
||||
import server.serveice.Wrapper;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
// 登录界面
|
||||
public class SignInView extends JPanel {
|
||||
private LoginPage loginPage;
|
||||
|
||||
JTextField account;
|
||||
JTextField password;
|
||||
|
||||
/**
|
||||
* 生成一个登录界面
|
||||
* 按照原型图中设计,创建两个文本输入框,用于让用户输入id和密码。
|
||||
* 创建一个按钮用于登录,为按钮添加SignIncheck的按下事件
|
||||
* 创建一个注册选项按钮,按下后该界面更换为注册界面。
|
||||
*/
|
||||
public SignInView(LoginPage loginPage) {
|
||||
this.loginPage = loginPage;
|
||||
|
||||
//面板
|
||||
this.setLayout(null);
|
||||
this.setSize(DesignToken.LOGIN_WIDTH, DesignToken.LOGIN_HEIGHT);
|
||||
this.setVisible(true);
|
||||
|
||||
//文本展示
|
||||
JLabel ja1 = new JLabel("登录");
|
||||
ja1.setBounds(165, 10, 80, 80);
|
||||
this.add(ja1);
|
||||
|
||||
|
||||
JLabel ja2 = new JLabel("账户:");
|
||||
ja2.setBounds(50, 80, 100, 30);
|
||||
this.add(ja2);
|
||||
|
||||
|
||||
JLabel ja3 = new JLabel("密码:");
|
||||
ja3.setBounds(50, 120, 100, 30);
|
||||
this.add(ja3);
|
||||
|
||||
|
||||
//按钮设计
|
||||
JButton jb1 = new JButton("注册");
|
||||
jb1.setBounds(230, 190, 59, 20);
|
||||
jb1.setBorderPainted(false);
|
||||
jb1.addActionListener(e -> {
|
||||
this.loginPage.exchangeToSignUpView();
|
||||
});
|
||||
this.add(jb1);
|
||||
|
||||
// JButton jb2=new JButton("忘记密码");
|
||||
// jb2.setBounds(290,190,90,20);
|
||||
// jb2.setBorderPainted(false);
|
||||
// jb2.addActionListener(e -> {
|
||||
//
|
||||
// });
|
||||
// add(jb2);
|
||||
|
||||
|
||||
JButton jb3 = new JButton("登录");
|
||||
jb3.setBounds(150, 160, 59, 50);
|
||||
jb3.addActionListener(e -> {
|
||||
signIncheck();
|
||||
});
|
||||
this.add(jb3);
|
||||
|
||||
//建立文本域
|
||||
account = new JTextField(DesignToken.MAX_FONT_SIZE);
|
||||
account.setBounds(80, 80, 220, 30);
|
||||
this.add(account);
|
||||
|
||||
password = new JPasswordField(DesignToken.MAX_FONT_SIZE);
|
||||
password.setBounds(80, 120, 220, 30);
|
||||
this.add(password);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前页面的用户id和密码
|
||||
* 如果当前服务器未在线,则直接向用户说明
|
||||
* 否则,发出登录请求
|
||||
*/
|
||||
private void signIncheck() {
|
||||
if (!Client.isConnected()) {
|
||||
loginPage.showMsgDialog("当前服务器未在线,请稍后再试");
|
||||
return;
|
||||
}
|
||||
|
||||
LocalData.get().setId(account.getText());
|
||||
ChatSender.addMsg(
|
||||
Wrapper.loginRequest(LocalData.get().getId(), password.getText())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package client.view.login;
|
||||
|
||||
import client.Client;
|
||||
import client.service.ChatSender;
|
||||
import client.service.LocalData;
|
||||
import client.view.LoginPage;
|
||||
import client.view.util.DesignToken;
|
||||
import server.serveice.Wrapper;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class SignUpView extends JPanel {
|
||||
private LoginPage loginPage;
|
||||
|
||||
private JTextField account;
|
||||
private JTextField password;
|
||||
private JTextField name;
|
||||
|
||||
/**
|
||||
* 生成一个注册界面
|
||||
* 按照原型图中设计,创建三个文本输入框,用于让用户输入id,密码,用户名
|
||||
* 创建一个按钮用于注册
|
||||
* 创建一个登录选项按钮,按下后该界面更换为登录界面。
|
||||
*/
|
||||
public SignUpView(LoginPage loginPage) {
|
||||
this.loginPage = loginPage;
|
||||
|
||||
//功能面板的建立
|
||||
this.setLayout(null);
|
||||
this.setSize(DesignToken.LOGIN_WIDTH, DesignToken.LOGIN_HEIGHT);
|
||||
this.setVisible(true);
|
||||
|
||||
//文本
|
||||
JLabel ja1 = new JLabel("注册");
|
||||
ja1.setBounds(165, 10, 80, 80);
|
||||
this.add(ja1);
|
||||
|
||||
JLabel ja2 = new JLabel("账户:");
|
||||
ja2.setBounds(50, 80, 100, 30);
|
||||
this.add(ja2);
|
||||
|
||||
JLabel ja3 = new JLabel("密码:");
|
||||
ja3.setBounds(50, 120, 100, 30);
|
||||
this.add(ja3);
|
||||
|
||||
JLabel ja4 = new JLabel("名字:");
|
||||
ja4.setBounds(50, 160, 100, 30);
|
||||
this.add(ja4);
|
||||
|
||||
|
||||
//监听按钮
|
||||
JButton jb1 = new JButton("注册");
|
||||
jb1.setBounds(150, 200, 59, 50);
|
||||
this.add(jb1);
|
||||
jb1.addActionListener(e -> {
|
||||
this.SignUpcheck();
|
||||
});
|
||||
|
||||
JButton jb2 = new JButton("已有账号?去登录");
|
||||
jb2.setBounds(220, 230, 150, 20);
|
||||
jb2.setBorderPainted(false);
|
||||
jb2.addActionListener(e -> {
|
||||
this.loginPage.exchangeToSignInView();
|
||||
});
|
||||
this.add(jb2);
|
||||
|
||||
//文本域
|
||||
account = new JTextField(DesignToken.MAX_FONT_SIZE);
|
||||
account.setBounds(80, 80, 220, 30);
|
||||
this.add(account);
|
||||
|
||||
password = new JTextField(DesignToken.MAX_FONT_SIZE);
|
||||
password.setBounds(80, 120, 220, 30);
|
||||
this.add(password);
|
||||
|
||||
name = new JTextField(DesignToken.MAX_FONT_SIZE);
|
||||
name.setBounds(80, 160, 220, 30);
|
||||
this.add(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前界面的文本的内容
|
||||
* 检查服务器是否链接
|
||||
* 检查注册的id,name,password是否合法
|
||||
* 都合适的话,发送注册请求。
|
||||
*/
|
||||
private void SignUpcheck() {
|
||||
if (!Client.isConnected()) {
|
||||
loginPage.showMsgDialog("当前服务器未在线,请稍后再试");
|
||||
return;
|
||||
}
|
||||
|
||||
String userId = account.getText();
|
||||
String userName = name.getText();
|
||||
String userPassword = password.getText();
|
||||
|
||||
if (userId.length() < 6 || userId.length() > 10 || !userId.matches("[a-zA-Z0-9_]+")) {
|
||||
loginPage.showMsgDialog("用户id只能包含字母大小写和数字下划线,且长度不得小于6,大于10");
|
||||
return;
|
||||
}
|
||||
|
||||
if (userName.contains(" ")) {
|
||||
loginPage.showMsgDialog("用户名不能包含空格");
|
||||
return;
|
||||
}
|
||||
|
||||
if (userPassword.contains(" ")) {
|
||||
loginPage.showMsgDialog("密码不能包含空格");
|
||||
return;
|
||||
}
|
||||
|
||||
LocalData.get().setId(userId);
|
||||
LocalData.get().setUserName(userId, userName);
|
||||
|
||||
ChatSender.addMsg(Wrapper.registerRequest(
|
||||
LocalData.get().getId(),
|
||||
password.getText(),
|
||||
name.getText()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
package client.view.main;
|
||||
|
||||
import client.service.ChatSender;
|
||||
import client.service.LocalData;
|
||||
import client.view.util.CircleCharIcon2;
|
||||
import client.view.util.DesignToken;
|
||||
import server.serveice.Wrapper;
|
||||
import util.MsgUtil;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.event.AncestorEvent;
|
||||
import javax.swing.event.AncestorListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static client.view.util.DesignToken.*;
|
||||
|
||||
// 群聊信息组件,包含底部的打字框和滚动的信息聊天信息
|
||||
public class ChatInfoView extends JPanel {
|
||||
|
||||
private static volatile ChatInfoView instance;
|
||||
|
||||
public static ChatInfoView get() {
|
||||
if (instance == null) {
|
||||
synchronized (ChatInfoView.class) {
|
||||
if (instance == null) {
|
||||
instance = new ChatInfoView();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
// 界面组件
|
||||
private JPanel messagePanel; // 使用JPanel来承载消息,可以自定义布局
|
||||
private List<JPanel> bubbles;
|
||||
private JScrollPane messageScrollPane;
|
||||
private JTextArea inputArea;
|
||||
private JScrollPane inputScrollPane;
|
||||
private JButton sendButton;
|
||||
private JPanel inputPanel;
|
||||
private JPanel buttonPanel;
|
||||
|
||||
// 样式相关
|
||||
private SimpleDateFormat timeFormat;
|
||||
private Color userColor = Color.decode(DesignToken.BUBBLE_COLOR_GREEN); // 用户消息气泡颜色
|
||||
private Color otherColor = Color.decode(DesignToken.BUBBLE_COLOR_WHITE); // 他人消息气泡颜色(白色)
|
||||
private Color systemColor = Color.decode(DesignToken.BACKGROUND_COLOR); // 系统消息背景色
|
||||
|
||||
// 头像颜色数组,用于不同用户的头像显示
|
||||
private final Color[] avatarColors = {
|
||||
Color.decode(DesignToken.BUBBLE_COLOR_BLUE),
|
||||
Color.decode(DesignToken.BUBBLE_COLOR_GRAY),
|
||||
Color.decode(DesignToken.BUBBLE_COLOR_RED),
|
||||
Color.decode(DesignToken.BUBBLE_COLOR_YELLOW),
|
||||
Color.decode(DesignToken.BUBBLE_COLOR_WHITE),
|
||||
};
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* 初始化界面组件和布局
|
||||
*/
|
||||
private ChatInfoView() {
|
||||
setLayout(new BorderLayout(0, 0));
|
||||
|
||||
// 初始化时间格式
|
||||
timeFormat = new SimpleDateFormat("HH:mm");
|
||||
|
||||
// 初始化消息显示区域
|
||||
initMessagePanel();
|
||||
|
||||
// 创建输入区域
|
||||
initInputPanel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化消息面板
|
||||
* 包含滚动条和消息气泡容器
|
||||
*/
|
||||
private void initMessagePanel() {
|
||||
bubbles = new ArrayList<>();
|
||||
|
||||
// 创建消息面板,使用垂直箱式布局
|
||||
messagePanel = new JPanel();
|
||||
messagePanel.setLayout(new BoxLayout(messagePanel, BoxLayout.Y_AXIS));
|
||||
messagePanel.setBackground(systemColor);
|
||||
messagePanel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||
|
||||
// 添加一个弹性空间,让新消息从底部开始
|
||||
messagePanel.add(Box.createVerticalGlue());
|
||||
|
||||
// 添加滚动条
|
||||
messageScrollPane = new JScrollPane(messagePanel);
|
||||
messageScrollPane.setBorder(new EmptyBorder(0, 0, 0, 0));
|
||||
messageScrollPane.getVerticalScrollBar().setUnitIncrement(16);
|
||||
messageScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
|
||||
// 设置视口的背景色
|
||||
JViewport viewport = messageScrollPane.getViewport();
|
||||
viewport.setBackground(systemColor);
|
||||
|
||||
// 添加到主面板
|
||||
this.add(messageScrollPane, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化输入面板
|
||||
* 包含输入文本框和发送按钮
|
||||
*/
|
||||
private void initInputPanel() {
|
||||
// 创建输入面板
|
||||
inputPanel = new JPanel(new BorderLayout(5, 5));
|
||||
inputPanel.setBackground(systemColor);
|
||||
inputPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||
|
||||
// 创建输入文本框
|
||||
inputArea = new JTextArea(3, 20);
|
||||
inputArea.setFont(new Font(DEFAULT_FONT, Font.PLAIN, DesignToken.FONT_SIZE));
|
||||
inputArea.setLineWrap(true);
|
||||
inputArea.setWrapStyleWord(true);
|
||||
inputArea.setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createLineBorder(Color.decode(DesignToken.EDGE_COLOR), 1),
|
||||
BorderFactory.createEmptyBorder(8, 8, 8, 8)));
|
||||
|
||||
// 设置提示文本
|
||||
inputArea.setToolTipText("输入消息,按Enter发送,Ctrl+Enter换行");
|
||||
|
||||
// 添加滚动条到输入框
|
||||
inputScrollPane = new JScrollPane(inputArea);
|
||||
inputScrollPane.setBorder(null);
|
||||
|
||||
// 创建发送按钮
|
||||
sendButton = createSendButton();
|
||||
|
||||
// 创建按钮面板
|
||||
buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 5));
|
||||
buttonPanel.setBackground(systemColor);
|
||||
buttonPanel.add(sendButton);
|
||||
setupListeners();
|
||||
|
||||
// 添加组件到输入面板
|
||||
inputPanel.add(inputScrollPane, BorderLayout.CENTER);
|
||||
inputPanel.add(buttonPanel, BorderLayout.SOUTH);
|
||||
|
||||
// 添加到主面板
|
||||
this.add(inputPanel, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新主题
|
||||
* 当主题变化时调用,用于更新所有组件的颜色
|
||||
*/
|
||||
public void updateTheme() {
|
||||
// 更新颜色变量
|
||||
userColor = Color.decode(DesignToken.BUBBLE_COLOR_GREEN);
|
||||
otherColor = Color.decode(DesignToken.BUBBLE_COLOR_WHITE);
|
||||
systemColor = Color.decode(DesignToken.BACKGROUND_COLOR);
|
||||
|
||||
// 更新组件背景
|
||||
if (messagePanel != null)
|
||||
messagePanel.setBackground(systemColor);
|
||||
if (messageScrollPane != null && messageScrollPane.getViewport() != null) {
|
||||
messageScrollPane.getViewport().setBackground(systemColor);
|
||||
}
|
||||
if (inputPanel != null)
|
||||
inputPanel.setBackground(systemColor);
|
||||
if (buttonPanel != null)
|
||||
buttonPanel.setBackground(systemColor);
|
||||
if (sendButton != null)
|
||||
sendButton.setBackground(userColor);
|
||||
if (inputArea != null) {
|
||||
inputArea.setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createLineBorder(Color.decode(DesignToken.EDGE_COLOR), 1),
|
||||
BorderFactory.createEmptyBorder(8, 8, 8, 8)));
|
||||
}
|
||||
|
||||
// 刷新当前聊天记录
|
||||
if (LocalData.get().getCurrentChatId() != null) {
|
||||
init(LocalData.get().getCurrentChatId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建发送按钮
|
||||
* 按钮文本为“发送”,字体为加粗,大小为 DesignToken.FONT_SIZE
|
||||
* 背景颜色为 DesignToken.BUBBLE_COLOR_GREEN,前景颜色为黑色
|
||||
* 点击时背景颜色为 DesignToken.BUBBLE_COLOR_GREEN,松开时恢复为 DesignToken.BUBBLE_COLOR_GREEN
|
||||
* 鼠标悬停时背景颜色为 DesignToken.BUBBLE_COLOR_GREEN
|
||||
*
|
||||
* @return 发送按钮
|
||||
*/
|
||||
private JButton createSendButton() {
|
||||
JButton button = new JButton("发送");
|
||||
button.setFont(new Font(DEFAULT_FONT, Font.BOLD, DesignToken.FONT_SIZE));
|
||||
button.setBackground(userColor);
|
||||
button.setForeground(Color.BLACK); // 强制设置字体颜色为黑色,确保在绿色背景下清晰可见
|
||||
button.setFocusPainted(false);
|
||||
button.setBorder(BorderFactory.createEmptyBorder(10, 25, 10, 25));
|
||||
button.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
|
||||
// 鼠标悬停效果
|
||||
button.addMouseListener(new java.awt.event.MouseAdapter() {
|
||||
public void mouseEntered(java.awt.event.MouseEvent evt) {
|
||||
button.setBackground(Color.decode(DesignToken.BUBBLE_COLOR_GREEN));
|
||||
}
|
||||
|
||||
public void mouseExited(java.awt.event.MouseEvent evt) {
|
||||
button.setBackground(userColor);
|
||||
}
|
||||
|
||||
public void mousePressed(java.awt.event.MouseEvent evt) {
|
||||
button.setBackground(userColor);
|
||||
}
|
||||
|
||||
public void mouseReleased(java.awt.event.MouseEvent evt) {
|
||||
button.setBackground(userColor);
|
||||
}
|
||||
});
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息发送事件
|
||||
* 点击发送按钮或按下Enter键发送消息
|
||||
*/
|
||||
private void setupListeners() {
|
||||
// 发送按钮点击事件
|
||||
sendButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
|
||||
// 回车发送消息,Ctrl+Enter换行
|
||||
inputArea.addKeyListener(new java.awt.event.KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(java.awt.event.KeyEvent e) {
|
||||
if (e.getKeyCode() == java.awt.event.KeyEvent.VK_ENTER) {
|
||||
if (e.isControlDown()) {
|
||||
// Ctrl+Enter 换行
|
||||
inputArea.append("\n");
|
||||
} else {
|
||||
// Enter 发送消息
|
||||
e.consume(); // 防止默认的换行行为
|
||||
sendMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 窗口显示时自动聚焦到输入框
|
||||
addAncestorListener(new AncestorListener() {
|
||||
@Override
|
||||
public void ancestorAdded(AncestorEvent e) {
|
||||
inputArea.requestFocusInWindow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ancestorRemoved(AncestorEvent event) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ancestorMoved(AncestorEvent event) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送信息
|
||||
* 先调用使用DataManager进行信息发送操作
|
||||
* 如果发送成功,则更新界面,添加信息到消息历史上
|
||||
* 如果发送未成功,则忽略这个操作(发射未成功表示程序出现了问题,在控制态输出问题)
|
||||
*/
|
||||
private void sendMessage() {
|
||||
String text = inputArea.getText().trim();
|
||||
if (!text.isEmpty()) {
|
||||
|
||||
String id = LocalData.get().getId();
|
||||
String currentChatId = LocalData.get().getCurrentChatId();
|
||||
String message = MsgUtil.combineMsg(id, LocalData.get().getUserName(id), text);
|
||||
|
||||
// 检查是群聊还是私聊
|
||||
if (LocalData.get().getGroupData(currentChatId) != null) {
|
||||
// 群聊
|
||||
ChatSender.addMsg(Wrapper.groupChat(message, id, currentChatId));
|
||||
} else {
|
||||
// 私聊:发送纯文本
|
||||
ChatSender.addMsg(Wrapper.privateChat(text, id, currentChatId));
|
||||
}
|
||||
|
||||
// 暂时保存消息
|
||||
LocalData.get().addChatMsg(
|
||||
LocalData.get().getCurrentChatId(),
|
||||
message);
|
||||
|
||||
// 添加用户消息
|
||||
addUserMessage(text);
|
||||
|
||||
// 清空输入框
|
||||
inputArea.setText("");
|
||||
|
||||
// 滚动到底部
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
// 聚焦回输入框
|
||||
inputArea.requestFocusInWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户消息
|
||||
* 用户消息右对齐
|
||||
*
|
||||
* @param content 消息内容
|
||||
*/
|
||||
public void addUserMessage(String content) {
|
||||
// 用户消息右对齐
|
||||
addMessageBubble(true, "我", content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加他人消息
|
||||
* 他人消息左对齐
|
||||
*
|
||||
* @param senderName 发送者名称
|
||||
* @param content 消息内容
|
||||
*/
|
||||
public void addOtherUserMessage(String senderName, String content) {
|
||||
// 他人消息左对齐
|
||||
addMessageBubble(false, senderName, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新当前聊天信息
|
||||
* 从本地数据中获取聊天记录,根据发送者ID判断是用户消息还是他人消息
|
||||
*
|
||||
* @param chatId 聊天ID
|
||||
*/
|
||||
public void setChatInfo(String chatId) {
|
||||
// 清空当前消息
|
||||
messagePanel.removeAll();
|
||||
messagePanel.add(Box.createVerticalGlue());
|
||||
bubbles.clear();
|
||||
|
||||
// 从本地数据中获取聊天记录
|
||||
List<String> messages = LocalData.get().getChatMsg(chatId);
|
||||
if (messages != null) {
|
||||
String myId = LocalData.get().getId();
|
||||
for (String msg : messages) {
|
||||
String[] split = MsgUtil.splitMsg(msg);
|
||||
// split[0] = senderId, split[1] = senderName, split[2] = content
|
||||
if (split.length >= 3) {
|
||||
if (split[0].equals(myId)) {
|
||||
addUserMessage(split[2]);
|
||||
} else {
|
||||
addOtherUserMessage(split[1], split[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新消息面板
|
||||
messagePanel.revalidate();
|
||||
messagePanel.repaint();
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加系统消息
|
||||
* 系统消息居中显示,字体为斜体,颜色为灰色
|
||||
*
|
||||
* @param content 系统消息内容
|
||||
*/
|
||||
public void addSystemMessage(String content) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
// 系统消息
|
||||
JPanel systemPanel = new JPanel();
|
||||
systemPanel.setLayout(new BorderLayout());
|
||||
systemPanel.setBackground(new Color(236, 236, 236));
|
||||
systemPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 40));
|
||||
|
||||
JLabel systemLabel = new JLabel(content);
|
||||
systemLabel.setFont(new Font(DEFAULT_FONT, Font.ITALIC, DesignToken.FONT_SIZE_SMALL));
|
||||
systemLabel.setForeground(Color.GRAY);
|
||||
systemLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
|
||||
systemPanel.add(systemLabel, BorderLayout.CENTER);
|
||||
|
||||
// 添加到消息面板顶部
|
||||
messagePanel.add(systemPanel, 0);
|
||||
messagePanel.revalidate();
|
||||
messagePanel.repaint();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加消息气泡
|
||||
* 根据是否是用户消息,创建不同的消息气泡样式
|
||||
*
|
||||
* @param isSelf 是否是用户消息
|
||||
* @param senderName 发送者名称
|
||||
* @param content 消息内容
|
||||
*/
|
||||
public void addMessageBubble(boolean isSelf, String senderName, String content) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
// 创建消息气泡面板
|
||||
JPanel messageBubblePanel = new JPanel();
|
||||
messageBubblePanel.setLayout(new BorderLayout(8, 0));
|
||||
messageBubblePanel.setBackground(systemColor);
|
||||
messageBubblePanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 200));
|
||||
|
||||
// 添加头像
|
||||
JLabel avatarPanel = new JLabel(new CircleCharIcon2(
|
||||
avatarColors[Math.abs(senderName.hashCode()) % avatarColors.length],
|
||||
Color.WHITE,
|
||||
senderName.substring(0, 1).toUpperCase(),
|
||||
40));
|
||||
|
||||
// 创建消息内容面板
|
||||
JPanel contentPanel = new JPanel();
|
||||
contentPanel.setLayout(new BorderLayout(0, 5));
|
||||
contentPanel.setOpaque(false);
|
||||
|
||||
// 创建发送者标签和时间标签
|
||||
String time = timeFormat.format(new Date());
|
||||
JLabel infoLabel = new JLabel(senderName + " " + time);
|
||||
infoLabel.setFont(new Font(DEFAULT_FONT, Font.PLAIN, FONT_SIZE_SMALL));
|
||||
infoLabel.setForeground(Color.GRAY);
|
||||
|
||||
// 创建消息气泡
|
||||
JTextArea messageLabel = new JTextArea(content);
|
||||
messageLabel.setEditable(false);
|
||||
messageLabel.setLineWrap(true);
|
||||
messageLabel.setWrapStyleWord(true);
|
||||
messageLabel.setFont(new Font(DEFAULT_FONT, Font.PLAIN, FONT_SIZE));
|
||||
messageLabel.setBorder(BorderFactory.createEmptyBorder(10, 15, 10, 15));
|
||||
|
||||
// 设置气泡颜色和样式
|
||||
if (isSelf) {
|
||||
// 用户消息:右对齐,绿色气泡
|
||||
messageLabel.setBackground(userColor);
|
||||
messageLabel.setForeground(Color.WHITE);
|
||||
infoLabel.setHorizontalAlignment(SwingConstants.RIGHT);
|
||||
contentPanel.add(infoLabel, BorderLayout.NORTH);
|
||||
contentPanel.add(messageLabel, BorderLayout.CENTER);
|
||||
|
||||
// 右对齐布局
|
||||
messageBubblePanel.add(contentPanel, BorderLayout.CENTER);
|
||||
messageBubblePanel.add(avatarPanel, BorderLayout.EAST);
|
||||
} else {
|
||||
// 他人消息:左对齐,白色气泡
|
||||
messageLabel.setBackground(otherColor);
|
||||
messageLabel.setForeground(Color.decode(DesignToken.COLOR_FONT_BLACK));
|
||||
messageLabel.setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createLineBorder(Color.decode(EDGE_COLOR), 1),
|
||||
BorderFactory.createEmptyBorder(10, 15, 10, 15)));
|
||||
infoLabel.setHorizontalAlignment(SwingConstants.LEFT);
|
||||
contentPanel.add(infoLabel, BorderLayout.NORTH);
|
||||
contentPanel.add(messageLabel, BorderLayout.CENTER);
|
||||
|
||||
// 左对齐布局
|
||||
messageBubblePanel.add(avatarPanel, BorderLayout.WEST);
|
||||
messageBubblePanel.add(contentPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
// 设置消息气泡的最大宽度(防止过宽)
|
||||
int maxBubbleWidth = 350;
|
||||
messageLabel.setSize(new Dimension(maxBubbleWidth, Integer.MAX_VALUE));
|
||||
int preferredHeight = messageLabel.getPreferredSize().height;
|
||||
messageLabel.setPreferredSize(new Dimension(maxBubbleWidth, preferredHeight));
|
||||
|
||||
// 将其添加到消息列表当中
|
||||
bubbles.add(messageBubblePanel);
|
||||
|
||||
// 添加到消息面板顶部(新消息在顶部显示)
|
||||
messagePanel.add(messageBubblePanel);
|
||||
messagePanel.revalidate();
|
||||
messagePanel.repaint();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除所有消息气泡(bubbles)
|
||||
* 清空输入栏的内容
|
||||
*/
|
||||
public void removeAllMessageBubble() {
|
||||
for (JPanel panel : bubbles) {
|
||||
messagePanel.remove(panel);
|
||||
}
|
||||
bubbles.clear();
|
||||
inputArea.setText("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据群聊id在DataManager中选择并加载群聊信息
|
||||
*
|
||||
* @param groupId 群聊id
|
||||
*/
|
||||
public void init(String groupId) {
|
||||
this.removeAllMessageBubble();
|
||||
List<String> messages = LocalData.get().getChatMsg(groupId);
|
||||
for (String text : messages) {
|
||||
String[] msgs = MsgUtil.splitMsg(text);
|
||||
|
||||
if (msgs[0].equals(LocalData.get().getId())) {
|
||||
addUserMessage(msgs[2]);
|
||||
} else {
|
||||
addMessageBubble(msgs[0].equals(LocalData.get().getId()), msgs[1], msgs[2]);
|
||||
}
|
||||
}
|
||||
|
||||
messagePanel.revalidate();
|
||||
messagePanel.repaint();
|
||||
}
|
||||
|
||||
private void scrollToBottom() {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
JScrollBar vertical = messageScrollPane.getVerticalScrollBar();
|
||||
vertical.setValue(vertical.getMaximum());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package client.view.main;
|
||||
|
||||
import client.service.LocalData;
|
||||
import client.view.util.DesignToken;
|
||||
import client.view.util.LimitSizePanel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
import static client.view.util.DesignToken.*;
|
||||
|
||||
/**
|
||||
* 内容界面组件
|
||||
* 用于显示聊天、群聊、好友个人信息等内容。
|
||||
* 点击不同选项可以切换到对应的功能界面。
|
||||
*/
|
||||
public class ContentView extends LimitSizePanel {
|
||||
private ChatInfoView chatInfoView;
|
||||
private GroupInfoView groupInfoView;
|
||||
|
||||
public ContentView() {
|
||||
super(CONTENT_PANEL_WIDTH_MIN);
|
||||
this.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
this.setLayout(new BorderLayout());
|
||||
|
||||
chatInfoView = ChatInfoView.get();
|
||||
chatInfoView.setPreferredSize(new Dimension(GROUP_CHAT_PANEL_WIDTH, this.getHeight()));
|
||||
chatInfoView.setMinimumSize(new Dimension(GROUP_CHAT_PANEL_WIDTH, this.getHeight()));
|
||||
|
||||
groupInfoView = GroupInfoView.get();
|
||||
groupInfoView.setPreferredSize(new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH, this.getHeight()));
|
||||
|
||||
exchangeToBlank();
|
||||
}
|
||||
|
||||
public void exchangeToBlank() {
|
||||
this.removeAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将内容组件更改为群聊组件
|
||||
* 依据groupId来从DataManager中获取群聊信息
|
||||
* 更新 chatInfoView, groupInfoView两个组件
|
||||
*/
|
||||
public void exchangeToChatRoom(String groupId) {
|
||||
this.removeAll();
|
||||
chatInfoView.init(groupId);
|
||||
this.add(chatInfoView, BorderLayout.CENTER);
|
||||
|
||||
if (LocalData.get().getGroupData(groupId) != null) {
|
||||
groupInfoView.updateInfo();
|
||||
this.add(groupInfoView, BorderLayout.EAST);
|
||||
}
|
||||
|
||||
this.revalidate();
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将内容组件更改为好友个人信息组件
|
||||
* 依据userId和userName来创建FriendProfileView组件
|
||||
* 更新 chatInfoView, groupInfoView两个组件
|
||||
*/
|
||||
public void exchangeToFriendProfile(String userId, String userName) {
|
||||
this.removeAll();
|
||||
FriendProfileView profileView = new FriendProfileView(userId, userName);
|
||||
this.add(profileView, BorderLayout.CENTER);
|
||||
this.revalidate();
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将内容组件更改为设置组件
|
||||
* 依据type来创建SettingsView组件
|
||||
* 更新 chatInfoView, groupInfoView两个组件
|
||||
*/
|
||||
public void exchangeToSettings(String type) {
|
||||
this.removeAll();
|
||||
SettingsView settingsView = new SettingsView(type);
|
||||
this.add(settingsView, BorderLayout.CENTER);
|
||||
this.revalidate();
|
||||
this.repaint();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package client.view.main;
|
||||
|
||||
import client.view.MainPage;
|
||||
import client.view.util.CircleCharIcon2;
|
||||
import client.view.util.DesignToken;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
/**
|
||||
* 好友列表项组件
|
||||
* 用于显示好友列表中的每个好友项,包括好友头像、好友名称等。
|
||||
* 点击好友项可以进入好友个人信息界面。
|
||||
*/
|
||||
public class FriendListItem extends JPanel {
|
||||
private final String userId;
|
||||
private final String userName;
|
||||
|
||||
JLabel icon;
|
||||
JPanel centerPanel;
|
||||
JLabel titleLabel;
|
||||
|
||||
/**
|
||||
* 构造好友列表项组件
|
||||
*
|
||||
* @param userId 好友用户ID
|
||||
* @param userName 好友用户名
|
||||
*/
|
||||
public FriendListItem(String userId, String userName) {
|
||||
this.userId = userId;
|
||||
this.userName = userName;
|
||||
|
||||
// 设置布局管理器
|
||||
setLayout(new BorderLayout(10, 0));
|
||||
setBorder(BorderFactory.createEmptyBorder(8, 10, 8, 10));
|
||||
setMaximumSize(new Dimension(Integer.MAX_VALUE, 70));
|
||||
setPreferredSize(new Dimension(200, 70));
|
||||
|
||||
// 左侧图标
|
||||
icon = new JLabel(new CircleCharIcon2(Color.LIGHT_GRAY, Color.WHITE,
|
||||
userName.substring(0, 1).toUpperCase(), 40));
|
||||
icon.setPreferredSize(new Dimension(40, 40));
|
||||
|
||||
// 中间区域 - 好友名称
|
||||
centerPanel = new JPanel(new GridLayout(1, 1));
|
||||
titleLabel = new JLabel(userName);
|
||||
titleLabel.setFont(new Font(DesignToken.DEFAULT_FONT, Font.BOLD, 14));
|
||||
centerPanel.add(titleLabel);
|
||||
|
||||
// 组装
|
||||
this.add(icon, BorderLayout.WEST);
|
||||
this.add(centerPanel, BorderLayout.CENTER);
|
||||
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
enterFriendProfile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
setBackground(UIManager.getColor("List.selectionBackground")); // 悬停效果
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
setBackground(UIManager.getColor("Panel.background")); // 恢复原背景
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
setBackground(UIManager.getColor("List.selectionInactiveBackground")); // 点击效果
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (contains(e.getPoint())) {
|
||||
setBackground(UIManager.getColor("List.selectionBackground"));
|
||||
} else {
|
||||
setBackground(UIManager.getColor("Panel.background"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入好友个人信息界面
|
||||
* 点击好友项时,切换到好友个人信息界面,显示与该好友相关的个人信息。
|
||||
*/
|
||||
public void enterFriendProfile() {
|
||||
MainPage.get().exchangeToFriendProfile(userId, userName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package client.view.main;
|
||||
|
||||
import client.service.LocalData;
|
||||
import client.view.util.CircleCharIcon2;
|
||||
import client.view.util.DesignToken;
|
||||
import server.data.UserData;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* 好友个人信息视图
|
||||
* 显示好友的头像、名称和ID,以及操作按钮(发送消息、删除好友)
|
||||
* 好友头像:显示好友的圆形头像
|
||||
* 好友名称:显示好友的名称,字体为默认字体,大小为24号,加粗
|
||||
* 用户ID:显示好友的唯一标识符,字体为默认字体,大小为16号,颜色为灰色
|
||||
* 发送消息按钮:点击后可以发送消息给好友
|
||||
* 删除好友按钮:点击后可以删除好友关系
|
||||
*/
|
||||
public class FriendProfileView extends JPanel {
|
||||
private String userId;
|
||||
private String userName;
|
||||
|
||||
public FriendProfileView(String userId, String userName) {
|
||||
this.userId = userId;
|
||||
this.userName = userName;
|
||||
initUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化UI组件
|
||||
* 设置布局为网格Bag布局,背景颜色为白色
|
||||
* 好友头像:显示好友的圆形头像,大小为80x80
|
||||
* 好友名称:显示好友的名称,字体为默认字体,大小为24号,加粗
|
||||
* 用户ID:显示好友的唯一标识符,字体为默认字体,大小为16号,颜色为灰色
|
||||
* 发送消息按钮:点击后可以发送消息给好友,大小为120x40,背景颜色为蓝色,文字颜色为白色
|
||||
* 删除好友按钮:点击后可以删除好友关系,大小为120x40,背景颜色为红色,文字颜色为白色
|
||||
*/
|
||||
private void initUI() {
|
||||
setLayout(new GridBagLayout());
|
||||
setBackground(Color.WHITE);
|
||||
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 0;
|
||||
gbc.insets = new Insets(10, 10, 10, 10);
|
||||
gbc.anchor = GridBagConstraints.CENTER;
|
||||
|
||||
// 好友头像
|
||||
JLabel icon = new JLabel(new CircleCharIcon2(Color.ORANGE, Color.WHITE,
|
||||
userName.substring(0, 1).toUpperCase(), 80));
|
||||
add(icon, gbc);
|
||||
|
||||
// 好友名称
|
||||
gbc.gridy++;
|
||||
JLabel nameLabel = new JLabel(userName);
|
||||
nameLabel.setFont(new Font(DesignToken.DEFAULT_FONT, Font.BOLD, 24));
|
||||
add(nameLabel, gbc);
|
||||
|
||||
// 用户ID
|
||||
gbc.gridy++;
|
||||
JLabel idLabel = new JLabel("ID: " + userId);
|
||||
idLabel.setForeground(Color.GRAY);
|
||||
add(idLabel, gbc);
|
||||
|
||||
// 获取并显示详细信息
|
||||
UserData friendData = LocalData.get().getUserDetail(userId);
|
||||
if (friendData != null) {
|
||||
addInfoLabel(friendData.getEmail(), gbc);
|
||||
addInfoLabel(friendData.getBirthday(), gbc);
|
||||
addInfoLabel(friendData.getAddress(), gbc);
|
||||
addInfoLabel(friendData.getSignature(), gbc);
|
||||
}
|
||||
|
||||
// 发送消息按钮
|
||||
gbc.gridy++;
|
||||
gbc.insets = new Insets(30, 10, 10, 10);
|
||||
JButton sendBtn = new JButton("发送消息");
|
||||
sendBtn.setPreferredSize(new Dimension(120, 40));
|
||||
sendBtn.setBackground(new Color(0, 122, 255));
|
||||
sendBtn.setForeground(Color.WHITE);
|
||||
sendBtn.setFocusPainted(false);
|
||||
sendBtn.addActionListener(e -> {
|
||||
// 更新消息列表
|
||||
SecondaryOptionView.get().updateMessageList(userId, userName, "", 0);
|
||||
|
||||
// 更新当前聊天ID
|
||||
client.service.LocalData.get().setCurrentChatId(userId);
|
||||
|
||||
// 切换到聊天房间
|
||||
client.view.MainPage.get().exchangeToChatRoom(userId);
|
||||
});
|
||||
add(sendBtn, gbc);
|
||||
|
||||
// 好友操作按钮
|
||||
gbc.gridy++;
|
||||
gbc.insets = new Insets(10, 10, 10, 10);
|
||||
JButton deleteBtn = new JButton("删除好友");
|
||||
deleteBtn.setPreferredSize(new Dimension(120, 40));
|
||||
deleteBtn.setBackground(new Color(220, 53, 69));
|
||||
deleteBtn.setForeground(Color.WHITE);
|
||||
deleteBtn.setFocusPainted(false);
|
||||
deleteBtn.addActionListener(e -> {
|
||||
JOptionPane.showMessageDialog(this, "删除好友功能开发中...");
|
||||
});
|
||||
add(deleteBtn, gbc);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加详细信息标签
|
||||
* 检查文本是否为空,如果不为空则添加到面板中
|
||||
* 标签字体为默认字体,大小为14号,颜色为深灰色
|
||||
*
|
||||
* @param text 要添加的详细信息文本
|
||||
* @param gbc 网格BagConstraints对象,用于布局
|
||||
*/
|
||||
private void addInfoLabel(String text, GridBagConstraints gbc) {
|
||||
if (text != null && !text.isEmpty()) {
|
||||
gbc.gridy++;
|
||||
gbc.insets = new Insets(2, 10, 2, 10);
|
||||
JLabel label = new JLabel(text);
|
||||
label.setFont(new Font(DesignToken.DEFAULT_FONT, Font.PLAIN, 14));
|
||||
label.setForeground(Color.DARK_GRAY);
|
||||
add(label, gbc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
package client.view.main;
|
||||
|
||||
import client.service.ChatSender;
|
||||
import client.service.LocalData;
|
||||
import client.view.MainPage;
|
||||
import client.view.util.DesignToken;
|
||||
import server.data.GroupData;
|
||||
import server.serveice.Wrapper;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* 聊天信息组件,用于展示当前聊天室的信息
|
||||
*/
|
||||
public class GroupInfoView extends JScrollPane {
|
||||
private static volatile GroupInfoView instance;
|
||||
|
||||
public static GroupInfoView get() {
|
||||
if (instance == null) {
|
||||
synchronized (GroupInfoView.class) {
|
||||
if (instance == null) {
|
||||
instance = new GroupInfoView();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private final JPanel mainPanel;
|
||||
private final JPanel groupInfoPanel;
|
||||
private final JPanel groupMemberPanel;
|
||||
|
||||
public GroupInfoView() {
|
||||
mainPanel = new JPanel();
|
||||
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); // 垂直布局
|
||||
|
||||
setPreferredSize(new Dimension(DesignToken.INFO_PANEL_WIDTH, DesignToken.WINDOW_ORI_HEIGHT));
|
||||
setBackground(Color.GRAY);
|
||||
|
||||
// 设置滚动策略
|
||||
setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
|
||||
groupInfoPanel = createGroupInfoPanel();
|
||||
groupMemberPanel = createGroupMemberPanel();
|
||||
|
||||
mainPanel.add(groupInfoPanel);
|
||||
|
||||
mainPanel.add(groupInfoPanel);
|
||||
mainPanel.add(createInviteButton());
|
||||
mainPanel.add(createExitButton());
|
||||
mainPanel.add(groupMemberPanel);
|
||||
|
||||
mainPanel.add(groupMemberPanel);
|
||||
|
||||
mainPanel.setMinimumSize(
|
||||
new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH, 0));
|
||||
mainPanel.setPreferredSize(
|
||||
new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH, DesignToken.WINDOW_ORI_HEIGHT));
|
||||
|
||||
this.setViewportView(mainPanel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建聊天成员信息面板
|
||||
*/
|
||||
public JPanel createGroupMemberPanel() {
|
||||
JPanel memberInfoPanel = new JPanel();
|
||||
memberInfoPanel.setPreferredSize(
|
||||
new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH, DesignToken.WINDOW_ORI_HEIGHT - 50));
|
||||
|
||||
memberInfoPanel.setMinimumSize(new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH, 0));
|
||||
|
||||
memberInfoPanel.setLayout(new BoxLayout(memberInfoPanel, BoxLayout.Y_AXIS));
|
||||
|
||||
// 添加成员标题
|
||||
JLabel memberTitle = new JLabel("群成员");
|
||||
memberTitle.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
memberTitle.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
||||
memberTitle.setForeground(Color.WHITE);
|
||||
memberTitle.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
|
||||
memberInfoPanel.add(memberTitle);
|
||||
|
||||
// 添加分隔线
|
||||
JSeparator separator = new JSeparator();
|
||||
separator.setForeground(Color.DARK_GRAY);
|
||||
separator.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
separator.setMaximumSize(new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH - 20, 1));
|
||||
|
||||
memberInfoPanel.add(separator);
|
||||
|
||||
return memberInfoPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建群信息组件
|
||||
*/
|
||||
public JPanel createGroupInfoPanel() {
|
||||
JPanel groupInfoPanel = new JPanel();
|
||||
groupInfoPanel.setPreferredSize(
|
||||
new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH, DesignToken.WINDOW_ORI_HEIGHT - 50));
|
||||
groupInfoPanel.setMinimumSize(new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH, 0));
|
||||
groupInfoPanel.setLayout(new BoxLayout(groupInfoPanel, BoxLayout.Y_AXIS));
|
||||
|
||||
// 添加标题
|
||||
JLabel titleLabel = new JLabel("群聊信息");
|
||||
titleLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 18));
|
||||
titleLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 15, 0));
|
||||
groupInfoPanel.add(titleLabel);
|
||||
|
||||
return groupInfoPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个邀请成员的按钮
|
||||
*/
|
||||
public JButton createInviteButton() {
|
||||
JButton inviteButton = new JButton("邀请");
|
||||
inviteButton.setAlignmentX(Component.CENTER_ALIGNMENT); // 居中对齐
|
||||
inviteButton.setPreferredSize(
|
||||
new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH - 20, 30));
|
||||
inviteButton.setMaximumSize(
|
||||
new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH - 20, 30));
|
||||
inviteButton.setMargin(
|
||||
new Insets(5, 10, 5, 10));
|
||||
|
||||
inviteButton.addActionListener(e -> MainPage.get().showGroupInviteDialog());
|
||||
return inviteButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个退出群聊的按钮
|
||||
*/
|
||||
public JButton createExitButton() {
|
||||
JButton exitButton = new JButton("退出");
|
||||
exitButton.setAlignmentX(Component.CENTER_ALIGNMENT); // 居中对齐
|
||||
exitButton.setPreferredSize(
|
||||
new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH - 20, 30));
|
||||
exitButton.setMaximumSize(
|
||||
new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH - 20, 30));
|
||||
exitButton.setMargin(
|
||||
new Insets(5, 10, 5, 10));
|
||||
|
||||
exitButton.addActionListener(e -> {
|
||||
ChatSender.addMsg(Wrapper.groupQuitRequest(
|
||||
LocalData.get().getId(),
|
||||
LocalData.get().getCurrentChatId()));
|
||||
});
|
||||
return exitButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 groupInfoPanel 和 groupMemberPanel 这两个组件
|
||||
*/
|
||||
public void updateInfo() {
|
||||
groupInfoPanel.removeAll();
|
||||
groupMemberPanel.removeAll();
|
||||
|
||||
String currentChatId = LocalData.get().getCurrentChatId();
|
||||
if (currentChatId == null || currentChatId.isEmpty()) {
|
||||
// 如果没有当前群聊,显示提示信息
|
||||
JLabel noGroupLabel = new JLabel("未选择群聊");
|
||||
noGroupLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
noGroupLabel.setForeground(Color.GRAY);
|
||||
groupInfoPanel.add(noGroupLabel);
|
||||
|
||||
groupInfoPanel.revalidate();
|
||||
groupInfoPanel.repaint();
|
||||
groupMemberPanel.revalidate();
|
||||
groupMemberPanel.repaint();
|
||||
return;
|
||||
}
|
||||
|
||||
GroupData groupData = LocalData.get().getGroupData(currentChatId);
|
||||
if (groupData == null) {
|
||||
JLabel errorLabel = new JLabel("群聊数据不存在");
|
||||
errorLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
errorLabel.setForeground(Color.RED);
|
||||
groupInfoPanel.add(errorLabel);
|
||||
|
||||
groupInfoPanel.revalidate();
|
||||
groupInfoPanel.repaint();
|
||||
groupMemberPanel.revalidate();
|
||||
groupMemberPanel.repaint();
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新群聊信息面板
|
||||
updateGroupInfoPanel(groupData);
|
||||
|
||||
// 更新群成员面板
|
||||
updateGroupMemberPanel(groupData);
|
||||
|
||||
groupInfoPanel.revalidate();
|
||||
groupInfoPanel.repaint();
|
||||
groupMemberPanel.revalidate();
|
||||
groupMemberPanel.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新群聊信息面板内容
|
||||
*/
|
||||
private void updateGroupInfoPanel(GroupData groupData) {
|
||||
// 添加标题
|
||||
JLabel titleLabel = new JLabel("群聊信息");
|
||||
titleLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 18));
|
||||
titleLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 15, 0));
|
||||
groupInfoPanel.add(titleLabel);
|
||||
|
||||
// 群聊名称
|
||||
JLabel nameLabel = new JLabel("群名: " + groupData.getGroupName());
|
||||
nameLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
nameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
||||
nameLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
|
||||
groupInfoPanel.add(nameLabel);
|
||||
|
||||
// 群聊ID
|
||||
JLabel idLabel = new JLabel("群ID: " + groupData.getGroupId());
|
||||
idLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
idLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
|
||||
idLabel.setForeground(Color.DARK_GRAY);
|
||||
idLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
|
||||
groupInfoPanel.add(idLabel);
|
||||
|
||||
// 成员数量
|
||||
JLabel memberCountLabel = new JLabel("成员: " + (groupData.getMembers() != null ? groupData.getMembers().size() : 0) + "人");
|
||||
memberCountLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
memberCountLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
|
||||
memberCountLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
|
||||
groupInfoPanel.add(memberCountLabel);
|
||||
|
||||
// 添加分隔线
|
||||
JSeparator separator = new JSeparator();
|
||||
separator.setForeground(Color.GRAY);
|
||||
separator.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
separator.setMaximumSize(new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH - 30, 1));
|
||||
groupInfoPanel.add(separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新群成员面板内容
|
||||
*/
|
||||
private void updateGroupMemberPanel(GroupData groupData) {
|
||||
// 添加成员标题
|
||||
JLabel memberTitle = new JLabel("群成员 (" + (groupData.getMembers() != null ? groupData.getMembers().size() : 0) + ")");
|
||||
memberTitle.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
memberTitle.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
||||
memberTitle.setForeground(Color.WHITE);
|
||||
memberTitle.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
|
||||
groupMemberPanel.add(memberTitle);
|
||||
|
||||
// 添加分隔线
|
||||
JSeparator separator = new JSeparator();
|
||||
separator.setForeground(Color.DARK_GRAY);
|
||||
separator.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
separator.setMaximumSize(new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH - 20, 1));
|
||||
groupMemberPanel.add(separator);
|
||||
|
||||
if (groupData.getMembers() == null || groupData.getMembers().isEmpty()) {
|
||||
JLabel noMemberLabel = new JLabel("暂无成员");
|
||||
noMemberLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
noMemberLabel.setForeground(Color.LIGHT_GRAY);
|
||||
noMemberLabel.setBorder(BorderFactory.createEmptyBorder(20, 0, 0, 0));
|
||||
groupMemberPanel.add(noMemberLabel);
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加成员列表
|
||||
for (GroupData.GroupMember memberId : groupData.getMembers()) {
|
||||
JPanel memberItemPanel =
|
||||
createMemberItemPanel(memberId.id);
|
||||
groupMemberPanel.add(memberItemPanel);
|
||||
}
|
||||
|
||||
// 添加底部空白,确保内容居中
|
||||
groupMemberPanel.add(Box.createVerticalGlue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建单个成员信息面板
|
||||
*/
|
||||
private JPanel createMemberItemPanel(String memberId) {
|
||||
JPanel memberItemPanel = new JPanel();
|
||||
memberItemPanel.setLayout(new BoxLayout(memberItemPanel, BoxLayout.X_AXIS));
|
||||
memberItemPanel.setBackground(new Color(40, 40, 40));
|
||||
memberItemPanel.setBorder(BorderFactory.createEmptyBorder(8, 15, 8, 15));
|
||||
memberItemPanel.setMaximumSize(new Dimension(DesignToken.GROUP_INFO_PANEL_WIDTH, 50));
|
||||
memberItemPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
|
||||
// 成员头像(使用圆形标签模拟)
|
||||
JLabel avatarLabel = new JLabel();
|
||||
avatarLabel.setOpaque(true);
|
||||
avatarLabel.setBackground(getMemberColor(memberId));
|
||||
avatarLabel.setPreferredSize(new Dimension(30, 30));
|
||||
avatarLabel.setMinimumSize(new Dimension(30, 30));
|
||||
avatarLabel.setMaximumSize(new Dimension(30, 30));
|
||||
avatarLabel.setBorder(BorderFactory.createLineBorder(Color.WHITE, 1));
|
||||
|
||||
// 设置圆形头像
|
||||
avatarLabel.setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createLineBorder(Color.WHITE, 1),
|
||||
BorderFactory.createEmptyBorder(2, 2, 2, 2)
|
||||
));
|
||||
|
||||
// 成员信息
|
||||
JPanel infoPanel = new JPanel();
|
||||
infoPanel.setLayout(new BoxLayout(infoPanel, BoxLayout.Y_AXIS));
|
||||
infoPanel.setBackground(new Color(40, 40, 40));
|
||||
infoPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
|
||||
|
||||
// 成员名称
|
||||
String memberName = LocalData.get().getUserName(memberId);
|
||||
if (memberName == null) {
|
||||
memberName = "用户" + memberId.substring(0, Math.min(6, memberId.length()));
|
||||
}
|
||||
|
||||
JLabel nameLabel = new JLabel(memberName);
|
||||
nameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
||||
nameLabel.setForeground(Color.WHITE);
|
||||
nameLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
|
||||
// 成员ID
|
||||
JLabel idLabel = new JLabel("ID: " + memberId.substring(0, Math.min(10, memberId.length())));
|
||||
idLabel.setFont(new Font("微软雅黑", Font.PLAIN, 10));
|
||||
idLabel.setForeground(Color.LIGHT_GRAY);
|
||||
idLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
|
||||
infoPanel.add(nameLabel);
|
||||
infoPanel.add(idLabel);
|
||||
|
||||
memberItemPanel.add(avatarLabel);
|
||||
memberItemPanel.add(infoPanel);
|
||||
memberItemPanel.add(Box.createHorizontalGlue());
|
||||
|
||||
// 如果是当前用户,添加标识
|
||||
if (memberId.equals(LocalData.get().getId())) {
|
||||
JLabel meLabel = new JLabel("(我)");
|
||||
meLabel.setFont(new Font("微软雅黑", Font.ITALIC, 11));
|
||||
meLabel.setForeground(new Color(100, 150, 255));
|
||||
memberItemPanel.add(meLabel);
|
||||
}
|
||||
|
||||
return memberItemPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户ID生成固定颜色(用于头像背景)
|
||||
*/
|
||||
private Color getMemberColor(String memberId) {
|
||||
// 简单的哈希算法生成固定颜色
|
||||
int hash = memberId.hashCode();
|
||||
int r = (hash & 0xFF0000) >> 16;
|
||||
int g = (hash & 0x00FF00) >> 8;
|
||||
int b = hash & 0x0000FF;
|
||||
|
||||
// 确保颜色不太暗
|
||||
r = Math.max(r, 50);
|
||||
g = Math.max(g, 50);
|
||||
b = Math.max(b, 50);
|
||||
|
||||
return new Color(r, g, b);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package client.view.main;
|
||||
|
||||
import client.service.LocalData;
|
||||
import client.view.MainPage;
|
||||
import client.view.util.CircleCharIcon2;
|
||||
import client.view.util.DesignToken;
|
||||
import client.view.util.RoundedRectCharIcon;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
// 群聊列表项组件
|
||||
public class GroupListItem extends JPanel implements Comparable<String> {
|
||||
private int unread;
|
||||
private final String groupId;
|
||||
|
||||
JLabel icon;
|
||||
JLabel badge;
|
||||
JPanel centerPanel;
|
||||
JLabel titleLabel;
|
||||
JPanel rightPanel; // 用于放置徽章
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param groupId 群聊ID
|
||||
* @param title 群聊标题
|
||||
* @param unread 未读消息数量
|
||||
*/
|
||||
public GroupListItem(String groupId, String title, int unread) {
|
||||
this.groupId = groupId;
|
||||
this.unread = unread;
|
||||
|
||||
// 设置布局管理器
|
||||
setLayout(new BorderLayout(10, 0));
|
||||
setBorder(BorderFactory.createEmptyBorder(8, 10, 8, 10));
|
||||
setMaximumSize(new Dimension(Integer.MAX_VALUE, 70));
|
||||
setPreferredSize(new Dimension(200, 70));
|
||||
|
||||
// 左侧图标
|
||||
boolean isGroup = LocalData.get().getGroupData(groupId) != null;
|
||||
if (isGroup) {
|
||||
icon = new JLabel(new RoundedRectCharIcon(Color.decode(DesignToken.BUBBLE_COLOR_BLUE), Color.WHITE,
|
||||
title.substring(0, 1).toUpperCase(), 40));
|
||||
} else {
|
||||
icon = new JLabel(new CircleCharIcon2(Color.ORANGE, Color.WHITE,
|
||||
title.substring(0, 1).toUpperCase(), 40));
|
||||
}
|
||||
icon.setPreferredSize(new Dimension(40, 40));
|
||||
|
||||
// 中间区域 - 群聊标题
|
||||
centerPanel = new JPanel(new GridLayout(1, 1));
|
||||
titleLabel = new JLabel(title);
|
||||
titleLabel.setFont(new Font(DesignToken.DEFAULT_FONT, Font.BOLD, 14));
|
||||
centerPanel.add(titleLabel);
|
||||
|
||||
// 右侧区域 - 未读消息徽章
|
||||
rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));
|
||||
rightPanel.setOpaque(false);
|
||||
|
||||
badge = new JLabel();
|
||||
badge.setForeground(Color.WHITE);
|
||||
badge.setBackground(Color.RED);
|
||||
badge.setOpaque(true);
|
||||
badge.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
badge.setFont(new Font("Microsoft YaHei", Font.BOLD, 10));
|
||||
badge.setBorder(BorderFactory.createEmptyBorder(2, 6, 2, 6));
|
||||
badge.setPreferredSize(new Dimension(20, 20));
|
||||
|
||||
updateBadge(); // 初始化徽章显示状态
|
||||
rightPanel.add(badge);
|
||||
|
||||
// 组装
|
||||
this.add(icon, BorderLayout.WEST);
|
||||
this.add(centerPanel, BorderLayout.CENTER);
|
||||
this.add(rightPanel, BorderLayout.EAST);
|
||||
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
enterGroup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
setBackground(new Color(240, 240, 240)); // 悬停效果
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
setBackground(UIManager.getColor("Panel.background")); // 恢复原背景
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
setBackground(new Color(220, 220, 220)); // 点击效果
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (contains(e.getPoint())) {
|
||||
setBackground(new Color(240, 240, 240));
|
||||
} else {
|
||||
setBackground(UIManager.getColor("Panel.background"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新徽章显示
|
||||
*/
|
||||
private void updateBadge() {
|
||||
if (unread > 0) {
|
||||
String badgeText = unread > 99 ? "99+" : String.valueOf(unread);
|
||||
badge.setText(badgeText);
|
||||
badge.setVisible(true);
|
||||
|
||||
// 根据文本长度调整徽章大小
|
||||
FontMetrics fm = badge.getFontMetrics(badge.getFont());
|
||||
int width = fm.stringWidth(badgeText) + 12;
|
||||
badge.setPreferredSize(new Dimension(width, 20));
|
||||
} else {
|
||||
badge.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新组件
|
||||
* 将组件上对应的信息修改。
|
||||
* newUnread 是加在原有的unread上的
|
||||
*/
|
||||
public void updateUI(String name, int newUnread) {
|
||||
titleLabel.setText(name);
|
||||
|
||||
// 更新图标的首字母显示
|
||||
boolean isGroup = LocalData.get().getGroupData(groupId) != null;
|
||||
if (isGroup) {
|
||||
icon.setIcon(new RoundedRectCharIcon(Color.decode(DesignToken.BUBBLE_COLOR_BLUE), Color.WHITE,
|
||||
name.substring(0, 1).toUpperCase(), 40));
|
||||
} else {
|
||||
icon.setIcon(new CircleCharIcon2(Color.ORANGE, Color.WHITE,
|
||||
name.substring(0, 1).toUpperCase(), 40));
|
||||
}
|
||||
|
||||
unread += newUnread;
|
||||
updateBadge();
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击事件
|
||||
* 当点击之后,使用UIUpdater来更新ContentView的UI,
|
||||
* 使得其加载新的群聊信息
|
||||
* 清空unread为0
|
||||
* UIUpdater在ContentView展示当前群聊信息的时候不会更新这个群聊的未读信息数量。
|
||||
*/
|
||||
public void enterGroup() {
|
||||
unread = 0;
|
||||
LocalData.get().setCurrentChatId(groupId);
|
||||
|
||||
updateBadge(); // 更新徽章显示
|
||||
|
||||
String name;
|
||||
if (LocalData.get().getFriends().containsKey(groupId)) {
|
||||
name = LocalData.get().getFriends().get(groupId);
|
||||
} else {
|
||||
name = LocalData.get().getGroupName(groupId);
|
||||
}
|
||||
|
||||
updateUI(name, 0);
|
||||
|
||||
MainPage.get().exchangeToChatRoom(groupId);
|
||||
|
||||
if (LocalData.get().getGroupData(groupId) != null) {
|
||||
System.out.println(
|
||||
"进入群聊:" + groupId +
|
||||
",人数:" + LocalData.get().getGroupData(groupId).getMemberCount() +
|
||||
", 信息数量: " + LocalData.get().getChatMsg(groupId).size());
|
||||
} else {
|
||||
System.out.println("进入私聊:" + groupId);
|
||||
}
|
||||
|
||||
MainPage.get().revalidate(); // 重新计算布局
|
||||
MainPage.get().repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取未读消息数量
|
||||
*/
|
||||
public int getUnread() {
|
||||
return unread;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群组ID
|
||||
*/
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现Comparable接口,用于排序
|
||||
* 按照群组ID进行排序
|
||||
*
|
||||
* @param o 要比较的对象
|
||||
* @return 比较结果
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(String o) {
|
||||
return this.groupId.compareTo(o);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
package client.view.main;
|
||||
|
||||
import client.view.MainPage;
|
||||
import client.view.util.LimitSizePanel;
|
||||
|
||||
import client.service.LocalData;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static client.view.util.DesignToken.SECONDARY_PANEL_WIDTH_MIN;
|
||||
|
||||
/**
|
||||
* 二级菜单栏组件
|
||||
* 用于显示聊天、好友、设置等二级选项。
|
||||
* 点击不同选项可以切换到对应的功能界面。
|
||||
*/
|
||||
public class SecondaryOptionView extends LimitSizePanel {
|
||||
private static volatile SecondaryOptionView instance;
|
||||
|
||||
public static SecondaryOptionView get() {
|
||||
if (instance == null) {
|
||||
synchronized (SecondaryOptionView.class) {
|
||||
if (instance == null) {
|
||||
instance = new SecondaryOptionView();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
// 二级选项模式
|
||||
private enum Mode {
|
||||
MESSAGE, FRIEND, GROUP, SETTING
|
||||
}
|
||||
|
||||
// 当前二级选项模式
|
||||
private Mode currentMode = Mode.MESSAGE;
|
||||
// 这是一个群聊ID,组件的映射
|
||||
private Map<String, GroupListItem> listItems;
|
||||
// 关于好友ID,组件的映射
|
||||
private JPanel chatContainer;
|
||||
// 好友列表滚动面板
|
||||
private JScrollPane scrollPane;
|
||||
// 创建群聊按钮
|
||||
private JButton createGroupButton;
|
||||
// 二级选项标题标签
|
||||
private JLabel titleLabel;
|
||||
|
||||
private SecondaryOptionView() {
|
||||
super(SECONDARY_PANEL_WIDTH_MIN);
|
||||
|
||||
init();
|
||||
|
||||
listItems = new LinkedHashMap<>();
|
||||
|
||||
// 添加右侧边框分割线
|
||||
this.setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, UIManager.getColor("Component.borderColor")));
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
// 创建顶部面板
|
||||
JPanel topPanel = new JPanel(new BorderLayout());
|
||||
topPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); // 增加内边距
|
||||
|
||||
titleLabel = new JLabel("消息"); // 或者 "群聊"/"好友",根据当前视图动态变化更好,这里先用通用标题
|
||||
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
||||
|
||||
// 调整“+”按钮样式,使其更像一个功能图标
|
||||
createGroupButton.setMargin(new Insets(2, 6, 2, 6));
|
||||
createGroupButton.setFocusPainted(false);
|
||||
|
||||
topPanel.add(titleLabel, BorderLayout.WEST);
|
||||
topPanel.add(createGroupButton, BorderLayout.EAST);
|
||||
|
||||
// 创建群聊容器
|
||||
chatContainer = new JPanel();
|
||||
chatContainer.setLayout(new BoxLayout(chatContainer, BoxLayout.Y_AXIS));
|
||||
|
||||
// 创建滚动面板
|
||||
scrollPane = new JScrollPane(chatContainer);
|
||||
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
scrollPane.setBorder(BorderFactory.createEmptyBorder()); // 移除默认边框
|
||||
|
||||
// 自定义滚动条UI
|
||||
JScrollBar verticalScrollBar = scrollPane.getVerticalScrollBar();
|
||||
verticalScrollBar.setUnitIncrement(16);
|
||||
verticalScrollBar.setPreferredSize(new Dimension(10, 0));
|
||||
|
||||
// 组装界面
|
||||
JPanel headerContainer = new JPanel(new BorderLayout());
|
||||
headerContainer.add(topPanel, BorderLayout.CENTER);
|
||||
// 添加底部分割线,同时为了更好的层次感,也可以考虑添加顶部分割线(如果需要与标题栏分隔)
|
||||
// 这里我们给上下都添加分割线,确保 headerContainer 与上面的 Window Title 和下面的 List 都有分隔线
|
||||
headerContainer
|
||||
.setBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, UIManager.getColor("Component.borderColor")));
|
||||
|
||||
this.add(headerContainer, BorderLayout.NORTH);
|
||||
this.add(scrollPane, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个用于创建群聊的按钮组件
|
||||
* 按下这个组件后弹出一个创建群聊的对话框,填写完成后进行创建群聊的操作(调用DataManager的createGroupChat方法)
|
||||
* 如果失败,则放弃创建群聊。
|
||||
*
|
||||
* @return 群聊创建按钮组件
|
||||
*/
|
||||
private JButton createGroupCreateBtn() {
|
||||
JButton groupCreateBtn = new JButton("+");
|
||||
return groupCreateBtn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化所有组件
|
||||
* 读取DataManager的信息,而后群聊信息创建为GroupListItem,加入到类中对应的列表中
|
||||
* 并将其添加到chatContainer中
|
||||
* 同样,好友信息创建为FriendListItem,加入到类中对应的列表中
|
||||
* 并添加到friendContainer中
|
||||
* 最后使用exchangeToGroupChat将群聊列表加入到聊天项容器中
|
||||
* 使用createGroupCreateBtn创建群聊创建按钮,并添加到chatContainer中
|
||||
*/
|
||||
public void init() {
|
||||
createGroupButton = createGroupCreateBtn();
|
||||
createGroupButton.addActionListener(e -> {
|
||||
showAddMenu(createGroupButton);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示添加菜单
|
||||
* 根据当前模式(群聊/好友/设置),显示不同的添加选项
|
||||
* 例如,在群聊模式下,显示创建群聊和加入群聊选项
|
||||
* 在好友模式下,显示添加好友选项
|
||||
* 在设置模式下,显示不同的设置选项
|
||||
*/
|
||||
private void showAddMenu(Component invoker) {
|
||||
JPopupMenu popupMenu = new JPopupMenu();
|
||||
|
||||
if (currentMode == Mode.GROUP) {
|
||||
JMenuItem createGroupItem = new JMenuItem("创建群聊");
|
||||
createGroupItem.addActionListener(e -> MainPage.get().showGroupCreateDialog());
|
||||
popupMenu.add(createGroupItem);
|
||||
|
||||
JMenuItem joinGroupItem = new JMenuItem("加入群聊");
|
||||
joinGroupItem.addActionListener(e -> {
|
||||
MainPage.get().showJoinGroupDialog();
|
||||
});
|
||||
popupMenu.add(joinGroupItem);
|
||||
|
||||
} else if (currentMode == Mode.FRIEND) {
|
||||
JMenuItem addFriendItem = new JMenuItem("添加好友");
|
||||
addFriendItem.addActionListener(e -> {
|
||||
MainPage.get().showAddFriendDialog();
|
||||
});
|
||||
popupMenu.add(addFriendItem);
|
||||
}
|
||||
|
||||
popupMenu.show(invoker, 0, invoker.getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新消息列表的UI
|
||||
* 根据传入的groupId,更新对应groupListItems的群聊列表项的UI
|
||||
* 这里需要先删除createGroupButton按钮,而后进行更新操作后再添加回来。
|
||||
* 如果没有对应的id,则创建新的GroupListItem,并添加到groupListItems中
|
||||
*/
|
||||
public void updateGroupList(String groupId, String title, int unreadCount) {
|
||||
if (listItems.containsKey(groupId)) {
|
||||
listItems.get(groupId).updateUI(title, unreadCount);
|
||||
} else {
|
||||
GroupListItem item = new GroupListItem(groupId, title, 0);
|
||||
listItems.put(groupId, item);
|
||||
}
|
||||
|
||||
// 只有在 MESSAGE 模式下才更新 UI 容器
|
||||
if (currentMode == Mode.MESSAGE) {
|
||||
// 简单处理:重新加载所有 item 保证顺序,或者只添加新的
|
||||
// 为了简单,如果它不在容器里,加进去
|
||||
GroupListItem item = listItems.get(groupId);
|
||||
boolean alreadyIn = false;
|
||||
for (Component c : chatContainer.getComponents()) {
|
||||
if (c == item) {
|
||||
alreadyIn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!alreadyIn) {
|
||||
chatContainer.add(item);
|
||||
}
|
||||
chatContainer.revalidate();
|
||||
chatContainer.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新消息列表(兼容群聊和私聊)
|
||||
*/
|
||||
public void updateMessageList(String id, String name, String content, int unread) {
|
||||
updateGroupList(id, name, unread);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果当前处于群聊模式,刷新群聊列表
|
||||
* 用于处理新加入群聊时的列表更新
|
||||
*/
|
||||
public void refreshIfInGroupMode() {
|
||||
if (currentMode == Mode.GROUP) {
|
||||
exchangeToGroupList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定的群聊列表
|
||||
* 如果当前模式是CHAT,且groupId存在于listItems中,
|
||||
* 则从chatContainer中移除对应的GroupListItem组件,
|
||||
* 并从listItems中删除该条目。
|
||||
* 最后调用chatContainer的revalidate和repaint方法更新UI。
|
||||
*/
|
||||
public void removeGroupListItem(String groupId) {
|
||||
if (listItems.containsKey(groupId)) {
|
||||
GroupListItem item = listItems.get(groupId);
|
||||
listItems.remove(groupId);
|
||||
if (currentMode == Mode.MESSAGE || currentMode == Mode.GROUP) {
|
||||
chatContainer.remove(item);
|
||||
chatContainer.revalidate();
|
||||
chatContainer.repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到设置列表模式
|
||||
* 将当前模式设置为SETTING,更新标题为"设置",隐藏创建群聊按钮,清空聊天项容器。
|
||||
* 然后添加个人信息和关于软件的设置项按钮到聊天项容器中。
|
||||
* 最后调用revalidate和repaint方法更新UI。
|
||||
*/
|
||||
public void exchangeToSettingList() {
|
||||
currentMode = Mode.SETTING;
|
||||
titleLabel.setText("设置");
|
||||
createGroupButton.setVisible(false);
|
||||
chatContainer.removeAll();
|
||||
|
||||
// 添加设置项
|
||||
chatContainer.add(createSettingItem("个人信息", "info"));
|
||||
chatContainer.add(createSettingItem("关于软件", "about"));
|
||||
|
||||
chatContainer.revalidate();
|
||||
chatContainer.repaint();
|
||||
|
||||
MainPage.get().exchangeToBlankContent(); // 右侧清空或显示默认页
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个设置项按钮
|
||||
* 按钮的文本为text,类型为type。
|
||||
* 按钮的对齐方式为居中对齐,最大宽度为Integer.MAX_VALUE,高度为50。
|
||||
* 点击按钮时,调用MainPage的exchangeToSettings方法,传入type参数。
|
||||
*
|
||||
* @param text 按钮的文本
|
||||
* @param type 按钮的类型
|
||||
* @return 一个设置项按钮组件
|
||||
*/
|
||||
private JButton createSettingItem(String text, String type) {
|
||||
JButton btn = new JButton(text);
|
||||
btn.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
btn.setMaximumSize(new Dimension(Integer.MAX_VALUE, 50));
|
||||
btn.setFocusPainted(false);
|
||||
btn.setBackground(Color.WHITE);
|
||||
btn.addActionListener(e -> MainPage.get().exchangeToSettings(type));
|
||||
return btn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到消息列表模式
|
||||
* 将当前模式设置为MESSAGE,更新标题为"消息",隐藏创建群聊按钮,清空聊天项容器。
|
||||
* 然后遍历listItems中的所有GroupListItem组件,添加到聊天项容器中。
|
||||
* 最后调用chatContainer的revalidate和repaint方法更新UI。
|
||||
*/
|
||||
public void exchangeToMessageList() {
|
||||
currentMode = Mode.MESSAGE;
|
||||
titleLabel.setText("消息");
|
||||
createGroupButton.setVisible(false);
|
||||
chatContainer.removeAll();
|
||||
|
||||
// 恢复消息列表
|
||||
for (GroupListItem item : listItems.values()) {
|
||||
chatContainer.add(item);
|
||||
}
|
||||
|
||||
chatContainer.revalidate();
|
||||
chatContainer.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到群聊列表模式
|
||||
* 将当前模式设置为GROUP,更新标题为"群聊",显示创建群聊按钮,清空聊天项容器。
|
||||
* 然后遍历LocalData中的所有群聊,创建GroupListItem组件并添加到聊天项容器中。
|
||||
* 最后调用chatContainer的revalidate和repaint方法更新UI。
|
||||
*/
|
||||
public void exchangeToGroupList() {
|
||||
currentMode = Mode.GROUP;
|
||||
titleLabel.setText("群聊");
|
||||
createGroupButton.setVisible(true);
|
||||
chatContainer.removeAll();
|
||||
|
||||
java.util.List<server.data.GroupData> groups = LocalData.get().getAllGroups();
|
||||
if (groups != null) {
|
||||
for (server.data.GroupData group : groups) {
|
||||
// 这里我们复用GroupListItem,未读数设为0
|
||||
GroupListItem item = new GroupListItem(group.getGroupId(), group.getGroupName(), 0);
|
||||
chatContainer.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
chatContainer.revalidate();
|
||||
chatContainer.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到好友列表模式
|
||||
* 将当前模式设置为FRIEND,更新标题为"好友",隐藏创建群聊按钮,清空聊天项容器。
|
||||
* 然后遍历LocalData中的好友列表,创建FriendListItem组件并添加到聊天项容器中。
|
||||
* 最后调用chatContainer的revalidate和repaint方法更新UI。
|
||||
*/
|
||||
public void exchangeToFriendList() {
|
||||
currentMode = Mode.FRIEND;
|
||||
titleLabel.setText("好友");
|
||||
createGroupButton.setVisible(true); // 显示加号按钮
|
||||
chatContainer.removeAll();
|
||||
|
||||
Map<String, String> friends = LocalData.get().getFriends();
|
||||
if (friends != null) {
|
||||
for (Map.Entry<String, String> entry : friends.entrySet()) {
|
||||
FriendListItem item = new FriendListItem(entry.getKey(), entry.getValue());
|
||||
chatContainer.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
chatContainer.revalidate();
|
||||
chatContainer.repaint();
|
||||
|
||||
MainPage.get().exchangeToBlankContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package client.view.main;
|
||||
|
||||
import client.service.ChatSender;
|
||||
import client.service.LocalData;
|
||||
import client.view.MainPage;
|
||||
import client.view.util.DesignToken;
|
||||
import server.data.UserData;
|
||||
import server.serveice.Wrapper;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* 设置界面组件
|
||||
* 用于显示用户个人信息和关于 LocalChatApp 的设置选项。
|
||||
* 包括用户ID、用户名、退出登录等功能。
|
||||
*/
|
||||
public class SettingsView extends JPanel {
|
||||
|
||||
public SettingsView(String type) {
|
||||
initUI(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化设置界面组件
|
||||
* 用于根据界面类型显示不同的设置内容。
|
||||
* 如果是 "info" 类型,显示用户个人信息,包括用户ID和用户名。
|
||||
* 如果是 "about" 类型,显示关于 LocalChatApp 的信息,包括版本号等。
|
||||
* @param type 界面类型,"info" 显示个人信息,"about" 显示关于 LocalChatApp 的信息
|
||||
*/
|
||||
private void initUI(String type) {
|
||||
setLayout(new BorderLayout());
|
||||
setBackground(Color.WHITE);
|
||||
|
||||
JPanel contentPanel = new JPanel();
|
||||
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
|
||||
contentPanel.setBorder(BorderFactory.createEmptyBorder(40, 40, 40, 40));
|
||||
contentPanel.setBackground(Color.WHITE);
|
||||
|
||||
if ("info".equals(type)) {
|
||||
addInfoContent(contentPanel);
|
||||
} else if ("about".equals(type)) {
|
||||
addAboutContent(contentPanel);
|
||||
}
|
||||
|
||||
add(contentPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加个人信息内容到设置界面
|
||||
* 包括用户ID、用户名、邮箱、生日、地址、签名等。
|
||||
* @param panel 用于添加组件的面板
|
||||
*/
|
||||
private void addInfoContent(JPanel panel) {
|
||||
JLabel title = new JLabel("个人信息");
|
||||
title.setFont(new Font(DesignToken.DEFAULT_FONT, Font.BOLD, 24));
|
||||
title.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
panel.add(title);
|
||||
panel.add(Box.createVerticalStrut(20));
|
||||
|
||||
String myId = LocalData.get().getId();
|
||||
UserData myData = LocalData.get().getUserDetail(myId);
|
||||
if (myData == null) {
|
||||
// 如果数据尚未同步,使用本地基本信息创建一个临时对象
|
||||
myData = new UserData(LocalData.get().getUserName(myId), myId, null);
|
||||
}
|
||||
|
||||
addLabel(panel, "用户ID:", myId);
|
||||
addLabel(panel, "用户名:", myData.getNickname());
|
||||
|
||||
// 可编辑字段
|
||||
JTextField emailField = addEditableField(panel, "邮箱:", myData.getEmail());
|
||||
JTextField birthdayField = addEditableField(panel, "生日:", myData.getBirthday());
|
||||
JTextField addressField = addEditableField(panel, "地址:", myData.getAddress());
|
||||
JTextField signatureField = addEditableField(panel, "个性签名:", myData.getSignature());
|
||||
|
||||
panel.add(Box.createVerticalStrut(30));
|
||||
|
||||
// 保存修改按钮
|
||||
JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
btnPanel.setBackground(Color.WHITE);
|
||||
btnPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
|
||||
// 保存修改按钮
|
||||
JButton saveBtn = new JButton("保存修改");
|
||||
final UserData currentData = myData;
|
||||
saveBtn.addActionListener(e -> {
|
||||
// 更新本地对象字段
|
||||
currentData.setEmail(emailField.getText().trim());
|
||||
currentData.setBirthday(birthdayField.getText().trim());
|
||||
currentData.setAddress(addressField.getText().trim());
|
||||
currentData.setSignature(signatureField.getText().trim());
|
||||
|
||||
// 发送更新请求
|
||||
ChatSender.addMsg(Wrapper.updateUserDetailRequest(myId, currentData));
|
||||
|
||||
// 更新本地缓存(虽然服务器会广播回来,但本地先更新体验更好)
|
||||
LocalData.get().updateUserDetails(currentData);
|
||||
|
||||
JOptionPane.showMessageDialog(this, "个人信息已保存", "提示", JOptionPane.INFORMATION_MESSAGE);
|
||||
});
|
||||
|
||||
// 退出登录按钮
|
||||
JButton logoutBtn = new JButton("退出登录");
|
||||
logoutBtn.addActionListener(e -> {
|
||||
int confirm = JOptionPane.showConfirmDialog(this, "确定要退出登录吗?", "提示", JOptionPane.YES_NO_OPTION);
|
||||
if (confirm == JOptionPane.YES_OPTION) {
|
||||
MainPage.get().openLogInPage();
|
||||
}
|
||||
});
|
||||
|
||||
// 添加按钮到面板
|
||||
btnPanel.add(saveBtn);
|
||||
btnPanel.add(logoutBtn);
|
||||
panel.add(btnPanel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加可编辑文本字段到设置界面
|
||||
* 用于用户输入个人信息的编辑。
|
||||
* @param panel 用于添加组件的面板
|
||||
* @param labelText 标签文本,描述字段的作用
|
||||
* @param value 初始文本字段值
|
||||
* @return 新创建的 JTextField 对象
|
||||
*/
|
||||
private JTextField addEditableField(JPanel panel, String labelText, String value) {
|
||||
JPanel fieldPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
fieldPanel.setBackground(Color.WHITE);
|
||||
fieldPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
|
||||
JLabel label = new JLabel(labelText);
|
||||
label.setFont(new Font(DesignToken.DEFAULT_FONT, Font.PLAIN, 16));
|
||||
label.setPreferredSize(new Dimension(80, 30));
|
||||
|
||||
JTextField textField = new JTextField(value, 20);
|
||||
textField.setFont(new Font(DesignToken.DEFAULT_FONT, Font.PLAIN, 14));
|
||||
|
||||
fieldPanel.add(label);
|
||||
fieldPanel.add(textField);
|
||||
panel.add(fieldPanel);
|
||||
return textField;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加关于 LocalChatApp 的内容到设置界面
|
||||
* 包括版本号、开发团队、应用描述等。
|
||||
* @param panel 用于添加组件的面板
|
||||
*/
|
||||
private void addAboutContent(JPanel panel) {
|
||||
JLabel title = new JLabel("关于 LocalChatApp");
|
||||
title.setFont(new Font(DesignToken.DEFAULT_FONT, Font.BOLD, 24));
|
||||
title.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
panel.add(title);
|
||||
panel.add(Box.createVerticalStrut(30));
|
||||
|
||||
JLabel version = new JLabel("版本: v1.0.0");
|
||||
version.setFont(new Font(DesignToken.DEFAULT_FONT, Font.PLAIN, 16));
|
||||
panel.add(version);
|
||||
|
||||
panel.add(Box.createVerticalStrut(10));
|
||||
JLabel author = new JLabel("开发团队: 添砖加瓦小组");
|
||||
author.setFont(new Font(DesignToken.DEFAULT_FONT, Font.PLAIN, 16));
|
||||
panel.add(author);
|
||||
|
||||
panel.add(Box.createVerticalStrut(10));
|
||||
JLabel desc = new JLabel("<html><body><p style='width:300px'>基于Java Swing和Socket开发的本地局域网聊天室。</p></body></html>");
|
||||
desc.setFont(new Font(DesignToken.DEFAULT_FONT, Font.PLAIN, 14));
|
||||
panel.add(desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加标签到设置界面
|
||||
* 用于显示键值对信息,例如用户ID和用户名。
|
||||
* @param panel 用于添加组件的面板
|
||||
* @param key 键,例如 "用户ID:"
|
||||
* @param value 值,例如 "10086"
|
||||
*/
|
||||
private void addLabel(JPanel panel, String key, String value) {
|
||||
// 创建一个行面板,用于添加键值对标签
|
||||
JPanel row = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
row.setBackground(Color.WHITE);
|
||||
row.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
|
||||
// 键标签
|
||||
JLabel k = new JLabel(key);
|
||||
k.setFont(new Font(DesignToken.DEFAULT_FONT, Font.BOLD, 14));
|
||||
k.setPreferredSize(new Dimension(80, 30));
|
||||
|
||||
// 值标签
|
||||
JLabel v = new JLabel(value);
|
||||
v.setFont(new Font(DesignToken.DEFAULT_FONT, Font.PLAIN, 14));
|
||||
|
||||
// 添加键值对标签到行面板
|
||||
row.add(k);
|
||||
row.add(v);
|
||||
panel.add(row);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package client.view.main;
|
||||
|
||||
import com.formdev.flatlaf.FlatDarkLaf;
|
||||
import com.formdev.flatlaf.FlatLaf;
|
||||
import com.formdev.flatlaf.FlatLightLaf;
|
||||
import client.view.util.CircleCharIcon2;
|
||||
import client.view.util.DesignToken;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
/**
|
||||
* 左侧侧边栏组件
|
||||
* 包含消息、好友、群聊、设置、黑暗模式切换按钮
|
||||
*/
|
||||
public class SideOptionView extends JPanel {
|
||||
private static volatile SideOptionView instance;
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
*
|
||||
* @return 单例实例
|
||||
*/
|
||||
public static SideOptionView get() {
|
||||
if (instance == null) {
|
||||
synchronized (SideOptionView.class) {
|
||||
if (instance == null) {
|
||||
instance = new SideOptionView();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照原型图,生成三个按钮组件:群聊,好友,设置组件
|
||||
* 每个组件都设置一个图标
|
||||
* 为每一个按钮配置一个事件
|
||||
* 群聊:exchangeToChatPage()/UIUpdate
|
||||
* 设置: exchangeToSettingPage()/UIUpdate。这个当前还没做,提示用户正在制作中。
|
||||
* 现在暂时没有设置相关功能
|
||||
*/
|
||||
public SideOptionView() {
|
||||
this.setLayout(new GridLayout(5, 1));
|
||||
|
||||
// this.setBackground(Color.GRAY);
|
||||
// 添加右侧边框分割线
|
||||
this.setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createMatteBorder(0, 0, 0, 1, UIManager.getColor("Component.borderColor")),
|
||||
BorderFactory.createEmptyBorder(5, 5, 5, 5)));
|
||||
|
||||
// 初始化五个核心按钮:消息、好友、群聊、设置、黑暗模式切换按钮
|
||||
initMessageButton();
|
||||
initFriendButton();
|
||||
initGroupButton();
|
||||
initSettingButton();
|
||||
initDarkModeButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化消息按钮
|
||||
* 点击后切换到消息页面
|
||||
*/
|
||||
private void initMessageButton() {
|
||||
JButton messageBtn = createIconButton("消", Color.LIGHT_GRAY);
|
||||
messageBtn.setToolTipText("消息");
|
||||
messageBtn.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
SecondaryOptionView.get().exchangeToMessageList();
|
||||
}
|
||||
});
|
||||
this.add(messageBtn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化好友按钮
|
||||
* 点击后切换到好友页面
|
||||
*/
|
||||
private void initFriendButton() {
|
||||
JButton friendBtn = createIconButton("友", Color.LIGHT_GRAY);
|
||||
friendBtn.setToolTipText("好友");
|
||||
friendBtn.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
SecondaryOptionView.get().exchangeToFriendList();
|
||||
}
|
||||
});
|
||||
this.add(friendBtn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化群聊按钮
|
||||
* 点击后切换到群聊页面
|
||||
*/
|
||||
private void initGroupButton() {
|
||||
JButton groupBtn = createIconButton("群", Color.LIGHT_GRAY);
|
||||
groupBtn.setToolTipText("群聊");
|
||||
groupBtn.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
SecondaryOptionView.get().exchangeToGroupList();
|
||||
}
|
||||
});
|
||||
this.add(groupBtn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化设置按钮
|
||||
* 点击后切换到设置页面
|
||||
*/
|
||||
private void initSettingButton() {
|
||||
JButton settingBtn = createIconButton("设", Color.LIGHT_GRAY);
|
||||
settingBtn.setToolTipText("设置");
|
||||
settingBtn.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
SecondaryOptionView.get().exchangeToSettingList();
|
||||
}
|
||||
});
|
||||
this.add(settingBtn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化黑暗模式切换按钮
|
||||
*/
|
||||
private void initDarkModeButton() {
|
||||
JButton darkModeBtn = createIconButton("黑", Color.DARK_GRAY);
|
||||
darkModeBtn.setToolTipText("切换模式");
|
||||
darkModeBtn.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (FlatLaf.isLafDark()) {
|
||||
try {
|
||||
DesignToken.setDarkMode(false);
|
||||
FlatLightLaf.setup();
|
||||
FlatLaf.updateUI();
|
||||
ChatInfoView.get().updateTheme();
|
||||
// 切换图标
|
||||
darkModeBtn.setIcon(new CircleCharIcon2(Color.DARK_GRAY, Color.WHITE, "黑", 36));
|
||||
darkModeBtn.setToolTipText("切换到黑暗模式");
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
DesignToken.setDarkMode(true);
|
||||
FlatDarkLaf.setup();
|
||||
FlatLaf.updateUI();
|
||||
ChatInfoView.get().updateTheme();
|
||||
// 切换图标
|
||||
darkModeBtn.setIcon(new CircleCharIcon2(Color.LIGHT_GRAY, Color.BLACK, "白", 36));
|
||||
darkModeBtn.setToolTipText("切换到明亮模式");
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
this.add(darkModeBtn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带圆形图标的按钮
|
||||
*/
|
||||
private JButton createIconButton(String text, Color bgColor) {
|
||||
JButton button = new JButton();
|
||||
button.setPreferredSize(new Dimension(40, 40));
|
||||
button.setIcon(new CircleCharIcon2(bgColor, Color.WHITE, text, 36));
|
||||
button.setBorderPainted(false);
|
||||
button.setContentAreaFilled(false);
|
||||
button.setFocusPainted(false);
|
||||
return button;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package client.view.util;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class CircleCharIcon2 implements Icon {
|
||||
private Color circleColor;
|
||||
private Color textColor;
|
||||
private String character;
|
||||
private int size;
|
||||
|
||||
public CircleCharIcon2(Color circleColor, Color textColor, String character, int size) {
|
||||
this.circleColor = circleColor;
|
||||
this.textColor = textColor;
|
||||
this.character = character;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||
Graphics2D g2d = (Graphics2D) g;
|
||||
|
||||
// 开启抗锯齿
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
// 绘制圆形
|
||||
g2d.setColor(circleColor);
|
||||
g2d.fillOval(x, y, size - 1, size - 1);
|
||||
|
||||
// 绘制字符
|
||||
g2d.setColor(textColor);
|
||||
g2d.setFont(new Font("Microsoft YaHei", Font.BOLD, size / 2));
|
||||
FontMetrics fm = g2d.getFontMetrics();
|
||||
int textWidth = fm.stringWidth(character);
|
||||
int textHeight = fm.getAscent();
|
||||
int textX = x + (size - textWidth) / 2;
|
||||
int textY = y + (size - textHeight) / 2 + (int) (fm.getAscent() / 1.5);
|
||||
g2d.drawString(character, textX, textY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package client.view.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用于设定 UI元素的各种变量
|
||||
*/
|
||||
public class DesignToken implements Serializable {
|
||||
private static final long serialVersionUID = -3791869536619757015L;
|
||||
|
||||
//============================窗口大小==============================
|
||||
// 主界面大小
|
||||
public final static int WINDOW_ORI_WIDTH = 1200;
|
||||
public final static int WINDOW_ORI_HEIGHT = 600;
|
||||
public final static int WINDOW_MIN_WIDTH = 800;
|
||||
public final static int WINDOW_MIN_HEIGHT = 600;
|
||||
// 登录界面大小
|
||||
public final static int LOGIN_WIDTH = 400;
|
||||
public final static int LOGIN_HEIGHT = 300;
|
||||
|
||||
// 主界面组件大小
|
||||
public final static int SIDE_PANEL_WIDTH = 50;
|
||||
public final static int SECONDARY_PANEL_WIDTH = 200;
|
||||
public final static int SECONDARY_PANEL_WIDTH_MIN = 150;
|
||||
public final static int CONTENT_PANEL_WIDTH = 570;
|
||||
|
||||
// 内容组件大小
|
||||
public final static int CHAT_PANEL_WIDTH = 400;
|
||||
public final static int INPUT_AREA_HEIGHT = 100;
|
||||
public final static int MSG_BUBBLE_WIDTH = 50;
|
||||
public final static int INFO_PANEL_WIDTH = 150;
|
||||
|
||||
public final static int CONTENT_PANEL_WIDTH_MIN = 300;
|
||||
public final static int GROUP_INFO_PANEL_WIDTH = 240;
|
||||
public final static int GROUP_CHAT_PANEL_WIDTH = 210;
|
||||
|
||||
//============================字体==================================
|
||||
public static int FONT_SIZE = 14;
|
||||
public static int FONT_SIZE_SMALL = 12;
|
||||
public static int FONT_SIZE_TITLE = 20;
|
||||
public static int FONT_SIZE_TITLE_MIN = 18;
|
||||
|
||||
public static String DEFAULT_FONT = "微软雅黑";
|
||||
|
||||
//=============================颜色=================================
|
||||
|
||||
// public static String COLOR_BACKGROUND = "#ddebee";
|
||||
|
||||
public static String BUBBLE_COLOR_GREEN = "#5aca58";
|
||||
public static String BUBBLE_COLOR_WHITE = "#c8cdcd";
|
||||
public static String BUBBLE_COLOR_GRAY = "#8a8a8a";
|
||||
public static String BUBBLE_COLOR_RED = "#ff4d4d";
|
||||
public static String BUBBLE_COLOR_BLUE = "#4097bc";
|
||||
public static String BUBBLE_COLOR_YELLOW = "#f7b500";
|
||||
|
||||
public static String BACKGROUND_COLOR = "#c7d9d9";
|
||||
public static String EDGE_COLOR = "#aab7b7";
|
||||
|
||||
public static String COLOR_FONT_BLUE = "#4097bc";// blue
|
||||
public static String COLOR_FONT_BLACK = "#000000";// black
|
||||
public static String COLOR_FONT_WHITE = "#ffffff";// white
|
||||
public static String COLOR_FONT_GRAY = "#8a8a8a";// gray
|
||||
public static String COLOR_FONT_GREEN = "#008000";// green
|
||||
public static String COLOR_FONT_RED = "#ff0000";// red
|
||||
public static String COLOR_FONT_YELLOW = "#ffff00";// yellow
|
||||
public static String COLOR_FONT_ORANGE = "#ff8c00";// orange
|
||||
public static String COLOR_FONT_PURPLE = "#800080";// purple
|
||||
|
||||
public static String COLOR_HEAD_BLACK = "#000000"; // black
|
||||
public static String COLOR_HEAD_GRAY = "#8a8a8a"; // gray
|
||||
public static String COLOR_HEAD_WHITE = "#ffffff"; // white
|
||||
public static String COLOR_HEAD_BLUE = "#4097bc"; // blue
|
||||
public static String COLOR_HEAD_GREEN = "#008000"; // green
|
||||
public static String COLOR_HEAD_RED = "#ff0000"; // red
|
||||
public static String COLOR_HEAD_YELLOW = "#ffff00"; // yellow
|
||||
public static String COLOR_HEAD_ORANGE = "#ff8c00"; // orange
|
||||
public static String COLOR_HEAD_PURPLE = "#800080"; // purple
|
||||
|
||||
public static void setDarkMode(boolean isDark) {
|
||||
if (isDark) {
|
||||
BACKGROUND_COLOR = "#3c3f41";
|
||||
EDGE_COLOR = "#5e6060";
|
||||
BUBBLE_COLOR_WHITE = "#505050"; // 白色气泡颜色(其他用户)
|
||||
COLOR_FONT_BLACK = "#dddddd"; // 黑色字体颜色(其他用户)
|
||||
BUBBLE_COLOR_GREEN = "#2e7d32"; // 绿色气泡颜色(自己)
|
||||
} else {
|
||||
BACKGROUND_COLOR = "#c7d9d9";
|
||||
EDGE_COLOR = "#aab7b7";
|
||||
BUBBLE_COLOR_WHITE = "#c8cdcd";
|
||||
COLOR_FONT_BLACK = "#000000";
|
||||
BUBBLE_COLOR_GREEN = "#5aca58";
|
||||
}
|
||||
}
|
||||
|
||||
//=============================文本域输入字数限制=================================
|
||||
public static int MAX_FONT_SIZE = 20;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package client.view.util;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* 一个可以限制最小宽度的面板,由于splitPane中
|
||||
*/
|
||||
public class LimitSizePanel extends JPanel {
|
||||
private int minWidth;
|
||||
|
||||
public LimitSizePanel(int minWidth) {
|
||||
super();
|
||||
this.minWidth = minWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMinimumSize() {
|
||||
// 设置最小尺寸
|
||||
Dimension dim = super.getMinimumSize();
|
||||
dim.width = Math.max(dim.width, minWidth);
|
||||
return dim;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
// 设置首选尺寸
|
||||
Dimension dim = super.getPreferredSize();
|
||||
dim.width = Math.max(dim.width, minWidth * 2);
|
||||
return dim;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package client.view.util;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* 圆角矩形字符图标
|
||||
*/
|
||||
public class RoundedRectCharIcon implements Icon {
|
||||
private Color bgColor; // 背景颜色
|
||||
private Color textColor; // 字体颜色
|
||||
private String character; // 显示的字符
|
||||
private int size; // 图标大小
|
||||
private int arc; // 圆角大小
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param bgColor 背景颜色
|
||||
* @param textColor 字体颜色
|
||||
* @param character 显示的字符
|
||||
* @param size 图标大小
|
||||
*/
|
||||
public RoundedRectCharIcon(Color bgColor, Color textColor, String character, int size) {
|
||||
this.bgColor = bgColor;
|
||||
this.textColor = textColor;
|
||||
this.character = character;
|
||||
this.size = size;
|
||||
this.arc = size / 3; // 圆角大小
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制图标
|
||||
*
|
||||
* @param c 组件
|
||||
* @param g 图形上下文
|
||||
* @param x 图标左上角的 x 坐标
|
||||
* @param y 图标左上角的 y 坐标
|
||||
*/
|
||||
@Override
|
||||
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||
Graphics2D g2d = (Graphics2D) g;
|
||||
|
||||
// 开启抗锯齿
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
// 绘制圆角矩形
|
||||
g2d.setColor(bgColor);
|
||||
g2d.fillRoundRect(x, y, size - 1, size - 1, arc, arc);
|
||||
|
||||
// 绘制字符
|
||||
g2d.setColor(textColor);
|
||||
g2d.setFont(new Font("Microsoft YaHei", Font.BOLD, size / 2));
|
||||
FontMetrics fm = g2d.getFontMetrics();
|
||||
int textWidth = fm.stringWidth(character);
|
||||
int textHeight = fm.getAscent();
|
||||
int textX = x + (size - textWidth) / 2;
|
||||
int textY = y + (size - textHeight) / 2 + (int) (fm.getAscent() / 1.5);
|
||||
g2d.drawString(character, textX, textY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图标宽度
|
||||
*
|
||||
* @return 图标宽度
|
||||
*/
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图标高度
|
||||
*
|
||||
* @return 图标高度
|
||||
*/
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user