Feature add incompletely played episodes to playback history

### Description

Resolves #7702

This PR adds partially played episodes to the Playlist History by repurposing the database column `KEY_PLAYBACK_COMPLETION_DATE` as `KEY_LAST_PLAYED_TIME_HISTORY`, as discussed here: https://github.com/AntennaPod/AntennaPod/issues/7702#issue-2933349528

|Before|After|
|---|---|
| ||

# Questions

Assuming the solution in this PR is correct, there are still some variables and functions that reference the old constant name KEY_PLAYBACK_COMPLETION_DATE. Should we rename them as well for consistency?

https://github.com/dominikfill/AntennaPod/blob/64112ccefd673cd06a76d6920ba39e4f4c0f4774/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java#L559-L569

https://github.com/dominikfill/AntennaPod/blob/64112ccefd673cd06a76d6920ba39e4f4c0f4774/model/src/main/java/de/danoeh/antennapod/model/feed/FeedMedia.java#L263-L271

https://github.com/dominikfill/AntennaPod/blob/64112ccefd673cd06a76d6920ba39e4f4c0f4774/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemFilterQuery.java#L25

https://github.com/dominikfill/AntennaPod/blob/64112ccefd673cd06a76d6920ba39e4f4c0f4774/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedMediaCursor.java#L31

