diff --git a/src/main/java/listening/linuxsuren/github/io/componet/CachedImage.java b/src/main/java/listening/linuxsuren/github/io/componet/CachedImage.java new file mode 100644 index 0000000..2f36768 --- /dev/null +++ b/src/main/java/listening/linuxsuren/github/io/componet/CachedImage.java @@ -0,0 +1,26 @@ +package listening.linuxsuren.github.io.componet; + +import listening.linuxsuren.github.io.server.CacheServer; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; + +public class CachedImage { + public static ImageIcon ScaledImageIcon(String url) { + return ScaledImageIcon(url, 80, 80); + } + + public static ImageIcon ScaledImageIcon(String url, int width, int height) { + try { + BufferedImage image = ImageIO.read(CacheServer.wrapURL(url)); + + return new ImageIcon(image.getScaledInstance(width, height, Image.SCALE_SMOOTH)); + } catch (IOException e) { + e.printStackTrace(); + } + return null; // TODO provide a default image + } +} diff --git a/src/main/java/listening/linuxsuren/github/io/componet/CollectionCardPanel.java b/src/main/java/listening/linuxsuren/github/io/componet/CollectionCardPanel.java index 97620c1..c5f8bf2 100644 --- a/src/main/java/listening/linuxsuren/github/io/componet/CollectionCardPanel.java +++ b/src/main/java/listening/linuxsuren/github/io/componet/CollectionCardPanel.java @@ -45,21 +45,13 @@ public void asyncLoad(CollectionService collectionService) { new Thread(() -> { collectionService.loadPodcast(podcast); - try { - BufferedImage image = ImageIO.read(CacheServer.wrapURL(podcast.getLogoURL())); + JLabel label = new JLabel(); + label.setMinimumSize(new Dimension(80, 80)); + label.setIcon(CachedImage.ScaledImageIcon(podcast.getLogoURL())); + label.addMouseListener(mouseListener); + label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - JLabel label = new JLabel(); - label.setMinimumSize(new Dimension(80, 80)); - label.setIcon(new ImageIcon(image.getScaledInstance(80, 80, Image.SCALE_SMOOTH))); - label.addMouseListener(mouseListener); - label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - - add(label); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } + add(label); repaint(); revalidate(); diff --git a/src/main/java/listening/linuxsuren/github/io/componet/CollectionPanel.java b/src/main/java/listening/linuxsuren/github/io/componet/CollectionPanel.java index 6958ae9..e0f70f4 100644 --- a/src/main/java/listening/linuxsuren/github/io/componet/CollectionPanel.java +++ b/src/main/java/listening/linuxsuren/github/io/componet/CollectionPanel.java @@ -39,7 +39,7 @@ public class CollectionPanel extends JPanel { private Podcast podcast; private JPanel episodeListPanel = new JPanel(); private final JComboBox yearBox = new JComboBox<>(); - private final JTextField searchField = new JTextField(15);; + private final JTextField searchField = new JTextField(15); private List eventList = new ArrayList<>(); public CollectionPanel(CollectionService collectionService) { diff --git a/src/main/java/listening/linuxsuren/github/io/componet/MainPanel.java b/src/main/java/listening/linuxsuren/github/io/componet/MainPanel.java index 58235e1..69e08fc 100644 --- a/src/main/java/listening/linuxsuren/github/io/componet/MainPanel.java +++ b/src/main/java/listening/linuxsuren/github/io/componet/MainPanel.java @@ -104,11 +104,7 @@ private JPanel createCenterPanel() { explorePanel.addEvent((e) -> { CollectionPanel panel = new CollectionPanel(collectionService); panel.loadPodcast(e); - panel.addEvent((ee) -> { - EpisodePanel episodePanel = new EpisodePanel(ee); - episodePanel.setPlayEvent(player); - breadCrumbPanel.append(episodePanel); - }); + panel.addEvent(showEpisode); JScrollPane scrollPane = new JScrollPane(panel); scrollPane.setName(e.getName()); @@ -163,6 +159,7 @@ private JPopupMenu createPopupMenu() { JMenuItem reloadMenu = new JMenuItem("Reload"); JMenuItem openConfigMenu = new JMenuItem("Open Config"); JMenuItem addRssMenu = new JMenuItem("Add RSS"); + JMenuItem recentEpisodeMenu = new JMenuItem("Recent Episodes"); laterMenu.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { @@ -192,11 +189,32 @@ public void actionPerformed(ActionEvent e) { addPodcastDialog.setVisible(true); } }); + recentEpisodeMenu.addActionListener(new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + RecentEpisodePanel recentEpisodePanel = new RecentEpisodePanel(); + recentEpisodePanel.reload(); + recentEpisodePanel.addEvent(showEpisode); + + ScrollPane recentEpisodeScrollPanel = new ScrollPane(); + recentEpisodeScrollPanel.add(recentEpisodePanel); + recentEpisodeScrollPanel.setName("Recent"); + + breadCrumbPanel.append(recentEpisodeScrollPanel); + } + }); popupMenu.add(laterMenu); popupMenu.add(reloadMenu); popupMenu.add(openConfigMenu); popupMenu.add(addRssMenu); + popupMenu.add(recentEpisodeMenu); return popupMenu; } + + private EpisodeEvent showEpisode = ((Episode episode) -> { + EpisodePanel episodePanel = new EpisodePanel(episode); + episodePanel.setPlayEvent(player); + breadCrumbPanel.append(episodePanel); + }); } diff --git a/src/main/java/listening/linuxsuren/github/io/componet/RecentEpisodePanel.java b/src/main/java/listening/linuxsuren/github/io/componet/RecentEpisodePanel.java new file mode 100644 index 0000000..1f8658b --- /dev/null +++ b/src/main/java/listening/linuxsuren/github/io/componet/RecentEpisodePanel.java @@ -0,0 +1,124 @@ +/* +Copyright 2024 LinuxSuRen. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package listening.linuxsuren.github.io.componet; + +import listening.linuxsuren.github.io.service.Episode; +import listening.linuxsuren.github.io.service.SimpleCollectionService; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; + +public class RecentEpisodePanel extends JPanel implements ReloadAble { + private final JComboBox recentBox = new JComboBox<>(); + private final JPanel centerPanel = new JPanel(); + private List eventList = new ArrayList<>(); + + public RecentEpisodePanel() { + JPanel toolbar = createToolbar(); + + // set the center panel + centerPanel.setLayout(new BoxLayout(centerPanel, BoxLayout.Y_AXIS)); + + this.setLayout(new BorderLayout()); + this.add(toolbar, BorderLayout.NORTH); + this.add(centerPanel, BorderLayout.CENTER); + } + + private JPanel createToolbar() { + recentBox.addItem(RecentType.Week); + recentBox.addItem(RecentType.BiWeek); + recentBox.addItem(RecentType.Month); + recentBox.addItemListener((e) -> reload()); + + JPanel panel = new JPanel(); + panel.add(recentBox); + return panel; + } + + @Override + public void reload() { + centerPanel.removeAll(); + + new Thread(() -> { + SimpleCollectionService service = new SimpleCollectionService(); + + RecentType recentType = (RecentType) recentBox.getSelectedItem(); + final ZonedDateTime expectedRange = + ZonedDateTime.now().minusDays(recentType == null ? RecentType.Week.getDays() : recentType.getDays()); + service.getAll().forEach(podcast -> { + service.getEpisode(podcast).stream().filter((e) -> e.getPublishDate().isAfter(expectedRange)). + forEach(episode -> { + EpisodeCard card = new EpisodeCard(episode); + card.addTrigger(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + eventList.forEach((episodeEvent -> { + episodeEvent.trigger(episode); + })); + } + }); + centerPanel.add(card); + }); + + revalidate(); + }); + }).start(); + } + + public void addEvent(EpisodeEvent e) { + eventList.add(e); + } +} + +class EpisodeCard extends JPanel { + private final JLabel logo = new JLabel(); + + public EpisodeCard(Episode episode) { + JLabel title = new JLabel(); + title.setText(episode.getTitle()); + BorderUtil.setInsideBorder(title, 10); + + logo.setCursor(new Cursor(Cursor.HAND_CURSOR)); + logo.setIcon(CachedImage.ScaledImageIcon(episode.getLogoURL())); + + this.setLayout(new BorderLayout()); + BorderUtil.setInsideBorder(this, 10); + this.add(logo, BorderLayout.WEST); + this.add(title, BorderLayout.CENTER); + } + + public void addTrigger(MouseAdapter e) { + logo.addMouseListener(e); + } +} + +enum RecentType { + Week, BiWeek, Month; + + int getDays() { + switch (this) { + case Week: return 7; + case BiWeek: return 14; + default: return 30; + } + } +} diff --git a/src/main/java/listening/linuxsuren/github/io/componet/ReloadAble.java b/src/main/java/listening/linuxsuren/github/io/componet/ReloadAble.java new file mode 100644 index 0000000..7c4055f --- /dev/null +++ b/src/main/java/listening/linuxsuren/github/io/componet/ReloadAble.java @@ -0,0 +1,21 @@ +/* +Copyright 2024 LinuxSuRen. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package listening.linuxsuren.github.io.componet; + +public interface ReloadAble { + void reload(); +} diff --git a/src/main/java/listening/linuxsuren/github/io/service/Episode.java b/src/main/java/listening/linuxsuren/github/io/service/Episode.java index fadc03b..7edbf89 100644 --- a/src/main/java/listening/linuxsuren/github/io/service/Episode.java +++ b/src/main/java/listening/linuxsuren/github/io/service/Episode.java @@ -31,6 +31,7 @@ public class Episode { private String link; private ZonedDateTime publishDate; private Duration duration; + private String logoURL; public Episode() {} @@ -130,6 +131,14 @@ public void setDuration(Duration duration) { this.duration = duration; } + public String getLogoURL() { + return logoURL; + } + + public void setLogoURL(String logoURL) { + this.logoURL = logoURL; + } + @Override public int hashCode() { if (audioURL == null) { diff --git a/src/main/java/listening/linuxsuren/github/io/service/SimpleCollectionService.java b/src/main/java/listening/linuxsuren/github/io/service/SimpleCollectionService.java index 8a1be5d..41090fc 100644 --- a/src/main/java/listening/linuxsuren/github/io/service/SimpleCollectionService.java +++ b/src/main/java/listening/linuxsuren/github/io/service/SimpleCollectionService.java @@ -19,6 +19,7 @@ import be.ceau.podcastparser.PodcastParser; import be.ceau.podcastparser.exceptions.InvalidFeedFormatException; import be.ceau.podcastparser.models.core.Feed; +import be.ceau.podcastparser.models.support.Image; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; @@ -148,6 +149,7 @@ public List parse(String rssAddress) { episode.setRssURL(rssAddress); episode.setDuration(i.getDuration()); episode.setPublishDate(i.getPubDate()); + episode.setLogoURL(feed.getImages().stream().findFirst().orElse(new Image()).getUrl()); if (i.getEnclosure() != null) { episode.setAudioURL(i.getEnclosure().getUrl()); episode.setLength(i.getEnclosure().getLength());