More updates.

This commit is contained in:
Andrew Lalis 2023-04-19 17:31:49 +02:00
parent 830ca054d8
commit 9f4e3813a0
8 changed files with 233 additions and 52 deletions

View File

@ -3,17 +3,25 @@ package com.github.andrewlalis.running_every_day;
import com.formdev.flatlaf.FlatLightLaf;
import com.github.andrewlalis.running_every_day.data.DataSource;
import com.github.andrewlalis.running_every_day.view.RecorderAppWindow;
import com.github.andrewlalis.running_every_day.view.WindowDataSourceCloser;
import javax.swing.*;
import java.sql.SQLException;
/**
* The main application entrypoint.
*/
public class RecorderApp {
public static void main(String[] args) throws SQLException {
DataSource dataSource = new DataSource("jdbc:sqlite:runs.db");
public static void main(String[] args) {
FlatLightLaf.setup();
try {
DataSource dataSource = new DataSource("jdbc:sqlite:runs.db");
var window = new RecorderAppWindow(dataSource);
window.addWindowListener(new WindowDataSourceCloser(dataSource));
window.setVisible(true);
} catch (SQLException e) {
JOptionPane.showMessageDialog(null, "Failed to open database: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
}
}
}

View File

@ -9,6 +9,9 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* A single object that serves as the application's data source.
*/
public class DataSource {
private final Connection conn;

View File

@ -35,6 +35,13 @@ public record RunRecord(
return (int) duration.getSeconds();
}
public String durationFormatted() {
if (duration.toHoursPart() > 0) {
return String.format("%d:%02d:%02d", duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart());
}
return String.format("%02d:%02d", duration.toMinutesPart(), duration.toSecondsPart());
}
public BigDecimal weightGrams() {
return weightKg.multiply(BigDecimal.valueOf(1000));
}

View File

@ -8,7 +8,7 @@ import java.util.List;
public record RunRecordRepository(Connection conn) {
public Page<RunRecord> findAll(Pagination pagination) throws SQLException {
String query = "SELECT id, date, start_time, distance, duration, weight, comment FROM run";
String query = "SELECT * FROM run ORDER BY date ASC";
return pagination.execute(conn, query, new RunRecord.Mapper());
}

View File

@ -0,0 +1,78 @@
package com.github.andrewlalis.running_every_day.view;
import com.github.andrewlalis.running_every_day.data.RunRecord;
import javax.swing.*;
import java.util.List;
public class RunRecordRowSorter extends RowSorter<RunRecord> {
@Override
public RunRecord getModel() {
return null;
}
@Override
public void toggleSortOrder(int column) {
}
@Override
public int convertRowIndexToModel(int index) {
return 0;
}
@Override
public int convertRowIndexToView(int index) {
return 0;
}
@Override
public void setSortKeys(List<? extends SortKey> keys) {
}
@Override
public List<? extends SortKey> getSortKeys() {
return null;
}
@Override
public int getViewRowCount() {
return 0;
}
@Override
public int getModelRowCount() {
return 0;
}
@Override
public void modelStructureChanged() {
}
@Override
public void allRowsChanged() {
}
@Override
public void rowsInserted(int firstRow, int endRow) {
}
@Override
public void rowsDeleted(int firstRow, int endRow) {
}
@Override
public void rowsUpdated(int firstRow, int endRow) {
}
@Override
public void rowsUpdated(int firstRow, int endRow, int column) {
}
}

View File

@ -1,22 +1,84 @@
package com.github.andrewlalis.running_every_day.view;
import com.github.andrewlalis.running_every_day.data.Page;
import com.github.andrewlalis.running_every_day.data.DataSource;
import com.github.andrewlalis.running_every_day.data.Pagination;
import com.github.andrewlalis.running_every_day.data.RunRecord;
import javax.swing.table.AbstractTableModel;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class RunRecordTableModel extends AbstractTableModel {
private final DataSource dataSource;
private List<RunRecord> records;
private int cachedPageCount;
private Pagination currentPage;
public RunRecordTableModel() {
public RunRecordTableModel(DataSource dataSource) {
this.dataSource = dataSource;
this.cachedPageCount = 0;
this.records = new ArrayList<>(0);
this.currentPage = new Pagination(0, 20);
}
public void setPage(Page<RunRecord> page) {
this.records = page.items();
public void setPageSize(int size) {
this.currentPage = new Pagination(0, size, currentPage.sorts());
loadPage();
}
public void firstPage() {
this.currentPage = new Pagination(0, currentPage.size(), currentPage.sorts());
loadPage();
}
public boolean canGoToFirstPage() {
return currentPage.page() != 0;
}
public void lastPage() {
int pageIdx = cachedPageCount > 0 ? cachedPageCount - 1 : 0;
this.currentPage = new Pagination(pageIdx, currentPage.size(), currentPage.sorts());
loadPage();
}
public boolean canGoToLastPage() {
return currentPage.page() < cachedPageCount - 1;
}
public void nextPage() {
this.currentPage = currentPage.nextPage();
loadPage();
}
public boolean canGoToNextPage() {
return currentPage.page() < cachedPageCount - 1;
}
public void previousPage() {
this.currentPage = currentPage.previousPage();
loadPage();
}
public boolean canGoToPreviousPage() {
return currentPage.page() > 0;
}
public Pagination getPagination() {
return currentPage;
}
private void loadPage() {
try {
var page = dataSource.runRecords().findAll(currentPage);
cachedPageCount = (int) dataSource.runRecords().pageCount(currentPage.size());
records = page.items();
this.fireTableDataChanged();
} catch (SQLException e) {
e.printStackTrace();
records = new ArrayList<>();
currentPage = new Pagination(0, 20);
}
}
@Override
@ -38,7 +100,7 @@ public class RunRecordTableModel extends AbstractTableModel {
case 1 -> r.date().toString();
case 2 -> r.startTime().toString();
case 3 -> r.distanceKm().toPlainString();
case 4 -> r.duration().toString();
case 4 -> r.durationFormatted();
case 5 -> r.weightKg().toPlainString();
case 6 -> r.comment();
default -> null;

View File

@ -1,81 +1,76 @@
package com.github.andrewlalis.running_every_day.view;
import com.github.andrewlalis.running_every_day.data.DataSource;
import com.github.andrewlalis.running_every_day.data.Pagination;
import javax.swing.*;
import java.awt.*;
import java.sql.SQLException;
/**
* A panel for displaying a table view of run records, and various controls for
* navigating the data.
*/
public class RunRecordsPanel extends JPanel {
private final DataSource dataSource;
private final RunRecordTableModel tableModel = new RunRecordTableModel();
private final RunRecordTableModel tableModel;
private final JTextField currentPageField;
private final JComboBox<Integer> pageSizeSelector;
private final JButton firstPageButton;
private final JButton previousPageButton;
private final JButton nextPageButton;
private final JButton lastPageButton;
private Pagination currentPage;
public RunRecordsPanel(DataSource dataSource) {
super(new BorderLayout());
this.dataSource = dataSource;
this.currentPage = new Pagination(0, 20);
this.tableModel = new RunRecordTableModel(dataSource);
var table = new JTable(tableModel);
table.getTableHeader().setReorderingAllowed(false);
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
table.getColumnModel().getColumn(0).setMaxWidth(40);
table.getColumnModel().getColumn(1).setMaxWidth(80);
table.getColumnModel().getColumn(2).setMaxWidth(80);
table.getColumnModel().getColumn(3).setMaxWidth(100);
table.getColumnModel().getColumn(4).setMaxWidth(80);
table.getColumnModel().getColumn(5).setMaxWidth(80);
var scrollPane = new JScrollPane(table, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
this.add(scrollPane, BorderLayout.CENTER);
JPanel paginationPanel = new JPanel(new GridLayout(1, 6));
firstPageButton = new JButton("First Page");
firstPageButton.addActionListener(e -> {
currentPage = new Pagination(0, currentPage.size(), currentPage.sorts());
loadPage();
tableModel.firstPage();
updateButtonStates();
});
previousPageButton = new JButton("Previous Page");
previousPageButton.addActionListener(e -> {
currentPage = currentPage.previousPage();
loadPage();
tableModel.previousPage();
updateButtonStates();
});
nextPageButton = new JButton("Next Page");
nextPageButton.addActionListener(e -> {
currentPage = currentPage.nextPage();
loadPage();
tableModel.nextPage();
updateButtonStates();
});
lastPageButton = new JButton("Last Page");
lastPageButton.addActionListener(e -> {
try {
long pageCount = dataSource.runRecords().pageCount(currentPage.size());
if (pageCount <= 0) pageCount = 1;
currentPage = new Pagination((int) (pageCount - 1), currentPage.size(), currentPage.sorts());
loadPage();
} catch (SQLException ex) {
ex.printStackTrace();
}
tableModel.lastPage();
updateButtonStates();
});
JPanel currentPagePanel = new JPanel();
currentPagePanel.add(new JLabel("Current Page: "));
this.currentPageField = new JTextField("1", 3);
// TODO: Add document change listener.
currentPagePanel.add(this.currentPageField);
JPanel pageSizePanel = new JPanel();
pageSizePanel.add(new JLabel("Page Size: "));
pageSizeSelector = new JComboBox<>(new Integer[]{5, 10, 20, 50, 100, 500});
pageSizeSelector.setSelectedItem(this.currentPage.size());
JComboBox<Integer> pageSizeSelector = new JComboBox<>(new Integer[]{5, 10, 20, 50, 100, 500});
pageSizeSelector.setSelectedItem(tableModel.getPagination().size());
pageSizeSelector.addItemListener(e -> {
currentPage = new Pagination(0, (Integer) e.getItem(), currentPage.sorts());
loadPage();
int size = (int) e.getItem();
tableModel.setPageSize(size);
updateButtonStates();
});
pageSizePanel.add(pageSizeSelector);
paginationPanel.add(firstPageButton);
paginationPanel.add(previousPageButton);
paginationPanel.add(currentPagePanel);
@ -84,21 +79,22 @@ public class RunRecordsPanel extends JPanel {
paginationPanel.add(lastPageButton);
this.add(paginationPanel, BorderLayout.SOUTH);
SwingUtilities.invokeLater(() -> {
tableModel.firstPage();
updateButtonStates();
});
SwingUtilities.invokeLater(this::loadPage);
JPanel actionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
JButton addActionButton = new JButton("Add a Record");
actionsPanel.add(addActionButton);
this.add(actionsPanel, BorderLayout.NORTH);
}
private void loadPage() {
try {
this.tableModel.setPage(dataSource.runRecords().findAll(this.currentPage));
long pageCount = dataSource.runRecords().pageCount(this.currentPage.size());
this.firstPageButton.setEnabled(this.currentPage.page() != 0);
this.previousPageButton.setEnabled(this.currentPage.page() > 0);
this.nextPageButton.setEnabled(this.currentPage.page() < pageCount - 1);
this.lastPageButton.setEnabled(this.currentPage.page() != pageCount - 1);
this.currentPageField.setText(String.valueOf(this.currentPage.page() + 1));
} catch (SQLException e) {
e.printStackTrace();
}
private void updateButtonStates() {
firstPageButton.setEnabled(tableModel.canGoToFirstPage());
nextPageButton.setEnabled(tableModel.canGoToNextPage());
previousPageButton.setEnabled(tableModel.canGoToPreviousPage());
lastPageButton.setEnabled(tableModel.canGoToLastPage());
currentPageField.setText(String.valueOf(tableModel.getPagination().page() + 1));
}
}

View File

@ -0,0 +1,27 @@
package com.github.andrewlalis.running_every_day.view;
import com.github.andrewlalis.running_every_day.data.DataSource;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.sql.SQLException;
/**
* Simple listener that closes the given datasource when the window closes.
*/
public class WindowDataSourceCloser extends WindowAdapter {
private final DataSource dataSource;
public WindowDataSourceCloser(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void windowClosing(WindowEvent e) {
try {
dataSource.close();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}