## Checklist
- [x] I have read the contribution guidelines: https://github.com/AntennaPod/AntennaPod/blob/develop/CONTRIBUTING.md#submit-a-pull-request
- [x] I have performed a self-review of my code
- [x] I have run the automated code checks using `./gradlew checkstyle spotbugsPlayDebug spotbugsDebug :app:lintPlayDebug`
- [x] My code follows the style guidelines of the AntennaPod project: https://github.com/AntennaPod/AntennaPod/wiki/Code-style
- [x] I have mentioned the corresponding issue and the relevant keyword (e.g., "Closes: #xy") in the description (see https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
- [ ] If it is a core feature, I have added automated tests

Add episodes to playback history even when not completing them
### Checklist

- [x] I have used the search function for [**OPEN**](https://github.com/AntennaPod/AntennaPod/issues) issues to see if someone else has already submitted the same feature request.
- [x] I have **also** used the search function for [**CLOSED**](https://github.com/AntennaPod/AntennaPod/issues?q=is%3Aissue+is%3Aclosed) issues to see if the feature was already implemented and is just waiting to be released, or if the feature was rejected.
- [x] I will describe the problem with as much detail as possible.
- [x] This request contains only one single feature, **not** a list of multiple (related) features.

### App version

3.7.0

### Feature or improvement you want

Sometimes I play an episode but do not complete it. I expect it to be displayed on the history screen nonetheless.

### Why should this be added?

_No response_

### Screenshots / Drawings / Technical details

Currently we have a column `completed_date`. Re-purpose that column into storing `last_played_date_history`. Note that there is also `last_played_date` which is for statistics. These should stay separate fields. Because columns cannot be renamed, just rename the references to it in Java (PodDbAdaper, FeedItem, etc). Fill that column with the last played date, just like the other column.

Hi, I would be interested to work on this. Just for clarification:
your idea is to repurpose `completion_date` to store the same value as `last_played_time`?

As of sqlite v. 3.28 its possible to rename columns, from what i could find:
https://stackoverflow.com/questions/805363/how-do-i-rename-a-column-in-an-sqlite-database-table

> Just for clarification: your idea is to repurpose completion_date to store the same value as last_played_time?

Exactly :) They wouldn't always be the same value, in case one of them is cleared (clear statistics vs clear playback history), but generally yes

> As of sqlite v. 3.28 its possible to rename columns

Ah, interesting. I didn't know that. However, we support Android versions that don't have sqlite 3.28 yet, so I would just rename the references in Java but keep the old sqlite column names internally
Thanks for clearing that up :)

diff --git a/app/src/main/java/de/danoeh/antennapod/ui/swipeactions/RemoveFromHistorySwipeAction.java b/app/src/main/java/de/danoeh/antennapod/ui/swipeactions/RemoveFromHistorySwipeAction.java
index 4e89be62308..3d99a4cba84 100644
--- a/app/src/main/java/de/danoeh/antennapod/ui/swipeactions/RemoveFromHistorySwipeAction.java
+++ b/app/src/main/java/de/danoeh/antennapod/ui/swipeactions/RemoveFromHistorySwipeAction.java
@@ -38,10 +38,10 @@ public String getTitle(Context context) {

     @Override
     public void performAction(FeedItem item, Fragment fragment, FeedItemFilter filter) {
-        Date playbackCompletionDate = item.getMedia().getPlaybackCompletionDate();
+        Date lastPlayedTimeHistory = item.getMedia().getLastPlayedTimeHistory();
         DBWriter.deleteFromPlaybackHistory(item);
         EventBus.getDefault().post(new MessageEvent(fragment.getString(R.string.removed_history_label),
-                context -> DBWriter.addItemToPlaybackHistory(item.getMedia(), playbackCompletionDate),
+                context -> DBWriter.addItemToPlaybackHistory(item.getMedia(), lastPlayedTimeHistory),
                 fragment.getString(R.string.undo)));
     }

diff --git a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItemFilter.java b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItemFilter.java
index 1faeda01220..08755fb18bc 100644
--- a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItemFilter.java
+++ b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItemFilter.java
@@ -117,7 +117,7 @@ public boolean matches(FeedItem item) {
         } else if (showNotFavorite && item.isTagged(FeedItem.TAG_FAVORITE)) {
             return false;
         } else if (showInHistory && item.getMedia() != null
-                && item.getMedia().getPlaybackCompletionDate().getTime() == 0) {
+                && item.getMedia().getLastPlayedTimeHistory().getTime() == 0) {
             return false;
         } else if (!includeNotSubscribed && item.getFeed() != null
                 && item.getFeed().getState() != Feed.STATE_SUBSCRIBED) {
diff --git a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedMedia.java b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedMedia.java
index 88d6a77141c..91e41ce229f 100644
--- a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedMedia.java
+++ b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedMedia.java
@@ -38,12 +38,12 @@ public class FeedMedia implements Playable {
     private long downloadDate;
     private int duration;
     private int position; // Current position in file
-    private long lastPlayedTime; // Last time this media was played (in ms)
+    private long lastPlayedTimeStatistics; // Last time this media was played (in ms)
     private int playedDuration; // How many ms of this file have been played
     private long size; // File size in Byte
     private String mimeType;
     @Nullable private volatile FeedItem item;
-    private Date playbackCompletionDate;
+    private Date lastPlayedTimeHistory;
     private int startPosition = -1;
     private int playedDurationWhenStarted;

@@ -66,8 +66,8 @@ public FeedMedia(FeedItem i, String downloadUrl, long size,

     public FeedMedia(long id, FeedItem item, int duration, int position,
                      long size, String mimeType, String localFileUrl, String downloadUrl,
-                     long downloadDate, Date playbackCompletionDate, int playedDuration,
-                     long lastPlayedTime) {
+                     long downloadDate, Date lastPlayedTimeHistory, int playedDuration,
+                     long lastPlayedTimeStatistics) {
         this.localFileUrl = localFileUrl;
         this.downloadUrl = downloadUrl;
         this.downloadDate = downloadDate;
@@ -80,17 +80,17 @@ public FeedMedia(long id, FeedItem item, int duration, int position,
         this.playedDurationWhenStarted = playedDuration;
         this.size = size;
         this.mimeType = mimeType;
-        this.playbackCompletionDate = playbackCompletionDate == null
-                ? null : (Date) playbackCompletionDate.clone();
-        this.lastPlayedTime = lastPlayedTime;
+        this.lastPlayedTimeHistory = lastPlayedTimeHistory == null
+                ? null : (Date) lastPlayedTimeHistory.clone();
+        this.lastPlayedTimeStatistics = lastPlayedTimeStatistics;
     }

     public FeedMedia(long id, FeedItem item, int duration, int position,
                      long size, String mimeType, String localFileUrl, String downloadUrl,
-                     long downloadDate, Date playbackCompletionDate, int playedDuration,
-                     Boolean hasEmbeddedPicture, long lastPlayedTime) {
+                     long downloadDate, Date lastPlayedTimeHistory, int playedDuration,
+                     Boolean hasEmbeddedPicture, long lastPlayedTimeStatistics) {
         this(id, item, duration, position, size, mimeType, localFileUrl, downloadUrl, downloadDate,
-                playbackCompletionDate, playedDuration, lastPlayedTime);
+                lastPlayedTimeHistory, playedDuration, lastPlayedTimeStatistics);
         this.hasEmbeddedPicture = hasEmbeddedPicture;
     }

@@ -178,8 +178,8 @@ public void setDuration(int duration) {
     }

     @Override
-    public void setLastPlayedTime(long lastPlayedTime) {
-        this.lastPlayedTime = lastPlayedTime;
+    public void setLastPlayedTimeStatistics(long lastPlayedTimeStatistics) {
+        this.lastPlayedTimeStatistics = lastPlayedTimeStatistics;
     }

     public int getPlayedDuration() {
@@ -199,8 +199,8 @@ public int getPosition() {
     }

     @Override
-    public long getLastPlayedTime() {
-        return lastPlayedTime;
+    public long getLastPlayedTimeStatistics() {
+        return lastPlayedTimeStatistics;
     }

     public void setPosition(int position) {
@@ -260,14 +260,14 @@ public void setItem(FeedItem item) {
         }
     }

-    public Date getPlaybackCompletionDate() {
-        return playbackCompletionDate == null
-                ? null : (Date) playbackCompletionDate.clone();
+    public Date getLastPlayedTimeHistory() {
+        return lastPlayedTimeHistory == null
+                ? null : (Date) lastPlayedTimeHistory.clone();
     }

-    public void setPlaybackCompletionDate(Date playbackCompletionDate) {
-        this.playbackCompletionDate = playbackCompletionDate == null
-                ? null : (Date) playbackCompletionDate.clone();
+    public void setLastPlayedTimeHistory(Date lastPlayedTimeHistory) {
+        this.lastPlayedTimeHistory = lastPlayedTimeHistory == null
+                ? null : (Date) lastPlayedTimeHistory.clone();
     }

     public boolean isInProgress() {
@@ -298,9 +298,9 @@ public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(localFileUrl);
         dest.writeString(downloadUrl);
         dest.writeLong(downloadDate);
-        dest.writeLong((playbackCompletionDate != null) ? playbackCompletionDate.getTime() : 0);
+        dest.writeLong((lastPlayedTimeHistory != null) ? lastPlayedTimeHistory.getTime() : 0);
         dest.writeInt(playedDuration);
-        dest.writeLong(lastPlayedTime);
+        dest.writeLong(lastPlayedTimeStatistics);
     }

     @Override
diff --git a/model/src/main/java/de/danoeh/antennapod/model/playback/Playable.java b/model/src/main/java/de/danoeh/antennapod/model/playback/Playable.java
index 8bf1c4e2065..c3fda817044 100644
--- a/model/src/main/java/de/danoeh/antennapod/model/playback/Playable.java
+++ b/model/src/main/java/de/danoeh/antennapod/model/playback/Playable.java
@@ -61,7 +61,7 @@ public interface Playable extends Parcelable, Serializable {
      * Returns last time (in ms) when this playable was played or 0
      * if last played time is unknown.
      */
-    long getLastPlayedTime();
+    long getLastPlayedTimeStatistics();

     /**
      * Returns the description of the item, if available.
@@ -100,7 +100,7 @@ public interface Playable extends Parcelable, Serializable {
     /**
      * @param lastPlayedTimestamp  timestamp in ms
      */
-    void setLastPlayedTime(long lastPlayedTimestamp);
+    void setLastPlayedTimeStatistics(long lastPlayedTimestamp);

     /**
      * This method should be called every time playback starts on this object.
diff --git a/model/src/main/java/de/danoeh/antennapod/model/playback/RemoteMedia.java b/model/src/main/java/de/danoeh/antennapod/model/playback/RemoteMedia.java
index c979488a6a7..4e98add2c79 100644
--- a/model/src/main/java/de/danoeh/antennapod/model/playback/RemoteMedia.java
+++ b/model/src/main/java/de/danoeh/antennapod/model/playback/RemoteMedia.java
@@ -40,7 +40,7 @@ public class RemoteMedia implements Playable {
     private List<Chapter> chapters;
     private int duration;
     private int position;
-    private long lastPlayedTime;
+    private long lastPlayedTimeStatistics;

     public RemoteMedia(String downloadUrl, String itemId, String feedUrl, String feedTitle,
                        String episodeTitle, String episodeLink, String feedAuthor,
@@ -159,8 +159,8 @@ public int getPosition() {
     }

     @Override
-    public long getLastPlayedTime() {
-        return lastPlayedTime;
+    public long getLastPlayedTimeStatistics() {
+        return lastPlayedTimeStatistics;
     }

     @Override
@@ -194,8 +194,8 @@ public void setDuration(int newDuration) {
     }

     @Override
-    public void setLastPlayedTime(long lastPlayedTimestamp) {
-        lastPlayedTime = lastPlayedTimestamp;
+    public void setLastPlayedTimeStatistics(long lastPlayedTimestamp) {
+        lastPlayedTimeStatistics = lastPlayedTimestamp;
     }

     @Override
@@ -255,7 +255,7 @@ public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(notes);
         dest.writeInt(duration);
         dest.writeInt(position);
-        dest.writeLong(lastPlayedTime);
+        dest.writeLong(lastPlayedTimeStatistics);
     }

     public static final Parcelable.Creator<RemoteMedia> CREATOR = new Parcelable.Creator<RemoteMedia>() {
@@ -266,7 +266,7 @@ public RemoteMedia createFromParcel(Parcel in) {
                     in.readString(), in.readString(), new Date(in.readLong()), in.readString());
             result.setDuration(in.readInt());
             result.setPosition(in.readInt());
-            result.setLastPlayedTime(in.readLong());
+            result.setLastPlayedTimeStatistics(in.readLong());
             return result;
         }

diff --git a/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/AutoDownloadManager.java b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/AutoDownloadManager.java
index 2eb1d1b56ea..82cfc522db0 100644
--- a/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/AutoDownloadManager.java
+++ b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/AutoDownloadManager.java
@@ -29,7 +29,7 @@ public static void setInstance(AutoDownloadManager instance) {

     /**
      * Removed downloaded episodes outside of the queue if the episode cache is full. Episodes with a smaller
-     * 'playbackCompletionDate'-value will be deleted first.
+     * 'lastPlayedTimeHistory'-value will be deleted first.
      *

      * This method should NOT be executed on the GUI thread.
      *
diff --git a/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/episode/autodownload/APCleanupAlgorithm.java b/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/episode/autodownload/APCleanupAlgorithm.java
index c8db7e7fd11..011f0169544 100644
--- a/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/episode/autodownload/APCleanupAlgorithm.java
+++ b/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/episode/autodownload/APCleanupAlgorithm.java
@@ -47,8 +47,8 @@ public int performCleanup(Context context, int numberOfEpisodesToDelete) {
         List<FeedItem> delete;

         Collections.sort(candidates, (lhs, rhs) -> {
-            Date l = lhs.getMedia().getPlaybackCompletionDate();
-            Date r = rhs.getMedia().getPlaybackCompletionDate();
+            Date l = lhs.getMedia().getLastPlayedTimeHistory();
+            Date r = rhs.getMedia().getLastPlayedTimeHistory();

             if (l == null) {
                 l = new Date();
@@ -105,8 +105,8 @@ private List<FeedItem> getCandidates() {
                 // make sure this candidate was played at least the proper amount of days prior
                 // to now
                 if (media != null
-                        && media.getPlaybackCompletionDate() != null
-                        && media.getPlaybackCompletionDate().before(mostRecentDateForDeletion)) {
+                        && media.getLastPlayedTimeHistory() != null
+                        && media.getLastPlayedTimeHistory().before(mostRecentDateForDeletion)) {
                     candidates.add(item);
                 }
             }
diff --git a/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/episode/autodownload/AutoDownloadManagerImpl.java b/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/episode/autodownload/AutoDownloadManagerImpl.java
index 2b0eb4d6293..dfcfaae9a80 100644
--- a/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/episode/autodownload/AutoDownloadManagerImpl.java
+++ b/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/episode/autodownload/AutoDownloadManagerImpl.java
@@ -43,7 +43,7 @@ public Future<?> autodownloadUndownloadedItems(final Context context) {

     /**
      * Removed downloaded episodes outside of the queue if the episode cache is full. Episodes with a smaller
-     * 'playbackCompletionDate'-value will be deleted first.
+     * 'lastPlayedTimeHistory'-value will be deleted first.
      *

      * This method should NOT be executed on the GUI thread.
      *
diff --git a/playback/base/src/main/java/de/danoeh/antennapod/playback/base/RewindAfterPauseUtils.java b/playback/base/src/main/java/de/danoeh/antennapod/playback/base/RewindAfterPauseUtils.java
index 3ea8e59c560..f8af620d7f1 100644
--- a/playback/base/src/main/java/de/danoeh/antennapod/playback/base/RewindAfterPauseUtils.java
+++ b/playback/base/src/main/java/de/danoeh/antennapod/playback/base/RewindAfterPauseUtils.java
@@ -19,12 +19,12 @@ public abstract class RewindAfterPauseUtils {

     /**
      * @param currentPosition  current position in a media file in ms
-     * @param lastPlayedTime  timestamp when was media paused
+     * @param lastPlayedTimeStatistics  timestamp when was media paused
      * @return  new rewinded position for playback in milliseconds
      */
-    public static int calculatePositionWithRewind(int currentPosition, long lastPlayedTime) {
-        if (currentPosition > 0 && lastPlayedTime > 0) {
-            long elapsedTime = System.currentTimeMillis() - lastPlayedTime;
+    public static int calculatePositionWithRewind(int currentPosition, long lastPlayedTimeStatistics) {
+        if (currentPosition > 0 && lastPlayedTimeStatistics > 0) {
+            long elapsedTime = System.currentTimeMillis() - lastPlayedTimeStatistics;
             long rewindTime = 0;

             if (elapsedTime > ELAPSED_TIME_FOR_LONG_REWIND) {
diff --git a/playback/cast/src/play/java/de/danoeh/antennapod/playback/cast/CastPsmp.java b/playback/cast/src/play/java/de/danoeh/antennapod/playback/cast/CastPsmp.java
index 07a3639d962..44abdabab28 100644
--- a/playback/cast/src/play/java/de/danoeh/antennapod/playback/cast/CastPsmp.java
+++ b/playback/cast/src/play/java/de/danoeh/antennapod/playback/cast/CastPsmp.java
@@ -334,7 +334,7 @@ private void playMediaObject(@NonNull final Playable playable, final boolean for
     public void resume() {
         int newPosition = RewindAfterPauseUtils.calculatePositionWithRewind(
                         media.getPosition(),
-                        media.getLastPlayedTime());
+                        media.getLastPlayedTimeStatistics());
         seekTo(newPosition);
         remoteMediaClient.play();
     }
@@ -353,7 +353,7 @@ public void prepare() {
             if (position > 0) {
                 position = RewindAfterPauseUtils.calculatePositionWithRewind(
                         position,
-                        media.getLastPlayedTime());
+                        media.getLastPlayedTimeStatistics());
             }
             remoteMediaClient.load(new MediaLoadRequestData.Builder()
                     .setMediaInfo(remoteMedia)
diff --git a/playback/service/src/main/java/de/danoeh/antennapod/playback/service/internal/LocalPSMP.java b/playback/service/src/main/java/de/danoeh/antennapod/playback/service/internal/LocalPSMP.java
index 37cacd262f7..543e5908bee 100644
--- a/playback/service/src/main/java/de/danoeh/antennapod/playback/service/internal/LocalPSMP.java
+++ b/playback/service/src/main/java/de/danoeh/antennapod/playback/service/internal/LocalPSMP.java
@@ -233,7 +233,7 @@ public void resume() {

                 if (playerStatus == PlayerStatus.PREPARED && media.getPosition() > 0) {
                     int newPosition = RewindAfterPauseUtils.calculatePositionWithRewind(
-                            media.getPosition(), media.getLastPlayedTime());
+                            media.getPosition(), media.getLastPlayedTimeStatistics());
                     seekTo(newPosition);
                 }
                 mediaPlayer.start();
diff --git a/playback/service/src/main/java/de/danoeh/antennapod/playback/service/internal/PlayableUtils.java b/playback/service/src/main/java/de/danoeh/antennapod/playback/service/internal/PlayableUtils.java
index beef456f95a..7e71db953ed 100644
--- a/playback/service/src/main/java/de/danoeh/antennapod/playback/service/internal/PlayableUtils.java
+++ b/playback/service/src/main/java/de/danoeh/antennapod/playback/service/internal/PlayableUtils.java
@@ -1,5 +1,7 @@
 package de.danoeh.antennapod.playback.service.internal;

+import java.util.Date;
+
 import de.danoeh.antennapod.storage.database.DBWriter;
 import de.danoeh.antennapod.model.feed.FeedItem;
 import de.danoeh.antennapod.model.feed.FeedMedia;
@@ -17,10 +19,11 @@ public abstract class PlayableUtils {
      */
     public static void saveCurrentPosition(Playable playable, int newPosition, long timestamp) {
         playable.setPosition(newPosition);
-        playable.setLastPlayedTime(timestamp);
+        playable.setLastPlayedTimeStatistics(timestamp);

         if (playable instanceof FeedMedia) {
             FeedMedia media = (FeedMedia) playable;
+            media.setLastPlayedTimeHistory(new Date(timestamp));
             FeedItem item = media.getItem();
             if (item != null && item.isNew()) {
                 DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId());
diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java
index 9daa1c137ca..b0b7009daa6 100644
--- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java
+++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java
@@ -45,7 +45,7 @@ static void upgrade(final SQLiteDatabase db, final int oldVersion, final int new
         }
         if (oldVersion <= 7) {
             db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
-                    + " ADD COLUMN " + PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE
+                    + " ADD COLUMN " + PodDBAdapter.KEY_LAST_PLAYED_TIME_HISTORY
                     + " INTEGER");
         }
         if (oldVersion <= 8) {
@@ -210,7 +210,7 @@ static void upgrade(final SQLiteDatabase db, final int oldVersion, final int new
         }
         if (oldVersion < 1040002) {
             db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
-                    + " ADD COLUMN " + PodDBAdapter.KEY_LAST_PLAYED_TIME + " INTEGER DEFAULT 0");
+                    + " ADD COLUMN " + PodDBAdapter.KEY_LAST_PLAYED_TIME_STATISTICS + " INTEGER DEFAULT 0");
         }
         if (oldVersion < 1040013) {
             db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_PUBDATE);
diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBWriter.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBWriter.java
index 93b961f9ae6..62e2d9504e9 100644
--- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBWriter.java
+++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBWriter.java
@@ -296,16 +296,16 @@ public static Future<?> addItemToPlaybackHistory(FeedMedia media) {
      * current date regardless of the current value.
      *
      * @param media FeedMedia that should be added to the playback history.
-     * @param date PlaybackCompletionDate for media
+     * @param date LastPlayedTimeHistory for media
      */
     public static Future<?> addItemToPlaybackHistory(final FeedMedia media, Date date) {
         return runOnDbThread(() -> {
             Log.d(TAG, "Adding item to playback history");
-            media.setPlaybackCompletionDate(date);
+            media.setLastPlayedTimeHistory(date);

             PodDBAdapter adapter = PodDBAdapter.getInstance();
             adapter.open();
-            adapter.setFeedMediaPlaybackCompletionDate(media);
+            adapter.setFeedMediaLastPlayedTimeHistory(media);
             adapter.close();
             EventBus.getDefault().post(PlaybackHistoryEvent.listUpdated());

diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/FeedItemPermutors.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/FeedItemPermutors.java
index a9c59e2bb52..7955ebbbbc4 100644
--- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/FeedItemPermutors.java
+++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/FeedItemPermutors.java
@@ -79,8 +79,8 @@ public static Permutor<FeedItem> getPermutor(@NonNull SortOrder sortOrder) {
                 comparator = (f1, f2) -> Long.compare(size(f2), size(f1));
                 break;
             case COMPLETION_DATE_NEW_OLD:
-                comparator = (f1, f2) -> f2.getMedia().getPlaybackCompletionDate()
-                        .compareTo(f1.getMedia().getPlaybackCompletionDate());
+                comparator = (f1, f2) -> f2.getMedia().getLastPlayedTimeHistory()
+                        .compareTo(f1.getMedia().getLastPlayedTimeHistory());
                 break;
             default:
                 throw new IllegalArgumentException("Permutor not implemented");
diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java
index e4b612b38bf..edc6934b462 100644
--- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java
+++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java
@@ -95,7 +95,6 @@ public class PodDBAdapter {
     public static final String KEY_FEED_IDENTIFIER = "feed_identifier";
     public static final String KEY_REASON_DETAILED = "reason_detailed";
     public static final String KEY_DOWNLOADSTATUS_TITLE = "title";
-    public static final String KEY_PLAYBACK_COMPLETION_DATE = "playback_completion_date";
     public static final String KEY_AUTO_DOWNLOAD_ENABLED = "auto_download"; // Both tables use the same key
     public static final String KEY_KEEP_UPDATED = "keep_updated";
     public static final String KEY_AUTO_DELETE_ACTION = "auto_delete_action";
@@ -109,7 +108,8 @@ public class PodDBAdapter {
     public static final String KEY_SORT_ORDER = "sort_order";
     public static final String KEY_LAST_UPDATE_FAILED = "last_update_failed";
     public static final String KEY_HAS_EMBEDDED_PICTURE = "has_embedded_picture";
-    public static final String KEY_LAST_PLAYED_TIME = "last_played_time";
+    public static final String KEY_LAST_PLAYED_TIME_HISTORY = "playback_completion_date";
+    public static final String KEY_LAST_PLAYED_TIME_STATISTICS = "last_played_time";
     public static final String KEY_INCLUDE_FILTER = "include_filter";
     public static final String KEY_EXCLUDE_FILTER = "exclude_filter";
     public static final String KEY_MINIMAL_DURATION_FILTER = "minimal_duration_filter";
@@ -197,11 +197,11 @@ public class PodDBAdapter {
             + " INTEGER," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL
             + " TEXT," + KEY_DOWNLOAD_DATE + " INTEGER," + KEY_POSITION
             + " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT,"
-            + KEY_PLAYBACK_COMPLETION_DATE + " INTEGER,"
+            + KEY_LAST_PLAYED_TIME_HISTORY + " INTEGER,"
             + KEY_FEEDITEM + " INTEGER,"
             + KEY_PLAYED_DURATION + " INTEGER,"
             + KEY_HAS_EMBEDDED_PICTURE + " INTEGER,"
-            + KEY_LAST_PLAYED_TIME + " INTEGER" + ")";
+            + KEY_LAST_PLAYED_TIME_STATISTICS + " INTEGER" + ")";

     private static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE "
             + TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE
@@ -292,11 +292,11 @@ public class PodDBAdapter {
             + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + ", "
             + TABLE_NAME_FEED_MEDIA + "." + KEY_SIZE + ", "
             + TABLE_NAME_FEED_MEDIA + "." + KEY_MIME_TYPE + ", "
-            + TABLE_NAME_FEED_MEDIA + "." + KEY_PLAYBACK_COMPLETION_DATE + ", "
+            + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME_HISTORY + ", "
             + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM + ", "
             + TABLE_NAME_FEED_MEDIA + "." + KEY_PLAYED_DURATION + ", "
             + TABLE_NAME_FEED_MEDIA + "." + KEY_HAS_EMBEDDED_PICTURE + ", "
-            + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME;
+            + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME_STATISTICS;

     private static final String KEYS_FEED =
             TABLE_NAME_FEEDS + "." + KEY_ID + " AS " + SELECT_KEY_FEED_ID + ", "
@@ -523,12 +523,12 @@ public long setMedia(FeedMedia media) {
         values.put(KEY_DOWNLOAD_DATE, media.getDownloadDate());
         values.put(KEY_FILE_URL, media.getLocalFileUrl());
         values.put(KEY_HAS_EMBEDDED_PICTURE, media.hasEmbeddedPicture());
-        values.put(KEY_LAST_PLAYED_TIME, media.getLastPlayedTime());
+        values.put(KEY_LAST_PLAYED_TIME_STATISTICS, media.getLastPlayedTimeStatistics());

-        if (media.getPlaybackCompletionDate() != null) {
-            values.put(KEY_PLAYBACK_COMPLETION_DATE, media.getPlaybackCompletionDate().getTime());
+        if (media.getLastPlayedTimeHistory() != null) {
+            values.put(KEY_LAST_PLAYED_TIME_HISTORY, media.getLastPlayedTimeHistory().getTime());
         } else {
-            values.put(KEY_PLAYBACK_COMPLETION_DATE, 0);
+            values.put(KEY_LAST_PLAYED_TIME_HISTORY, 0);
         }
         if (media.getItem() != null) {
             values.put(KEY_FEEDITEM, media.getItem().getId());
@@ -548,7 +548,8 @@ public void setFeedMediaPlaybackInformation(FeedMedia media) {
             values.put(KEY_POSITION, media.getPosition());
             values.put(KEY_DURATION, media.getDuration());
             values.put(KEY_PLAYED_DURATION, media.getPlayedDuration());
-            values.put(KEY_LAST_PLAYED_TIME, media.getLastPlayedTime());
+            values.put(KEY_LAST_PLAYED_TIME_STATISTICS, media.getLastPlayedTimeStatistics());
+            values.put(KEY_LAST_PLAYED_TIME_HISTORY, media.getLastPlayedTimeHistory().getTime());
             db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?",
                     new String[]{String.valueOf(media.getId())});
         } else {
@@ -556,15 +557,15 @@ public void setFeedMediaPlaybackInformation(FeedMedia media) {
         }
     }

-    public void setFeedMediaPlaybackCompletionDate(FeedMedia media) {
+    public void setFeedMediaLastPlayedTimeHistory(FeedMedia media) {
         if (media.getId() != 0) {
             ContentValues values = new ContentValues();
-            values.put(KEY_PLAYBACK_COMPLETION_DATE, media.getPlaybackCompletionDate().getTime());
+            values.put(KEY_LAST_PLAYED_TIME_HISTORY, media.getLastPlayedTimeHistory().getTime());
             values.put(KEY_PLAYED_DURATION, media.getPlayedDuration());
             db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?",
                     new String[]{String.valueOf(media.getId())});
         } else {
-            Log.e(TAG, "setFeedMediaPlaybackCompletionDate: ID of media was 0");
+            Log.e(TAG, "setFeedMediaLastPlayedTimeHistory: ID of media was 0");
         }
     }

@@ -954,7 +955,7 @@ public void removeFeed(Feed feed) {

     public void clearPlaybackHistory() {
         ContentValues values = new ContentValues();
-        values.put(KEY_PLAYBACK_COMPLETION_DATE, 0);
+        values.put(KEY_LAST_PLAYED_TIME_HISTORY, 0);
         db.update(TABLE_NAME_FEED_MEDIA, values, null, null);
     }

@@ -1061,7 +1062,7 @@ public Cursor getNextInQueue(final FeedItem item) {

     public final Cursor getPausedQueueCursor(int limit) {
         final String hasPositionOrRecentlyPlayed = TABLE_NAME_FEED_MEDIA + "."  + KEY_POSITION + " >= 1000"
-                + " OR " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME
+                + " OR " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME_STATISTICS
                 + " >= " + (System.currentTimeMillis() - 30000);
         final String query = "SELECT " + KEYS_FEED_ITEM_WITHOUT_DESCRIPTION + ", " + KEYS_FEED_MEDIA
                 + " FROM " + TABLE_NAME_QUEUE
@@ -1069,7 +1070,7 @@ public final Cursor getPausedQueueCursor(int limit) {
                 + " ON " + SELECT_KEY_ITEM_ID + " = " + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
                 +  JOIN_FEED_ITEM_AND_MEDIA
                 + " ORDER BY (CASE WHEN " + hasPositionOrRecentlyPlayed + " THEN "
-                    + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME + " ELSE 0 END) DESC , "
+                    + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME_STATISTICS + " ELSE 0 END) DESC , "
                 + TABLE_NAME_QUEUE + "." + KEY_ID
                 + " LIMIT " + limit;
         return db.rawQuery(query, null);
@@ -1124,13 +1125,14 @@ public final Cursor getFeedEpisodeCountCursor(long feedId, FeedItemFilter filter
     }

     public Cursor getRandomEpisodesCursor(int limit, int seed) {
+        long oneHourAgo = System.currentTimeMillis() - 1000L * 3600L;
         final String allItems = SELECT_FEED_ITEMS_AND_MEDIA
                 + " WHERE (" + KEY_READ + " = " + FeedItem.NEW + " OR " + KEY_READ + " = " + FeedItem.UNPLAYED + ") "
                     // Only from the last two years. Older episodes often contain broken covers and stuff like that
                     + " AND " + KEY_PUBDATE + " > " + (System.currentTimeMillis() - 1000L * 3600L * 24L * 356L * 2)
                     // Hide episodes that have been played but not completed
-                    + " AND (" + KEY_LAST_PLAYED_TIME + " == 0"
-                        + " OR " + KEY_LAST_PLAYED_TIME + " > " + (System.currentTimeMillis() - 1000L * 3600L) + ")"
+                    + " AND (" + KEY_LAST_PLAYED_TIME_STATISTICS + " == 0"
+                        + " OR " + KEY_LAST_PLAYED_TIME_STATISTICS + " > " + oneHourAgo + ")"
                     + " AND " + SELECT_WHERE_FEED_IS_SUBSCRIBED;
         final String query = "SELECT MAX(" + randomEpisodeNumber(seed) + "), * FROM (" + allItems + ")"
                 + " GROUP BY " + KEY_FEED
@@ -1184,7 +1186,7 @@ public final Cursor getFeedItemCursorByUrl(List<String> urls) {
         }
         final String query = SELECT_FEED_ITEMS_AND_MEDIA
                 + " WHERE " + KEY_DOWNLOAD_URL + " IN (" + urlsString + ")"
-                + " ORDER BY " + KEY_PLAYBACK_COMPLETION_DATE + " DESC";
+                + " ORDER BY " + KEY_LAST_PLAYED_TIME_HISTORY + " DESC";
         return db.rawQuery(query, null);
     }

@@ -1206,10 +1208,10 @@ public final Cursor getFeedItemCursor(final String guid, final String episodeUrl

     public final Cursor getMonthlyStatisticsCursor() {
         final String query = "SELECT SUM(" + KEY_PLAYED_DURATION + ") AS total_duration"
-                + ", strftime('%m', datetime(" + KEY_LAST_PLAYED_TIME + "/1000, 'unixepoch')) AS month"
-                + ", strftime('%Y', datetime(" + KEY_LAST_PLAYED_TIME + "/1000, 'unixepoch')) AS year"
+                + ", strftime('%m', datetime(" + KEY_LAST_PLAYED_TIME_STATISTICS + "/1000, 'unixepoch')) AS month"
+                + ", strftime('%Y', datetime(" + KEY_LAST_PLAYED_TIME_STATISTICS + "/1000, 'unixepoch')) AS year"
                 + " FROM " + TABLE_NAME_FEED_MEDIA
-                + " WHERE " + KEY_LAST_PLAYED_TIME + " > 0 AND " + KEY_PLAYED_DURATION + " > 0"
+                + " WHERE " + KEY_LAST_PLAYED_TIME_STATISTICS + " > 0 AND " + KEY_PLAYED_DURATION + " > 0"
                 + " GROUP BY year, month"
                 + " ORDER BY year, month";
         return db.rawQuery(query, null);
@@ -1217,16 +1219,16 @@ public final Cursor getMonthlyStatisticsCursor() {

     public final Cursor getFeedStatisticsCursor(boolean includeMarkedAsPlayed, long timeFilterFrom,
                                                 long timeFilterTo, long sixMonthsAgo) {
-        final String lastPlayedTime = TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME;
-        String wasStarted = TABLE_NAME_FEED_MEDIA + "." + KEY_PLAYBACK_COMPLETION_DATE + " > 0"
+        final String lastPlayedTimeStatistics = TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME_STATISTICS;
+        String wasStarted = TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME_HISTORY + " > 0"
                 + " AND " + TABLE_NAME_FEED_MEDIA + "." + KEY_PLAYED_DURATION + " > 0";
         if (includeMarkedAsPlayed) {
             wasStarted = "(" + wasStarted + ") OR "
                     + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + "=" + FeedItem.PLAYED + " OR "
                     + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + "> 0";
         }
-        final String timeFilter = lastPlayedTime + ">=" + timeFilterFrom
-                + " AND " + lastPlayedTime + "<" + timeFilterTo;
+        final String timeFilter = lastPlayedTimeStatistics + ">=" + timeFilterFrom
+                + " AND " + lastPlayedTimeStatistics + "<" + timeFilterTo;
         String playedTime = TABLE_NAME_FEED_MEDIA + "." + KEY_PLAYED_DURATION;
         if (includeMarkedAsPlayed) {
             playedTime = "(CASE WHEN " + playedTime + " != 0"
@@ -1238,8 +1240,9 @@ public final Cursor getFeedStatisticsCursor(boolean includeMarkedAsPlayed, long

         final String query = "SELECT " + KEYS_FEED + ", "
                         + "COUNT(*) AS num_episodes, "
-                        + "MIN(CASE WHEN " + lastPlayedTime + " > 0"
-                                + " THEN " + lastPlayedTime + " ELSE " + Long.MAX_VALUE + " END) AS oldest_date, "
+                        + "MIN(CASE WHEN " + lastPlayedTimeStatistics + " > 0"
+                                + " THEN " + lastPlayedTimeStatistics
+                                + " ELSE " + Long.MAX_VALUE + " END) AS oldest_date, "
                         + "SUM(CASE WHEN (" + wasStarted + ") THEN 1 ELSE 0 END) AS episodes_started, "
                         + "IFNULL(SUM(CASE WHEN (" + timeFilter + ")"
                                 + " THEN (" + playedTime + ") ELSE 0 END), 0) AS played_time, "
@@ -1263,10 +1266,10 @@ public final Cursor getFeedStatisticsCursor(boolean includeMarkedAsPlayed, long
     public final Cursor getTimeBetweenReleaseAndPlayback(long timeFilterFrom, long timeFilterTo) {
         final String from = " FROM " + TABLE_NAME_FEED_ITEMS
                 + JOIN_FEED_ITEM_AND_MEDIA
-                + " WHERE " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME + ">=" + timeFilterFrom
+                + " WHERE " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME_STATISTICS + ">=" + timeFilterFrom
                         + " AND " + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + ">=" + timeFilterFrom
-                        + " AND " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME + "<" + timeFilterTo;
-        final String query = "SELECT " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME
+                        + " AND " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME_STATISTICS + "<" + timeFilterTo;
+        final String query = "SELECT " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME_STATISTICS
                 + " - " + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + " AS diff"
                 + from
                 + " ORDER BY diff ASC"
diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemFilterQuery.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemFilterQuery.java
index 481107cfac2..f5f0a95c358 100644
--- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemFilterQuery.java
+++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemFilterQuery.java
@@ -22,7 +22,7 @@ public static String generateFrom(FeedItemFilter filter) {
         // The keys used within this method, but explicitly combined with their table
         String keyRead = PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_READ;
         String keyPosition = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_POSITION;
-        String keyCompletionDate = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE;
+        String keyCompletionDate = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_LAST_PLAYED_TIME_HISTORY;
         String keyDownloaded = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_DOWNLOAD_DATE;
         String keyMediaId = PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_ID;
         String keyItemId = PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_ID;
diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemSortQuery.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemSortQuery.java
index 236e9cbcf90..96a3a509fe3 100644
--- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemSortQuery.java
+++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemSortQuery.java
@@ -23,7 +23,7 @@ public static String generateFrom(SortOrder sortOrder) {
                 return PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_SIZE + " " + "DESC";
             case COMPLETION_DATE_NEW_OLD:
                 return PodDBAdapter.TABLE_NAME_FEED_MEDIA + "."
-                        + PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE + " " + "DESC";
+                        + PodDBAdapter.KEY_LAST_PLAYED_TIME_HISTORY + " " + "DESC";
             case DATE_OLD_NEW:
                 return PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_PUBDATE + " " + "ASC";
             case EPISODE_FILENAME_A_Z:
diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedMediaCursor.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedMediaCursor.java
index 51b25584fbb..ecd2c9c856f 100644
--- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedMediaCursor.java
+++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedMediaCursor.java
@@ -13,7 +13,7 @@
  */
 public class FeedMediaCursor extends CursorWrapper {
     private final int indexId;
-    private final int indexPlaybackCompletionDate;
+    private final int indexLastPlayedTimeHistory;
     private final int indexDuration;
     private final int indexPosition;
     private final int indexSize;
@@ -22,13 +22,13 @@ public class FeedMediaCursor extends CursorWrapper {
     private final int indexDownloadUrl;
     private final int indexDownloadDate;
     private final int indexPlayedDuration;
-    private final int indexLastPlayedTime;
+    private final int indexLastPlayedTimeStatistics;
     private final int indexHasEmbeddedPicture;

     public FeedMediaCursor(Cursor cursor) {
         super(cursor);
         indexId = cursor.getColumnIndexOrThrow(PodDBAdapter.SELECT_KEY_MEDIA_ID);
-        indexPlaybackCompletionDate = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE);
+        indexLastPlayedTimeHistory = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_LAST_PLAYED_TIME_HISTORY);
         indexDuration = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_DURATION);
         indexPosition = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_POSITION);
         indexSize = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_SIZE);
@@ -37,7 +37,7 @@ public FeedMediaCursor(Cursor cursor) {
         indexDownloadUrl = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_DOWNLOAD_URL);
         indexDownloadDate = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_DOWNLOAD_DATE);
         indexPlayedDuration = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_PLAYED_DURATION);
-        indexLastPlayedTime = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_LAST_PLAYED_TIME);
+        indexLastPlayedTimeStatistics = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_LAST_PLAYED_TIME_STATISTICS);
         indexHasEmbeddedPicture = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE);
     }

@@ -46,8 +46,8 @@ public FeedMediaCursor(Cursor cursor) {
      */
     @NonNull
     public FeedMedia getFeedMedia() {
-        long playbackCompletionTime = getLong(indexPlaybackCompletionDate);
-        Date playbackCompletionDate = playbackCompletionTime > 0 ? new Date(playbackCompletionTime) : null;
+        long lastPlayedTimeHistoryTime = getLong(indexLastPlayedTimeHistory);
+        Date lastPlayedTimeHistory = lastPlayedTimeHistoryTime > 0 ? new Date(lastPlayedTimeHistoryTime) : null;

         Boolean hasEmbeddedPicture;
         switch (getInt(indexHasEmbeddedPicture)) {
@@ -72,10 +72,10 @@ public FeedMedia getFeedMedia() {
                 getString(indexFileUrl),
                 getString(indexDownloadUrl),
                 getLong(indexDownloadDate),
-                playbackCompletionDate,
+                lastPlayedTimeHistory,
                 getInt(indexPlayedDuration),
                 hasEmbeddedPicture,
-                getLong(indexLastPlayedTime)
+                getLong(indexLastPlayedTimeStatistics)
         );
     }
 }

diff --git a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java
index 4b93dcee9b9..8eef573d07a 100644
--- a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java
+++ b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java
@@ -183,7 +183,7 @@ public void addLocalFeedData(boolean downloadEpisodes) throws Exception {

             queue.add(feed.getItems().get(0));
             if (feed.getItems().get(1).hasMedia()) {
-                feed.getItems().get(1).getMedia().setPlaybackCompletionDate(new Date());
+                feed.getItems().get(1).getMedia().setLastPlayedTimeHistory(new Date());
             }
         }
         localFeedDataAdded = true;
diff --git a/net/download/service/src/test/java/de/danoeh/antennapod/net/download/service/episode/autodownload/DbCleanupTests.java b/net/download/service/src/test/java/de/danoeh/antennapod/net/download/service/episode/autodownload/DbCleanupTests.java
index c9a707327a0..737b4b93677 100644
--- a/net/download/service/src/test/java/de/danoeh/antennapod/net/download/service/episode/autodownload/DbCleanupTests.java
+++ b/net/download/service/src/test/java/de/danoeh/antennapod/net/download/service/episode/autodownload/DbCleanupTests.java
@@ -130,9 +130,9 @@ void populateItems(final int numItems, Feed feed, List<FeedItem> items,
                        boolean addToFavorites) throws IOException {
         for (int i = 0; i < numItems; i++) {
             Date itemDate = new Date(numItems - i);
-            Date playbackCompletionDate = null;
+            Date lastPlayedTimeHistory = null;
             if (itemState == FeedItem.PLAYED) {
-                playbackCompletionDate = itemDate;
+                lastPlayedTimeHistory = itemDate;
             }
             FeedItem item = new FeedItem(0, "title", "id" + i, "link", itemDate, itemState, feed);

@@ -140,7 +140,7 @@ void populateItems(final int numItems, Feed feed, List<FeedItem> items,
             assertTrue(f.createNewFile());
             files.add(f);
             item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m",
-                    f.getAbsolutePath(), "url", System.currentTimeMillis(), playbackCompletionDate, 0, 0));
+                    f.getAbsolutePath(), "url", System.currentTimeMillis(), lastPlayedTimeHistory, 0, 0));
             items.add(item);
         }

diff --git a/net/download/service/src/test/java/de/danoeh/antennapod/net/download/service/episode/autodownload/DbReaderTest.java b/net/download/service/src/test/java/de/danoeh/antennapod/net/download/service/episode/autodownload/DbReaderTest.java
index a3dc9bd6fae..8f54609b013 100644
--- a/net/download/service/src/test/java/de/danoeh/antennapod/net/download/service/episode/autodownload/DbReaderTest.java
+++ b/net/download/service/src/test/java/de/danoeh/antennapod/net/download/service/episode/autodownload/DbReaderTest.java
@@ -324,9 +324,9 @@ public void testGetPlaybackHistoryLength() {
                 adapter.open();
                 for (int i = 0; i < playedItems; ++i) {
                     FeedMedia m = feed.getItems().get(i).getMedia();
-                    m.setPlaybackCompletionDate(new Date(i + 1));
+                    m.setLastPlayedTimeHistory(new Date(i + 1));

-                    adapter.setFeedMediaPlaybackCompletionDate(m);
+                    adapter.setFeedMediaLastPlayedTimeHistory(m);
                 }
                 adapter.close();

@@ -506,8 +506,8 @@ public void testGetPlaybackHistory() {
             adapter.open();
             for (int i = 0; i < playedItems; i++) {
                 FeedMedia m = feed.getItems().get(i).getMedia();
-                m.setPlaybackCompletionDate(new Date(i + 1));
-                adapter.setFeedMediaPlaybackCompletionDate(m);
+                m.setLastPlayedTimeHistory(new Date(i + 1));
+                adapter.setFeedMediaLastPlayedTimeHistory(m);
                 ids[ids.length - 1 - i] = m.getItem().getId();
             }
             adapter.close();
@@ -520,7 +520,7 @@ public void testGetPlaybackHistory() {
                     numReturnedItems, saved.size());
             for (int i = 0; i < numReturnedItems; i++) {
                 FeedItem item = saved.get(i);
-                assertNotNull(item.getMedia().getPlaybackCompletionDate());
+                assertNotNull(item.getMedia().getLastPlayedTimeHistory());
                 assertEquals(String.format("Wrong sort order with offset %d and limit %d: ",
                                 paramOffset, paramLimit),
                         item.getId(), ids[paramOffset + i]);
diff --git a/net/download/service/src/test/java/de/danoeh/antennapod/net/download/service/episode/autodownload/DbWriterTest.java b/net/download/service/src/test/java/de/danoeh/antennapod/net/download/service/episode/autodownload/DbWriterTest.java
index 4fc41a73b4c..d61e2076661 100644
--- a/net/download/service/src/test/java/de/danoeh/antennapod/net/download/service/episode/autodownload/DbWriterTest.java
+++ b/net/download/service/src/test/java/de/danoeh/antennapod/net/download/service/episode/autodownload/DbWriterTest.java
@@ -94,7 +94,8 @@ public void tearDown() {
     @Test
     public void testSetFeedMediaPlaybackInformation() throws Exception {
         final int position = 50;
-        final long lastPlayedTime = 1000;
+        final long lastPlayedTimeStatistics = 1000;
+        final Date lastPlayedTimeHistory = new Date();
         final int playedDuration = 60;
         final int duration = 100;

@@ -110,8 +111,9 @@ public void testSetFeedMediaPlaybackInformation() throws Exception {
         DBWriter.setFeedItem(item).get(TIMEOUT, TimeUnit.SECONDS);

         media.setPosition(position);
-        media.setLastPlayedTime(lastPlayedTime);
+        media.setLastPlayedTimeStatistics(lastPlayedTimeStatistics);
         media.setPlayedDuration(playedDuration);
+        media.setLastPlayedTimeHistory(lastPlayedTimeHistory);

         DBWriter.setFeedMediaPlaybackInformation(item.getMedia()).get(TIMEOUT, TimeUnit.SECONDS);

@@ -119,7 +121,8 @@ public void testSetFeedMediaPlaybackInformation() throws Exception {
         FeedMedia mediaFromDb = itemFromDb.getMedia();

         assertEquals(position, mediaFromDb.getPosition());
-        assertEquals(lastPlayedTime, mediaFromDb.getLastPlayedTime());
+        assertEquals(lastPlayedTimeStatistics, mediaFromDb.getLastPlayedTimeStatistics());
+        assertEquals(lastPlayedTimeHistory, mediaFromDb.getLastPlayedTimeHistory());
         assertEquals(playedDuration, mediaFromDb.getPlayedDuration());
         assertEquals(duration, mediaFromDb.getDuration());
     }
@@ -467,12 +470,12 @@ public void testDeleteFeedItems() throws Exception {
         adapter.close();
     }

-    private FeedMedia playbackHistorySetup(Date playbackCompletionDate) {
+    private FeedMedia playbackHistorySetup(Date lastPlayedTimeHistory) {
         Feed feed = new Feed("url", null, "title");
         feed.setItems(new ArrayList<>());
         FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), FeedItem.PLAYED, feed);
         FeedMedia media = new FeedMedia(0, item, 10, 0, 1, "mime", null,
-                "url", 0, playbackCompletionDate, 0, 0);
+                "url", 0, lastPlayedTimeHistory, 0, 0);
         feed.getItems().add(item);
         item.setMedia(media);
         PodDBAdapter adapter = PodDBAdapter.getInstance();
@@ -493,7 +496,7 @@ public void testAddItemToPlaybackHistoryNotPlayedYet() throws Exception {
         adapter.close();

         assertNotNull(media);
-        assertNotNull(media.getPlaybackCompletionDate());
+        assertNotNull(media.getLastPlayedTimeHistory());
     }

     @Test
@@ -508,8 +511,8 @@ public void testAddItemToPlaybackHistoryAlreadyPlayed() throws Exception {
         adapter.close();

         assertNotNull(media);
-        assertNotNull(media.getPlaybackCompletionDate());
-        assertNotEquals(media.getPlaybackCompletionDate().getTime(), oldDate);
+        assertNotNull(media.getLastPlayedTimeHistory());
+        assertNotEquals(media.getLastPlayedTimeHistory().getTime(), oldDate);
     }

     @SuppressWarnings("SameParameterValue")


### Description

closes #7578

### Checklist

- [x] I have read the contribution guidelines: https://github.com/AntennaPod/AntennaPod/blob/develop/CONTRIBUTING.md#submit-a-pull-request
- [x] I have performed a self-review of my code
- [x] I have run the automated code checks using `./gradlew checkstyle spotbugsPlayDebug spotbugsDebug :app:lintPlayDebug`
- [x] My code follows the style guidelines of the AntennaPod project: https://github.com/AntennaPod/AntennaPod/wiki/Code-style
- [x] I have mentioned the corresponding issue and the relevant keyword (e.g., "Closes: #xy") in the description (see https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
- [x] If it is a core feature, I have added automated tests

Highlight plain-text links in episode description
### Checklist

- [X] I have used the search function for [**OPEN**](https://github.com/AntennaPod/AntennaPod/issues) issues to see if someone else has already submitted the same feature request.
- [X] I have **also** used the search function for [**CLOSED**](https://github.com/AntennaPod/AntennaPod/issues?q=is%3Aissue+is%3Aclosed) issues to see if the feature was already implemented and is just waiting to be released, or if the feature was rejected.
- [X] I will describe the problem with as much detail as possible.
- [X] This request contains only one single feature, **not** a list of multiple (related) features.

### App version

3.6.0

### Feature or improvement you want

Sometimes podcast authors add links not as proper html `<a>` tags but just as plain text `https://domain.com/`

It's an easy fix to treat such links as normal clickable links (see screenshots)

### Why should this be added?

This improves UX, users don't have to copy-paste links

### Screenshots / Drawings / Technical details

I have unit tests that seems to cover all the cases, tried it on my device for a couple of weeks, so far no issues

Before:

![before](https://github.com/user-attachments/assets/ae5431d9-ccb0-4975-9e8b-53528a4c7fc9)

After:

![after](https://github.com/user-attachments/assets/8aae3162-1df6-4abd-9f5c-9ef98017d83c)

Could you open a PR please, so we can have a look at your solution?

./gradlew :app:testFreeDebugUnitTest --tests "de.danoeh.antennapod.ui.cleaner.PlainTextLinksConverterTest"

diff --git a/app/src/main/java/de/danoeh/antennapod/ui/cleaner/PlainTextLinksConverter.java b/app/src/main/java/de/danoeh/antennapod/ui/cleaner/PlainTextLinksConverter.java
new file mode 100644
index 00000000000..8f7d59e2d9d
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/ui/cleaner/PlainTextLinksConverter.java
@@ -0,0 +1,145 @@
+package de.danoeh.antennapod.ui.cleaner;
+
+import androidx.annotation.NonNull;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.Node;
+import org.jsoup.nodes.TextNode;
+import org.jsoup.select.NodeTraversor;
+import org.jsoup.select.NodeVisitor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PlainTextLinksConverter {
+    private static final Pattern HTTP_LINK_REGEX = Pattern.compile(
+            "(?:https?://(?:www\\.)?|www\\.)"   // http(s)://[www.] OR www.
+                    + "[-a-zA-Z0-9@:%._+~#=]{1,256}"  // Domain name
+                    + "\\.[a-zA-Z]{2,6}\\b"           // Top-level domain
+                    + "[-a-zA-Z0-9@:%_+.*~#?!&$/=()\\[\\],;]*", // Path, query params
+            Pattern.CASE_INSENSITIVE
+    );
+    protected static final List<String> NOT_ALLOWED_END_CHARS = List.of(
+            ".", ",", ";", ":", "?", "!", ")", "(", "[", "]", "-", "_", "~", "#", "@", "$", "*", "+");
+
+    private static final String STARTS_WITH_HTTP = "(?i)https?://.*";
+    private static final String ANCHOR_TAG = "a";
+    private static final String ANCHOR_ADDRESS = "href";
+
+    /**
+     * Provided text can be an HTML document or plain text.
+     * It may contain a mixture of plain-text links and HTML links.
+     * Only plain-text links will be converted to HTML {@code } tags.
+     */
+    public static String convertLinksToHtml(String text) {
+        if (text == null || text.isEmpty()) {
+            return text;
+        }
+        try {
+            Document doc = Jsoup.parse(text);
+            convertLinksToHtml(doc);
+            return doc.body().html();
+        } catch (Exception e) {
+            return text;
+        }
+    }
+
+    public static void convertLinksToHtml(Document doc) {
+        if (doc == null) {
+            return;
+        }
+        NodeTraversor.traverse(new LinkConvertingVisitor(), doc.body());
+    }
+
+    /**
+     * Ensures that URLs are only converted if they are not already part of an existing anchor tag.
+     * Document structure remains untouched, logic affects only {@link TextNode} - leaf element with no tags in it.
+     * One {@link TextNode} is replaced with multiple {@link Element}s:
+     *
{@link TextNode} with text before the link

+     *
{@link Element} with the link tag

+     *
{@link TextNode} with text after the link

+     */
+    private static class LinkConvertingVisitor implements NodeVisitor {
+        @Override
+        public void head(@NonNull Node node, int depth) {
+            if (!(node instanceof TextNode textNode)) {
+                return;
+            } else if (isInsideAnchor(textNode)) {
+                return;
+            }
+            String originalText = textNode.getWholeText();
+            Matcher matcher = HTTP_LINK_REGEX.matcher(originalText);
+
+            if (!matcher.find()) {
+                return;
+            }
+            List<Node> newNodes = new ArrayList<>();
+            int lastEnd = 0;
+            matcher.reset();
+
+            while (matcher.find()) {
+                String url = matcher.group();
+                if (endsWithPunctuation(url)) {
+                    continue;
+                }
+                if (matcher.start() > lastEnd) {
+                    newNodes.add(new TextNode(originalText.substring(lastEnd, matcher.start())));
+                }
+                newNodes.add(link(url));
+                lastEnd = matcher.end();
+            }
+
+            if (lastEnd < originalText.length()) {
+                newNodes.add(new TextNode(originalText.substring(lastEnd)));
+            }
+
+            if (!newNodes.isEmpty()) {
+                Node parent = textNode.parent();
+                if (parent instanceof Element parentElement) {
+                    int index = textNode.siblingIndex();
+                    textNode.remove();
+                    parentElement.insertChildren(index, newNodes);
+                }
+            }
+        }
+
+        private static Element link(String detectedUrl) {
+            var url = detectedUrl;
+            if (!detectedUrl.matches(STARTS_WITH_HTTP)) {
+                url = "https://" + url;
+            }
+            return new Element(ANCHOR_TAG).attr(ANCHOR_ADDRESS, url).text(detectedUrl);
+        }
+
+        @Override
+        public void tail(@NonNull Node node, int depth) {
+            //not needed
+        }
+    }
+
+    private static boolean isInsideAnchor(Node node) {
+        Node current = node;
+        while (current != null) {
+            if (current instanceof Element currentElement) {
+                if (ANCHOR_TAG.equalsIgnoreCase(currentElement.tagName())) {
+                    return true;
+                }
+            }
+            current = current.parent();
+        }
+        return false;
+    }
+
+    private static boolean endsWithPunctuation(String url) {
+        for (String endChar : NOT_ALLOWED_END_CHARS) {
+            if (url.endsWith(endChar)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/ui/cleaner/ShownotesCleaner.java b/app/src/main/java/de/danoeh/antennapod/ui/cleaner/ShownotesCleaner.java
index 8c6dc5f9a01..54f251ef682 100644
--- a/app/src/main/java/de/danoeh/antennapod/ui/cleaner/ShownotesCleaner.java
+++ b/app/src/main/java/de/danoeh/antennapod/ui/cleaner/ShownotesCleaner.java
@@ -76,6 +76,7 @@ private String colorToHtml(Context context, int colorAttr) {

     /**
      * Applies an app-specific CSS stylesheet and adds timecode links (optional).
+     * Also converts plain-text links to HTML links.
      *

      * This method does NOT change the original shownotes string of the shownotesProvider object and it should
      * also not be changed by the caller.
@@ -99,6 +100,7 @@ public String processShownotes() {
         Document document = Jsoup.parse(shownotes);
         cleanCss(document);
         document.head().appendElement("style").attr("type", "text/css").text(webviewStyle);
+        PlainTextLinksConverter.convertLinksToHtml(document);
         addTimecodes(document);
         document.body().attr("dir", "auto");
         return document.toString();

diff --git a/app/src/test/java/de/danoeh/antennapod/ui/cleaner/PlainTextLinksConverterTest.java b/app/src/test/java/de/danoeh/antennapod/ui/cleaner/PlainTextLinksConverterTest.java
new file mode 100644
index 00000000000..36e55d02e3b
--- /dev/null
+++ b/app/src/test/java/de/danoeh/antennapod/ui/cleaner/PlainTextLinksConverterTest.java
@@ -0,0 +1,154 @@
+package de.danoeh.antennapod.ui.cleaner;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static de.danoeh.antennapod.ui.cleaner.PlainTextLinksConverter.NOT_ALLOWED_END_CHARS;
+import org.junit.Test;
+import java.util.List;
+
+public class PlainTextLinksConverterTest {
+
+    @Test
+    public void testConvertPlainTextLinksToHtml() {
+        final String link1 = "https://url.to/link";
+        final String textWithLink = "text  " + link1;
+        assertEquals("text " + makeLinkHtml(link1), PlainTextLinksConverter.convertLinksToHtml(textWithLink));
+
+        final String link2 = "https://t.me/link";
+        final String textWithLink2 = "text " + link2;
+        assertEquals("text " + makeLinkHtml(link2), PlainTextLinksConverter.convertLinksToHtml(textWithLink2));
+
+        final String text = "artist here:  www.example.com";
+        final String expected = "artist here: www.example.com";
+        assertEquals(expected, PlainTextLinksConverter.convertLinksToHtml(text));
+
+        final String textWithTwoLinks = "text " + link1 + " and " + link2;
+        final String expectedTwoLinks = "text " + makeLinkHtml(link1) + " and " + makeLinkHtml(link2);
+        assertEquals(expectedTwoLinks, PlainTextLinksConverter.convertLinksToHtml(textWithTwoLinks));
+
+        final String textWithMixturePlainTextAndHtml = "text " + link1 + " and " + makeLinkHtml(link2);
+        final String expectedMixture = "text " + makeLinkHtml(link1) + " and " + makeLinkHtml(link2);
+        assertEquals(expectedMixture, PlainTextLinksConverter.convertLinksToHtml(textWithMixturePlainTextAndHtml));
+
+        final String textWithSpecialChars = "text'" + link1 + " and=" + link2;
+        final String expectedWithSpecialChars = "text'" + makeLinkHtml(link1) + " and=" + makeLinkHtml(link2);
+        assertEquals(expectedWithSpecialChars, PlainTextLinksConverter.convertLinksToHtml(textWithSpecialChars));
+
+        final String linkWithParams = "http://t.me/link#mark?param1=1&param2=true;param3=true";
+        final String textWithParams = "text " + linkWithParams + " after-text";
+        final String expectedWithParams = "text " + makeLinkHtml(linkWithParams) + " after-text";
+        assertEquals(expectedWithParams, PlainTextLinksConverter.convertLinksToHtml(textWithParams));
+
+        final String linkWithComma = "https://example.org/%D0%%86_(%D1%%BC,_2020";
+        final String textWithComma = "text " + linkWithComma;
+        assertEquals("text " + makeLinkHtml(linkWithComma), PlainTextLinksConverter.convertLinksToHtml(textWithComma));
+
+        final String linkWithDot = "https://www.ietf.org/rfc/rfc3986.txt";
+        final String textWithDot = "text " + linkWithDot;
+        assertEquals("text " + makeLinkHtml(linkWithDot), PlainTextLinksConverter.convertLinksToHtml(textWithDot));
+
+        final String linkWithTilda = "https://www.example.org/valid/-~.,$/url/";
+        final String textWithTilda = "text " + linkWithTilda;
+        assertEquals("text " + makeLinkHtml(linkWithTilda), PlainTextLinksConverter.convertLinksToHtml(textWithTilda));
+
+        final String linkWithExclamation = "http://www.example.com/index.php?id=123&v=wall#!/index.php?id=234";
+        final String textWithExclamation = "text " + linkWithExclamation;
+        assertEquals("text " + makeLinkHtml(linkWithExclamation),
+                PlainTextLinksConverter.convertLinksToHtml(textWithExclamation));
+
+        final String linkWithBrackets = "http://www.example.com/index.php?bar[]=1&bar[]=2";
+        final String textWithBrackets = "text " + linkWithBrackets;
+        assertEquals("text " + makeLinkHtml(linkWithBrackets),
+                PlainTextLinksConverter.convertLinksToHtml(textWithBrackets));
+
+        final String linkWithAsterisk = "https://archive.org/web/*/http://www.example.com/";
+        final String textWithAsterisk = "text " + linkWithAsterisk;
+        assertEquals("text " + makeLinkHtml(linkWithAsterisk),
+                PlainTextLinksConverter.convertLinksToHtml(textWithAsterisk));
+    }
+
+    @Test
+    public void testBrokenLinksAreNotCreated() {
+        final String linkWithBrackets = "Sign up now (http://example.com/abc)";
+        assertEquals(linkWithBrackets, PlainTextLinksConverter.convertLinksToHtml(linkWithBrackets));
+
+        final String linkWithBrackets2 = "Sign up now (http://example.com/abc)! please";
+        assertEquals(linkWithBrackets2, PlainTextLinksConverter.convertLinksToHtml(linkWithBrackets2));
+
+        final String linkWithDot = "To read on, visit https://example.com.";
+        assertEquals(linkWithDot, PlainTextLinksConverter.convertLinksToHtml(linkWithDot));
+
+        //we choose to ignore links like this, even though they are valid
+        final String validLinkIgnored = "Visit https://example.com/wiki_(url+rules)";
+        assertEquals(validLinkIgnored, PlainTextLinksConverter.convertLinksToHtml(validLinkIgnored));
+
+        final String link = "https://example.com/abc";
+        NOT_ALLOWED_END_CHARS.forEach(end ->
+                assertEquals(link + end, PlainTextLinksConverter.convertLinksToHtml(link + end))
+        );
+
+        final String firstLinkIgnored = "(" + link + ") and " + link;
+        assertEquals("(https://example.com/abc) and " + makeLinkHtml(link),
+                PlainTextLinksConverter.convertLinksToHtml(firstLinkIgnored));
+
+        final String secondLinkIgnored = "text " + link + " and (" + link + ")";
+        assertEquals("text " + makeLinkHtml(link) + " and (https://example.com/abc)",
+                PlainTextLinksConverter.convertLinksToHtml(secondLinkIgnored));
+
+        final String middleLinkIgnored = "text " + link + " and (" + link + ") and " + link;
+        assertEquals("text " + makeLinkHtml(link) + " and (https://example.com/abc) and " + makeLinkHtml(link),
+                PlainTextLinksConverter.convertLinksToHtml(middleLinkIgnored));
+    }
+
+    @Test
+    public void testExistingLinksArePreserved() {
+        var links = List.of(
+                "Click http://url.to/link, this link",
+                "domain.org",
+                "you can find it on our new website http://xy.org",
+                "you can find it on our new website http://xy.org",
+                "

",
+                "text \n<audio src=\"https://url.to/i.mp3\" alt=\"https://url.to/i.mp3\">\n  text \n</audio>",
+                "AI interview - 01:57:01",
+                "sign up for our premium feed here! https://example.com/join",
+                "you can do so here:https://example.com/button",
+                "LINKS:Example",
+                "Example",
+                "
A picture of the photo in question
",
+                "https://example.com/shop/book/",
+                "https://example.me/yH6x/gx5ywe7g",
+                ""
+        );
+        links.forEach(link -> assertEquals(link, PlainTextLinksConverter.convertLinksToHtml(link)));
+    }
+
+    @Test
+    public void testConvertToHtmlWhenNoLinksAreDetected() {
+        assertNull(PlainTextLinksConverter.convertLinksToHtml((String) null));
+        assertEquals("", PlainTextLinksConverter.convertLinksToHtml(""));
+
+        final String text = "plain text";
+        assertEquals(text, PlainTextLinksConverter.convertLinksToHtml(text));
+
+        final String specialCharacters = "text with ' special \" characters !@#$%^&*()<>?123";
+        var expected = specialCharacters.replace("&", "&");
+        expected = expected.replace("<", "<");
+        expected = expected.replace(">", ">");
+        assertEquals(expected, PlainTextLinksConverter.convertLinksToHtml(specialCharacters));
+
+        final String textWithDots = "\"Text With...Dots Works\"";
+        assertEquals(textWithDots, PlainTextLinksConverter.convertLinksToHtml(textWithDots));
+    }
+
+    /**
+     * Adds {@code ..} around provided string
+     */
+    private static String makeLinkHtml(String plain) {
+        if (plain == null || plain.isEmpty()) {
+            return "";
+        }
+        String encodedPlain = plain.replace("&", "&");
+        return "" + encodedPlain + "";
+    }
+}
\ No newline at end of file