diff --git a/.idea/dictionaries/admin.xml b/.idea/dictionaries/admin.xml index 798ea1f12..e49854e04 100644 --- a/.idea/dictionaries/admin.xml +++ b/.idea/dictionaries/admin.xml @@ -4,6 +4,7 @@ koin kotatsu manga + upsert \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt index 22e24649b..321e2dfb9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt +++ b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt @@ -54,5 +54,5 @@ class KotatsuApp : Application() { applicationContext, MangaDatabase::class.java, "kotatsu-db" - ) + ).fallbackToDestructiveMigration() //TODO remove } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/HistoryDao.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/HistoryDao.kt index 6fd4877be..24ad5443e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/HistoryDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/HistoryDao.kt @@ -1,12 +1,41 @@ package org.koitharu.kotatsu.core.db -import androidx.room.Dao -import androidx.room.Query +import androidx.room.* import org.koitharu.kotatsu.core.db.entity.HistoryEntity +import org.koitharu.kotatsu.core.db.entity.HistoryWithManga +import org.koitharu.kotatsu.core.db.entity.MangaEntity + @Dao -interface HistoryDao { +abstract class HistoryDao { + + /** + * @hide + */ + @Transaction + @Query("SELECT * FROM history ORDER BY :orderBy LIMIT :limit OFFSET :offset") + abstract suspend fun getAll(offset: Int, limit: Int, orderBy: String): List + + @Query("DELETE FROM history") + abstract suspend fun clear() + + @Insert(onConflict = OnConflictStrategy.IGNORE) + abstract suspend fun insert(entity: HistoryEntity): Long + + @Insert(onConflict = OnConflictStrategy.IGNORE) + abstract suspend fun insertManga(manga: MangaEntity): Long + + @Query("UPDATE history SET page = :page, chapter_id = :chapterId, updated_at = :updatedAt WHERE manga_id = :mangaId") + abstract suspend fun update(mangaId: Long, page: Int, chapterId: Long, updatedAt: Long): Int + + suspend fun update(entity: HistoryWithManga) = update(entity.manga.id, entity.history.page, entity.history.chapterId, entity.history.updatedAt) + + @Transaction + suspend open fun upsert(entity: HistoryWithManga) { + if (update(entity) == 0) { + insertManga(entity.manga) + insert(entity.history) + } + } - @Query("SELECT * FROM history") - suspend fun getAll(): List } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt index 59c0f7499..29cc9cfef 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt @@ -3,6 +3,13 @@ package org.koitharu.kotatsu.core.db import androidx.room.Database import androidx.room.RoomDatabase import org.koitharu.kotatsu.core.db.entity.HistoryEntity +import org.koitharu.kotatsu.core.db.entity.MangaEntity +import org.koitharu.kotatsu.core.db.entity.TagEntity -@Database(entities = [HistoryEntity::class], version = 1) -abstract class MangaDatabase : RoomDatabase() \ No newline at end of file +@Database(entities = [MangaEntity::class, TagEntity::class, HistoryEntity::class], version = 1) +abstract class MangaDatabase : RoomDatabase() { + + abstract fun historyDao(): HistoryDao + + abstract fun tagsDao(): TagsDao +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/TagsDao.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/TagsDao.kt new file mode 100644 index 000000000..0cb75f74e --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/TagsDao.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.core.db + +import androidx.room.Dao +import androidx.room.Query +import androidx.room.Transaction +import org.koitharu.kotatsu.core.db.entity.TagEntity + +@Dao +interface TagsDao { + + @Transaction + @Query("SELECT * FROM tags") + fun getAllTags(): List +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/HistoryEntity.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/HistoryEntity.kt index c4e29ed73..1f130127c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/HistoryEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/HistoryEntity.kt @@ -1,9 +1,25 @@ package org.koitharu.kotatsu.core.db.entity +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import org.koitharu.kotatsu.core.model.MangaHistory +import java.util.* @Entity(tableName = "history") data class HistoryEntity( - @PrimaryKey val id: Long -) \ No newline at end of file + @PrimaryKey(autoGenerate = false) + @ColumnInfo(name = "manga_id") val mangaId: Long, + @ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis(), + @ColumnInfo(name = "updated_at") val updatedAt: Long, + @ColumnInfo(name = "chapter_id") val chapterId: Long, + @ColumnInfo(name = "page") val page: Int +) { + + fun toMangaHistory() = MangaHistory( + createdAt = Date(createdAt), + updatedAt = Date(updatedAt), + chapterId = chapterId, + page = page + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/HistoryWithManga.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/HistoryWithManga.kt new file mode 100644 index 000000000..3b6e4d34b --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/HistoryWithManga.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.core.db.entity + +import androidx.room.Embedded +import androidx.room.Relation + +data class HistoryWithManga( + @Embedded val history: HistoryEntity, + @Relation( + parentColumn = "manga_id", + entityColumn = "manga_id" + ) + val manga: MangaEntity +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt new file mode 100644 index 000000000..b25baf73d --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt @@ -0,0 +1,55 @@ +package org.koitharu.kotatsu.core.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.model.MangaState + +@Entity(tableName = "manga") +data class MangaEntity( + @PrimaryKey(autoGenerate = false) + @ColumnInfo(name = "manga_id") val id: Long, + @ColumnInfo(name = "title") val title: String, + @ColumnInfo(name = "localized_title") val localizedTitle: String? = null, + @ColumnInfo(name = "url") val url: String, + @ColumnInfo(name = "rating") val rating: Float = Manga.NO_RATING, //normalized value [0..1] or -1 + @ColumnInfo(name = "cover_url") val coverUrl: String, + @ColumnInfo(name = "large_cover_url") val largeCoverUrl: String? = null, + @ColumnInfo(name = "summary") val summary: String, + @ColumnInfo(name = "state") val state: String? = null, + @ColumnInfo(name = "source") val source: String +) { + + fun toManga() = Manga( + id = this.id, + title = this.title, + localizedTitle = this.localizedTitle, + summary = this.summary, + state = this.state?.let { MangaState.valueOf(it) }, + rating = this.rating, + url = this.url, + coverUrl = this.coverUrl, + largeCoverUrl = this.largeCoverUrl, + source = MangaSource.valueOf(this.source) +// tags = this.tags.map(TagEntity::toMangaTag).toSet() + ) + + companion object { + + fun from(manga: Manga) = MangaEntity( + id = manga.id, + url = manga.url, + source = manga.source.name, + largeCoverUrl = manga.largeCoverUrl, + coverUrl = manga.coverUrl, + localizedTitle = manga.localizedTitle, + rating = manga.rating, + state = manga.state?.name, + summary = manga.summary, +// tags = manga.tags.map(TagEntity.Companion::fromMangaTag), + title = manga.title + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt new file mode 100644 index 000000000..db26ace78 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.core.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity + +@Entity(tableName = "manga_tags", primaryKeys = ["manga_id", "tag_id"]) +data class MangaTagsEntity( + @ColumnInfo(name = "manga_id") val mangaId: Long, + @ColumnInfo(name = "tag_id") val tagId: Long +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TagEntity.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TagEntity.kt new file mode 100644 index 000000000..1af937d06 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TagEntity.kt @@ -0,0 +1,34 @@ +package org.koitharu.kotatsu.core.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.model.MangaTag +import org.koitharu.kotatsu.utils.ext.longHashCode + +@Entity(tableName = "tags") +data class TagEntity( + @PrimaryKey(autoGenerate = false) + @ColumnInfo(name = "tag_id") val id: Long, + @ColumnInfo(name = "title") val title: String, + @ColumnInfo(name = "key") val key: String, + @ColumnInfo(name = "source") val source: String +) { + + fun toMangaTag() = MangaTag( + key = this.key, + title = this.title, + source = MangaSource.valueOf(this.source) + ) + + companion object { + + fun fromMangaTag(tag: MangaTag) = TagEntity( + title = tag.title, + key = tag.key, + source = tag.source.name, + id = "${tag.key}_${tag.source.name}".longHashCode() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaHistory.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaHistory.kt new file mode 100644 index 000000000..82219dbef --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaHistory.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.core.model + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize +import java.util.* + +@Parcelize +data class MangaHistory( + val createdAt: Date, + val updatedAt: Date, + val chapterId: Long, + val page: Int +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaInfo.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaInfo.kt new file mode 100644 index 000000000..3871a462f --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaInfo.kt @@ -0,0 +1,6 @@ +package org.koitharu.kotatsu.core.model + +data class MangaInfo ( + val manga: Manga, + val extra: E +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt index b163c7929..423e3ddfb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt @@ -4,7 +4,6 @@ import android.os.Parcelable import kotlinx.android.parcel.Parcelize import org.koitharu.kotatsu.domain.MangaRepository import org.koitharu.kotatsu.domain.repository.ReadmangaRepository -import kotlin.reflect.KClass @Parcelize enum class MangaSource(val title: String, val cls: Class): Parcelable { diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaTag.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaTag.kt index af867377f..aaa871d44 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaTag.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaTag.kt @@ -6,5 +6,6 @@ import kotlinx.android.parcel.Parcelize @Parcelize data class MangaTag( val title: String, - val key: String + val key: String, + val source: MangaSource ) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt index 54c1f2a72..15e326310 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -3,8 +3,6 @@ package org.koitharu.kotatsu.core.prefs import android.content.Context import android.content.SharedPreferences import android.content.res.Resources -import androidx.core.content.edit -import androidx.lifecycle.LifecycleOwner import androidx.preference.PreferenceManager import org.koitharu.kotatsu.R import org.koitharu.kotatsu.utils.delegates.prefs.EnumPreferenceDelegate @@ -13,7 +11,7 @@ class AppSettings private constructor(private val resources: Resources, private constructor(context: Context) : this(context.resources, PreferenceManager.getDefaultSharedPreferences(context)) - var listMode by EnumPreferenceDelegate(ListMode::class.java, resources.getString(R.string.key_list_mode), ListMode.LIST) + var listMode by EnumPreferenceDelegate(ListMode::class.java, resources.getString(R.string.key_list_mode), ListMode.DETAILED_LIST) fun subscribe(listener: SharedPreferences.OnSharedPreferenceChangeListener) { prefs.registerOnSharedPreferenceChangeListener(listener) diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/BaseMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/domain/BaseMangaRepository.kt new file mode 100644 index 000000000..544e1748f --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/domain/BaseMangaRepository.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.domain + +import org.koitharu.kotatsu.core.model.MangaPage +import org.koitharu.kotatsu.core.model.SortOrder + +abstract class BaseMangaRepository(protected val loaderContext: MangaLoaderContext) : MangaRepository { + + override val sortOrders: Set get() = emptySet() + + override val isSearchAvailable get() = true + + override suspend fun getPageFullUrl(page: MangaPage) : String = page.url +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/HistoryRepository.kt b/app/src/main/java/org/koitharu/kotatsu/domain/HistoryRepository.kt new file mode 100644 index 000000000..cacc6a872 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/domain/HistoryRepository.kt @@ -0,0 +1,71 @@ +package org.koitharu.kotatsu.domain + +import org.koin.core.KoinComponent +import org.koin.core.inject +import org.koitharu.kotatsu.core.db.MangaDatabase +import org.koitharu.kotatsu.core.db.entity.HistoryEntity +import org.koitharu.kotatsu.core.db.entity.HistoryWithManga +import org.koitharu.kotatsu.core.db.entity.MangaEntity +import org.koitharu.kotatsu.core.model.* +import java.io.Closeable + +class HistoryRepository() : KoinComponent, MangaRepository, Closeable { + + private val db: MangaDatabase by inject() + + override val sortOrders: Set = setOf(SortOrder.NEWEST, SortOrder.POPULARITY) + + override val isSearchAvailable = false + + override suspend fun getList( + offset: Int, + query: String?, + sortOrder: SortOrder?, + tags: Set? + ): List = getHistory(offset, query, sortOrder, tags).map { x -> x.manga } + + suspend fun getHistory( + offset: Int, + query: String? = null, + sortOrder: SortOrder? = null, + tags: Set? = null + ): List> { + val entities = db.historyDao().getAll(offset, 20, "updated_by") + return entities.map { x -> MangaInfo(x.manga.toManga(), x.history.toMangaHistory()) } + } + + override suspend fun getDetails(manga: Manga): Manga { + throw UnsupportedOperationException("History repository does not support getDetails() method") + } + + override suspend fun getPages(chapter: MangaChapter): List { + throw UnsupportedOperationException("History repository does not support getPages() method") + } + + override suspend fun getPageFullUrl(page: MangaPage) = page.url + + suspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int) { + val dao = db.historyDao() + val entity = HistoryEntity( + mangaId = manga.id, + createdAt = System.currentTimeMillis(), + updatedAt = System.currentTimeMillis(), + chapterId = chapterId, + page = page + ) + dao.upsert( + HistoryWithManga( + history = entity, + manga = MangaEntity.from(manga) + ) + ) + } + + suspend fun clear() { + db.historyDao().clear() + } + + override fun close() { + db.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/MangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/domain/MangaRepository.kt index eb4e35438..453658073 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/MangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/domain/MangaRepository.kt @@ -1,18 +1,21 @@ package org.koitharu.kotatsu.domain -import org.koitharu.kotatsu.core.model.* +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.model.MangaChapter +import org.koitharu.kotatsu.core.model.MangaPage +import org.koitharu.kotatsu.core.model.SortOrder -abstract class MangaRepository(protected val loaderContext: MangaLoaderContext) { +interface MangaRepository { - open val sortOrders: Set get() = emptySet() + val sortOrders: Set - open val isSearchAvailable get() = true + val isSearchAvailable: Boolean - abstract suspend fun getList(offset: Int, query: String? = null, sortOrder: SortOrder? = null, tags: Set? = null): List + suspend fun getList(offset: Int, query: String? = null, sortOrder: SortOrder? = null, tags: Set? = null): List - abstract suspend fun getDetails(manga: Manga) : Manga + suspend fun getDetails(manga: Manga) : Manga - abstract suspend fun getPages(chapter: MangaChapter) : List + suspend fun getPages(chapter: MangaChapter) : List - open suspend fun getPageFullUrl(page: MangaPage) : String = page.url + suspend fun getPageFullUrl(page: MangaPage) : String } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/repository/ReadmangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/domain/repository/ReadmangaRepository.kt index de2876aac..9bf8e3ff4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/repository/ReadmangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/domain/repository/ReadmangaRepository.kt @@ -1,14 +1,13 @@ package org.koitharu.kotatsu.domain.repository -import androidx.core.text.HtmlCompat import androidx.core.text.parseAsHtml import org.koitharu.kotatsu.core.model.* +import org.koitharu.kotatsu.domain.BaseMangaRepository import org.koitharu.kotatsu.domain.MangaLoaderContext -import org.koitharu.kotatsu.domain.MangaRepository import org.koitharu.kotatsu.domain.exceptions.ParseException import org.koitharu.kotatsu.utils.ext.* -class ReadmangaRepository(loaderContext: MangaLoaderContext) : MangaRepository(loaderContext) { +class ReadmangaRepository(loaderContext: MangaLoaderContext) : BaseMangaRepository(loaderContext) { override suspend fun getList( offset: Int, @@ -47,7 +46,8 @@ class ReadmangaRepository(loaderContext: MangaLoaderContext) : MangaRepository(l ?.map { MangaTag( title = it.text(), - key = it.attr("href").substringAfterLast('/') + key = it.attr("href").substringAfterLast('/'), + source = MangaSource.READMANGA_RU ) }?.toSet() }.orEmpty(), diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/main/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/main/MainActivity.kt index d4c38b256..dc3471926 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/main/MainActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/main/MainActivity.kt @@ -10,7 +10,8 @@ import kotlinx.android.synthetic.main.activity_main.* import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.ui.common.BaseActivity -import org.koitharu.kotatsu.ui.main.list.MangaListFragment +import org.koitharu.kotatsu.ui.main.list.history.HistoryListFragment +import org.koitharu.kotatsu.ui.main.list.remote.RemoteListFragment class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener { @@ -29,7 +30,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList navigationView.setNavigationItemSelectedListener(this) if (!supportFragmentManager.isStateSaved) { - setPrimaryFragment(MangaListFragment.newInstance(MangaSource.READMANGA_RU)) + setPrimaryFragment(RemoteListFragment.newInstance(MangaSource.READMANGA_RU)) } } @@ -51,9 +52,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList override fun onNavigationItemSelected(item: MenuItem): Boolean { if (item.groupId == R.id.group_remote_sources) { val source = MangaSource.values().getOrNull(item.itemId) ?: return false - setPrimaryFragment(MangaListFragment.newInstance(source)) + setPrimaryFragment(RemoteListFragment.newInstance(source)) } else when (item.itemId) { - R.id.nav_history -> Unit + R.id.nav_history -> setPrimaryFragment(HistoryListFragment.newInstance()) R.id.nav_favourites -> Unit R.id.nav_local_storage -> Unit else -> return false diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/ListModeSelectDialog.kt b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/ListModeSelectDialog.kt index 42a5b744a..7bbc298bd 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/ListModeSelectDialog.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/ListModeSelectDialog.kt @@ -46,6 +46,8 @@ class ListModeSelectDialog : AlertDialogFragment(R.layout.dialog_list_mode), Vie private const val TAG = "ListModeSelectDialog" - fun show(fm: FragmentManager) = ListModeSelectDialog().show(fm, TAG) + fun show(fm: FragmentManager) = ListModeSelectDialog().show(fm, + TAG + ) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaGridHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaGridHolder.kt index fac613527..60eda6c6a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaGridHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaGridHolder.kt @@ -5,17 +5,17 @@ import coil.api.load import coil.request.RequestDisposable import kotlinx.android.synthetic.main.item_manga_grid.* import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.model.MangaInfo import org.koitharu.kotatsu.ui.common.list.BaseViewHolder -class MangaGridHolder(parent: ViewGroup) : BaseViewHolder(parent, R.layout.item_manga_grid) { +class MangaGridHolder(parent: ViewGroup) : BaseViewHolder>(parent, R.layout.item_manga_grid) { private var coverRequest: RequestDisposable? = null - override fun onBind(data: Manga) { + override fun onBind(data: MangaInfo) { coverRequest?.dispose() - textView_title.text = data.title - coverRequest = imageView_cover.load(data.coverUrl) { + textView_title.text = data.manga.title + coverRequest = imageView_cover.load(data.manga.coverUrl) { crossfade(true) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListAdapter.kt index 7a963ad65..ea0568cbb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListAdapter.kt @@ -1,21 +1,21 @@ package org.koitharu.kotatsu.ui.main.list import android.view.ViewGroup -import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.model.MangaInfo import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener -class MangaListAdapter(onItemClickListener: OnRecyclerItemClickListener) : - BaseRecyclerAdapter(onItemClickListener) { +class MangaListAdapter(onItemClickListener: OnRecyclerItemClickListener>) : + BaseRecyclerAdapter>(onItemClickListener) { var listMode: ListMode = ListMode.LIST override fun onCreateViewHolder(parent: ViewGroup) = when(listMode) { - ListMode.LIST -> MangaListHolder(parent) - ListMode.DETAILED_LIST -> MangaListDetailsHolder(parent) + ListMode.LIST -> MangaListHolder(parent) + ListMode.DETAILED_LIST -> MangaListDetailsHolder(parent) ListMode.GRID -> MangaGridHolder(parent) } - override fun onGetItemId(item: Manga) = item.id + override fun onGetItemId(item: MangaInfo) = item.manga.id } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListDetailsHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListDetailsHolder.kt index a875683f6..ddf0428b7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListDetailsHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListDetailsHolder.kt @@ -8,29 +8,30 @@ import coil.request.RequestDisposable import kotlinx.android.synthetic.main.item_manga_list_details.* import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.model.MangaInfo import org.koitharu.kotatsu.ui.common.list.BaseViewHolder import org.koitharu.kotatsu.utils.ext.textAndVisible import kotlin.math.roundToInt -class MangaListDetailsHolder(parent: ViewGroup) : BaseViewHolder(parent, R.layout.item_manga_list_details) { +class MangaListDetailsHolder(parent: ViewGroup) : BaseViewHolder>(parent, R.layout.item_manga_list_details) { private var coverRequest: RequestDisposable? = null @SuppressLint("SetTextI18n") - override fun onBind(data: Manga) { + override fun onBind(data: MangaInfo) { coverRequest?.dispose() - textView_title.text = data.title - textView_subtitle.textAndVisible = data.localizedTitle - coverRequest = imageView_cover.load(data.coverUrl) { + textView_title.text = data.manga.title + textView_subtitle.textAndVisible = data.manga.localizedTitle + coverRequest = imageView_cover.load(data.manga.coverUrl) { crossfade(true) } - if(data.rating == Manga.NO_RATING) { + if(data.manga.rating == Manga.NO_RATING) { textView_rating.isVisible = false } else { - textView_rating.text = "${(data.rating * 10).roundToInt()}/10" + textView_rating.text = "${(data.manga.rating * 10).roundToInt()}/10" textView_rating.isVisible = true } - textView_tags.text = data.tags.joinToString(", ") { + textView_tags.text = data.manga.tags.joinToString(", ") { it.title } } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListFragment.kt index e3be5c421..2378a8ffe 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListFragment.kt @@ -13,26 +13,23 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_list.* -import moxy.ktx.moxyPresenter import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.model.MangaInfo import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.ui.common.BaseFragment import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener import org.koitharu.kotatsu.ui.common.list.PaginationScrollListener import org.koitharu.kotatsu.ui.common.list.SpacingItemDecoration import org.koitharu.kotatsu.ui.details.MangaDetailsActivity -import org.koitharu.kotatsu.utils.ext.* +import org.koitharu.kotatsu.utils.ext.clearItemDecorations +import org.koitharu.kotatsu.utils.ext.firstItem +import org.koitharu.kotatsu.utils.ext.getDisplayMessage +import org.koitharu.kotatsu.utils.ext.hasItems -class MangaListFragment : BaseFragment(R.layout.fragment_list), MangaListView, - PaginationScrollListener.Callback, OnRecyclerItemClickListener { +abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), MangaListView, + PaginationScrollListener.Callback, OnRecyclerItemClickListener> { - private val presenter by moxyPresenter(factory = ::MangaListPresenter) - - private val source by arg(ARG_SOURCE) - - private lateinit var adapter: MangaListAdapter + private lateinit var adapter: MangaListAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -46,7 +43,7 @@ class MangaListFragment : BaseFragment(R.layout.fragment_list), MangaListView, recyclerView.adapter = adapter recyclerView.addOnScrollListener(PaginationScrollListener(4, this)) swipeRefreshLayout.setOnRefreshListener { - presenter.loadList(source, 0) + onRequestMoreItems(0) } settings.subscribe(this) } @@ -58,7 +55,9 @@ class MangaListFragment : BaseFragment(R.layout.fragment_list), MangaListView, override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - presenter.loadList(source, 0) + if (!recyclerView.hasItems) { + onRequestMoreItems(0) + } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -74,19 +73,16 @@ class MangaListFragment : BaseFragment(R.layout.fragment_list), MangaListView, else -> super.onOptionsItemSelected(item) } - override fun onItemClick(item: Manga, position: Int, view: View) { - startActivity(MangaDetailsActivity.newIntent(context ?: return, item)) + override fun onItemClick(item: MangaInfo, position: Int, view: View) { + startActivity(MangaDetailsActivity.newIntent(context ?: return, item.manga)) } - override fun onRequestMoreItems(offset: Int) { - presenter.loadList(source, offset) - } - - override fun onListChanged(list: List) { + override fun onListChanged(list: List>) { adapter.replaceData(list) + layout_holder.isVisible = list.isEmpty() } - override fun onListAppended(list: List) { + override fun onListAppended(list: List>) { adapter.appendData(list) } @@ -101,10 +97,9 @@ class MangaListFragment : BaseFragment(R.layout.fragment_list), MangaListView, progressBar.isVisible = isLoading && !hasItems swipeRefreshLayout.isRefreshing = isLoading && hasItems swipeRefreshLayout.isEnabled = !progressBar.isVisible - } - - override fun getTitle(): CharSequence? { - return source.title + if (isLoading) { + layout_holder.isVisible = false + } } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { @@ -133,13 +128,4 @@ class MangaListFragment : BaseFragment(R.layout.fragment_list), MangaListView, adapter.notifyDataSetChanged() recyclerView.firstItem = position } - - companion object { - - private const val ARG_SOURCE = "provider" - - fun newInstance(provider: MangaSource) = MangaListFragment().withArgs(1) { - putParcelable(ARG_SOURCE, provider) - } - } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListHolder.kt index 1fa828792..1405fadc9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListHolder.kt @@ -5,19 +5,19 @@ import coil.api.load import coil.request.RequestDisposable import kotlinx.android.synthetic.main.item_manga_list.* import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.model.MangaInfo import org.koitharu.kotatsu.ui.common.list.BaseViewHolder import org.koitharu.kotatsu.utils.ext.textAndVisible -class MangaListHolder(parent: ViewGroup) : BaseViewHolder(parent, R.layout.item_manga_list) { +class MangaListHolder(parent: ViewGroup) : BaseViewHolder>(parent, R.layout.item_manga_list) { private var coverRequest: RequestDisposable? = null - override fun onBind(data: Manga) { + override fun onBind(data: MangaInfo) { coverRequest?.dispose() - textView_title.text = data.title - textView_subtitle.textAndVisible = data.localizedTitle - coverRequest = imageView_cover.load(data.coverUrl) { + textView_title.text = data.manga.title + textView_subtitle.textAndVisible = data.manga.localizedTitle + coverRequest = imageView_cover.load(data.manga.coverUrl) { crossfade(true) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListView.kt b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListView.kt index 8583261f3..d13cfe196 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListView.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListView.kt @@ -2,15 +2,15 @@ package org.koitharu.kotatsu.ui.main.list import moxy.MvpView import moxy.viewstate.strategy.* -import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.model.MangaInfo -interface MangaListView : MvpView { +interface MangaListView : MvpView { @StateStrategyType(AddToEndSingleTagStrategy::class, tag = "content") - fun onListChanged(list: List) + fun onListChanged(list: List>) @StateStrategyType(AddToEndStrategy::class, tag = "content") - fun onListAppended(list: List) + fun onListAppended(list: List>) @StateStrategyType(AddToEndSingleStrategy::class) fun onLoadingChanged(isLoading: Boolean) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/history/HistoryListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/history/HistoryListFragment.kt new file mode 100644 index 000000000..138886624 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/history/HistoryListFragment.kt @@ -0,0 +1,49 @@ +package org.koitharu.kotatsu.ui.main.list.history + +import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import kotlinx.android.synthetic.main.fragment_list.* +import moxy.ktx.moxyPresenter +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.model.MangaHistory +import org.koitharu.kotatsu.ui.main.list.MangaListFragment +import org.koitharu.kotatsu.ui.main.list.MangaListView + +class HistoryListFragment : MangaListFragment(), MangaListView{ + + private val presenter by moxyPresenter(factory = ::HistoryListPresenter) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + textView_holder.setText(R.string.history_is_empty) + } + + override fun onRequestMoreItems(offset: Int) { + presenter.loadList(offset) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.opt_history, menu) + super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(item: MenuItem) = when(item.itemId) { + R.id.action_clear_history -> { + presenter.clearHistory() + true + } + else -> super.onOptionsItemSelected(item) + } + + override fun getTitle(): CharSequence? { + return getString(R.string.history) + } + + companion object { + + fun newInstance() = HistoryListFragment() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/history/HistoryListPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/history/HistoryListPresenter.kt new file mode 100644 index 000000000..62299dced --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/history/HistoryListPresenter.kt @@ -0,0 +1,70 @@ +package org.koitharu.kotatsu.ui.main.list.history + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import moxy.InjectViewState +import okhttp3.internal.closeQuietly +import org.koitharu.kotatsu.BuildConfig +import org.koitharu.kotatsu.core.model.MangaHistory +import org.koitharu.kotatsu.domain.HistoryRepository +import org.koitharu.kotatsu.ui.common.BasePresenter +import org.koitharu.kotatsu.ui.main.list.MangaListView + +@InjectViewState +class HistoryListPresenter : BasePresenter>() { + + private lateinit var repository: HistoryRepository + + override fun onFirstViewAttach() { + repository = HistoryRepository() + super.onFirstViewAttach() + } + + fun loadList(offset: Int) { + launch { + viewState.onLoadingChanged(true) + try { + val list = withContext(Dispatchers.IO) { + repository.getHistory(offset = offset) + } + if (offset == 0) { + viewState.onListChanged(list) + } else { + viewState.onListAppended(list) + } + } catch (e: Exception) { + if (BuildConfig.DEBUG) { + e.printStackTrace() + } + viewState.onError(e) + } finally { + viewState.onLoadingChanged(false) + } + } + } + + fun clearHistory() { + launch { + viewState.onLoadingChanged(true) + try { + withContext(Dispatchers.IO) { + repository.clear() + } + viewState.onListChanged(emptyList()) + } catch (e: Exception) { + if (BuildConfig.DEBUG) { + e.printStackTrace() + } + viewState.onError(e) + } finally { + viewState.onLoadingChanged(false) + } + } + } + + override fun onDestroy() { + repository.closeQuietly() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/remote/RemoteListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/remote/RemoteListFragment.kt new file mode 100644 index 000000000..48279956f --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/remote/RemoteListFragment.kt @@ -0,0 +1,39 @@ +package org.koitharu.kotatsu.ui.main.list.remote + +import android.os.Bundle +import android.view.View +import kotlinx.android.synthetic.main.fragment_list.* +import moxy.ktx.moxyPresenter +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.ui.main.list.MangaListFragment +import org.koitharu.kotatsu.utils.ext.withArgs + +class RemoteListFragment : MangaListFragment() { + + private val presenter by moxyPresenter(factory = ::RemoteListPresenter) + + private val source by arg(ARG_SOURCE) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + textView_holder.setText(R.string.nothing_found) + } + + override fun onRequestMoreItems(offset: Int) { + presenter.loadList(source, offset) + } + + override fun getTitle(): CharSequence? { + return source.title + } + + companion object { + + private const val ARG_SOURCE = "provider" + + fun newInstance(provider: MangaSource) = RemoteListFragment().withArgs(1) { + putParcelable(ARG_SOURCE, provider) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/remote/RemoteListPresenter.kt similarity index 77% rename from app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListPresenter.kt rename to app/src/main/java/org/koitharu/kotatsu/ui/main/list/remote/RemoteListPresenter.kt index be1e88480..6085e24b2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/MangaListPresenter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/remote/RemoteListPresenter.kt @@ -1,16 +1,18 @@ -package org.koitharu.kotatsu.ui.main.list +package org.koitharu.kotatsu.ui.main.list.remote import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import moxy.InjectViewState import org.koitharu.kotatsu.BuildConfig +import org.koitharu.kotatsu.core.model.MangaInfo import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.domain.MangaProviderFactory import org.koitharu.kotatsu.ui.common.BasePresenter +import org.koitharu.kotatsu.ui.main.list.MangaListView @InjectViewState -class MangaListPresenter : BasePresenter() { +class RemoteListPresenter : BasePresenter>() { fun loadList(source: MangaSource, offset: Int) { launch { @@ -19,6 +21,7 @@ class MangaListPresenter : BasePresenter() { val list = withContext(Dispatchers.IO) { MangaProviderFactory.create(source) .getList(offset) + .map { MangaInfo(it, Unit) } } if (offset == 0) { viewState.onListChanged(list) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt index 65d207e7c..d3d519f23 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt @@ -6,12 +6,10 @@ import android.os.Bundle import android.view.Menu import android.view.MenuItem import androidx.core.view.isVisible -import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.activity_reader.* import moxy.ktx.moxyPresenter import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.ui.common.BaseActivity import org.koitharu.kotatsu.utils.ext.showDialog @@ -58,6 +56,11 @@ class ReaderActivity : BaseActivity(), ReaderView { super.onDestroy() } + override fun onPause() { + presenter.addToHistory(manga, chapterId, pager.currentItem) + super.onPause() + } + override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.opt_reader_top, menu) return super.onCreateOptionsMenu(menu) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderPresenter.kt index 632d20cff..b325b8d87 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderPresenter.kt @@ -5,12 +5,14 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import moxy.InjectViewState import org.koitharu.kotatsu.BuildConfig +import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaChapter +import org.koitharu.kotatsu.domain.HistoryRepository import org.koitharu.kotatsu.domain.MangaProviderFactory import org.koitharu.kotatsu.ui.common.BasePresenter @InjectViewState -class ReaderPresenter() : BasePresenter() { +class ReaderPresenter : BasePresenter() { fun loadChapter(chapter: MangaChapter) { launch { @@ -31,4 +33,12 @@ class ReaderPresenter() : BasePresenter() { } } + fun addToHistory(manga: Manga, chapterId: Long, page: Int) { + launch(Dispatchers.IO) { + HistoryRepository().use { + it.addOrUpdate(manga, chapterId, page) + } + } + } + } \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 57a1b4cfa..000000000 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_list.xml b/app/src/main/res/layout/fragment_list.xml index 8707eb71e..5debf1924 100644 --- a/app/src/main/res/layout/fragment_list.xml +++ b/app/src/main/res/layout/fragment_list.xml @@ -1,5 +1,6 @@ - + android:text="?android:textColorSecondary" + android:textAppearance="@style/TextAppearance.AppCompat.Medium" + tools:text="@tools:sample/lorem[3]" /> diff --git a/app/src/main/res/menu/opt_history.xml b/app/src/main/res/menu/opt_history.xml new file mode 100644 index 000000000..f1cd6968b --- /dev/null +++ b/app/src/main/res/menu/opt_history.xml @@ -0,0 +1,12 @@ + + + + + + \ 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 65f62af29..64c37a017 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,4 +19,7 @@ Chapter %d of %d Close Try again + Clear history + Nothing found + History is empty \ No newline at end of file