提交之GitHub

This commit is contained in:
2026-02-08 23:58:00 +08:00
commit b4f25e99b1
43 changed files with 7926 additions and 0 deletions
+115
View File
@@ -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);
}
}
+503
View File
@@ -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);
}
}
+102
View File
@@ -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())
);
}
}
+120
View File
@@ -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);
}
/**
* 获取当前界面的文本的内容
* 检查服务器是否链接
* 检查注册的idnamepassword是否合法
* 都合适的话,发送注册请求。
*/
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()));
}
}
+530
View File
@@ -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());
});
}
}
+84
View File
@@ -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();
}
}
+95
View File
@@ -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);
}
}
+127
View File
@@ -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);
}
}
}
+366
View File
@@ -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);
}
}
+214
View File
@@ -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();
}
}
+198
View File
@@ -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);
}
}
+177
View File
@@ -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;
}
}
+50
View File
@@ -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;
}
}
+97
View File
@@ -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;
}
+32
View File
@@ -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;
}
}