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:
Jay 2020-03-28 01:23:22 -04:00
parent 719aa751b8
commit 6a7762212b
38 changed files with 1414 additions and 164 deletions

View File

@ -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()

View File

@ -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()

View File

@ -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}

View File

@ -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)

View File

@ -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.
*

View File

@ -263,6 +263,8 @@ class Downloader(
// Start downloader if needed
if (autoStart && wasEmpty) {
DownloadService.start(this@Downloader.context)
} else if (!isRunning) {
notifier.onDownloadPaused()
}
}
}

View File

@ -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 -> {

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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.

View File

@ -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()

View File

@ -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
}

View File

@ -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)

View File

@ -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>()

View File

@ -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)
}
}*/
}

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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()
}
}
}

View File

@ -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() }
}

View File

@ -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())

View 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>

View 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>

View 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>

View File

@ -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>

View 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>

View 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>

View File

@ -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"

View File

@ -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"/>

View File

@ -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>

View File

@ -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>

View 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>

View File

@ -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>