Gaming Logbook is a desktop application for keeping a "log" about the games I have played. In the app, the user can add, edit, delete, search and filter entries (essentially a CRUD application). It also features an interface for creating and switching themes. The app is implemented using Java's Swing library and it was planned through an iterative designing process (multiple concept images, evaluation techniques).
Sometime during 2021 I started to keep a simple log about the games I have played. Ever since then I have kept on adding new entries as I play new games. Before the app I kept the logs in my private discord for gaming notes and a backup in a txt file. Then a programming course about desiging UI and implementing it came around and I wanted to make something practical. Moving my logs to a proper app was the first thing that came to my mind.
During the course we had to pair up and were given some criteria that the app would have to meet (basically contain enough UI elements and follow a planning template). Otherwise we had free reign over the project. It took around a month to plan and gather information before actually beginning to code anything substantial. Getting started on the programming was rough. Swing's layout managers were difficult to grasp. Thinking about the UI elements as groups of boxes helped a lot. The implementation took quite a lot of time, but the outcome was surprisingly good (for a first app).
I probably ended up coding and investing a lot more time than my pair did, though they were very much involved throughout the project. This was because we were essentially making an app for me, so I wanted it to be as polished as possible. For the most part we were both involved in each part of the project. The only split was that my pair focused more on the database for storing the log and the "entry adding screen" while I focused on the "main screen" and its functionalities. The interface system for changing the theme was a bonus thing that I decided to add while polishing out the app.
Here are some of the early concept drawings from the plans
One issue we tackled was dealing with dates. We wanted to have the dates be sortable from latest to oldest and vice versa using the automatic sorting that Swing's JTable provides. We ended up using ISO8601 formatted strings (yyyy-MM-dd) for storing the dates. That made the dates sort correctly without having to implement the sorting ourselves. We then created a custom renderer for displaying the dates in dd-MM-yyyy format:
public class DateColumnRenderer extends DefaultTableCellRenderer {
private static final SimpleDateFormat ISO8601_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
private static final SimpleDateFormat DISPLAY_FORMAT = new SimpleDateFormat("dd.MM.yyyy");
@Override
protected void setValue(Object value) {
if (value != null && value instanceof String) {
String date = (String) value;
try {
Date formattedDate = ISO8601_FORMAT.parse(date);
setText(DISPLAY_FORMAT.format(formattedDate));
} catch (ParseException e) {
setText(date); // Shouldn't ever happen as user input is checked
// before it's put in anything
}
} else {
setText("");
}
}
}
Then inside the JTable class we simply set the date column to utilize the above renderer:
DateColumnRenderer dateRenderer = new DateColumnRenderer();
dateRenderer.setHorizontalAlignment(SwingConstants.CENTER);
getColumn("Date").setCellRenderer(dateRenderer);
When making the GUI I decided to make a class for handling the theme of the app. We could have used a ready Look and Feel, but I felt it would be a better learning experience to try to do custom theme handling. The "Theme handler" defines the different parts that can be customized in an enum as well as the current active theme.
public class ThemeHandler {
private static AppTheme currentTheme;
// An UIBuilder class creates the UI, initializing this class
// and setting the theme based on launch arguments
public static enum AppColors {
TextColor,
ToolTipBackground,
NegativeButtons,
NegativeButtonHover,
NegativeButtonPressed,
// And so on...
}
public static void setCurrentTheme(AppTheme theme) {
currentTheme = theme;
}
public static AppTheme getCurrentTheme() {
return currentTheme;
}
public static Color getColor(AppColors appColor) {
if(currentTheme != null) {
return currentTheme.getColor(appColor);
}
else {
return Color.white;
}
}
}
At first the above ThemeHandler had all the colors and image paths defined inside it, but then I relized I could allow for custom themes via an interface. Each theme would implement a simple interface: AppTheme. The new system utilized a design pattern called "strategy pattern" (found that out later on).
public interface AppTheme {
// Note: All of these snippets had proper javadocs.
// They were left out to make this more compact.
public Color getColor(AppColors appColor);
public BufferedImage getBufferedImage(AppImages appImage);
public Image getImage(AppImages appImage);
public ImageIcon getImageIcon(AppImages appImage);
}
public class DarkMode implements AppTheme {
private final Map colorMap = new HashMap<>();
private final Map imageMap = new HashMap<>();
public DarkMode() {
colorMap.put(AppColors.TextColor, Color.white);
colorMap.put(AppColors.ToolTipBackground, Color.darkGray);
colorMap.put(AppColors.NegativeButtons, new Color(248, 81, 73));
colorMap.put(AppColors.NegativeButtonHover, Color.red);
colorMap.put(AppColors.NegativeButtonPressed, Color.red);
// ...
initializeImages();
}
private void initializeImages() {
imageMap.put(AppImages.AddEntryButtonIcon, "/images/AddEntryButtonIcon.png");
imageMap.put(AppImages.AppLogo, "/images/AppLogo.png");
imageMap.put(AppImages.backButton, "/images/DarkMode/BackButton.png");
// ...
}
@Override
public Color getColor(AppColors appColor) {
return colorMap.getOrDefault(appColor, Color.white);
}
@Override
public BufferedImage getBufferedImage(AppImages appImage) {
BufferedImage img = null;
try {
img = ImageIO.read(ImageHandler.class.getResource(imageMap.get(appImage)));
} catch (IOException e) {
e.printStackTrace();
System.out.println("Potential typo in enum: " + imageMap.get(appImage));
}
return img;
}
// Rest of the implementations...
}