mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 20:31:51 +01:00
New Recents Page + Download updates
Recent tab is now a combination of recent reads and updates, filtered by unread and an option to go to both old pages (which will be updated later) Download button now has an option to start downloading a chapter now, which unpauses downloading and moves it to the top of the queue If a chapter is added to a paused download queue with the notification swiped away, the notification returns
This commit is contained in:
parent
719aa751b8
commit
6a7762212b
@ -6,10 +6,12 @@ import eu.kanade.tachiyomi.data.database.DbProvider
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
||||
import eu.kanade.tachiyomi.data.database.resolvers.ChapterBackupPutResolver
|
||||
import eu.kanade.tachiyomi.data.database.resolvers.ChapterProgressPutResolver
|
||||
import eu.kanade.tachiyomi.data.database.resolvers.ChapterSourceOrderPutResolver
|
||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver
|
||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver
|
||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||
import java.util.Date
|
||||
|
||||
@ -34,6 +36,16 @@ interface ChapterQueries : DbProvider {
|
||||
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
fun getUpdatedManga(date: Date, search: String = "") = db.get()
|
||||
.listOfObjects(MangaChapterHistory::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(getRecentsQueryDistinct(search))
|
||||
.args(date.time)
|
||||
.observesTables(ChapterTable.TABLE)
|
||||
.build())
|
||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
fun getChapter(id: Long) = db.get()
|
||||
.`object`(Chapter::class.java)
|
||||
.withQuery(Query.builder()
|
||||
|
@ -48,6 +48,21 @@ interface HistoryQueries : DbProvider {
|
||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
/**
|
||||
* Returns history of recent manga containing last read chapter in 25s
|
||||
* @param date recent date range
|
||||
* @offset offset the db by
|
||||
*/
|
||||
fun getRecentsWithUnread(date: Date, search: String = "") = db.get()
|
||||
.listOfObjects(MangaChapterHistory::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.query(getRecentReadWithUnreadChapters(search))
|
||||
.args(date.time)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build())
|
||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
fun getHistoryByMangaId(mangaId: Long) = db.get()
|
||||
.listOfObjects(History::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
|
@ -61,6 +61,29 @@ fun getRecentsQuery() = """
|
||||
ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the recent chapters of manga from the library up to a date.
|
||||
*/
|
||||
fun getRecentsQueryDistinct(search: String) = """
|
||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*
|
||||
FROM ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||
JOIN (
|
||||
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID},MAX(${Chapter.TABLE}.${Chapter.COL_DATE_UPLOAD})
|
||||
FROM ${Chapter.TABLE} JOIN ${Manga.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||
WHERE ${Chapter.COL_DATE_FETCH} > ?
|
||||
AND ${Chapter.COL_READ} = 0
|
||||
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS newest_chapter
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = newest_chapter.${Chapter.COL_MANGA_ID}
|
||||
WHERE ${Manga.COL_FAVORITE} = 1
|
||||
AND newest_chapter.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
|
||||
AND lower(${Manga.COL_TITLE}) LIKE '%$search%'
|
||||
ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC
|
||||
LIMIT 8
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the recently read chapters of manga from the library up to a date.
|
||||
* The max_last_read table contains the most recent chapters grouped by manga
|
||||
@ -114,6 +137,45 @@ fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") = """
|
||||
LIMIT $limit
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the recently read chapters of manga from the library up to a date.
|
||||
* The max_last_read table contains the most recent chapters grouped by manga
|
||||
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read
|
||||
* and are read after the given time period
|
||||
*/
|
||||
fun getRecentReadWithUnreadChapters(search: String = "") = """
|
||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
||||
FROM (
|
||||
SELECT ${Manga.TABLE}.*
|
||||
FROM ${Manga.TABLE}
|
||||
LEFT JOIN (
|
||||
SELECT ${Chapter.COL_MANGA_ID}, COUNT(*) AS unread
|
||||
FROM ${Chapter.TABLE}
|
||||
WHERE ${Chapter.COL_READ} = 0
|
||||
GROUP BY ${Chapter.COL_MANGA_ID}
|
||||
) AS C
|
||||
ON ${Manga.COL_ID} = C.${Chapter.COL_MANGA_ID}
|
||||
WHERE C.unread > 0
|
||||
GROUP BY ${Manga.COL_ID}
|
||||
ORDER BY ${Manga.COL_TITLE}
|
||||
) AS ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||
JOIN ${History.TABLE}
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
JOIN (
|
||||
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID}, MAX(${History.TABLE}.${History.COL_LAST_READ}) as ${History.COL_LAST_READ}
|
||||
FROM ${Chapter.TABLE} JOIN ${History.TABLE}
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
|
||||
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ?
|
||||
AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%'
|
||||
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
|
||||
LIMIT 8
|
||||
"""
|
||||
|
||||
fun getHistoryByMangaId() = """
|
||||
SELECT ${History.TABLE}.*
|
||||
FROM ${History.TABLE}
|
||||
|
@ -5,6 +5,7 @@ import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
||||
import eu.kanade.tachiyomi.data.database.mappers.ChapterGetResolver
|
||||
import eu.kanade.tachiyomi.data.database.mappers.HistoryGetResolver
|
||||
import eu.kanade.tachiyomi.data.database.mappers.MangaGetResolver
|
||||
import eu.kanade.tachiyomi.data.database.models.HistoryImpl
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
||||
|
||||
class MangaChapterHistoryGetResolver : DefaultGetResolver<MangaChapterHistory>() {
|
||||
@ -38,12 +39,13 @@ class MangaChapterHistoryGetResolver : DefaultGetResolver<MangaChapterHistory>()
|
||||
val chapter = chapterResolver.mapFromCursor(cursor)
|
||||
|
||||
// Get history object
|
||||
val history = historyGetResolver.mapFromCursor(cursor)
|
||||
val history = try { historyGetResolver.mapFromCursor(cursor) } catch (e: Exception) { HistoryImpl() }
|
||||
|
||||
// Make certain column conflicts are dealt with
|
||||
manga.id = chapter.manga_id
|
||||
manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl"))
|
||||
chapter.id = history.chapter_id
|
||||
if (history.id != null)
|
||||
chapter.id = history.chapter_id
|
||||
|
||||
// Return result
|
||||
return MangaChapterHistory(manga, chapter, history)
|
||||
|
@ -99,6 +99,20 @@ class DownloadManager(val context: Context) {
|
||||
DownloadService.callListeners(false)
|
||||
}
|
||||
|
||||
fun startDownloadNow(chapter: Chapter) {
|
||||
val download = downloader.queue.find { it.chapter.id == chapter.id } ?: return
|
||||
val queue = downloader.queue.toMutableList()
|
||||
queue.remove(download)
|
||||
queue.add(0, download)
|
||||
reorderQueue(queue)
|
||||
if (isPaused()) {
|
||||
if (DownloadService.isRunning(context))
|
||||
downloader.start()
|
||||
else
|
||||
DownloadService.start(context)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorders the download queue.
|
||||
*
|
||||
|
@ -263,6 +263,8 @@ class Downloader(
|
||||
// Start downloader if needed
|
||||
if (autoStart && wasEmpty) {
|
||||
DownloadService.start(this@Downloader.context)
|
||||
} else if (!isRunning) {
|
||||
notifier.onDownloadPaused()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import android.view.ViewGroup
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowManager
|
||||
import android.webkit.WebView
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.ColorUtils
|
||||
@ -53,6 +52,7 @@ import eu.kanade.tachiyomi.ui.library.LibraryListController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
|
||||
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
|
||||
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
|
||||
import eu.kanade.tachiyomi.ui.recents.RecentsController
|
||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||
import eu.kanade.tachiyomi.ui.setting.SettingsController
|
||||
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
|
||||
@ -61,6 +61,9 @@ import eu.kanade.tachiyomi.util.system.launchUI
|
||||
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets
|
||||
import eu.kanade.tachiyomi.util.view.updateLayoutParams
|
||||
import eu.kanade.tachiyomi.util.view.updatePadding
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.abs
|
||||
import kotlinx.android.synthetic.main.main_activity.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@ -68,9 +71,6 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.abs
|
||||
|
||||
open class MainActivity : BaseActivity(), DownloadServiceListener {
|
||||
|
||||
@ -151,24 +151,24 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
|
||||
else LibraryController(), id
|
||||
)
|
||||
R.id.nav_recents -> {
|
||||
if (preferences.showRecentUpdates().getOrDefault()) setRoot(
|
||||
RecentChaptersController(),
|
||||
id
|
||||
)
|
||||
else setRoot(RecentlyReadController(), id)
|
||||
setRoot(RecentsController(), id)
|
||||
// if (preferences.showRecentUpdates().getOrDefault()) setRoot(
|
||||
// RecentChaptersController(), id
|
||||
// )
|
||||
// else setRoot(RecentlyReadController(), id)
|
||||
}
|
||||
R.id.nav_catalogues -> setRoot(CatalogueController(), id)
|
||||
}
|
||||
} else if (currentRoot.tag()?.toIntOrNull() == id) {
|
||||
if (router.backstackSize == 1) {
|
||||
when (id) {
|
||||
R.id.nav_recents -> {
|
||||
/*R.id.nav_recents -> {
|
||||
val showRecents = preferences.showRecentUpdates().getOrDefault()
|
||||
if (!showRecents) setRoot(RecentChaptersController(), id)
|
||||
else setRoot(RecentlyReadController(), id)
|
||||
preferences.showRecentUpdates().set(!showRecents)
|
||||
updateRecentsIcon()
|
||||
}
|
||||
}*/
|
||||
R.id.nav_library -> {
|
||||
val controller =
|
||||
router.getControllerWithTag(id.toString()) as? LibraryController
|
||||
@ -192,7 +192,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
container.systemUiVisibility =
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
updateRecentsIcon()
|
||||
// updateRecentsIcon()
|
||||
|
||||
supportActionBar?.setDisplayShowCustomEnabled(true)
|
||||
|
||||
@ -309,7 +309,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateRecentsIcon() {
|
||||
/*fun updateRecentsIcon() {
|
||||
bottom_nav.menu.findItem(R.id.nav_recents).icon = AppCompatResources.getDrawable(
|
||||
this,
|
||||
if (preferences.showRecentUpdates()
|
||||
@ -317,7 +317,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
|
||||
) R.drawable.recent_updates_selector_24dp
|
||||
else R.drawable.recent_read_selector_24dp
|
||||
)
|
||||
}
|
||||
}*/
|
||||
|
||||
override fun startSupportActionMode(callback: androidx.appcompat.view.ActionMode.Callback): androidx.appcompat.view.ActionMode? {
|
||||
window?.statusBarColor = getResourceColor(R.attr.colorPrimaryVariant)
|
||||
@ -387,9 +387,14 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
|
||||
when (intent.action) {
|
||||
SHORTCUT_LIBRARY -> bottom_nav.selectedItemId = R.id.nav_library
|
||||
SHORTCUT_RECENTLY_UPDATED, SHORTCUT_RECENTLY_READ -> {
|
||||
preferences.showRecentUpdates().set(intent.action == SHORTCUT_RECENTLY_UPDATED)
|
||||
// preferences.showRecentUpdates().set(intent.action == SHORTCUT_RECENTLY_UPDATED)
|
||||
bottom_nav.selectedItemId = R.id.nav_recents
|
||||
updateRecentsIcon()
|
||||
val controller: Controller = when (intent.action) {
|
||||
SHORTCUT_RECENTLY_UPDATED -> RecentChaptersController()
|
||||
else -> RecentlyReadController()
|
||||
}
|
||||
router.pushController(controller.withFadeTransaction())
|
||||
// updateRecentsIcon()
|
||||
}
|
||||
SHORTCUT_CATALOGUES -> bottom_nav.selectedItemId = R.id.nav_catalogues
|
||||
SHORTCUT_EXTENSIONS -> {
|
||||
|
@ -24,7 +24,7 @@ class MangaDetailsAdapter(
|
||||
|
||||
var items: List<ChapterItem> = emptyList()
|
||||
|
||||
val coverListener: MangaHeaderInterface = controller
|
||||
val delegate: MangaDetailsInterface = controller
|
||||
|
||||
val readColor = context.getResourceColor(android.R.attr.textColorHint)
|
||||
|
||||
@ -67,13 +67,15 @@ class MangaDetailsAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
interface MangaDetailsInterface : MangaHeaderInterface, DownloadInterface
|
||||
|
||||
interface MangaHeaderInterface {
|
||||
fun coverColor(): Int?
|
||||
fun mangaPresenter(): MangaDetailsPresenter
|
||||
fun prepareToShareManga()
|
||||
fun openInWebView()
|
||||
fun startDownloadRange(position: Int)
|
||||
fun readNextChapter()
|
||||
fun downloadChapter(position: Int)
|
||||
fun topCoverHeight(): Int
|
||||
fun tagClicked(text: String)
|
||||
fun showChapterFilter()
|
||||
@ -81,6 +83,10 @@ class MangaDetailsAdapter(
|
||||
fun copyToClipboard(content: String, label: Int)
|
||||
fun zoomImageFromThumb(thumbView: View)
|
||||
fun showTrackingSheet()
|
||||
fun startDownloadRange(position: Int)
|
||||
}
|
||||
|
||||
interface DownloadInterface {
|
||||
fun downloadChapter(position: Int)
|
||||
fun startDownloadNow(position: Int)
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ class MangaDetailsController : BaseController,
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
FlexibleAdapter.OnItemLongClickListener,
|
||||
ActionMode.Callback,
|
||||
MangaDetailsAdapter.MangaHeaderInterface,
|
||||
MangaDetailsAdapter.MangaDetailsInterface,
|
||||
FlexibleAdapter.OnItemMoveListener,
|
||||
ChangeMangaCategoriesDialog.Listener {
|
||||
|
||||
@ -577,7 +577,7 @@ class MangaDetailsController : BaseController,
|
||||
setOnQueryTextChangeListener(searchView) {
|
||||
query = it ?: ""
|
||||
if (query.isNotEmpty()) {
|
||||
(recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder)?.collaspe()
|
||||
(recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder)?.collapse()
|
||||
} else (recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder)?.expand()
|
||||
|
||||
adapter?.setFilter(query)
|
||||
@ -717,6 +717,11 @@ class MangaDetailsController : BaseController,
|
||||
}
|
||||
}
|
||||
|
||||
override fun startDownloadNow(position: Int) {
|
||||
val chapter = (adapter?.getItem(position) as? ChapterItem) ?: return
|
||||
presenter.startDownloadingNow(chapter)
|
||||
}
|
||||
|
||||
private fun downloadChapters(chapters: List<ChapterItem>) {
|
||||
val view = view
|
||||
presenter.downloadChapters(chapters)
|
||||
|
@ -136,6 +136,9 @@ class MangaDetailsPresenter(
|
||||
for (chapter in chapters) {
|
||||
if (downloadManager.isChapterDownloaded(chapter, manga)) {
|
||||
chapter.status = Download.DOWNLOADED
|
||||
} else if (downloadManager.hasQueue()) {
|
||||
chapter.status = downloadManager.queue.find { it.chapter.id == chapter.id }
|
||||
?.status ?: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -276,6 +279,10 @@ class MangaDetailsPresenter(
|
||||
return chapters.maxBy { it.chapter_number }?.chapter_number
|
||||
}
|
||||
|
||||
fun startDownloadingNow(chapter: Chapter) {
|
||||
downloadManager.startDownloadNow(chapter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the given list of chapters with the manager.
|
||||
* @param chapters the list of chapters to download.
|
||||
|
@ -34,9 +34,9 @@ class MangaHeaderHolder(
|
||||
) : BaseFlexibleViewHolder(view, adapter) {
|
||||
|
||||
init {
|
||||
start_reading_button.setOnClickListener { adapter.coverListener.readNextChapter() }
|
||||
start_reading_button.setOnClickListener { adapter.delegate.readNextChapter() }
|
||||
top_view.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
height = adapter.coverListener.topCoverHeight()
|
||||
height = adapter.delegate.topCoverHeight()
|
||||
}
|
||||
more_button.setOnClickListener { expandDesc() }
|
||||
manga_summary.setOnClickListener { expandDesc() }
|
||||
@ -47,30 +47,30 @@ class MangaHeaderHolder(
|
||||
more_button_group.visible()
|
||||
}
|
||||
manga_genres_tags.setOnTagClickListener {
|
||||
adapter.coverListener.tagClicked(it)
|
||||
adapter.delegate.tagClicked(it)
|
||||
}
|
||||
filter_button.setOnClickListener { adapter.coverListener.showChapterFilter() }
|
||||
filters_text.setOnClickListener { adapter.coverListener.showChapterFilter() }
|
||||
chapters_title.setOnClickListener { adapter.coverListener.showChapterFilter() }
|
||||
webview_button.setOnClickListener { adapter.coverListener.openInWebView() }
|
||||
share_button.setOnClickListener { adapter.coverListener.prepareToShareManga() }
|
||||
filter_button.setOnClickListener { adapter.delegate.showChapterFilter() }
|
||||
filters_text.setOnClickListener { adapter.delegate.showChapterFilter() }
|
||||
chapters_title.setOnClickListener { adapter.delegate.showChapterFilter() }
|
||||
webview_button.setOnClickListener { adapter.delegate.openInWebView() }
|
||||
share_button.setOnClickListener { adapter.delegate.prepareToShareManga() }
|
||||
favorite_button.setOnClickListener {
|
||||
adapter.coverListener.favoriteManga(false)
|
||||
adapter.delegate.favoriteManga(false)
|
||||
}
|
||||
favorite_button.setOnLongClickListener {
|
||||
adapter.coverListener.favoriteManga(true)
|
||||
adapter.delegate.favoriteManga(true)
|
||||
true
|
||||
}
|
||||
manga_full_title.setOnLongClickListener {
|
||||
adapter.coverListener.copyToClipboard(manga_full_title.text.toString(), R.string.manga_info_full_title_label)
|
||||
adapter.delegate.copyToClipboard(manga_full_title.text.toString(), R.string.manga_info_full_title_label)
|
||||
true
|
||||
}
|
||||
manga_author.setOnLongClickListener {
|
||||
adapter.coverListener.copyToClipboard(manga_author.text.toString(), R.string.manga_info_author_label)
|
||||
adapter.delegate.copyToClipboard(manga_author.text.toString(), R.string.manga_info_author_label)
|
||||
true
|
||||
}
|
||||
manga_cover.setOnClickListener { adapter.coverListener.zoomImageFromThumb(cover_card) }
|
||||
track_button.setOnClickListener { adapter.coverListener.showTrackingSheet() }
|
||||
manga_cover.setOnClickListener { adapter.delegate.zoomImageFromThumb(cover_card) }
|
||||
track_button.setOnClickListener { adapter.delegate.showTrackingSheet() }
|
||||
if (startExpanded)
|
||||
expandDesc()
|
||||
}
|
||||
@ -86,7 +86,7 @@ class MangaHeaderHolder(
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(item: MangaHeaderItem, manga: Manga) {
|
||||
val presenter = adapter.coverListener.mangaPresenter()
|
||||
val presenter = adapter.delegate.mangaPresenter()
|
||||
manga_full_title.text = manga.title
|
||||
|
||||
if (manga.genre.isNullOrBlank().not())
|
||||
@ -109,7 +109,7 @@ class MangaHeaderHolder(
|
||||
more_button_group.gone()
|
||||
} else more_button_group.visible()
|
||||
}
|
||||
if (adapter.hasFilter()) collaspe()
|
||||
if (adapter.hasFilter()) collapse()
|
||||
else expand()
|
||||
}
|
||||
manga_summary_label.text = itemView.context.getString(R.string.about_this,
|
||||
@ -139,7 +139,7 @@ class MangaHeaderHolder(
|
||||
)
|
||||
checked(!item.isLocked && manga.favorite)
|
||||
}
|
||||
true_backdrop.setBackgroundColor(adapter.coverListener.coverColor()
|
||||
true_backdrop.setBackgroundColor(adapter.delegate.coverColor()
|
||||
?: itemView.context.getResourceColor(android.R.attr.colorBackground))
|
||||
|
||||
val tracked = presenter.isTracked() && !item.isLocked
|
||||
@ -180,7 +180,7 @@ class MangaHeaderHolder(
|
||||
chapters_title.text = itemView.resources.getQuantityString(R.plurals.chapters, count, count)
|
||||
|
||||
top_view.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
height = adapter.coverListener.topCoverHeight()
|
||||
height = adapter.delegate.topCoverHeight()
|
||||
}
|
||||
|
||||
manga_status.text = (itemView.context.getString(when (manga.status) {
|
||||
@ -243,7 +243,7 @@ class MangaHeaderHolder(
|
||||
}
|
||||
|
||||
fun updateTracking() {
|
||||
val presenter = adapter.coverListener.mangaPresenter() ?: return
|
||||
val presenter = adapter.delegate.mangaPresenter() ?: return
|
||||
val tracked = presenter.isTracked()
|
||||
with(track_button) {
|
||||
text = itemView.context.getString(if (tracked) R.string.action_filter_tracked
|
||||
@ -255,7 +255,7 @@ class MangaHeaderHolder(
|
||||
}
|
||||
}
|
||||
|
||||
fun collaspe() {
|
||||
fun collapse() {
|
||||
sub_item_group.gone()
|
||||
if (more_button.visibility == View.VISIBLE || more_button.visibility == View.INVISIBLE)
|
||||
more_button_group.invisible()
|
||||
|
@ -25,7 +25,7 @@ class ChapterHolder(
|
||||
init {
|
||||
download_button.setOnClickListener { downloadOrRemoveMenu() }
|
||||
download_button.setOnLongClickListener {
|
||||
adapter.coverListener.startDownloadRange(adapterPosition)
|
||||
adapter.delegate.startDownloadRange(adapterPosition)
|
||||
true
|
||||
}
|
||||
}
|
||||
@ -33,7 +33,7 @@ class ChapterHolder(
|
||||
private fun downloadOrRemoveMenu() {
|
||||
val chapter = adapter.getItem(adapterPosition) as? ChapterItem ?: return
|
||||
if (chapter.status == Download.NOT_DOWNLOADED || chapter.status == Download.ERROR) {
|
||||
adapter.coverListener.downloadChapter(adapterPosition)
|
||||
adapter.delegate.downloadChapter(adapterPosition)
|
||||
} else {
|
||||
download_button.post {
|
||||
// Create a PopupMenu, giving it the clicked view for an anchor
|
||||
@ -42,14 +42,19 @@ class ChapterHolder(
|
||||
// Inflate our menu resource into the PopupMenu's Menu
|
||||
popup.menuInflater.inflate(R.menu.chapter_download, popup.menu)
|
||||
|
||||
popup.menu.findItem(R.id.action_start).isVisible = chapter.status == Download.QUEUE
|
||||
|
||||
// Hide download and show delete if the chapter is downloaded
|
||||
if (chapter.status != Download.DOWNLOADED) popup.menu.findItem(R.id.action_delete).title = download_button.context.getString(
|
||||
R.string.action_cancel
|
||||
)
|
||||
|
||||
// Set a listener so we are notified if a menu item is clicked
|
||||
popup.setOnMenuItemClickListener { _ ->
|
||||
adapter.coverListener.downloadChapter(adapterPosition)
|
||||
popup.setOnMenuItemClickListener { item ->
|
||||
when (item.itemId) {
|
||||
R.id.action_delete -> adapter.delegate.downloadChapter(adapterPosition)
|
||||
R.id.action_start -> adapter.delegate.startDownloadNow(adapterPosition)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,6 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.jakewharton.rxbinding.support.v4.widget.refreshes
|
||||
@ -19,24 +18,17 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.main.RootSearchInterface
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
|
||||
import eu.kanade.tachiyomi.util.view.scrollViewWith
|
||||
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
|
||||
import eu.kanade.tachiyomi.util.view.snack
|
||||
import kotlinx.android.synthetic.main.main_activity.*
|
||||
import kotlinx.android.synthetic.main.recent_chapters_controller.*
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* Fragment that shows recent chapters.
|
||||
@ -49,7 +41,6 @@ class RecentChaptersController : NucleusController<RecentChaptersPresenter>(),
|
||||
FlexibleAdapter.OnItemLongClickListener,
|
||||
FlexibleAdapter.OnUpdateListener,
|
||||
ConfirmDeleteChaptersDialog.Listener,
|
||||
RootSearchInterface,
|
||||
RecentChaptersAdapter.OnCoverClickListener {
|
||||
|
||||
init {
|
||||
@ -86,7 +77,7 @@ class RecentChaptersController : NucleusController<RecentChaptersPresenter>(),
|
||||
*/
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
view.applyWindowInsetsForRootController(activity!!.bottom_nav)
|
||||
// view.applyWindowInsetsForController()
|
||||
|
||||
view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS)
|
||||
// Init RecyclerView and adapter
|
||||
@ -347,43 +338,15 @@ class RecentChaptersController : NucleusController<RecentChaptersPresenter>(),
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.recent_updates, menu)
|
||||
val searchItem = menu.findItem(R.id.action_search)
|
||||
val searchView = searchItem.actionView as SearchView
|
||||
searchView.queryHint = resources?.getString(R.string.action_search)
|
||||
if (query.isNotEmpty()) {
|
||||
searchItem.expandActionView()
|
||||
searchView.setQuery(query, true)
|
||||
searchView.clearFocus()
|
||||
}
|
||||
setOnQueryTextChangeListener(searchView) {
|
||||
if (query != it) {
|
||||
query = it ?: return@setOnQueryTextChangeListener false
|
||||
adapter?.setFilter(query)
|
||||
adapter?.performFilter()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
// Fixes problem with the overflow icon showing up in lieu of search
|
||||
searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
|
||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||
activity?.invalidateOptionsMenu()
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_recents -> {
|
||||
router.setRoot(
|
||||
R.id.action_sort -> {
|
||||
/*router.setRoot(
|
||||
RecentlyReadController().withFadeTransaction().tag(R.id.nav_recents.toString()))
|
||||
Injekt.get<PreferencesHelper>().showRecentUpdates().set(false)
|
||||
(activity as? MainActivity)?.updateRecentsIcon()
|
||||
(activity as? MainActivity)?.updateRecentsIcon()*/
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
|
@ -19,7 +19,7 @@ import uy.kohesive.injekt.injectLazy
|
||||
* @constructor creates an instance of the adapter.
|
||||
*/
|
||||
class RecentlyReadAdapter(controller: RecentlyReadController) :
|
||||
FlexibleAdapter<IFlexible<*>>(null, controller, true) {
|
||||
FlexibleAdapter<IFlexible<*>>(null, controller, true) {
|
||||
|
||||
val sourceManager by injectLazy<SourceManager>()
|
||||
|
||||
|
@ -15,25 +15,17 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.backup.BackupRestoreService
|
||||
import eu.kanade.tachiyomi.data.database.models.History
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BaseController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||
import eu.kanade.tachiyomi.ui.catalogue.browse.ProgressItem
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.main.RootSearchInterface
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
|
||||
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
|
||||
import eu.kanade.tachiyomi.util.view.scrollViewWith
|
||||
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
|
||||
import kotlinx.android.synthetic.main.main_activity.*
|
||||
import kotlinx.android.synthetic.main.recently_read_controller.*
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* Fragment that shows recently read manga.
|
||||
@ -46,7 +38,6 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle),
|
||||
RecentlyReadAdapter.OnRemoveClickListener,
|
||||
RecentlyReadAdapter.OnResumeClickListener,
|
||||
RecentlyReadAdapter.OnCoverClickListener,
|
||||
RootSearchInterface,
|
||||
RemoveHistoryDialog.Listener {
|
||||
|
||||
init {
|
||||
@ -69,7 +60,7 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle),
|
||||
private var recentItems: MutableList<RecentlyReadItem>? = null
|
||||
|
||||
override fun getTitle(): String? {
|
||||
return resources?.getString(R.string.label_recent_manga)
|
||||
return resources?.getString(R.string.history)
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
@ -83,7 +74,7 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle),
|
||||
*/
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
view.applyWindowInsetsForRootController(activity!!.bottom_nav)
|
||||
// view.applyWindowInsetsForController()
|
||||
// Initialize adapter
|
||||
adapter = RecentlyReadAdapter(this)
|
||||
recycler.adapter = adapter
|
||||
@ -232,7 +223,7 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle),
|
||||
})
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
/*override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_recents -> {
|
||||
router.setRoot(
|
||||
@ -242,5 +233,5 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle),
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.History
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.util.system.executeOnIO
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
import java.util.Calendar
|
||||
import java.util.Comparator
|
||||
@ -36,13 +37,13 @@ class RecentlyReadPresenter(private val view: RecentlyReadController) {
|
||||
* Get all recent manga up to a point
|
||||
* @return list of history
|
||||
*/
|
||||
private fun getRecentMangaLimit(search: String = ""): List<RecentlyReadItem> {
|
||||
private suspend fun getRecentMangaLimit(search: String = ""): List<RecentlyReadItem> {
|
||||
// Set date for recent manga
|
||||
val cal = Calendar.getInstance()
|
||||
cal.time = Date()
|
||||
cal.add(Calendar.YEAR, -50)
|
||||
|
||||
return db.getRecentMangaLimit(cal.time, lastCount, search).executeAsBlocking()
|
||||
return db.getRecentMangaLimit(cal.time, lastCount, search).executeOnIO()
|
||||
.map(::RecentlyReadItem)
|
||||
}
|
||||
|
||||
@ -57,7 +58,7 @@ class RecentlyReadPresenter(private val view: RecentlyReadController) {
|
||||
}
|
||||
|
||||
suspend fun refresh(search: String? = null): List<RecentlyReadItem> {
|
||||
val manga = withContext(Dispatchers.IO) { getRecentMangaLimit(search ?: "") }
|
||||
val manga = getRecentMangaLimit(search ?: "")
|
||||
checkIfNew(manga.size, search)
|
||||
lastSearch = search ?: lastSearch
|
||||
lastCount = manga.size
|
||||
|
@ -0,0 +1,32 @@
|
||||
package eu.kanade.tachiyomi.ui.recents
|
||||
|
||||
import android.widget.ImageView
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaDetailsAdapter
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
|
||||
class RecentMangaAdapter(val delegate: RecentsInterface) :
|
||||
FlexibleAdapter<IFlexible<RecentMangaHolder>>(null, delegate, true) {
|
||||
|
||||
val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols()
|
||||
.apply { decimalSeparator = '.' })
|
||||
|
||||
interface RecentsInterface : RecentMangaInterface, MangaDetailsAdapter.DownloadInterface
|
||||
|
||||
interface RecentMangaInterface {
|
||||
fun onCoverClick(position: Int)
|
||||
fun markAsRead(position: Int)
|
||||
fun setCover(manga: Manga, view: ImageView)
|
||||
}
|
||||
|
||||
override fun onItemSwiped(position: Int, direction: Int) {
|
||||
super.onItemSwiped(position, direction)
|
||||
when (direction) {
|
||||
ItemTouchHelper.LEFT -> delegate.markAsRead(position)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
package eu.kanade.tachiyomi.ui.recents
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.util.view.visibleIf
|
||||
import java.util.Date
|
||||
import kotlinx.android.synthetic.main.download_button.*
|
||||
import kotlinx.android.synthetic.main.recent_manga_item.*
|
||||
|
||||
class RecentMangaHolder(
|
||||
view: View,
|
||||
val adapter: RecentMangaAdapter
|
||||
) : BaseFlexibleViewHolder(view, adapter) {
|
||||
|
||||
init {
|
||||
cover_thumbnail.setOnClickListener { adapter.delegate.onCoverClick(adapterPosition) }
|
||||
download_button.setOnClickListener { downloadOrRemoveMenu() }
|
||||
}
|
||||
|
||||
private fun downloadOrRemoveMenu() {
|
||||
val chapter = adapter.getItem(adapterPosition) as? RecentMangaItem ?: return
|
||||
if (chapter.status == Download.NOT_DOWNLOADED || chapter.status == Download.ERROR) {
|
||||
adapter.delegate.downloadChapter(adapterPosition)
|
||||
} else {
|
||||
download_button.post {
|
||||
// Create a PopupMenu, giving it the clicked view for an anchor
|
||||
val popup = PopupMenu(download_button.context, download_button)
|
||||
|
||||
// Inflate our menu resource into the PopupMenu's Menu
|
||||
popup.menuInflater.inflate(R.menu.chapter_download, popup.menu)
|
||||
|
||||
popup.menu.findItem(R.id.action_start).isVisible = chapter.status == Download.QUEUE
|
||||
|
||||
// Hide download and show delete if the chapter is downloaded
|
||||
if (chapter.status != Download.DOWNLOADED) popup.menu.findItem(R.id.action_delete).title = download_button.context.getString(
|
||||
R.string.action_cancel
|
||||
)
|
||||
|
||||
// Set a listener so we are notified if a menu item is clicked
|
||||
popup.setOnMenuItemClickListener { item ->
|
||||
when (item.itemId) {
|
||||
R.id.action_delete -> adapter.delegate.downloadChapter(adapterPosition)
|
||||
R.id.action_start -> adapter.delegate.startDownloadNow(adapterPosition)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
// Finally show the PopupMenu
|
||||
popup.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun bind(item: RecentMangaItem) {
|
||||
download_button.visibleIf(item.mch.manga.source != LocalSource.ID)
|
||||
title.text = item.mch.manga.title
|
||||
val holder = (adapter.delegate as RecentsHolder)
|
||||
val isSearch =
|
||||
(holder.adapter.getItem(holder.adapterPosition) as RecentsItem).recentType == RecentsItem.SEARCH
|
||||
subtitle.text = item.chapter.name
|
||||
body.text = if (isSearch) when {
|
||||
item.chapter.id != item.mch.chapter.id -> body.context.getString(
|
||||
R.string.last_read_chapter_x, adapter.decimalFormat.format(
|
||||
item.mch.chapter.chapter_number
|
||||
) + " (${DateUtils.getRelativeTimeSpanString(
|
||||
item.mch.history.last_read, Date().time, DateUtils.MINUTE_IN_MILLIS
|
||||
)})"
|
||||
)
|
||||
item.mch.history.id == null -> body.context.getString(
|
||||
R.string.uploaded_x, DateUtils.getRelativeTimeSpanString(
|
||||
item.chapter.date_upload, Date().time, DateUtils.HOUR_IN_MILLIS
|
||||
).toString()
|
||||
)
|
||||
else -> body.context.getString(
|
||||
R.string.last_read_x, DateUtils.getRelativeTimeSpanString(
|
||||
item.mch.history.last_read, Date().time, DateUtils.MINUTE_IN_MILLIS
|
||||
).toString()
|
||||
)
|
||||
} else when {
|
||||
item.chapter.id != item.mch.chapter.id -> body.context.getString(
|
||||
R.string.last_read_chapter_x, adapter.decimalFormat.format(
|
||||
item.mch.chapter.chapter_number
|
||||
)
|
||||
)
|
||||
item.mch.history.id == null -> DateUtils.getRelativeTimeSpanString(
|
||||
item.chapter.date_upload, Date().time, DateUtils.HOUR_IN_MILLIS
|
||||
).toString()
|
||||
item.chapter.pages_left > 0 -> itemView.resources.getQuantityString(
|
||||
R.plurals.pages_left, item.chapter.pages_left, item.chapter.pages_left
|
||||
)
|
||||
else -> ""
|
||||
}
|
||||
adapter.delegate.setCover(item.mch.manga, cover_thumbnail)
|
||||
notifyStatus(
|
||||
if (adapter.isSelected(adapterPosition)) Download.CHECKED else item.status,
|
||||
item.progress
|
||||
)
|
||||
}
|
||||
|
||||
fun notifyStatus(status: Int, progress: Int) = with(download_button) {
|
||||
setDownloadStatus(status, progress)
|
||||
}
|
||||
|
||||
override fun getFrontView(): View {
|
||||
return front_view
|
||||
}
|
||||
|
||||
override fun getRearRightView(): View {
|
||||
return right_view
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package eu.kanade.tachiyomi.ui.recents
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
|
||||
class RecentMangaItem(val mch: MangaChapterHistory, val chapter: Chapter) :
|
||||
AbstractFlexibleItem<RecentMangaHolder>
|
||||
() {
|
||||
|
||||
private var _status: Int = 0
|
||||
|
||||
val progress: Int
|
||||
get() {
|
||||
val pages = download?.pages ?: return 0
|
||||
return pages.map(Page::progress).average().toInt()
|
||||
}
|
||||
|
||||
var status: Int
|
||||
get() = download?.status ?: _status
|
||||
set(value) { _status = value }
|
||||
|
||||
@Transient var download: Download? = null
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.recent_manga_item
|
||||
}
|
||||
|
||||
override fun createViewHolder(
|
||||
view: View,
|
||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
|
||||
): RecentMangaHolder {
|
||||
return RecentMangaHolder(view, adapter as RecentMangaAdapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(
|
||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||
holder: RecentMangaHolder,
|
||||
position: Int,
|
||||
payloads: MutableList<Any?>?
|
||||
) {
|
||||
|
||||
holder.bind(this)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other is RecentMangaItem) {
|
||||
return chapter.id == other.chapter.id
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return chapter.id!!.hashCode()
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package eu.kanade.tachiyomi.ui.recents
|
||||
|
||||
import android.widget.ImageView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
|
||||
class RecentsAdapter(val delegate: RecentsInterface) :
|
||||
FlexibleAdapter<IFlexible<RecentsHolder>>(null, delegate, true) {
|
||||
|
||||
interface RecentsInterface {
|
||||
fun resumeManga(manga: Manga, chapter: Chapter)
|
||||
fun showManga(manga: Manga)
|
||||
fun markAsRead(manga: Manga, chapter: Chapter)
|
||||
fun downloadChapter(item: RecentMangaItem)
|
||||
fun downloadChapterNow(chapter: Chapter)
|
||||
fun setCover(manga: Manga, view: ImageView)
|
||||
fun viewAll(position: Int)
|
||||
}
|
||||
}
|
@ -0,0 +1,256 @@
|
||||
package eu.kanade.tachiyomi.ui.recents
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BaseController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.main.RootSearchInterface
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
|
||||
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
|
||||
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
|
||||
import eu.kanade.tachiyomi.util.view.scrollViewWith
|
||||
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
|
||||
import eu.kanade.tachiyomi.util.view.snack
|
||||
import kotlinx.android.synthetic.main.main_activity.*
|
||||
import kotlinx.android.synthetic.main.recently_read_controller.*
|
||||
|
||||
/**
|
||||
* Fragment that shows recently read manga.
|
||||
* Uses R.layout.fragment_recently_read.
|
||||
* UI related actions should be called from here.
|
||||
*/
|
||||
class RecentsController(bundle: Bundle? = null) : BaseController(bundle),
|
||||
FlexibleAdapter.OnUpdateListener,
|
||||
RecentsAdapter.RecentsInterface,
|
||||
RootSearchInterface {
|
||||
|
||||
init {
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter containing the recent manga.
|
||||
*/
|
||||
private val adapter = RecentsAdapter(this)
|
||||
|
||||
private var presenter = RecentsPresenter(this)
|
||||
private var recentItems: List<RecentsItem>? = null
|
||||
private var snack: Snackbar? = null
|
||||
private var lastChapterId: Long? = null
|
||||
|
||||
override fun getTitle(): String? {
|
||||
return resources?.getString(R.string.short_recents)
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
return inflater.inflate(R.layout.recently_read_controller, container, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when view is created
|
||||
*
|
||||
* @param view created view
|
||||
*/
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
view.applyWindowInsetsForRootController(activity!!.bottom_nav)
|
||||
// Initialize adapter
|
||||
recycler.adapter = adapter
|
||||
recycler.layoutManager = LinearLayoutManager(view.context)
|
||||
recycler.setHasFixedSize(true)
|
||||
|
||||
scrollViewWith(recycler, skipFirstSnap = true)
|
||||
|
||||
if (recentItems != null) adapter.updateDataSet(recentItems!!.toList())
|
||||
presenter.onCreate()
|
||||
}
|
||||
|
||||
override fun onActivityResumed(activity: Activity) {
|
||||
super.onActivityResumed(activity)
|
||||
if (view != null)
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
snack?.dismiss()
|
||||
presenter.onDestroy()
|
||||
snack = null
|
||||
}
|
||||
|
||||
fun refresh() = presenter.getRecents()
|
||||
|
||||
fun showLists(recents: List<RecentsItem>) {
|
||||
recentItems = recents
|
||||
adapter.updateDataSet(recents)
|
||||
if (lastChapterId != null) {
|
||||
refreshItem(lastChapterId ?: 0L)
|
||||
lastChapterId = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUpdateEmptyView(size: Int) {
|
||||
if (size > 0) {
|
||||
empty_view?.hide()
|
||||
} else {
|
||||
empty_view?.show(R.drawable.ic_history_white_128dp, R.string
|
||||
.information_no_recent_manga)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateChapterDownload(download: Download) {
|
||||
if (view == null) return
|
||||
for (i in 0 until adapter.itemCount) {
|
||||
val holder = recycler.findViewHolderForAdapterPosition(i) as? RecentsHolder ?: continue
|
||||
if (holder.updateChapterDownload(download)) break
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshItem(chapterId: Long) {
|
||||
for (i in 0 until adapter.itemCount) {
|
||||
val holder = recycler.findViewHolderForAdapterPosition(i) as? RecentsHolder ?: continue
|
||||
holder.refreshChapter(chapterId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun downloadChapter(item: RecentMangaItem) {
|
||||
val view = view ?: return
|
||||
val chapter = item.chapter
|
||||
val manga = item.mch.manga
|
||||
if (item.status != Download.NOT_DOWNLOADED && item.status != Download.ERROR) {
|
||||
presenter.deleteChapter(chapter, manga)
|
||||
} else {
|
||||
if (item.status == Download.ERROR) DownloadService.start(view.context)
|
||||
else presenter.downloadChapter(manga, chapter)
|
||||
}
|
||||
}
|
||||
|
||||
override fun downloadChapterNow(chapter: Chapter) {
|
||||
presenter.startDownloadChapterNow(chapter)
|
||||
}
|
||||
|
||||
override fun showManga(manga: Manga) = router.pushController(MangaDetailsController(manga).withFadeTransaction())
|
||||
|
||||
override fun resumeManga(manga: Manga, chapter: Chapter) {
|
||||
val activity = activity ?: return
|
||||
val intent = ReaderActivity.newIntent(activity, manga, chapter)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
override fun markAsRead(manga: Manga, chapter: Chapter) {
|
||||
val lastRead = chapter.last_page_read
|
||||
val pagesLeft = chapter.pages_left
|
||||
lastChapterId = chapter.id
|
||||
presenter.markChapterRead(chapter, true)
|
||||
snack =
|
||||
view?.snack(R.string.marked_as_read, Snackbar.LENGTH_INDEFINITE) {
|
||||
anchorView = activity?.bottom_nav
|
||||
var undoing = false
|
||||
setAction(R.string.action_undo) {
|
||||
presenter.markChapterRead(chapter, false, lastRead, pagesLeft)
|
||||
undoing = true
|
||||
}
|
||||
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
||||
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
|
||||
super.onDismissed(transientBottomBar, event)
|
||||
if (!undoing && presenter.preferences.removeAfterMarkedAsRead()) {
|
||||
lastChapterId = chapter.id
|
||||
presenter.deleteChapter(chapter, manga)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
(activity as? MainActivity)?.setUndoSnackBar(snack)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.recents, menu)
|
||||
val searchItem = menu.findItem(R.id.action_search)
|
||||
val searchView = searchItem.actionView as SearchView
|
||||
searchView.queryHint = view?.context?.getString(R.string.search_recents)
|
||||
if (presenter.query.isNotEmpty()) {
|
||||
searchItem.expandActionView()
|
||||
searchView.setQuery(presenter.query, true)
|
||||
searchView.clearFocus()
|
||||
}
|
||||
setOnQueryTextChangeListener(searchView) {
|
||||
if (presenter.query != it) {
|
||||
presenter.query = it ?: return@setOnQueryTextChangeListener false
|
||||
refresh()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
||||
super.onChangeStarted(handler, type)
|
||||
if (type.isEnter) {
|
||||
if (type == ControllerChangeType.POP_EXIT) {
|
||||
presenter.onCreate()
|
||||
}
|
||||
setHasOptionsMenu(true)
|
||||
} else {
|
||||
snack?.dismiss()
|
||||
setHasOptionsMenu(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setCover(manga: Manga, view: ImageView) {
|
||||
val activity = activity ?: return
|
||||
GlideApp.with(activity).load(manga).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString())).into(view)
|
||||
}
|
||||
|
||||
override fun viewAll(position: Int) {
|
||||
val recentsType = (adapter.getItem(position) as? RecentsItem)?.recentType ?: return
|
||||
val controller: Controller = when (recentsType) {
|
||||
RecentsItem.NEW_CHAPTERS -> RecentChaptersController()
|
||||
RecentsItem.CONTINUE_READING -> RecentlyReadController()
|
||||
else -> return
|
||||
}
|
||||
router.pushController(controller.withFadeTransaction())
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_refresh -> {
|
||||
if (!LibraryUpdateService.isRunning()) {
|
||||
val view = view ?: return true
|
||||
LibraryUpdateService.start(view.context)
|
||||
snack = view.snack(R.string.updating_library) {
|
||||
anchorView = (activity as? MainActivity)?.bottom_nav
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
package eu.kanade.tachiyomi.ui.recents
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.view.updateLayoutParams
|
||||
import eu.kanade.tachiyomi.util.view.visibleIf
|
||||
import kotlinx.android.synthetic.main.recents_item.*
|
||||
|
||||
class RecentsHolder(
|
||||
view: View,
|
||||
val adapter: RecentsAdapter
|
||||
) : BaseFlexibleViewHolder(view, adapter),
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
FlexibleAdapter.OnUpdateListener,
|
||||
RecentMangaAdapter.RecentsInterface {
|
||||
|
||||
private val subAdapter = RecentMangaAdapter(this)
|
||||
|
||||
init {
|
||||
recycler.adapter = subAdapter
|
||||
subAdapter.isSwipeEnabled = true
|
||||
val manager = LinearLayoutManager(view.context)
|
||||
recycler.layoutManager = manager
|
||||
recycler.addItemDecoration(
|
||||
DividerItemDecoration(
|
||||
recycler.context, DividerItemDecoration.VERTICAL
|
||||
)
|
||||
)
|
||||
recycler.setHasFixedSize(true)
|
||||
view_all.setOnClickListener { adapter.delegate.viewAll(adapterPosition) }
|
||||
subAdapter.itemTouchHelperCallback.setSwipeFlags(
|
||||
ItemTouchHelper.LEFT
|
||||
)
|
||||
}
|
||||
|
||||
fun bind(item: RecentsItem) {
|
||||
when (item.recentType) {
|
||||
RecentsItem.CONTINUE_READING -> {
|
||||
title.setText(R.string.continue_reading)
|
||||
view_all.setText(R.string.view_history)
|
||||
}
|
||||
RecentsItem.NEW_CHAPTERS -> {
|
||||
title.setText(R.string.new_chapters)
|
||||
view_all.setText(R.string.view_all_updates)
|
||||
}
|
||||
RecentsItem.NEWLY_ADDED -> {
|
||||
title.setText(R.string.new_additions)
|
||||
}
|
||||
}
|
||||
title.visibleIf(item.recentType != RecentsItem.SEARCH)
|
||||
view_all.visibleIf(item.recentType == RecentsItem.CONTINUE_READING || item.recentType == RecentsItem.NEW_CHAPTERS)
|
||||
recycler.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin = if (adapterPosition == adapter.itemCount - 1) 0 else 12.dpToPx
|
||||
}
|
||||
subAdapter.updateDataSet(item.mangaList)
|
||||
}
|
||||
|
||||
override fun downloadChapter(position: Int) {
|
||||
val item = (subAdapter.getItem(position) as? RecentMangaItem) ?: return
|
||||
adapter.delegate.downloadChapter(item)
|
||||
}
|
||||
|
||||
override fun startDownloadNow(position: Int) {
|
||||
val chapter = (subAdapter.getItem(position) as? RecentMangaItem)?.chapter ?: return
|
||||
adapter.delegate.downloadChapterNow(chapter)
|
||||
}
|
||||
|
||||
override fun onCoverClick(position: Int) {
|
||||
val manga = (subAdapter.getItem(position) as? RecentMangaItem)?.mch?.manga ?: return
|
||||
adapter.delegate.showManga(manga)
|
||||
}
|
||||
|
||||
override fun onItemClick(view: View?, position: Int): Boolean {
|
||||
val item = (subAdapter.getItem(position) as? RecentMangaItem) ?: return false
|
||||
adapter.delegate.resumeManga(item.mch.manga, item.chapter)
|
||||
return true
|
||||
}
|
||||
|
||||
fun updateChapterDownload(download: Download): Boolean {
|
||||
val holder = getHolder(download.chapter) ?: return false
|
||||
holder.notifyStatus(download.status, download.progress)
|
||||
return true
|
||||
}
|
||||
|
||||
fun refreshChapter(chapterId: Long) {
|
||||
val item = (adapter.getItem(adapterPosition) as? RecentsItem) ?: return
|
||||
val recentItemPos = item.mangaList.indexOfFirst { it.mch.chapter.id == chapterId }
|
||||
if (recentItemPos > -1) subAdapter.notifyItemChanged(recentItemPos)
|
||||
}
|
||||
|
||||
override fun onUpdateEmptyView(size: Int) {
|
||||
if (size > 0) {
|
||||
empty_view?.hide()
|
||||
} else {
|
||||
val recentsType = (adapter.getItem(adapterPosition) as? RecentsItem)?.recentType ?: return
|
||||
when (recentsType) {
|
||||
RecentsItem.CONTINUE_READING ->
|
||||
empty_view?.show(R.drawable.ic_history_white_128dp, R.string.information_no_recent_manga)
|
||||
RecentsItem.NEW_CHAPTERS ->
|
||||
empty_view?.show(R.drawable.ic_update_black_128dp, R.string.information_no_recent)
|
||||
RecentsItem.NEWLY_ADDED ->
|
||||
empty_view?.show(R.drawable.recent_read_outline_128dp, R.string.information_no_recent)
|
||||
RecentsItem.SEARCH ->
|
||||
empty_view?.show(R.drawable.search_128dp, R.string.no_search_result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getHolder(chapter: Chapter): RecentMangaHolder? {
|
||||
return recycler?.findViewHolderForItemId(chapter.id!!) as? RecentMangaHolder
|
||||
}
|
||||
|
||||
override fun setCover(manga: Manga, view: ImageView) {
|
||||
adapter.delegate.setCover(manga, view)
|
||||
}
|
||||
|
||||
override fun markAsRead(position: Int) {
|
||||
val item = (subAdapter.getItem(position) as RecentMangaItem)
|
||||
adapter.delegate.markAsRead(item.mch.manga, item.chapter)
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package eu.kanade.tachiyomi.ui.recents
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
class RecentsItem(val recentType: Int, val mangaList: List<RecentMangaItem>) :
|
||||
AbstractFlexibleItem<RecentsHolder>() {
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.recents_item
|
||||
}
|
||||
|
||||
override fun createViewHolder(
|
||||
view: View,
|
||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
|
||||
): RecentsHolder {
|
||||
return RecentsHolder(view, adapter as RecentsAdapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(
|
||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||
holder: RecentsHolder,
|
||||
position: Int,
|
||||
payloads: MutableList<Any?>?
|
||||
) {
|
||||
|
||||
holder.bind(this)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other is RecentsItem) {
|
||||
return recentType == other.recentType
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return recentType.hashCode()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CONTINUE_READING = 0
|
||||
const val NEW_CHAPTERS = 1
|
||||
const val NEWLY_ADDED = 2
|
||||
const val SEARCH = 3
|
||||
}
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
package eu.kanade.tachiyomi.ui.recents
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
||||
import eu.kanade.tachiyomi.data.library.LibraryServiceListener
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.util.system.executeOnIO
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class RecentsPresenter(
|
||||
val controller: RecentsController,
|
||||
val preferences: PreferencesHelper = Injekt.get(),
|
||||
private val downloadManager: DownloadManager = Injekt.get(),
|
||||
private val db: DatabaseHelper = Injekt.get()
|
||||
) : DownloadQueue.DownloadListener, LibraryServiceListener {
|
||||
|
||||
private var scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||
|
||||
private var recentItems = listOf<RecentMangaItem>()
|
||||
var groupedRecentItems = listOf<RecentsItem>()
|
||||
var query = ""
|
||||
|
||||
fun onCreate() {
|
||||
downloadManager.addListener(this)
|
||||
LibraryUpdateService.setListener(this)
|
||||
getRecents()
|
||||
}
|
||||
|
||||
fun getRecents() {
|
||||
scope.launch {
|
||||
val cal = Calendar.getInstance()
|
||||
cal.time = Date()
|
||||
if (query.isNotEmpty()) cal.add(Calendar.YEAR, -50)
|
||||
else cal.add(Calendar.MONTH, -1)
|
||||
val cReading =
|
||||
if (query.isEmpty())
|
||||
db.getRecentsWithUnread(cal.time, query).executeOnIO()
|
||||
else
|
||||
db.getRecentMangaLimit(cal.time, 8, query).executeOnIO()
|
||||
val rUpdates = db.getUpdatedManga(cal.time, query).executeOnIO()
|
||||
rUpdates.forEach {
|
||||
it.history.last_read = it.chapter.date_upload
|
||||
}
|
||||
val mangaList = (cReading + rUpdates).sortedByDescending {
|
||||
it.history.last_read
|
||||
}.distinctBy {
|
||||
if (query.isEmpty()) it.manga.id else it.chapter.id }
|
||||
recentItems = mangaList.mapNotNull {
|
||||
val chapter = if (it.chapter.read) getNextChapter(it.manga)
|
||||
else it.chapter
|
||||
if (chapter == null) if (query.isNotEmpty()) RecentMangaItem(it, it.chapter)
|
||||
else null
|
||||
else RecentMangaItem(it, chapter)
|
||||
}
|
||||
setDownloadedChapters(recentItems)
|
||||
if (query.isEmpty()) {
|
||||
val nChaptersItems = RecentsItem(
|
||||
RecentsItem.NEW_CHAPTERS,
|
||||
recentItems.filter { it.mch.history.id == null }.take(4)
|
||||
)
|
||||
val cReadingItems = RecentsItem(
|
||||
RecentsItem.CONTINUE_READING,
|
||||
recentItems.filter { it.mch.history.id != null }.take(
|
||||
8 - nChaptersItems.mangaList.size
|
||||
)
|
||||
)
|
||||
// TODO: Add Date Added
|
||||
groupedRecentItems = listOf(cReadingItems, nChaptersItems).sortedByDescending {
|
||||
it.mangaList.firstOrNull()?.mch?.history?.last_read ?: 0
|
||||
}
|
||||
} else {
|
||||
groupedRecentItems = listOf(RecentsItem(RecentsItem.SEARCH, recentItems))
|
||||
}
|
||||
withContext(Dispatchers.Main) { controller.showLists(groupedRecentItems) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNextChapter(manga: Manga): Chapter? {
|
||||
val chapters = db.getChapters(manga).executeAsBlocking()
|
||||
return chapters.sortedByDescending { it.source_order }.find { !it.read }
|
||||
}
|
||||
|
||||
fun onDestroy() {
|
||||
downloadManager.removeListener(this)
|
||||
LibraryUpdateService.removeListener(this)
|
||||
}
|
||||
|
||||
fun cancelScope() {
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and assigns the list of downloaded chapters.
|
||||
*
|
||||
* @param chapters the list of chapter from the database.
|
||||
*/
|
||||
private fun setDownloadedChapters(chapters: List<RecentMangaItem>) {
|
||||
for (item in chapters) {
|
||||
if (downloadManager.isChapterDownloaded(item.chapter, item.mch.manga)) {
|
||||
item.status = Download.DOWNLOADED
|
||||
} else if (downloadManager.hasQueue()) {
|
||||
item.status = downloadManager.queue.find { it.chapter.id == item.chapter.id }
|
||||
?.status ?: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateDownload(download: Download) {
|
||||
recentItems.find { it.chapter.id == download.chapter.id }?.download = download
|
||||
scope.launch(Dispatchers.Main) {
|
||||
controller.updateChapterDownload(download)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUpdateManga(manga: LibraryManga) {
|
||||
getRecents()
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given list of chapter.
|
||||
* @param chapter the chapter to delete.
|
||||
*/
|
||||
fun deleteChapter(chapter: Chapter, manga: Manga, update: Boolean = true) {
|
||||
val source = Injekt.get<SourceManager>().getOrStub(manga.source)
|
||||
downloadManager.deleteChapters(listOf(chapter), manga, source)
|
||||
|
||||
if (update) {
|
||||
val item = recentItems.find { it.chapter.id == chapter.id } ?: return
|
||||
item.apply {
|
||||
status = Download.NOT_DOWNLOADED
|
||||
download = null
|
||||
}
|
||||
|
||||
controller.showLists(groupedRecentItems)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the given list of chapters with the manager.
|
||||
* @param chapter the chapter to download.
|
||||
*/
|
||||
fun downloadChapter(manga: Manga, chapter: Chapter) {
|
||||
downloadManager.downloadChapters(manga, listOf(chapter))
|
||||
}
|
||||
|
||||
fun startDownloadChapterNow(chapter: Chapter) {
|
||||
downloadManager.startDownloadNow(chapter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the selected chapter list as read/unread.
|
||||
* @param selectedChapters the list of selected chapters.
|
||||
* @param read whether to mark chapters as read or unread.
|
||||
*/
|
||||
fun markChapterRead(
|
||||
chapter: Chapter,
|
||||
read: Boolean,
|
||||
lastRead: Int? = null,
|
||||
pagesLeft: Int? = null
|
||||
) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
chapter.apply {
|
||||
this.read = read
|
||||
if (!read) {
|
||||
last_page_read = lastRead ?: 0
|
||||
pages_left = pagesLeft ?: 0
|
||||
}
|
||||
}
|
||||
db.updateChaptersProgress(listOf(chapter)).executeAsBlocking()
|
||||
getRecents()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetListOfObjects
|
||||
import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetObject
|
||||
import com.pushtorefresh.storio.sqlite.operations.put.PreparedPutObject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
suspend fun <T> PreparedGetListOfObjects<T>.executeOnIO(): List<T> {
|
||||
return withContext(Dispatchers.IO) { executeAsBlocking() }
|
||||
}
|
||||
|
||||
suspend fun <T> PreparedGetObject<T>.executeOnIO(): T? {
|
||||
return withContext(Dispatchers.IO) { executeAsBlocking() }
|
||||
}
|
||||
|
||||
suspend fun <T> PreparedPutObject<T>.executeOnIO() {
|
||||
withContext(Dispatchers.IO) { executeAsBlocking() }
|
||||
}
|
@ -323,6 +323,7 @@ fun Controller.scrollViewWith(
|
||||
recycler: RecyclerView,
|
||||
padBottom: Boolean = false,
|
||||
customPadding: Boolean = false,
|
||||
skipFirstSnap: Boolean = false,
|
||||
swipeRefreshLayout: SwipeRefreshLayout? = null,
|
||||
afterInsets: ((WindowInsets) -> Unit)? = null
|
||||
) {
|
||||
@ -370,7 +371,7 @@ fun Controller.scrollViewWith(
|
||||
val closerToTop = abs(activity!!.appbar.y) - halfWay > 0
|
||||
val atTop = (!customPadding &&
|
||||
(recycler.layoutManager as LinearLayoutManager)
|
||||
.findFirstVisibleItemPosition() < 2) ||
|
||||
.findFirstVisibleItemPosition() < 2 && !skipFirstSnap) ||
|
||||
!recycler.canScrollVertically(-1)
|
||||
activity!!.appbar.animate().y(
|
||||
if (closerToTop && !atTop) (-activity!!.appbar.height.toFloat())
|
||||
|
8
app/src/main/res/drawable/recent_read_outline_128dp.xml
Normal file
8
app/src/main/res/drawable/recent_read_outline_128dp.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<!-- drawable/clock_outline.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="128dp"
|
||||
android:width="128dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22C6.47,22 2,17.5 2,12A10,10 0 0,1 12,2M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z" />
|
||||
</vector>
|
24
app/src/main/res/drawable/round_play_background_large.xml
Normal file
24
app/src/main/res/drawable/round_play_background_large.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:background="#FFFF00"
|
||||
android:color="?android:attr/colorAccent">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?android:attr/colorAccent" />
|
||||
<corners android:radius="80dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:id="@android:id/background">
|
||||
<shape
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="80dp"/>
|
||||
<size
|
||||
android:height="100dp"
|
||||
android:width="100dp" />
|
||||
<solid android:color="#AD212121"/>
|
||||
<stroke android:width="0.1dp" android:color="#EDEDED" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
9
app/src/main/res/drawable/search_128dp.xml
Normal file
9
app/src/main/res/drawable/search_128dp.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
||||
</vector>
|
@ -1,35 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipe_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipe_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler"
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/recent_chapters_item"/>
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<eu.kanade.tachiyomi.widget.EmptyView
|
||||
android:id="@+id/empty_view"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_height="wrap_content"/>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
tools:listitem="@layout/recent_chapters_item" />
|
||||
|
||||
</FrameLayout>
|
||||
<eu.kanade.tachiyomi.widget.EmptyView
|
||||
android:id="@+id/empty_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
177
app/src/main/res/layout/recent_manga_item.xml
Normal file
177
app/src/main/res/layout/recent_manga_item.xml
Normal file
@ -0,0 +1,177 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/recent_manga_layout"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/colorBackground">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/right_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
android:background="@color/material_green_800"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/read"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:tint="@color/md_white_1000"
|
||||
android:layout_gravity="end|center"
|
||||
android:layout_marginEnd="21dp"
|
||||
android:src="@drawable/eye" />
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/front_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="?attr/selectable_list_drawable"
|
||||
android:minHeight="@dimen/material_component_lists_single_line_with_avatar_height">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/card_layout"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
app:layout_constrainedHeight="true"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/card"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cover_thumbnail"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="?android:attr/colorBackground"
|
||||
android:maxHeight="60dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:background="?android:attr/colorBackground"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@mipmap/ic_launcher" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</FrameLayout>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
style="@style/TextAppearance.Regular.Body1.SemiBold"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/card_layout"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="New Chapter" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/subtitle"
|
||||
style="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="14dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="14sp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
tools:text="Manga title" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/body"
|
||||
style="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginBottom="14dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download_button"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/subtitle"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
tools:text="3 Days ago" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="3dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:background="#66000000"
|
||||
android:indeterminate="false"
|
||||
android:progress="10"
|
||||
android:progressTint="?colorAccent"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toStartOf="@id/download_button"
|
||||
app:layout_constraintStart_toStartOf="@id/title"
|
||||
app:layout_constraintTop_toBottomOf="@id/subtitle" />
|
||||
|
||||
<include
|
||||
layout="@layout/download_button"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/subtitle" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/bottom_line"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="6dp"
|
||||
android:orientation="horizontal"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="card_layout,body" />
|
||||
|
||||
<View
|
||||
android:id="@+id/padding"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="6dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/bottom_line" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</FrameLayout>
|
53
app/src/main/res/layout/recents_item.xml
Normal file
53
app/src/main/res/layout/recents_item.xml
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/recent_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@string/label_recent_updates" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/view_all"
|
||||
style="@style/Theme.Widget.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="@string/view_all"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/title"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:nestedScrollingEnabled="true"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
<eu.kanade.tachiyomi.widget.EmptyView
|
||||
android:id="@+id/empty_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -8,7 +8,7 @@
|
||||
android:title="@string/label_library" />
|
||||
<item
|
||||
android:id="@+id/nav_recents"
|
||||
android:icon="@drawable/recent_updates_selector_24dp"
|
||||
android:icon="@drawable/recent_read_selector_24dp"
|
||||
android:title="@string/short_recents" />
|
||||
<item
|
||||
android:id="@+id/nav_catalogues"
|
||||
|
@ -1,5 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:id="@+id/action_start"
|
||||
android:title="@string/start_downloading_now"
|
||||
android:icon="@drawable/ic_delete_white_24dp"/>
|
||||
<item android:id="@+id/action_delete"
|
||||
android:title="@string/action_remove_download"
|
||||
android:icon="@drawable/ic_delete_white_24dp"/>
|
||||
|
@ -1,25 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_search"
|
||||
android:icon="@drawable/ic_search_white_24dp"
|
||||
android:title="@string/action_search"
|
||||
android:visible="false"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="ifRoom|collapseActionView" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_recents"
|
||||
android:icon="@drawable/ic_history_black_24dp"
|
||||
android:title="@string/label_recent_manga"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:icon="@drawable/ic_settings_white_24dp"
|
||||
android:title="@string/label_settings"
|
||||
app:showAsAction="never"
|
||||
/>
|
||||
android:id="@+id/action_sort"
|
||||
android:icon="@drawable/ic_sort_white_24dp"
|
||||
android:title="@string/action_sort"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
@ -4,20 +4,6 @@
|
||||
android:id="@+id/action_search"
|
||||
android:icon="@drawable/ic_search_white_24dp"
|
||||
android:title="@string/action_search"
|
||||
android:visible="false"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="ifRoom|collapseActionView" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_recents"
|
||||
android:icon="@drawable/ic_update_black_24dp"
|
||||
android:title="@string/label_recent_updates"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:icon="@drawable/ic_settings_white_24dp"
|
||||
android:title="@string/label_settings"
|
||||
app:showAsAction="never"
|
||||
/>
|
||||
</menu>
|
23
app/src/main/res/menu/recents.xml
Normal file
23
app/src/main/res/menu/recents.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".MainActivity">
|
||||
<item
|
||||
android:id="@+id/action_search"
|
||||
android:icon="@drawable/ic_search_white_24dp"
|
||||
android:title="@string/action_search"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
android:visible="false"
|
||||
app:showAsAction="ifRoom|collapseActionView" />
|
||||
<item
|
||||
android:id="@+id/action_refresh"
|
||||
android:icon="@drawable/ic_refresh_white_24dp"
|
||||
android:title="@string/action_update_library"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:icon="@drawable/ic_settings_white_24dp"
|
||||
android:title="@string/label_settings"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
@ -19,7 +19,9 @@
|
||||
<string name="label_library">Library</string>
|
||||
<string name="label_recent_manga">Recently read</string>
|
||||
<string name="label_catalogues">Catalogues</string>
|
||||
<string name="label_recent_updates">Recently updated</string>
|
||||
<string name="label_recent_updates">Recent updates</string>
|
||||
<string name="new_chapters">New chapters</string>
|
||||
<string name="new_additions">New additions</string>
|
||||
<string name="label_selected">Selected: %1$d</string>
|
||||
<string name="label_migration">Source migration</string>
|
||||
<string name="label_extensions">Extensions</string>
|
||||
@ -84,6 +86,7 @@
|
||||
<string name="action_remove_bookmark">Remove bookmark</string>
|
||||
<string name="action_delete">Delete</string>
|
||||
<string name="action_remove_download">Remove download</string>
|
||||
<string name="start_downloading_now">Start downloading now</string>
|
||||
<string name="action_update_library">Update library</string>
|
||||
<string name="action_edit">Edit</string>
|
||||
<string name="action_edit_cover">Edit cover</string>
|
||||
@ -459,7 +462,6 @@
|
||||
<item quantity="one">New chapter</item>
|
||||
<item quantity="other">%d unread</item>
|
||||
</plurals>
|
||||
<string name="new_chapter">New</string>
|
||||
<plurals name="download_count">
|
||||
<item quantity="one">1 downloaded</item>
|
||||
<item quantity="other">%d downloaded</item>
|
||||
@ -754,4 +756,14 @@
|
||||
<string name="sort_by_chapter_number">Sort by chapter number</string>
|
||||
<string name="newest_first">Newest to oldest</string>
|
||||
<string name="oldest_first">Oldest to newest</string>
|
||||
<string name="view_all">View All</string>
|
||||
<string name="previously_read_chapter">Previously read Chapter %1$s</string>
|
||||
<string name="last_read_chapter_x">Last read Chapter %1$s</string>
|
||||
<string name="last_read_x">Last read %1$s</string>
|
||||
<string name="uploaded_x">Uploaded %1$s</string>
|
||||
<string name="view_history">View history</string>
|
||||
<string name="view_all_updates">View all updates</string>
|
||||
<string name="marked_as_read">Marked as read</string>
|
||||
<string name="no_search_result">No search results</string>
|
||||
<string name="search_recents">Search recents…</string>
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user