diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt index 39245162d5..778376f5be 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt @@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.data.database.queries.* * This class provides operations to manage the database through its interfaces. */ open class DatabaseHelper(context: Context) -: MangaQueries, ChapterQueries, MangaSyncQueries, CategoryQueries, MangaCategoryQueries { +: MangaQueries, ChapterQueries, MangaSyncQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries { override val db = DefaultStorIOSQLite.builder() .sqliteOpenHelper(DbOpenHelper(context)) @@ -18,6 +18,7 @@ open class DatabaseHelper(context: Context) .addTypeMapping(MangaSync::class.java, MangaSyncSQLiteTypeMapping()) .addTypeMapping(Category::class.java, CategorySQLiteTypeMapping()) .addTypeMapping(MangaCategory::class.java, MangaCategorySQLiteTypeMapping()) + .addTypeMapping(History::class.java, HistorySQLiteTypeMapping()) .build() inline fun inTransaction(block: () -> Unit) = db.inTransaction(block) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenHelper.kt index 60924901f9..5790260de6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenHelper.kt @@ -17,7 +17,7 @@ class DbOpenHelper(context: Context) /** * Version of the database. */ - const val DATABASE_VERSION = 2 + const val DATABASE_VERSION = 3 } override fun onCreate(db: SQLiteDatabase) = with(db) { @@ -26,11 +26,13 @@ class DbOpenHelper(context: Context) execSQL(MangaSyncTable.createTableQuery) execSQL(CategoryTable.createTableQuery) execSQL(MangaCategoryTable.createTableQuery) + execSQL(HistoryTable.createTableQuery) // DB indexes execSQL(MangaTable.createUrlIndexQuery) execSQL(MangaTable.createFavoriteIndexQuery) execSQL(ChapterTable.createMangaIdIndexQuery) + execSQL(HistoryTable.createChapterIdIndexQuery) } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { @@ -41,10 +43,15 @@ class DbOpenHelper(context: Context) db.execSQL("""UPDATE mangas SET thumbnail_url = REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4""") } + if (oldVersion < 3) { + // Initialize history tables + db.execSQL(HistoryTable.createTableQuery) + db.execSQL(HistoryTable.createChapterIdIndexQuery) + } } override fun onConfigure(db: SQLiteDatabase) { db.setForeignKeyConstraintsEnabled(true) } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.java b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.java new file mode 100644 index 0000000000..78d769c088 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.java @@ -0,0 +1,58 @@ +package eu.kanade.tachiyomi.data.database.models; + +import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn; +import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType; + +import java.io.Serializable; + +import eu.kanade.tachiyomi.data.database.tables.HistoryTable; + +/** + * Object containing the history statistics of a chapter + */ +@StorIOSQLiteType(table = HistoryTable.TABLE) +public class History implements Serializable { + + /** + * Id of history object. + */ + @StorIOSQLiteColumn(name = HistoryTable.COL_ID, key = true) + public Long id; + + /** + * Chapter id of history object. + */ + @StorIOSQLiteColumn(name = HistoryTable.COL_CHAPTER_ID) + public long chapter_id; + + /** + * Last time chapter was read in time long format + */ + @StorIOSQLiteColumn(name = HistoryTable.COL_LAST_READ) + public long last_read; + + /** + * Total time chapter was read - todo not yet implemented + */ + @StorIOSQLiteColumn(name = HistoryTable.COL_TIME_READ) + public long time_read; + + /** + * Empty history constructor + */ + public History() { + } + + /** + * History constructor + * + * @param chapter chapter object + * @return history object + */ + public static History create(Chapter chapter) { + History history = new History(); + history.chapter_id = chapter.id; + return history; + } +} + diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.java b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.java new file mode 100644 index 0000000000..67a59a404a --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.java @@ -0,0 +1,27 @@ +package eu.kanade.tachiyomi.data.database.models; + +/** + * Object containing manga, chapter and history + */ +public class MangaChapterHistory { + /** + * Object containing manga and chapter + */ + public MangaChapter mangaChapter; + + /** + * Object containing history + */ + public History history; + + /** + * MangaChapterHistory constructor + * + * @param mangaChapter object containing manga and chapter + * @param history object containing history + */ + public MangaChapterHistory(MangaChapter mangaChapter, History history) { + this.mangaChapter = mangaChapter; + this.history = history; + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt new file mode 100644 index 0000000000..6b51775df7 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt @@ -0,0 +1,53 @@ +package eu.kanade.tachiyomi.data.database.queries + +import com.pushtorefresh.storio.sqlite.queries.RawQuery +import eu.kanade.tachiyomi.data.database.DbProvider +import eu.kanade.tachiyomi.data.database.models.History +import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory +import eu.kanade.tachiyomi.data.database.resolvers.HistoryLastReadPutResolver +import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver +import eu.kanade.tachiyomi.data.database.tables.HistoryTable +import java.util.* + +interface HistoryQueries : DbProvider { + + /** + * Insert history into database + * @param history object containing history information + */ + fun insertHistory(history: History) = db.put().`object`(history).prepare() + + /** + * Returns history of recent manga containing last read chapter + * @param date recent date range + */ + fun getRecentManga(date: Date) = db.get() + .listOfObjects(MangaChapterHistory::class.java) + .withQuery(RawQuery.builder() + .query(getRecentMangasQuery()) + .args(date.time) + .observesTables(HistoryTable.TABLE) + .build()) + .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) + .prepare() + + fun getHistoryByMangaId(mangaId: Long) = db.get() + .listOfObjects(History::class.java) + .withQuery(RawQuery.builder() + .query(getHistoryByMangaId()) + .args(mangaId) + .observesTables(HistoryTable.TABLE) + .build()) + .prepare() + + + /** + * Updates the history last read. + * Inserts history object if not yet in database + * @param history history object + */ + fun updateHistoryLastRead(history: History) = db.put() + .`object`(history) + .withPutResolver(HistoryLastReadPutResolver()) + .prepare() +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt index 305fd3824e..59b0d0a9ab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.database.queries import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter +import eu.kanade.tachiyomi.data.database.tables.HistoryTable as History import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga @@ -39,6 +40,38 @@ fun getRecentsQuery() = """ ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC """ +/** + * 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 + * @return return limit is 25 + */ +fun getRecentMangasQuery() = """ + SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.* + FROM ${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} + ORDER BY max_last_read.${History.COL_LAST_READ} DESC + LIMIT 25 +""" + +fun getHistoryByMangaId() = """ + SELECT ${History.TABLE}.* + FROM ${History.TABLE} + JOIN ${Chapter.TABLE} + ON ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID} + WHERE ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID} +""" /** * Query to get the categories for a manga. diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt new file mode 100644 index 0000000000..4d2146321e --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt @@ -0,0 +1,52 @@ +package eu.kanade.tachiyomi.data.database.resolvers + +import android.content.ContentValues +import android.support.annotation.NonNull +import com.pushtorefresh.storio.sqlite.StorIOSQLite +import com.pushtorefresh.storio.sqlite.operations.put.PutResolver +import com.pushtorefresh.storio.sqlite.operations.put.PutResult +import com.pushtorefresh.storio.sqlite.queries.UpdateQuery +import eu.kanade.tachiyomi.data.database.inTransactionReturn +import eu.kanade.tachiyomi.data.database.models.History +import eu.kanade.tachiyomi.data.database.tables.HistoryTable + +class HistoryLastReadPutResolver : PutResolver() { + + /** + * Updates last_read time of chapter + */ + override fun performPut(@NonNull db: StorIOSQLite, @NonNull history: History): PutResult = db.inTransactionReturn { + // Create put query + val updateQuery = mapToUpdateQuery(history) + val contentValues = mapToContentValues(history) + + // Execute query + val numberOfRowsUpdated = db.internal().update(updateQuery, contentValues) + + // If chapter not found in history insert into database + if (numberOfRowsUpdated == 0) { + db.put().`object`(history).prepare().asRxObservable().subscribe() + } + // Update result + PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) + } + + /** + * Creates update query + * @param history object + */ + fun mapToUpdateQuery(history: History) = UpdateQuery.builder() + .table(HistoryTable.TABLE) + .where("${HistoryTable.COL_CHAPTER_ID} = ?") + .whereArgs(history.chapter_id) + .build() + + /** + * Create content query + * @param history object + */ + fun mapToContentValues(history: History) = ContentValues(1).apply { + put(HistoryTable.COL_LAST_READ, history.last_read) + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt index 880372f7c1..8745fb398c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt @@ -20,7 +20,7 @@ class MangaChapterGetResolver : DefaultGetResolver() { val manga = mangaGetResolver.mapFromCursor(cursor) val chapter = chapterGetResolver.mapFromCursor(cursor) manga.id = chapter.manga_id - manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl")); + manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl")) return MangaChapter(manga, chapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt new file mode 100644 index 0000000000..c5cb48f672 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt @@ -0,0 +1,51 @@ +package eu.kanade.tachiyomi.data.database.resolvers + +import android.database.Cursor +import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver +import eu.kanade.tachiyomi.data.database.models.* + +class MangaChapterHistoryGetResolver : DefaultGetResolver() { + companion object { + val INSTANCE = MangaChapterHistoryGetResolver() + } + + /** + * Manga get resolver + */ + private val mangaGetResolver = MangaStorIOSQLiteGetResolver() + + /** + * Chapter get resolver + */ + private val chapterResolver = ChapterStorIOSQLiteGetResolver() + + /** + * History get resolver + */ + private val historyGetResolver = HistoryStorIOSQLiteGetResolver() + + /** + * Map correct objects from cursor result + */ + override fun mapFromCursor(cursor: Cursor): MangaChapterHistory { + // Get manga object + val manga = mangaGetResolver.mapFromCursor(cursor) + + // Get chapter object + val chapter = chapterResolver.mapFromCursor(cursor) + + // Get history object + val history = historyGetResolver.mapFromCursor(cursor) + + // Make certain column conflicts are dealt with + manga.id = chapter.manga_id + manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl")) + chapter.id = history.chapter_id + + // Create mangaChapter object + val mangaChapter = MangaChapter(manga, chapter) + + // Return result + return MangaChapterHistory(mangaChapter, history) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt new file mode 100644 index 0000000000..d552b8fbd2 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt @@ -0,0 +1,48 @@ +package eu.kanade.tachiyomi.data.database.tables + +object HistoryTable { + + /** + * Table name + */ + const val TABLE = "history" + + /** + * Id column name + */ + const val COL_ID = "${TABLE}_id" + + /** + * Chapter id column name + */ + const val COL_CHAPTER_ID = "${TABLE}_chapter_id" + + /** + * Last read column name + */ + const val COL_LAST_READ = "${TABLE}_last_read" + + /** + * Time read column name + */ + const val COL_TIME_READ = "${TABLE}_time_read" + + /** + * query to create history table + */ + val createTableQuery: String + get() = """CREATE TABLE $TABLE( + $COL_ID INTEGER NOT NULL PRIMARY KEY, + $COL_CHAPTER_ID INTEGER NOT NULL UNIQUE, + $COL_LAST_READ LONG, + $COL_TIME_READ LONG, + FOREIGN KEY($COL_CHAPTER_ID) REFERENCES ${ChapterTable.TABLE} (${ChapterTable.COL_ID}) + ON DELETE CASCADE + )""" + + /** + * query to index history chapter id + */ + val createChapterIdIndexQuery: String + get() = "CREATE INDEX ${TABLE}_${COL_CHAPTER_ID}_index ON $TABLE($COL_CHAPTER_ID)" +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt b/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt index 4856e8b12a..6084c44ed2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt @@ -24,7 +24,8 @@ import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter import eu.kanade.tachiyomi.ui.manga.info.MangaInfoPresenter import eu.kanade.tachiyomi.ui.manga.myanimelist.MyAnimeListPresenter import eu.kanade.tachiyomi.ui.reader.ReaderPresenter -import eu.kanade.tachiyomi.ui.recent.RecentChaptersPresenter +import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersPresenter +import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadPresenter import eu.kanade.tachiyomi.ui.setting.SettingsActivity import javax.inject.Singleton @@ -42,6 +43,7 @@ interface AppComponent { fun inject(myAnimeListPresenter: MyAnimeListPresenter) fun inject(categoryPresenter: CategoryPresenter) fun inject(recentChaptersPresenter: RecentChaptersPresenter) + fun inject(recentlyReadPresenter: RecentlyReadPresenter) fun inject(backupPresenter: BackupPresenter) fun inject(mainActivity: MainActivity) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 300cef6a42..14bd33423f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -13,7 +13,8 @@ import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.catalogue.CatalogueFragment import eu.kanade.tachiyomi.ui.download.DownloadFragment import eu.kanade.tachiyomi.ui.library.LibraryFragment -import eu.kanade.tachiyomi.ui.recent.RecentChaptersFragment +import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersFragment +import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadFragment import eu.kanade.tachiyomi.ui.setting.SettingsActivity import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.toolbar.* @@ -50,6 +51,7 @@ class MainActivity : BaseActivity() { when (item.itemId) { R.id.nav_drawer_library -> setFragment(LibraryFragment.newInstance()) R.id.nav_drawer_recent_updates -> setFragment(RecentChaptersFragment.newInstance()) + R.id.nav_drawer_recent_manga -> setFragment(RecentlyReadFragment.newInstance()) R.id.nav_drawer_catalogues -> setFragment(CatalogueFragment.newInstance()) R.id.nav_drawer_downloads -> setFragment(DownloadFragment.newInstance()) R.id.nav_drawer_settings -> startActivity(Intent(this, SettingsActivity::class.java)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index da546352bb..a37593a6bc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -4,6 +4,7 @@ import android.os.Bundle import eu.kanade.tachiyomi.data.cache.ChapterCache 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.data.database.models.MangaSync import eu.kanade.tachiyomi.data.download.DownloadManager @@ -24,6 +25,7 @@ import rx.schedulers.Schedulers import rx.subjects.PublishSubject import timber.log.Timber import java.io.File +import java.util.* import javax.inject.Inject class ReaderPresenter : BasePresenter() { @@ -289,6 +291,7 @@ class ReaderPresenter : BasePresenter() { } } + // Check whether the given chapter is downloaded fun isChapterDownloaded(chapter: Chapter): Boolean { return downloadManager.isChapterDownloaded(source, manga, chapter) @@ -340,6 +343,12 @@ class ReaderPresenter : BasePresenter() { } } db.updateChapterProgress(chapter).asRxObservable().subscribe() + // Update last read data + db.updateHistoryLastRead(History.create(chapter) + .apply { last_read = Date().time }) + .asRxObservable() + .doOnError { Timber.e(it.message) } + .subscribe() } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersAdapter.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersAdapter.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersAdapter.kt index 1bfe5ef522..20436ea823 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersAdapter.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.recent +package eu.kanade.tachiyomi.ui.recent_updates import android.support.v7.widget.RecyclerView import android.view.View @@ -72,7 +72,7 @@ class RecentChaptersAdapter(val fragment: RecentChaptersFragment) : FlexibleAdap // Check which view type and set correct values. when (viewType) { VIEW_TYPE_CHAPTER -> { - view = parent.inflate(R.layout.item_recent_chapter) + view = parent.inflate(R.layout.item_recent_chapters) return RecentChaptersHolder(view, this, fragment) } VIEW_TYPE_SECTION -> { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersFragment.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersFragment.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersFragment.kt index 3737170baf..40110bd731 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersFragment.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.recent +package eu.kanade.tachiyomi.ui.recent_updates import android.os.Bundle import android.support.v4.app.DialogFragment @@ -213,7 +213,7 @@ class RecentChaptersFragment : BaseRxFragment(), Action */ fun onNextMangaChapters(chapters: List) { (activity as MainActivity).updateEmptyView(chapters.isEmpty(), - R.string.information_no_recent, R.drawable.ic_history_black_128dp) + R.string.information_no_recent, R.drawable.ic_update_black_128dp) destroyActionModeIfNeeded() adapter.setItems(chapters) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersHolder.kt similarity index 95% rename from app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersHolder.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersHolder.kt index 1c02cc665c..592f0fabe3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersHolder.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.recent +package eu.kanade.tachiyomi.ui.recent_updates import android.view.View import android.widget.PopupMenu @@ -7,11 +7,11 @@ import eu.kanade.tachiyomi.data.database.models.MangaChapter import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder import eu.kanade.tachiyomi.util.getResourceColor -import kotlinx.android.synthetic.main.item_recent_chapter.view.* +import kotlinx.android.synthetic.main.item_recent_chapters.view.* /** * Holder that contains chapter item - * Uses R.layout.item_recent_chapter. + * Uses R.layout.item_recent_chapters. * UI related actions should be called from here. * * @param view the inflated view for this holder. @@ -19,7 +19,7 @@ import kotlinx.android.synthetic.main.item_recent_chapter.view.* * @param listener a listener to react to single tap and long tap events. * @constructor creates a new recent chapter holder. */ -class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapter, listener: FlexibleViewHolder.OnListItemClickListener) : +class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapter, listener: OnListItemClickListener) : FlexibleViewHolder(view, adapter, listener) { /** * Color of read chapter diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt similarity index 99% rename from app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersPresenter.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt index cc4aaadbfc..f133213e8a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.recent +package eu.kanade.tachiyomi.ui.recent_updates import android.os.Bundle import eu.kanade.tachiyomi.data.database.DatabaseHelper diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/SectionViewHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/SectionViewHolder.kt similarity index 93% rename from app/src/main/java/eu/kanade/tachiyomi/ui/recent/SectionViewHolder.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/SectionViewHolder.kt index a629ce2074..bbf37c978d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/SectionViewHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/SectionViewHolder.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.recent +package eu.kanade.tachiyomi.ui.recent_updates import android.support.v7.widget.RecyclerView import android.text.format.DateUtils diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt new file mode 100644 index 0000000000..ce289a4c6c --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt @@ -0,0 +1,52 @@ +package eu.kanade.tachiyomi.ui.recently_read + +import android.support.v7.widget.RecyclerView +import android.view.ViewGroup +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory +import eu.kanade.tachiyomi.util.inflate + +/** + * Adapter of RecentlyReadHolder. + * Connection between Fragment and Holder + * Holder updates should be called from here. + * + * @param fragment a RecentlyReadFragment object + * @constructor creates an instance of the adapter. + */ +class RecentlyReadAdapter(val fragment: RecentlyReadFragment) : FlexibleAdapter() { + /** + * Called when ViewHolder is created + * @param parent parent View + * @param viewType int containing viewType + */ + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder? { + val view = parent.inflate(R.layout.item_recent_manga) + return RecentlyReadHolder(view, this) + } + + /** + * Called when ViewHolder is bind + * @param holder bind holder + * @param position position of holder + */ + override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) { + val item = getItem(position) as MangaChapterHistory + (holder as RecentlyReadHolder).onSetValues(item) + } + + /** + * Update items + * @param items items + */ + fun setItems(items: List) { + mItems = items + notifyDataSetChanged() + } + + override fun updateDataSet(param: String?) { + // Empty function + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadFragment.kt new file mode 100644 index 0000000000..fa0ac02ba8 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadFragment.kt @@ -0,0 +1,129 @@ +package eu.kanade.tachiyomi.ui.recently_read + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import eu.kanade.tachiyomi.R +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.data.database.models.MangaChapterHistory +import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment +import eu.kanade.tachiyomi.ui.main.MainActivity +import eu.kanade.tachiyomi.ui.manga.MangaActivity +import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager +import kotlinx.android.synthetic.main.fragment_recent_manga.* +import nucleus.factory.RequiresPresenter + +/** + * Fragment that shows recently read manga. + * Uses R.layout.fragment_recent_manga. + * UI related actions should be called from here. + */ +@RequiresPresenter(RecentlyReadPresenter::class) +class RecentlyReadFragment : BaseRxFragment() { + companion object { + /** + * Create new RecentChaptersFragment. + * + */ + @JvmStatic + fun newInstance(): RecentlyReadFragment { + return RecentlyReadFragment() + } + } + + /** + * Adapter containing the recent manga. + */ + lateinit var adapter: RecentlyReadAdapter + private set + + /** + * Called when view gets created + * + * @param inflater layout inflater + * @param container view group + * @param savedState status of saved state + */ + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_recent_manga, container, false) + } + + /** + * Called when view is created + * + * @param view created view + * @param savedInstanceState status of saved sate + */ + override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { + // Initialize adapter + recycler.layoutManager = NpaLinearLayoutManager(activity) + adapter = RecentlyReadAdapter(this) + recycler.setHasFixedSize(true) + recycler.adapter = adapter + + // Update toolbar text + setToolbarTitle(R.string.label_recent_manga) + } + + /** + * Populate adapter with chapters + * + * @param mangaHistory list of manga history + */ + fun onNextManga(mangaHistory: List) { + (activity as MainActivity).updateEmptyView(mangaHistory.isEmpty(), + R.string.information_no_recent_manga, R.drawable.ic_glasses_black_128dp) + + adapter.setItems(mangaHistory) + } + + /** + * Reset last read of chapter to 0L + * @param history history belonging to chapter + */ + fun removeFromHistory(history: History) { + presenter.removeFromHistory(history) + adapter.notifyDataSetChanged() + } + + /** + * Removes all chapters belonging to manga from library + * @param mangaId id of manga + */ + fun removeAllFromHistory(mangaId: Long) { + presenter.removeAllFromHistory(mangaId) + adapter.notifyDataSetChanged() + } + + /** + * Open chapter to continue reading + * @param chapter chapter that is opened + * @param manga manga belonging to chapter + */ + fun openChapter(chapter: Chapter, manga: Manga) { + val intent = ReaderActivity.newIntent(activity, manga, chapter) + startActivity(intent) + } + + /** + * Open manga info page + * @param manga manga belonging to info page + */ + fun openMangaInfo(manga: Manga) { + val intent = MangaActivity.newIntent(activity, manga, true) + startActivity(intent) + } + + /** + * Returns the timestamp of last read + * @param history history containing time of last read + */ + fun getLastRead(history: History): String? { + return presenter.getLastRead(history) + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt new file mode 100644 index 0000000000..fbc7e6e22b --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt @@ -0,0 +1,96 @@ +package eu.kanade.tachiyomi.ui.recently_read + +import android.support.v7.widget.RecyclerView +import android.view.View +import com.afollestad.materialdialogs.MaterialDialog +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory +import eu.kanade.tachiyomi.data.source.SourceManager +import kotlinx.android.synthetic.main.dialog_remove_recently.view.* +import kotlinx.android.synthetic.main.item_recent_manga.view.* +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols + +/** + * Holder that contains recent manga item + * Uses R.layout.item_recent_manga. + * UI related actions should be called from here. + * + * @param view the inflated view for this holder. + * @param adapter the adapter handling this holder. + * @constructor creates a new recent chapter holder. + */ +class RecentlyReadHolder(view: View, private val adapter: RecentlyReadAdapter) : + RecyclerView.ViewHolder(view) { + + /** + * DecimalFormat used to display correct chapter number + */ + private val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' }) + + /** + * Set values of view + * + * @param item item containing history information + */ + fun onSetValues(item: MangaChapterHistory) { + // Retrieve objects + val manga = item.mangaChapter.manga + val chapter = item.mangaChapter.chapter + val history = item.history + + // Set manga title + itemView.manga_title.text = manga.title + + // Set source + chapter title + val formattedNumber = decimalFormat.format(chapter.chapter_number.toDouble()) + itemView.manga_source.text = itemView.context.getString(R.string.recent_manga_source) + .format(SourceManager(adapter.fragment.context).get(manga.source)?.name, formattedNumber) + + // Set last read timestamp title + itemView.last_read.text = adapter.fragment.getLastRead(history) + + // Set cover + if (!manga.thumbnail_url.isNullOrEmpty()) { + Glide.with(itemView.context) + .load(manga) + .diskCacheStrategy(DiskCacheStrategy.RESULT) + .centerCrop() + .into(itemView.cover) + } + + // Set remove clickListener + itemView.remove.setOnClickListener { + MaterialDialog.Builder(itemView.context) + .title(R.string.action_remove) + .customView(R.layout.dialog_remove_recently, true) + .positiveText(R.string.action_remove) + .negativeText(android.R.string.cancel) + .onPositive { materialDialog, dialogAction -> + // Check if user wants all chapters reset + if (materialDialog.customView?.removeAll?.isChecked as Boolean) { + adapter.fragment.removeAllFromHistory(manga.id) + } else { + adapter.fragment.removeFromHistory(history) + } + } + .onNegative { materialDialog, dialogAction -> + materialDialog.dismiss() + } + .show(); + } + + // Set continue reading clickListener + itemView.resume.setOnClickListener { + adapter.fragment.openChapter(chapter, manga) + } + + // Set open manga info clickListener + itemView.cover.setOnClickListener { + adapter.fragment.openMangaInfo(manga) + } + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt new file mode 100644 index 0000000000..552409be20 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt @@ -0,0 +1,97 @@ +package eu.kanade.tachiyomi.ui.recently_read + +import android.os.Bundle +import eu.kanade.tachiyomi.data.database.DatabaseHelper +import eu.kanade.tachiyomi.data.database.models.History +import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory +import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter +import rx.Observable +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers +import timber.log.Timber +import java.text.SimpleDateFormat +import java.util.* +import javax.inject.Inject + +/** + * The id of the restartable. + */ +const private val GET_RECENT_MANGA = 1 + +/** + * Presenter of RecentlyReadFragment. + * Contains information and data for fragment. + * Observable updates should be called from here. + */ +class RecentlyReadPresenter : BasePresenter() { + /** + * Used to connect to database + */ + @Inject lateinit var db: DatabaseHelper + + override fun onCreate(savedState: Bundle?) { + super.onCreate(savedState) + + // Used to get recent manga + restartableLatestCache(GET_RECENT_MANGA, + { getRecentMangaObservable() }, + { recentMangaFragment, manga -> + // Update adapter to show recent manga's + recentMangaFragment.onNextManga(manga) + } + ) + + if (savedState == null) { + // Start fetching recent manga + start(GET_RECENT_MANGA) + } + } + + /** + * Get recent manga observable + * @return list of history + */ + fun getRecentMangaObservable(): Observable> { + // Set date for recent manga + val cal = Calendar.getInstance() + cal.time = Date() + cal.add(Calendar.MONTH, -1) + + return db.getRecentManga(cal.time).asRxObservable() + .observeOn(AndroidSchedulers.mainThread()) + } + + /** + * Reset last read of chapter to 0L + * @param history history belonging to chapter + */ + fun removeFromHistory(history: History) { + history.last_read = 0L + db.updateHistoryLastRead(history).asRxObservable() + .doOnError { Timber.e(it.message) }.subscribe() + } + + /** + * Removes all chapters belonging to manga from library + * @param mangaId id of manga + */ + fun removeAllFromHistory(mangaId: Long) { + db.getHistoryByMangaId(mangaId).asRxObservable() + .take(1) + .flatMapIterable { it } + .doOnError { Timber.e(it.message) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ result -> removeFromHistory(result) }) + } + + /** + * Returns the timestamp of last read + * @param history history containing time of last read + */ + fun getLastRead(history: History): String? { + return SimpleDateFormat("dd-MM-yyyy HH:mm", + Locale.getDefault()).format(Date(history.last_read)) + } + +} diff --git a/app/src/main/res/drawable/ic_glasses_black_128dp.xml b/app/src/main/res/drawable/ic_glasses_black_128dp.xml new file mode 100644 index 0000000000..fbf52def5a --- /dev/null +++ b/app/src/main/res/drawable/ic_glasses_black_128dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_glasses_black_24dp.xml b/app/src/main/res/drawable/ic_glasses_black_24dp.xml new file mode 100644 index 0000000000..d2977ea352 --- /dev/null +++ b/app/src/main/res/drawable/ic_glasses_black_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_history_black_128dp.xml b/app/src/main/res/drawable/ic_history_black_128dp.xml deleted file mode 100644 index fb950d63bf..0000000000 --- a/app/src/main/res/drawable/ic_history_black_128dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_history_black_24dp.xml b/app/src/main/res/drawable/ic_history_black_24dp.xml deleted file mode 100644 index a61de1bc9e..0000000000 --- a/app/src/main/res/drawable/ic_history_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_update_black_128dp.xml b/app/src/main/res/drawable/ic_update_black_128dp.xml new file mode 100644 index 0000000000..5b89fc3c3b --- /dev/null +++ b/app/src/main/res/drawable/ic_update_black_128dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_update_black_24dp.xml b/app/src/main/res/drawable/ic_update_black_24dp.xml new file mode 100644 index 0000000000..a1a7cbdfd3 --- /dev/null +++ b/app/src/main/res/drawable/ic_update_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/dialog_remove_recently.xml b/app/src/main/res/layout/dialog_remove_recently.xml new file mode 100644 index 0000000000..9167c2944c --- /dev/null +++ b/app/src/main/res/layout/dialog_remove_recently.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_recent_chapters.xml b/app/src/main/res/layout/fragment_recent_chapters.xml index 7e290ae7c7..4c7d43bdbf 100644 --- a/app/src/main/res/layout/fragment_recent_chapters.xml +++ b/app/src/main/res/layout/fragment_recent_chapters.xml @@ -10,6 +10,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:descendantFocusability="blocksDescendants" - tools:listitem="@layout/item_recent_chapter"/> + tools:listitem="@layout/item_recent_chapters"> + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_recent_manga.xml b/app/src/main/res/layout/fragment_recent_manga.xml new file mode 100644 index 0000000000..88737848a8 --- /dev/null +++ b/app/src/main/res/layout/fragment_recent_manga.xml @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_recent_chapter.xml b/app/src/main/res/layout/item_recent_chapters.xml similarity index 100% rename from app/src/main/res/layout/item_recent_chapter.xml rename to app/src/main/res/layout/item_recent_chapters.xml diff --git a/app/src/main/res/layout/item_recent_manga.xml b/app/src/main/res/layout/item_recent_manga.xml new file mode 100644 index 0000000000..b8c3e1ed0a --- /dev/null +++ b/app/src/main/res/layout/item_recent_manga.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_navigation.xml b/app/src/main/res/menu/menu_navigation.xml index 279f437726..f236741f58 100644 --- a/app/src/main/res/menu/menu_navigation.xml +++ b/app/src/main/res/menu/menu_navigation.xml @@ -7,10 +7,15 @@ android:id="@+id/nav_drawer_library" android:icon="@drawable/ic_book_black_24dp" android:title="@string/label_library" /> + + ac + + @string/scale_type_fit_screen + + + + remove + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 46f099f12d..30a1487d07 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -70,4 +70,6 @@ #263238 + #F44336 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8cf2594853..de372754c6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,6 +7,7 @@ Settings Download queue My library + Recently read Recent updates Catalogues Categories @@ -46,6 +47,8 @@ Previous chapter Next chapter Retry + Remove + Resume Open in browser Change display mode Cancel @@ -248,6 +251,10 @@ Status Chapters + + This will remove the read date of this chapter. Are you sure? + Reset all chapters for this manga + Downloading… @@ -266,6 +273,9 @@ Backup successfully restored Restoring backup. Please wait… + + %1$s - Ch.%2$s + An error occurred while downloading chapters. You can try again in the downloads section @@ -302,12 +312,13 @@ Update available - Backdrop image of selected manga - Cover of selected manga + Backdrop image of manga + Cover of manga No downloads No recent chapters + No recently read manga Empty library @@ -316,4 +327,5 @@ A page is missing in directory A page is not loaded No wifi connection available + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index ae9a3c45f7..ec175c46c4 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -121,6 +121,10 @@ ?attr/colorAccent + + @@ -137,6 +141,12 @@ eu.kanade.tachiyomi.widget.FABAnimationUpDown + +