diff --git a/app/src/main/java/org/schabi/newpipe/database/Migrations.java b/app/src/main/java/org/schabi/newpipe/database/Migrations.java index 825ec5fd5..fdfd04a84 100644 --- a/app/src/main/java/org/schabi/newpipe/database/Migrations.java +++ b/app/src/main/java/org/schabi/newpipe/database/Migrations.java @@ -23,7 +23,7 @@ public class Migrations { database.execSQL("CREATE INDEX `index_search_history_search` ON `search_history` (`search`)"); database.execSQL("CREATE TABLE IF NOT EXISTS `streams` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `title` TEXT, `stream_type` TEXT, `duration` INTEGER, `uploader` TEXT, `thumbnail_url` TEXT)"); database.execSQL("CREATE UNIQUE INDEX `index_streams_service_id_url` ON `streams` (`service_id`, `url`)"); - database.execSQL("CREATE TABLE IF NOT EXISTS `stream_history` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )"); + database.execSQL("CREATE TABLE IF NOT EXISTS `stream_history` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )"); database.execSQL("CREATE INDEX `index_stream_history_stream_id` ON `stream_history` (`stream_id`)"); database.execSQL("CREATE TABLE IF NOT EXISTS `stream_state` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )"); database.execSQL("CREATE TABLE IF NOT EXISTS `playlists` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `thumbnail_url` TEXT)"); @@ -45,8 +45,8 @@ public class Migrations { // Once the streams have PKs, join them with the normalized history table // and populate it with the remaining data from watch history - database.execSQL("INSERT INTO stream_history (stream_id, access_date)" + - "SELECT uid, creation_date " + + database.execSQL("INSERT INTO stream_history (stream_id, access_date, repeat_count)" + + "SELECT uid, creation_date, 1 " + "FROM watch_history INNER JOIN streams " + "ON watch_history.service_id == streams.service_id " + "AND watch_history.url == streams.url " + diff --git a/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java b/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java index 257c1ec3d..b0a3c3a3c 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java @@ -4,6 +4,7 @@ import android.arch.persistence.room.Dao; import android.arch.persistence.room.Query; import android.support.annotation.Nullable; +import org.schabi.newpipe.database.BasicDAO; import org.schabi.newpipe.database.history.model.SearchHistoryEntry; import java.util.List; @@ -21,8 +22,8 @@ public interface SearchHistoryDAO extends HistoryDAO { String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC"; - @Query("SELECT * FROM " + TABLE_NAME + " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")") - @Override + @Query("SELECT * FROM " + TABLE_NAME + + " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")") @Nullable SearchHistoryEntry getLatestEntry(); diff --git a/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java b/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java index fe19d362e..fd7a1b96f 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java @@ -14,6 +14,7 @@ import java.util.List; import io.reactivex.Flowable; +import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_REPEAT_COUNT; import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_LATEST_DATE; import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_WATCH_COUNT; import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID; @@ -59,7 +60,7 @@ public abstract class StreamHistoryDAO implements HistoryDAO onSelectedListener; + private OnLocalItemGesture onSelectedListener; public LocalItemBuilder(Context context) { this.context = context; @@ -46,11 +46,11 @@ public class LocalItemBuilder { return imageLoader; } - public OnCustomItemGesture getOnItemSelectedListener() { + public OnLocalItemGesture getOnItemSelectedListener() { return onSelectedListener; } - public void setOnItemSelectedListener(OnCustomItemGesture listener) { + public void setOnItemSelectedListener(OnLocalItemGesture listener) { this.onSelectedListener = listener; } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/LocalItemListAdapter.java b/app/src/main/java/org/schabi/newpipe/fragments/local/LocalItemListAdapter.java index 9c621a55c..af1a0f666 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/LocalItemListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/LocalItemListAdapter.java @@ -66,7 +66,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter listener) { + public void setSelectedListener(OnLocalItemGesture listener) { localItemBuilder.setOnItemSelectedListener(listener); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/local/LocalPlaylistFragment.java index 2c4af25d9..d54a4b4ae 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/LocalPlaylistFragment.java @@ -151,7 +151,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment() { + itemListAdapter.setSelectedListener(new OnLocalItemGesture() { @Override public void selected(LocalItem selectedItem) { if (selectedItem instanceof PlaylistStreamEntry) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/OnCustomItemGesture.java b/app/src/main/java/org/schabi/newpipe/fragments/local/OnLocalItemGesture.java similarity index 86% rename from app/src/main/java/org/schabi/newpipe/fragments/local/OnCustomItemGesture.java rename to app/src/main/java/org/schabi/newpipe/fragments/local/OnLocalItemGesture.java index 0b65c595a..5cede4c67 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/OnCustomItemGesture.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/OnLocalItemGesture.java @@ -5,7 +5,7 @@ import android.support.v7.widget.RecyclerView; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.extractor.InfoItem; -public abstract class OnCustomItemGesture { +public abstract class OnLocalItemGesture { public abstract void selected(T selectedItem); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/PlaylistAppendDialog.java b/app/src/main/java/org/schabi/newpipe/fragments/local/PlaylistAppendDialog.java index 6ed357e36..302a37002 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/PlaylistAppendDialog.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/PlaylistAppendDialog.java @@ -13,15 +13,11 @@ import android.widget.Toast; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; +import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.database.stream.model.StreamEntity; -import org.schabi.newpipe.extractor.InfoItem; -import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; -import org.schabi.newpipe.info_list.InfoListAdapter; -import org.schabi.newpipe.info_list.OnInfoItemGesture; -import org.schabi.newpipe.info_list.stored.LocalPlaylistInfoItem; import org.schabi.newpipe.playlist.PlayQueueItem; import java.util.ArrayList; @@ -34,7 +30,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog { private static final String TAG = PlaylistAppendDialog.class.getCanonicalName(); private RecyclerView playlistRecyclerView; - private InfoListAdapter playlistAdapter; + private LocalItemListAdapter playlistAdapter; public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) { PlaylistAppendDialog dialog = new PlaylistAppendDialog(); @@ -69,8 +65,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog { @Override public void onAttach(Context context) { super.onAttach(context); - playlistAdapter = new InfoListAdapter(getActivity()); - playlistAdapter.useMiniItemVariants(true); + playlistAdapter = new LocalItemListAdapter(getActivity()); } /*////////////////////////////////////////////////////////////////////////// @@ -97,13 +92,13 @@ public final class PlaylistAppendDialog extends PlaylistDialog { newPlaylistButton.setOnClickListener(ignored -> openCreatePlaylistDialog()); - playlistAdapter.setOnPlaylistSelectedListener(new OnInfoItemGesture() { + playlistAdapter.setSelectedListener(new OnLocalItemGesture() { @Override - public void selected(PlaylistInfoItem selectedItem) { - if (!(selectedItem instanceof LocalPlaylistInfoItem) || getStreams() == null) + public void selected(LocalItem selectedItem) { + if (!(selectedItem instanceof PlaylistMetadataEntry) || getStreams() == null) return; - final long playlistId = ((LocalPlaylistInfoItem) selectedItem).getPlaylistId(); + final long playlistId = ((PlaylistMetadataEntry) selectedItem).uid; final Toast successToast = Toast.makeText(getContext(), "Added", Toast.LENGTH_SHORT); @@ -123,13 +118,8 @@ public final class PlaylistAppendDialog extends PlaylistDialog { return; } - List playlistInfoItems = new ArrayList<>(metadataEntries.size()); - for (final PlaylistMetadataEntry metadataEntry : metadataEntries) { - playlistInfoItems.add(metadataEntry.toStoredPlaylistInfoItem()); - } - playlistAdapter.clearStreamItemList(); - playlistAdapter.addInfoItemList(playlistInfoItems); + playlistAdapter.addInfoItemList(metadataEntries); playlistRecyclerView.setVisibility(View.VISIBLE); }); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/BookmarkFragment.java index 01b433052..0bd0fa00f 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/BookmarkFragment.java @@ -22,7 +22,7 @@ import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.local.LocalItemListAdapter; import org.schabi.newpipe.fragments.local.LocalPlaylistManager; -import org.schabi.newpipe.fragments.local.OnCustomItemGesture; +import org.schabi.newpipe.fragments.local.OnLocalItemGesture; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.NavigationHelper; @@ -136,7 +136,7 @@ public class BookmarkFragment extends BaseStateFragment() { + itemListAdapter.setSelectedListener(new OnLocalItemGesture() { @Override public void selected(LocalItem selectedItem) { // Requires the parent fragment to find holder for fragment replacement diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/StatisticsPlaylistFragment.java index 5c2959d9c..cb2d671cc 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/StatisticsPlaylistFragment.java @@ -18,7 +18,7 @@ import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.stream.StreamStatisticsEntry; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.fragments.local.BaseLocalListFragment; -import org.schabi.newpipe.fragments.local.OnCustomItemGesture; +import org.schabi.newpipe.fragments.local.OnLocalItemGesture; import org.schabi.newpipe.history.HistoryRecordManager; import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.playlist.PlayQueue; @@ -122,7 +122,7 @@ public abstract class StatisticsPlaylistFragment protected void initListeners() { super.initListeners(); - itemListAdapter.setSelectedListener(new OnCustomItemGesture() { + itemListAdapter.setSelectedListener(new OnLocalItemGesture() { @Override public void selected(LocalItem selectedItem) { if (selectedItem instanceof StreamStatisticsEntry) { diff --git a/app/src/main/java/org/schabi/newpipe/history/HistoryEntryAdapter.java b/app/src/main/java/org/schabi/newpipe/history/HistoryEntryAdapter.java index f61e8eb7d..4170a1139 100644 --- a/app/src/main/java/org/schabi/newpipe/history/HistoryEntryAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/history/HistoryEntryAdapter.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.history; import android.content.Context; +import android.content.res.Resources; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; @@ -22,11 +23,13 @@ public abstract class HistoryEntryAdapter private final ArrayList mEntries; private final DateFormat mDateFormat; + private final Context mContext; private OnHistoryItemClickListener onHistoryItemClickListener = null; public HistoryEntryAdapter(Context context) { super(); + mContext = context; mEntries = new ArrayList<>(); mDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, Localization.getPreferredLocale(context)); @@ -51,6 +54,10 @@ public abstract class HistoryEntryAdapter return mDateFormat.format(date); } + protected String getFormattedViewString(final long viewCount) { + return Localization.shortViewCount(mContext, viewCount); + } + @Override public int getItemCount() { return mEntries.size(); diff --git a/app/src/main/java/org/schabi/newpipe/history/HistoryRecordManager.java b/app/src/main/java/org/schabi/newpipe/history/HistoryRecordManager.java index 9d9b74b30..839b8c89b 100644 --- a/app/src/main/java/org/schabi/newpipe/history/HistoryRecordManager.java +++ b/app/src/main/java/org/schabi/newpipe/history/HistoryRecordManager.java @@ -62,7 +62,16 @@ public class HistoryRecordManager { final Date currentTime = new Date(); return Maybe.fromCallable(() -> database.runInTransaction(() -> { final long streamId = streamTable.upsert(new StreamEntity(info)); - return streamHistoryTable.insert(new StreamHistoryEntity(streamId, currentTime)); + StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(); + + if (latestEntry != null && latestEntry.getStreamUid() == streamId) { + streamHistoryTable.delete(latestEntry); + latestEntry.setAccessDate(currentTime); + latestEntry.setRepeatCount(latestEntry.getRepeatCount() + 1); + return streamHistoryTable.insert(latestEntry); + } else { + return streamHistoryTable.insert(new StreamHistoryEntity(streamId, currentTime)); + } })).subscribeOn(Schedulers.io()); } diff --git a/app/src/main/java/org/schabi/newpipe/history/SearchHistoryFragment.java b/app/src/main/java/org/schabi/newpipe/history/SearchHistoryFragment.java index a8bba0573..e40a79368 100644 --- a/app/src/main/java/org/schabi/newpipe/history/SearchHistoryFragment.java +++ b/app/src/main/java/org/schabi/newpipe/history/SearchHistoryFragment.java @@ -14,6 +14,8 @@ import android.widget.TextView; import org.schabi.newpipe.R; import org.schabi.newpipe.database.history.model.SearchHistoryEntry; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import java.util.Collection; @@ -99,12 +101,12 @@ public class SearchHistoryFragment extends HistoryFragment { private static class ViewHolder extends RecyclerView.ViewHolder { private final TextView search; - private final TextView time; + private final TextView info; public ViewHolder(View itemView) { super(itemView); search = itemView.findViewById(R.id.search); - time = itemView.findViewById(R.id.time); + info = itemView.findViewById(R.id.info); } } @@ -125,7 +127,11 @@ public class SearchHistoryFragment extends HistoryFragment { @Override void onBindViewHolder(ViewHolder holder, SearchHistoryEntry entry, int position) { holder.search.setText(entry.getSearch()); - holder.time.setText(getFormattedDate(entry.getCreationDate())); + + final String info = Localization.concatenateStrings( + getFormattedDate(entry.getCreationDate()), + NewPipe.getNameOfService(entry.getServiceId())); + holder.info.setText(info); } } } diff --git a/app/src/main/java/org/schabi/newpipe/history/WatchedHistoryFragment.java b/app/src/main/java/org/schabi/newpipe/history/WatchedHistoryFragment.java index 026d5ee16..7913c9a28 100644 --- a/app/src/main/java/org/schabi/newpipe/history/WatchedHistoryFragment.java +++ b/app/src/main/java/org/schabi/newpipe/history/WatchedHistoryFragment.java @@ -123,7 +123,16 @@ public class WatchedHistoryFragment extends HistoryFragment @Override void onBindViewHolder(ViewHolder holder, StreamHistoryEntry entry, int position) { - holder.date.setText(getFormattedDate(entry.accessDate)); + final String formattedDate = getFormattedDate(entry.accessDate); + final String info; + if (entry.repeatCount > 1) { + info = Localization.concatenateStrings(formattedDate, + getFormattedViewString(entry.repeatCount)); + } else { + info = formattedDate; + } + + holder.info.setText(info); holder.streamTitle.setText(entry.title); holder.uploader.setText(entry.uploader); holder.duration.setText(Localization.getDurationString(entry.duration)); @@ -133,7 +142,7 @@ public class WatchedHistoryFragment extends HistoryFragment } private static class ViewHolder extends RecyclerView.ViewHolder { - private final TextView date; + private final TextView info; private final TextView streamTitle; private final ImageView thumbnailView; private final TextView uploader; @@ -142,7 +151,7 @@ public class WatchedHistoryFragment extends HistoryFragment public ViewHolder(View itemView) { super(itemView); thumbnailView = itemView.findViewById(R.id.itemThumbnailView); - date = itemView.findViewById(R.id.itemAdditionalDetails); + info = itemView.findViewById(R.id.itemAdditionalDetails); streamTitle = itemView.findViewById(R.id.itemVideoTitleView); uploader = itemView.findViewById(R.id.itemUploaderView); duration = itemView.findViewById(R.id.itemDurationView); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/stored/LocalPlaylistInfoItem.java b/app/src/main/java/org/schabi/newpipe/info_list/stored/LocalPlaylistInfoItem.java deleted file mode 100644 index b0afe1948..000000000 --- a/app/src/main/java/org/schabi/newpipe/info_list/stored/LocalPlaylistInfoItem.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.schabi.newpipe.info_list.stored; - -import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; - -import static org.schabi.newpipe.util.Constants.NO_SERVICE_ID; -import static org.schabi.newpipe.util.Constants.NO_URL; - -public final class LocalPlaylistInfoItem extends PlaylistInfoItem { - private final long playlistId; - - public LocalPlaylistInfoItem(final long playlistId, final String name) { - super(NO_SERVICE_ID, NO_URL, name); - - this.playlistId = playlistId; - } - - public long getPlaylistId() { - return playlistId; - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/Constants.java b/app/src/main/java/org/schabi/newpipe/util/Constants.java index 4238424d9..a6aec96e2 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Constants.java +++ b/app/src/main/java/org/schabi/newpipe/util/Constants.java @@ -12,5 +12,4 @@ public class Constants { public static final String KEY_MAIN_PAGE_CHANGE = "key_main_page_change"; public static final int NO_SERVICE_ID = -1; - public static final String NO_URL = ""; } diff --git a/app/src/main/res/layout/item_search_history.xml b/app/src/main/res/layout/item_search_history.xml index a89882c0c..2c40ca1d1 100644 --- a/app/src/main/res/layout/item_search_history.xml +++ b/app/src/main/res/layout/item_search_history.xml @@ -13,7 +13,7 @@ android:paddingTop="8dp">