Migrate to MVVM

This commit is contained in:
Koitharu
2020-11-17 21:47:22 +02:00
parent eaac271143
commit 7d24286c55
273 changed files with 1926 additions and 2115 deletions

View File

@@ -84,17 +84,12 @@ dependencies {
implementation 'androidx.room:room-ktx:2.2.5'
kapt 'androidx.room:room-compiler:2.2.5'
implementation 'com.github.moxy-community:moxy:2.2.0'
implementation 'com.github.moxy-community:moxy-androidx:2.2.0'
implementation 'com.github.moxy-community:moxy-material:2.2.0'
implementation 'com.github.moxy-community:moxy-ktx:2.2.0'
kapt 'com.github.moxy-community:moxy-compiler:2.2.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.okio:okio:2.9.0'
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'org.koin:koin-android:2.2.0'
implementation 'org.koin:koin-android-viewmodel:2.2.0'
implementation 'io.coil-kt:coil-base:1.0.0'
implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0'
implementation 'com.tomclaw.cache:cache:1.0'

View File

@@ -23,7 +23,7 @@
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute">
<activity android:name=".ui.list.MainActivity">
<activity android:name="org.koitharu.kotatsu.main.ui.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -32,62 +32,62 @@
android:name="android.app.default_searchable"
android:value=".ui.search.SearchActivity" />
</activity>
<activity android:name=".ui.details.MangaDetailsActivity">
<activity android:name="org.koitharu.kotatsu.details.ui.DetailsActivity">
<intent-filter>
<action android:name="${applicationId}.action.VIEW_MANGA" />
</intent-filter>
</activity>
<activity android:name=".ui.reader.ReaderActivity" />
<activity android:name="org.koitharu.kotatsu.reader.ui.ReaderActivity" />
<activity
android:name=".ui.search.SearchActivity"
android:name="org.koitharu.kotatsu.search.ui.SearchActivity"
android:label="@string/search" />
<activity
android:name=".ui.settings.SettingsActivity"
android:name="org.koitharu.kotatsu.settings.SettingsActivity"
android:label="@string/settings" />
<activity
android:name=".ui.reader.SimpleSettingsActivity"
android:name="org.koitharu.kotatsu.reader.ui.SimpleSettingsActivity"
android:label="@string/settings">
<intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".ui.browser.BrowserActivity" />
<activity android:name="org.koitharu.kotatsu.browser.BrowserActivity" />
<activity
android:name=".ui.utils.CrashActivity"
android:name="org.koitharu.kotatsu.core.ui.CrashActivity"
android:label="@string/error_occurred"
android:theme="@android:style/Theme.DeviceDefault"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name="org.koitharu.kotatsu.ui.list.favourites.categories.CategoriesActivity"
android:name="org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity"
android:label="@string/favourites_categories"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".ui.widget.shelf.ShelfConfigActivity"
android:name="org.koitharu.kotatsu.widget.shelf.ShelfConfigActivity"
android:label="@string/manga_shelf">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<activity
android:name=".ui.search.global.GlobalSearchActivity"
android:name="org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity"
android:label="@string/search" />
<activity
android:name=".ui.utils.protect.ProtectActivity"
android:name="org.koitharu.kotatsu.main.ui.protect.ProtectActivity"
android:windowSoftInputMode="adjustResize" />
<service
android:name=".ui.download.DownloadService"
android:name="org.koitharu.kotatsu.download.DownloadService"
android:foregroundServiceType="dataSync" />
<service
android:name=".ui.widget.shelf.ShelfWidgetService"
android:name="org.koitharu.kotatsu.widget.shelf.ShelfWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<service
android:name=".ui.widget.recent.RecentWidgetService"
android:name="org.koitharu.kotatsu.widget.recent.RecentWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<provider
android:name=".ui.search.MangaSuggestionsProvider"
android:name="org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider"
android:authorities="${applicationId}.MangaSuggestionsProvider"
android:exported="false" />
<provider
@@ -101,7 +101,7 @@
</provider>
<receiver
android:name=".ui.widget.shelf.ShelfWidgetProvider"
android:name="org.koitharu.kotatsu.widget.shelf.ShelfWidgetProvider"
android:label="@string/manga_shelf">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@@ -111,7 +111,7 @@
android:resource="@xml/widget_shelf" />
</receiver>
<receiver
android:name=".ui.widget.recent.RecentWidgetProvider"
android:name="org.koitharu.kotatsu.widget.recent.RecentWidgetProvider"
android:label="@string/recent_manga">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />

View File

@@ -6,25 +6,29 @@ import androidx.appcompat.app.AppCompatDelegate
import org.koin.android.ext.android.get
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import org.koin.dsl.module
import org.koitharu.kotatsu.core.backup.BackupRepository
import org.koitharu.kotatsu.core.backup.RestoreRepository
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.db.databaseModule
import org.koitharu.kotatsu.core.github.githubModule
import org.koitharu.kotatsu.core.local.PagesCache
import org.koitharu.kotatsu.core.network.networkModule
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
import org.koitharu.kotatsu.core.parser.parserModule
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.domain.MangaDataRepository
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.domain.MangaSearchRepository
import org.koitharu.kotatsu.domain.favourites.FavouritesRepository
import org.koitharu.kotatsu.domain.history.HistoryRepository
import org.koitharu.kotatsu.domain.tracking.TrackingRepository
import org.koitharu.kotatsu.ui.base.uiModule
import org.koitharu.kotatsu.ui.utils.AppCrashHandler
import org.koitharu.kotatsu.ui.widget.WidgetUpdater
import org.koitharu.kotatsu.core.ui.AppCrashHandler
import org.koitharu.kotatsu.core.ui.uiModule
import org.koitharu.kotatsu.details.detailsModule
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.favouritesModule
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.historyModule
import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.local.localModule
import org.koitharu.kotatsu.main.mainModule
import org.koitharu.kotatsu.reader.readerModule
import org.koitharu.kotatsu.remotelist.remoteListModule
import org.koitharu.kotatsu.search.searchModule
import org.koitharu.kotatsu.settings.settingsModule
import org.koitharu.kotatsu.tracker.trackerModule
import org.koitharu.kotatsu.widget.WidgetUpdater
class KotatsuApp : Application() {
@@ -62,20 +66,18 @@ class KotatsuApp : Application() {
networkModule,
databaseModule,
githubModule,
parserModule,
uiModule,
module {
single { FavouritesRepository(get()) }
single { HistoryRepository(get()) }
single { TrackingRepository(get(), get()) }
single { MangaDataRepository(get()) }
single { BackupRepository(get()) }
single { RestoreRepository(get()) }
single { MangaSearchRepository() }
single { MangaLoaderContext() }
single { AppSettings(get()) }
single { PagesCache(get()) }
}
parserModule,
mainModule,
searchModule,
localModule,
favouritesModule,
historyModule,
remoteListModule,
detailsModule,
trackerModule,
settingsModule,
readerModule
)
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.domain
package org.koitharu.kotatsu.base.domain
import androidx.room.withTransaction
import org.koitharu.kotatsu.core.db.MangaDatabase

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.domain
package org.koitharu.kotatsu.base.domain
import okhttp3.*
import org.koin.core.component.KoinComponent

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.domain
package org.koitharu.kotatsu.base.domain
import org.koin.core.component.KoinComponent
import org.koin.core.component.get

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.domain
package org.koitharu.kotatsu.base.domain
import android.graphics.BitmapFactory
import android.net.Uri

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base
package org.koitharu.kotatsu.base.ui
import android.app.Dialog
import android.os.Bundle
@@ -6,11 +6,11 @@ import android.view.View
import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AlertDialog
import moxy.MvpAppCompatDialogFragment
import androidx.fragment.app.DialogFragment
abstract class AlertDialogFragment(
@LayoutRes private val layoutResId: Int
) : MvpAppCompatDialogFragment() {
) : DialogFragment() {
private var rootView: View? = null

View File

@@ -1,15 +1,15 @@
package org.koitharu.kotatsu.ui.base
package org.koitharu.kotatsu.base.ui
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import moxy.MvpAppCompatActivity
import org.koin.android.ext.android.get
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
abstract class BaseActivity : MvpAppCompatActivity() {
abstract class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
if (get<AppSettings>().isAmoledTheme) {

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base
package org.koitharu.kotatsu.base.ui
import android.app.Dialog
import android.os.Bundle
@@ -7,11 +7,11 @@ import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatDialog
import moxy.MvpBottomSheetDialogFragment
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.koitharu.kotatsu.utils.UiUtils
abstract class BaseBottomSheet(@LayoutRes private val layoutResId: Int) :
MvpBottomSheetDialogFragment() {
BottomSheetDialogFragment() {
final override fun onCreateView(
inflater: LayoutInflater,

View File

@@ -1,14 +1,14 @@
package org.koitharu.kotatsu.ui.base
package org.koitharu.kotatsu.base.ui
import android.content.Context
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import coil.ImageLoader
import moxy.MvpAppCompatFragment
import org.koin.android.ext.android.inject
abstract class BaseFragment(
@LayoutRes contentLayoutId: Int
) : MvpAppCompatFragment(contentLayoutId) {
) : Fragment(contentLayoutId) {
protected val coil by inject<ImageLoader>()

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base
package org.koitharu.kotatsu.base.ui
import android.graphics.Color
import android.os.Build

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base
package org.koitharu.kotatsu.base.ui
import androidx.annotation.StringRes
import androidx.preference.PreferenceFragmentCompat

View File

@@ -0,0 +1,5 @@
package org.koitharu.kotatsu.base.ui
import androidx.lifecycle.LifecycleService
abstract class BaseService : LifecycleService()

View File

@@ -0,0 +1,40 @@
package org.koitharu.kotatsu.base.ui
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.*
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.utils.SingleLiveEvent
abstract class BaseViewModel : ViewModel() {
val onError = SingleLiveEvent<Throwable>()
val isLoading = MutableLiveData(false)
protected fun launchJob(
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job = viewModelScope.launch(createErrorHandler(), start, block)
protected fun launchLoadingJob(
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job = viewModelScope.launch(createErrorHandler(), start) {
isLoading.value = true
try {
block()
} finally {
isLoading.value = false
}
}
private fun createErrorHandler() = CoroutineExceptionHandler { _, throwable ->
if (BuildConfig.DEBUG) {
throwable.printStackTrace()
}
if (throwable !is CancellationException) {
onError.call(throwable)
}
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.dialog
package org.koitharu.kotatsu.base.ui.dialog
import android.annotation.SuppressLint
import android.content.Context

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.dialog
package org.koitharu.kotatsu.base.ui.dialog
import android.content.Context
import android.content.DialogInterface
@@ -10,7 +10,7 @@ import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.android.synthetic.main.item_storage.view.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.utils.ext.getStorageName
import org.koitharu.kotatsu.utils.ext.inflate
import org.koitharu.kotatsu.utils.ext.longHashCode

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.dialog
package org.koitharu.kotatsu.base.ui.dialog
import android.annotation.SuppressLint
import android.content.Context

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.list
package org.koitharu.kotatsu.base.ui.list
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.list
package org.koitharu.kotatsu.base.ui.list
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.list
package org.koitharu.kotatsu.base.ui.list
import android.view.View
import android.view.ViewGroup

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.list
package org.koitharu.kotatsu.base.ui.list
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.list
package org.koitharu.kotatsu.base.ui.list
import android.view.View

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.list
package org.koitharu.kotatsu.base.ui.list
import androidx.recyclerview.widget.RecyclerView

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.list
package org.koitharu.kotatsu.base.ui.list
import android.view.ViewGroup

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.list
package org.koitharu.kotatsu.base.ui.list
import android.view.View
import android.view.ViewGroup

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.list.decor
package org.koitharu.kotatsu.base.ui.list.decor
import android.content.Context
import android.graphics.Canvas

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.list.decor
package org.koitharu.kotatsu.base.ui.list.decor
import android.graphics.Canvas
import android.graphics.Rect

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.list.decor
package org.koitharu.kotatsu.base.ui.list.decor
import android.graphics.Rect
import android.view.View

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.widgets
package org.koitharu.kotatsu.base.ui.widgets
import android.content.Context
import android.util.AttributeSet

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.widgets
package org.koitharu.kotatsu.base.ui.widgets
import android.content.Context
import android.util.AttributeSet

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base.widgets
package org.koitharu.kotatsu.base.ui.widgets
import android.content.Context
import android.util.AttributeSet

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.browser
package org.koitharu.kotatsu.browser
import android.annotation.SuppressLint
import android.content.ActivityNotFoundException
@@ -11,7 +11,7 @@ import android.view.MenuItem
import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.activity_browser.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.ui.base.BaseActivity
import org.koitharu.kotatsu.base.ui.BaseActivity
@SuppressLint("SetJavaScriptEnabled")
class BrowserActivity : BaseActivity(), BrowserCallback {

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.browser
package org.koitharu.kotatsu.browser
interface BrowserCallback {

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.browser
package org.koitharu.kotatsu.browser
import android.graphics.Bitmap
import android.webkit.WebResourceRequest

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.utils.cloudflare
package org.koitharu.kotatsu.browser.cloudflare
interface CloudFlareCallback {

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.utils.cloudflare
package org.koitharu.kotatsu.browser.cloudflare
import android.graphics.Bitmap
import android.webkit.CookieManager

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.utils.cloudflare
package org.koitharu.kotatsu.browser.cloudflare
import android.annotation.SuppressLint
import android.os.Bundle
@@ -10,8 +10,8 @@ import androidx.core.view.isInvisible
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import kotlinx.android.synthetic.main.fragment_cloudflare.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.network.UserAgentInterceptor
import org.koitharu.kotatsu.ui.base.AlertDialogFragment
import org.koitharu.kotatsu.utils.ext.stringArgument
import org.koitharu.kotatsu.utils.ext.withArgs

View File

@@ -4,7 +4,11 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.*
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.history.data.HistoryEntity
class BackupRepository(private val db: MangaDatabase) {

View File

@@ -3,7 +3,11 @@ package org.koitharu.kotatsu.core.backup
import androidx.room.withTransaction
import org.json.JSONObject
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.*
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.history.data.HistoryEntity
import org.koitharu.kotatsu.utils.ext.getStringOrNull
import org.koitharu.kotatsu.utils.ext.iterator
import org.koitharu.kotatsu.utils.ext.map

View File

@@ -4,6 +4,12 @@ import androidx.room.Database
import androidx.room.RoomDatabase
import org.koitharu.kotatsu.core.db.dao.*
import org.koitharu.kotatsu.core.db.entity.*
import org.koitharu.kotatsu.favourites.data.FavouriteCategoriesDao
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.favourites.data.FavouritesDao
import org.koitharu.kotatsu.history.data.HistoryDao
import org.koitharu.kotatsu.history.data.HistoryEntity
@Database(
entities = [

View File

@@ -15,7 +15,7 @@ import androidx.room.PrimaryKey
)
]
)
data class TrackEntity (
data class TrackEntity(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "manga_id") val mangaId: Long,
@ColumnInfo(name = "chapters_total") val totalChapters: Int,

View File

@@ -1,3 +1,4 @@
package org.koitharu.kotatsu.core.exceptions
class ParseException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
class ParseException(message: String? = null, cause: Throwable? = null) :
RuntimeException(message, cause)

View File

@@ -4,9 +4,10 @@ import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import org.koin.core.context.GlobalContext
import org.koin.core.error.NoBeanDefFoundException
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
import org.koin.core.qualifier.named
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.site.*
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
@Suppress("SpellCheckingInspection")
@Parcelize
@@ -30,6 +31,7 @@ enum class MangaSource(
// HENTAILIB("HentaiLib", "ru", HentaiLibRepository::class.java)
@get:Throws(NoBeanDefFoundException::class)
@Deprecated("")
val repository: MangaRepository
get() = GlobalContext.get().get(cls.kotlin)
get() = GlobalContext.get().get(named(this))
}

View File

@@ -5,10 +5,10 @@ import kotlinx.android.parcel.Parcelize
import java.util.*
@Parcelize
data class MangaTracking (
data class MangaTracking(
val manga: Manga,
val knownChaptersCount: Int,
val lastChapterId: Long,
val lastNotifiedChapterId: Long,
val lastCheck: Date?
): Parcelable
) : Parcelable

View File

@@ -5,9 +5,9 @@ import kotlinx.android.parcel.Parcelize
import java.util.*
@Parcelize
data class TrackingLogItem (
data class TrackingLogItem(
val id: Long,
val manga: Manga,
val chapters: List<String>,
val createdAt: Date
): Parcelable
) : Parcelable

View File

@@ -6,13 +6,18 @@ interface MangaRepository {
val sortOrders: Set<SortOrder>
suspend fun getList(offset: Int, query: String? = null, sortOrder: SortOrder? = null, tag: MangaTag? = null): List<Manga>
suspend fun getList(
offset: Int,
query: String? = null,
sortOrder: SortOrder? = null,
tag: MangaTag? = null
): List<Manga>
suspend fun getDetails(manga: Manga) : Manga
suspend fun getDetails(manga: Manga): Manga
suspend fun getPages(chapter: MangaChapter) : List<MangaPage>
suspend fun getPages(chapter: MangaChapter): List<MangaPage>
suspend fun getPageFullUrl(page: MangaPage) : String
suspend fun getPageFullUrl(page: MangaPage): String
suspend fun getTags(): Set<MangaTag>
}

View File

@@ -1,22 +1,25 @@
package org.koitharu.kotatsu.core.parser
import org.koin.dsl.bind
import org.koin.core.qualifier.named
import org.koin.dsl.module
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.parser.site.*
val parserModule
get() = module {
single { LocalMangaRepository(get()) } bind MangaRepository::class
factory { ReadmangaRepository(get()) } bind MangaRepository::class
factory { MintMangaRepository(get()) } bind MangaRepository::class
factory { SelfMangaRepository(get()) } bind MangaRepository::class
factory { MangaChanRepository(get()) } bind MangaRepository::class
factory { DesuMeRepository(get()) } bind MangaRepository::class
factory { HenChanRepository(get()) } bind MangaRepository::class
factory { YaoiChanRepository(get()) } bind MangaRepository::class
factory { MangaTownRepository(get()) } bind MangaRepository::class
factory { MangaLibRepository(get()) } bind MangaRepository::class
factory { NudeMoonRepository(get()) } bind MangaRepository::class
factory { MangareadRepository(get()) } bind MangaRepository::class
single { MangaLoaderContext() }
factory<MangaRepository>(named(MangaSource.READMANGA_RU)) { ReadmangaRepository(get()) }
factory<MangaRepository>(named(MangaSource.MINTMANGA)) { MintMangaRepository(get()) }
factory<MangaRepository>(named(MangaSource.SELFMANGA)) { SelfMangaRepository(get()) }
factory<MangaRepository>(named(MangaSource.MANGACHAN)) { MangaChanRepository(get()) }
factory<MangaRepository>(named(MangaSource.DESUME)) { DesuMeRepository(get()) }
factory<MangaRepository>(named(MangaSource.HENCHAN)) { HenChanRepository(get()) }
factory<MangaRepository>(named(MangaSource.YAOICHAN)) { YaoiChanRepository(get()) }
factory<MangaRepository>(named(MangaSource.MANGATOWN)) { MangaTownRepository(get()) }
factory<MangaRepository>(named(MangaSource.MANGALIB)) { MangaLibRepository(get()) }
factory<MangaRepository>(named(MangaSource.NUDEMOON)) { NudeMoonRepository(get()) }
factory<MangaRepository>(named(MangaSource.MANGAREAD)) { MangareadRepository(get()) }
}

View File

@@ -1,10 +1,10 @@
package org.koitharu.kotatsu.core.parser
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.domain.MangaLoaderContext
abstract class RemoteMangaRepository(
protected val loaderContext: MangaLoaderContext

View File

@@ -1,11 +1,11 @@
package org.koitharu.kotatsu.core.parser.site
import androidx.collection.arraySetOf
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.*
import java.util.*

View File

@@ -1,11 +1,11 @@
package org.koitharu.kotatsu.core.parser.site
import androidx.collection.arraySetOf
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.*
import java.util.*
import kotlin.collections.ArrayList

View File

@@ -2,11 +2,11 @@ package org.koitharu.kotatsu.core.parser.site
import androidx.collection.arraySetOf
import androidx.core.net.toUri
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.*
import java.util.*

View File

@@ -1,8 +1,8 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.longHashCode
import org.koitharu.kotatsu.utils.ext.mapToSet
import org.koitharu.kotatsu.utils.ext.parseHtml

View File

@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class MangaChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(loaderContext) {

View File

@@ -4,11 +4,11 @@ import androidx.collection.ArraySet
import androidx.collection.arraySetOf
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.*
import java.util.*
import kotlin.collections.ArrayList

View File

@@ -2,11 +2,11 @@ package org.koitharu.kotatsu.core.parser.site
import androidx.collection.arraySetOf
import org.intellij.lang.annotations.Language
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.*
import java.util.*

View File

@@ -1,11 +1,11 @@
package org.koitharu.kotatsu.core.parser.site
import androidx.collection.arraySetOf
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.*
import java.util.*

View File

@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class MintMangaRepository(loaderContext: MangaLoaderContext) : GroupleRepository(loaderContext) {

View File

@@ -1,11 +1,11 @@
package org.koitharu.kotatsu.core.parser.site
import androidx.collection.arraySetOf
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.*
import java.util.*
import java.util.regex.Pattern

View File

@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class ReadmangaRepository(loaderContext: MangaLoaderContext) : GroupleRepository(loaderContext) {

View File

@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class SelfMangaRepository(loaderContext: MangaLoaderContext) : GroupleRepository(loaderContext) {

View File

@@ -1,10 +1,10 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.longHashCode
import org.koitharu.kotatsu.utils.ext.parseHtml
import org.koitharu.kotatsu.utils.ext.withDomain

View File

@@ -8,7 +8,7 @@ import androidx.collection.arraySetOf
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.utils.delegates.prefs.*
import java.io.File

View File

@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.utils.delegates.prefs.LongPreferenceDelegate
class AppWidgetConfig private constructor(
private val prefs: SharedPreferences,
val widgetId: Int
) : SharedPreferences by prefs {
) : SharedPreferences by prefs {
var categoryId by LongPreferenceDelegate(CATEGORY_ID, 0L)

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.utils
package org.koitharu.kotatsu.core.ui
import android.content.Context
import android.content.Intent

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.base
package org.koitharu.kotatsu.core.ui
import android.content.Context
import android.view.View

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.utils
package org.koitharu.kotatsu.core.ui
import android.app.Activity
import android.content.ActivityNotFoundException
@@ -10,7 +10,7 @@ import android.view.MenuItem
import android.view.View
import kotlinx.android.synthetic.main.activity_crash.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.ui.list.MainActivity
import org.koitharu.kotatsu.main.ui.MainActivity
import org.koitharu.kotatsu.utils.ShareHelper
class CrashActivity : Activity(), View.OnClickListener {

View File

@@ -1,11 +1,11 @@
package org.koitharu.kotatsu.ui.base
package org.koitharu.kotatsu.core.ui
import coil.ComponentRegistry
import coil.ImageLoader
import okhttp3.OkHttpClient
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
import org.koitharu.kotatsu.core.local.CbzFetcher
import org.koitharu.kotatsu.local.data.CbzFetcher
val uiModule
get() = module {

View File

@@ -0,0 +1,11 @@
package org.koitharu.kotatsu.details
import org.koin.android.viewmodel.dsl.viewModel
import org.koin.dsl.module
import org.koitharu.kotatsu.details.ui.DetailsViewModel
val detailsModule
get() = module {
viewModel { DetailsViewModel(get(), get(), get(), get(), get(), get()) }
}

View File

@@ -1,13 +1,13 @@
package org.koitharu.kotatsu.ui.details
package org.koitharu.kotatsu.details.ui
import android.graphics.Color
import android.view.ViewGroup
import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.item_chapter.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.domain.history.ChapterExtra
import org.koitharu.kotatsu.ui.base.list.BaseViewHolder
import org.koitharu.kotatsu.history.domain.ChapterExtra
import org.koitharu.kotatsu.utils.ext.getThemeColor
class ChapterHolder(parent: ViewGroup) :

View File

@@ -1,11 +1,11 @@
package org.koitharu.kotatsu.ui.details
package org.koitharu.kotatsu.details.ui
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.base.ui.list.BaseRecyclerAdapter
import org.koitharu.kotatsu.base.ui.list.OnRecyclerItemClickListener
import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.domain.history.ChapterExtra
import org.koitharu.kotatsu.ui.base.list.BaseRecyclerAdapter
import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener
import org.koitharu.kotatsu.history.domain.ChapterExtra
class ChaptersAdapter(onItemClickListener: OnRecyclerItemClickListener<MangaChapter>) :
BaseRecyclerAdapter<MangaChapter, ChapterExtra>(onItemClickListener) {

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.details
package org.koitharu.kotatsu.details.ui
import android.app.ActivityOptions
import android.os.Bundle
@@ -12,22 +12,22 @@ import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.fragment_chapters.*
import moxy.ktx.moxyPresenter
import org.koin.android.viewmodel.ext.android.sharedViewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.ui.base.BaseFragment
import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener
import org.koitharu.kotatsu.ui.download.DownloadService
import org.koitharu.kotatsu.ui.reader.ReaderActivity
import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.OnRecyclerItemClickListener
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.download.DownloadService
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.utils.ext.resolveDp
class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), MangaDetailsView,
class ChaptersFragment : BaseFragment(R.layout.fragment_chapters),
OnRecyclerItemClickListener<MangaChapter>, ActionMode.Callback {
@Suppress("unused")
private val presenter by moxyPresenter {
MangaDetailsPresenter.getInstance(activity.hashCode())
}
private val viewModel by sharedViewModel<DetailsViewModel>()
private var manga: Manga? = null
@@ -45,33 +45,32 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), MangaDetailsV
)
recyclerView_chapters.setHasFixedSize(true)
recyclerView_chapters.adapter = adapter
viewModel.mangaData.observe(viewLifecycleOwner, this::onMangaUpdated)
viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)
viewModel.history.observe(viewLifecycleOwner, this::onHistoryChanged)
viewModel.newChapters.observe(viewLifecycleOwner, this::onNewChaptersChanged)
}
override fun onMangaUpdated(manga: Manga) {
private fun onMangaUpdated(manga: Manga) {
this.manga = manga
adapter.replaceData(manga.chapters.orEmpty())
scrollToCurrent()
}
override fun onLoadingStateChanged(isLoading: Boolean) {
private fun onLoadingStateChanged(isLoading: Boolean) {
progressBar.isVisible = isLoading
}
override fun onError(e: Throwable) = Unit //handled in activity
override fun onMangaRemoved(manga: Manga) = Unit //handled in activity
override fun onHistoryChanged(history: MangaHistory?) {
private fun onHistoryChanged(history: MangaHistory?) {
adapter.currentChapterId = history?.chapterId
scrollToCurrent()
}
override fun onNewChaptersChanged(newChapters: Int) {
private fun onNewChaptersChanged(newChapters: Int) {
adapter.newChaptersCount = newChapters
}
override fun onFavouriteChanged(categories: List<FavouriteCategory>) = Unit
override fun onItemClick(item: MangaChapter, position: Int, view: View) {
if (adapter.checkedItemsCount != 0) {
adapter.toggleItemChecked(item.id)

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.details
package org.koitharu.kotatsu.details.ui
import android.content.Context
import android.content.Intent
@@ -18,28 +18,22 @@ import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import kotlinx.android.synthetic.main.activity_details.*
import kotlinx.coroutines.launch
import moxy.MvpDelegate
import moxy.ktx.moxyPresenter
import org.koin.android.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.browser.BrowserActivity
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.ui.base.BaseActivity
import org.koitharu.kotatsu.ui.browser.BrowserActivity
import org.koitharu.kotatsu.ui.download.DownloadService
import org.koitharu.kotatsu.download.DownloadService
import org.koitharu.kotatsu.utils.MangaShortcut
import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.getThemeColor
class MangaDetailsActivity : BaseActivity(), MangaDetailsView,
TabLayoutMediator.TabConfigurationStrategy {
class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrategy {
private val presenter by moxyPresenter {
MangaDetailsPresenter.getInstance(hashCode())
}
private val viewModel by viewModel<DetailsViewModel>()
private var manga: Manga? = null
@@ -49,28 +43,27 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView,
supportActionBar?.setDisplayHomeAsUpEnabled(true)
pager.adapter = MangaDetailsAdapter(this)
TabLayoutMediator(tabs, pager, this).attach()
if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) {
if (savedInstanceState == null) {
intent?.getParcelableExtra<Manga>(EXTRA_MANGA)?.let {
presenter.loadDetails(it, true)
viewModel.loadDetails(it, true)
} ?: intent?.getLongExtra(EXTRA_MANGA_ID, 0)?.takeUnless { it == 0L }?.let {
presenter.findMangaById(it)
viewModel.findMangaById(it)
} ?: finishAfterTransition()
}
viewModel.mangaData.observe(this, ::onMangaUpdated)
viewModel.onMangaRemoved.observe(this, ::onMangaRemoved)
viewModel.onError.observe(this, ::onError)
viewModel.newChapters.observe(this, ::onNewChaptersChanged)
}
override fun onMangaUpdated(manga: Manga) {
private fun onMangaUpdated(manga: Manga) {
this.manga = manga
title = manga.title
invalidateOptionsMenu()
}
override fun onHistoryChanged(history: MangaHistory?) = Unit
override fun onFavouriteChanged(categories: List<FavouriteCategory>) = Unit
override fun onLoadingStateChanged(isLoading: Boolean) = Unit
override fun onMangaRemoved(manga: Manga) {
private fun onMangaRemoved(manga: Manga) {
Toast.makeText(
this, getString(R.string._s_deleted_from_local_storage, manga.title),
Toast.LENGTH_SHORT
@@ -78,7 +71,7 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView,
finishAfterTransition()
}
override fun onError(e: Throwable) {
private fun onError(e: Throwable) {
if (manga == null) {
Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show()
finishAfterTransition()
@@ -87,7 +80,7 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView,
}
}
override fun onNewChaptersChanged(newChapters: Int) {
private fun onNewChaptersChanged(newChapters: Int) {
val tab = tabs.getTabAt(1) ?: return
if (newChapters == 0) {
tab.removeBadge()
@@ -132,7 +125,7 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView,
.setTitle(R.string.delete_manga)
.setMessage(getString(R.string.text_delete_local_manga, m.title))
.setPositiveButton(R.string.delete) { _, _ ->
presenter.deleteLocal(m)
viewModel.deleteLocal(m)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
@@ -174,7 +167,7 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView,
R.id.action_shortcut -> {
manga?.let {
lifecycleScope.launch {
if (!MangaShortcut(it).requestPinShortcut(this@MangaDetailsActivity)) {
if (!MangaShortcut(it).requestPinShortcut(this@DetailsActivity)) {
Snackbar.make(
pager,
R.string.operation_not_supported,
@@ -217,11 +210,11 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView,
const val ACTION_MANGA_VIEW = "${BuildConfig.APPLICATION_ID}.action.VIEW_MANGA"
fun newIntent(context: Context, manga: Manga) =
Intent(context, MangaDetailsActivity::class.java)
Intent(context, DetailsActivity::class.java)
.putExtra(EXTRA_MANGA, manga)
fun newIntent(context: Context, mangaId: Long) =
Intent(context, MangaDetailsActivity::class.java)
Intent(context, DetailsActivity::class.java)
.putExtra(EXTRA_MANGA_ID, mangaId)
}
}

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.ui.details
package org.koitharu.kotatsu.details.ui
import android.os.Bundle
import android.text.Spanned
import android.view.View
import androidx.core.net.toUri
@@ -10,32 +11,36 @@ import kotlinx.android.synthetic.main.fragment_details.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import moxy.ktx.moxyPresenter
import org.koin.android.viewmodel.ext.android.sharedViewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.ui.base.BaseFragment
import org.koitharu.kotatsu.ui.list.favourites.categories.select.FavouriteCategoriesDialog
import org.koitharu.kotatsu.ui.reader.ReaderActivity
import org.koitharu.kotatsu.ui.search.MangaSearchSheet
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesDialog
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.search.ui.MangaSearchSheet
import org.koitharu.kotatsu.utils.FileSizeUtils
import org.koitharu.kotatsu.utils.ext.*
import kotlin.math.roundToInt
class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetailsView,
View.OnClickListener,
class DetailsFragment : BaseFragment(R.layout.fragment_details), View.OnClickListener,
View.OnLongClickListener {
@Suppress("unused")
private val presenter by moxyPresenter {
MangaDetailsPresenter.getInstance(activity.hashCode())
}
private val viewModel by sharedViewModel<DetailsViewModel>()
private var manga: Manga? = null
private var history: MangaHistory? = null
override fun onMangaUpdated(manga: Manga) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.mangaData.observe(viewLifecycleOwner, ::onMangaUpdated)
viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged)
viewModel.favouriteCategories.observe(viewLifecycleOwner, ::onFavouriteChanged)
viewModel.history.observe(viewLifecycleOwner, ::onHistoryChanged)
}
private fun onMangaUpdated(manga: Manga) {
this.manga = manga
imageView_cover.newImageRequest(manga.largeCoverUrl ?: manga.coverUrl)
.fallback(R.drawable.ic_placeholder)
@@ -59,7 +64,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
text = it,
iconRes = R.drawable.ic_chip_user,
tag = it,
onClickListener = this@MangaDetailsFragment
onClickListener = this@DetailsFragment
)
}
}
@@ -68,7 +73,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
text = it.title,
iconRes = R.drawable.ic_chip_tag,
tag = it,
onClickListener = this@MangaDetailsFragment
onClickListener = this@DetailsFragment
)
}
manga.url.toUri().toFileOrNull()?.let { f ->
@@ -81,7 +86,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
text = FileSizeUtils.formatBytes(context, size),
iconRes = R.drawable.ic_chip_storage,
tag = it,
onClickListener = this@MangaDetailsFragment
onClickListener = this@DetailsFragment
)
}
}
@@ -92,12 +97,12 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
updateReadButton()
}
override fun onHistoryChanged(history: MangaHistory?) {
private fun onHistoryChanged(history: MangaHistory?) {
this.history = history
updateReadButton()
}
override fun onFavouriteChanged(categories: List<FavouriteCategory>) {
private fun onFavouriteChanged(categories: List<FavouriteCategory>) {
imageView_favourite.setImageResource(
if (categories.isEmpty()) {
R.drawable.ic_heart_outline
@@ -107,16 +112,10 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
)
}
override fun onLoadingStateChanged(isLoading: Boolean) {
private fun onLoadingStateChanged(isLoading: Boolean) {
progressBar.isVisible = isLoading
}
override fun onError(e: Throwable) = Unit //handled in activity
override fun onMangaRemoved(manga: Manga) = Unit //handled in activity
override fun onNewChaptersChanged(newChapters: Int) = Unit
override fun onClick(v: View) {
when {
v.id == R.id.imageView_favourite -> {

View File

@@ -0,0 +1,135 @@
package org.koitharu.kotatsu.details.ui
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.base.domain.MangaDataRepository
import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.domain.OnFavouritesChangeListener
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.domain.OnHistoryChangeListener
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.onFirst
import org.koitharu.kotatsu.utils.ext.safe
import java.io.IOException
class DetailsViewModel(
private val historyRepository: HistoryRepository,
private val favouritesRepository: FavouritesRepository,
private val localMangaRepository: LocalMangaRepository,
private val trackingRepository: TrackingRepository,
private val searchRepository: MangaSearchRepository,
private val mangaDataRepository: MangaDataRepository
) : MangaListViewModel(), OnHistoryChangeListener, OnFavouritesChangeListener {
val mangaData = MutableLiveData<Manga>()
val newChapters = MutableLiveData<Int>(0)
val onMangaRemoved = SingleLiveEvent<Manga>()
val history = MutableLiveData<MangaHistory?>()
val favouriteCategories = MutableLiveData<List<FavouriteCategory>>()
init {
HistoryRepository.subscribe(this)
FavouritesRepository.subscribe(this)
}
fun findMangaById(id: Long) {
launchLoadingJob {
val manga = mangaDataRepository.findMangaById(id)
?: throw MangaNotFoundException("Cannot find manga by id")
mangaData.value = manga
loadDetails(manga, true)
}
}
fun loadDetails(manga: Manga, force: Boolean = false) {
if (!force && mangaData.value == manga) {
return
}
loadHistory(manga)
mangaData.value = manga
loadFavourite(manga)
launchLoadingJob {
val data = withContext(Dispatchers.Default) {
manga.source.repository.getDetails(manga)
}
mangaData.value = data
newChapters.value = trackingRepository.getNewChaptersCount(manga.id)
}
}
fun deleteLocal(manga: Manga) {
launchLoadingJob {
withContext(Dispatchers.Default) {
val original = localMangaRepository.getRemoteManga(manga)
localMangaRepository.delete(manga) || throw IOException("Unable to delete file")
safe {
historyRepository.deleteOrSwap(manga, original)
}
}
onMangaRemoved.call(manga)
}
}
private fun loadHistory(manga: Manga) {
launchJob {
history.value = historyRepository.getOne(manga)
}
}
private fun loadFavourite(manga: Manga) {
launchJob {
favouriteCategories.value = favouritesRepository.getCategories(manga.id)
}
}
fun loadRelated() {
val manga = mangaData.value ?: return
searchRepository.globalSearch(manga.title)
.map { list ->
list.filter { x -> x.id != manga.id }
}.filterNot { x -> x.isEmpty() }
.flowOn(Dispatchers.IO)
.catch { e ->
if (e is IOException) {
onError.call(e)
}
}
.onEmpty {
content.value = emptyList()
isLoading.value = false
}.onCompletion {
// TODO
}.onFirst {
isLoading.value = false
}.onEach {
content.value = content.value.orEmpty() + it
}
}
override fun onHistoryChanged() {
loadHistory(mangaData.value ?: return)
}
override fun onFavouritesChanged(mangaId: Long) {
val manga = mangaData.value ?: return
if (mangaId == manga.id) {
loadFavourite(manga)
}
}
override fun onCleared() {
HistoryRepository.unsubscribe(this)
FavouritesRepository.unsubscribe(this)
super.onCleared()
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.details
package org.koitharu.kotatsu.details.ui
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
@@ -8,8 +8,8 @@ class MangaDetailsAdapter(activity: FragmentActivity) : FragmentStateAdapter(act
override fun getItemCount() = 3
override fun createFragment(position: Int): Fragment = when(position) {
0 -> MangaDetailsFragment()
override fun createFragment(position: Int): Fragment = when (position) {
0 -> DetailsFragment()
1 -> ChaptersFragment()
2 -> RelatedMangaFragment()
else -> throw IndexOutOfBoundsException("No fragment for position $position")

View File

@@ -0,0 +1,22 @@
package org.koitharu.kotatsu.details.ui
import android.os.Bundle
import android.view.View
import org.koin.android.viewmodel.ext.android.sharedViewModel
import org.koitharu.kotatsu.list.ui.MangaListFragment
class RelatedMangaFragment : MangaListFragment() {
override val viewModel by sharedViewModel<DetailsViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
isSwipeRefreshEnabled = false
}
override fun onRequestMoreItems(offset: Int) {
if (offset == 0) {
viewModel.loadRelated()
}
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.download
package org.koitharu.kotatsu.download
import android.app.Notification
import android.app.NotificationChannel
@@ -12,7 +12,7 @@ import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.ui.details.MangaDetailsActivity
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import kotlin.math.roundToInt
@@ -24,7 +24,8 @@ class DownloadNotification(private val context: Context) {
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
&& manager.getNotificationChannel(CHANNEL_ID) == null) {
&& manager.getNotificationChannel(CHANNEL_ID) == null
) {
val channel = NotificationChannel(
CHANNEL_ID,
context.getString(R.string.downloads),
@@ -145,7 +146,7 @@ class DownloadNotification(private val context: Context) {
private fun createIntent(context: Context, manga: Manga) = PendingIntent.getActivity(
context,
manga.hashCode(),
MangaDetailsActivity.newIntent(context, manga),
DetailsActivity.newIntent(context, manga),
PendingIntent.FLAG_CANCEL_CURRENT
)
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.download
package org.koitharu.kotatsu.download
import android.content.Context
import android.content.Intent
@@ -7,6 +7,7 @@ import android.os.PowerManager
import android.webkit.MimeTypeMap
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import coil.ImageLoader
import coil.request.ImageRequest
import kotlinx.coroutines.*
@@ -19,13 +20,13 @@ import org.koin.android.ext.android.inject
import org.koin.core.context.GlobalContext
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.local.PagesCache
import org.koitharu.kotatsu.base.ui.BaseService
import org.koitharu.kotatsu.base.ui.dialog.CheckBoxAlertDialog
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.domain.local.MangaZip
import org.koitharu.kotatsu.ui.base.BaseService
import org.koitharu.kotatsu.ui.base.dialog.CheckBoxAlertDialog
import org.koitharu.kotatsu.local.data.MangaZip
import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.utils.CacheUtils
import org.koitharu.kotatsu.utils.ext.*
import java.io.File
@@ -54,8 +55,9 @@ class DownloadService : BaseService() {
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "kotatsu:downloading")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
when (intent.action) {
ACTION_DOWNLOAD_START -> {
val manga = intent.getParcelableExtra<Manga>(EXTRA_MANGA)
val chapters = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toArraySet()
@@ -77,7 +79,7 @@ class DownloadService : BaseService() {
}
private fun downloadManga(manga: Manga, chaptersIds: Set<Long>?, startId: Int): Job {
return serviceScope.launch(Dispatchers.Default) {
return lifecycleScope.launch(Dispatchers.Default) {
mutex.lock()
wakeLock.acquire(TimeUnit.HOURS.toMillis(1))
notification.fillFrom(manga)

View File

@@ -0,0 +1,18 @@
package org.koitharu.kotatsu.favourites
import org.koin.android.viewmodel.dsl.viewModel
import org.koin.dsl.module
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListViewModel
val favouritesModule
get() = module {
single { FavouritesRepository(get()) }
viewModel { (categoryId: Long) ->
FavouritesListViewModel(categoryId, get())
}
viewModel { FavouritesCategoriesViewModel(get()) }
}

View File

@@ -1,7 +1,6 @@
package org.koitharu.kotatsu.core.db.dao
package org.koitharu.kotatsu.favourites.data
import androidx.room.*
import org.koitharu.kotatsu.core.db.entity.FavouriteCategoryEntity
@Dao
abstract class FavouriteCategoriesDao {

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.core.db.entity
package org.koitharu.kotatsu.favourites.data
import androidx.room.ColumnInfo
import androidx.room.Entity

View File

@@ -1,8 +1,9 @@
package org.koitharu.kotatsu.core.db.entity
package org.koitharu.kotatsu.favourites.data
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import org.koitharu.kotatsu.core.db.entity.MangaEntity
@Entity(
tableName = "favourites", primaryKeys = ["manga_id", "category_id"], foreignKeys = [

View File

@@ -1,8 +1,11 @@
package org.koitharu.kotatsu.core.db.entity
package org.koitharu.kotatsu.favourites.data
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.MangaTagsEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
data class FavouriteManga(
@Embedded val favourite: FavouriteEntity,

View File

@@ -1,8 +1,6 @@
package org.koitharu.kotatsu.core.db.dao
package org.koitharu.kotatsu.favourites.data
import androidx.room.*
import org.koitharu.kotatsu.core.db.entity.FavouriteEntity
import org.koitharu.kotatsu.core.db.entity.FavouriteManga
import org.koitharu.kotatsu.core.db.entity.MangaEntity
@Dao

View File

@@ -1,16 +1,16 @@
package org.koitharu.kotatsu.domain.favourites
package org.koitharu.kotatsu.favourites.domain
import androidx.collection.ArraySet
import androidx.room.withTransaction
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.FavouriteCategoryEntity
import org.koitharu.kotatsu.core.db.entity.FavouriteEntity
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.utils.ext.mapToSet
class FavouritesRepository(private val db: MangaDatabase) {

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.domain.favourites
package org.koitharu.kotatsu.favourites.domain
fun interface OnFavouritesChangeListener {

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.list.favourites
package org.koitharu.kotatsu.favourites.ui
import android.os.Bundle
import android.view.Menu
@@ -8,25 +8,25 @@ import android.view.View
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayoutMediator
import kotlinx.android.synthetic.main.fragment_favourites.*
import moxy.ktx.moxyPresenter
import org.koin.android.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.domain.favourites.FavouritesRepository
import org.koitharu.kotatsu.domain.favourites.OnFavouritesChangeListener
import org.koitharu.kotatsu.ui.base.BaseFragment
import org.koitharu.kotatsu.ui.list.favourites.categories.CategoriesActivity
import org.koitharu.kotatsu.ui.list.favourites.categories.CategoriesEditDelegate
import org.koitharu.kotatsu.ui.list.favourites.categories.FavouriteCategoriesPresenter
import org.koitharu.kotatsu.ui.list.favourites.categories.FavouriteCategoriesView
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.domain.OnFavouritesChangeListener
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate
import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel
import org.koitharu.kotatsu.utils.ext.showPopupMenu
import java.util.*
import kotlin.collections.ArrayList
class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites),
FavouriteCategoriesView, OnFavouritesChangeListener, FavouritesTabLongClickListener,
OnFavouritesChangeListener, FavouritesTabLongClickListener,
CategoriesEditDelegate.CategoriesEditCallback {
private val presenter by moxyPresenter(factory = ::FavouriteCategoriesPresenter)
private val viewModel by viewModel<FavouritesCategoriesViewModel>()
private val editDelegate by lazy(LazyThreadSafetyMode.NONE) {
CategoriesEditDelegate(requireContext(), this)
}
@@ -42,6 +42,9 @@ class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites),
pager.adapter = adapter
TabLayoutMediator(tabs, pager, adapter).attach()
FavouritesRepository.subscribe(this)
viewModel.categories.observe(viewLifecycleOwner, ::onCategoriesChanged)
viewModel.onError.observe(viewLifecycleOwner, ::onError)
}
override fun onDestroyView() {
@@ -49,7 +52,7 @@ class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites),
super.onDestroyView()
}
override fun onCategoriesChanged(categories: List<FavouriteCategory>) {
fun onCategoriesChanged(categories: List<FavouriteCategory>) {
val data = ArrayList<FavouriteCategory>(categories.size + 1)
data += FavouriteCategory(0L, getString(R.string.all_favourites), -1, Date())
data += categories
@@ -75,16 +78,14 @@ class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites),
return getString(R.string.favourites)
}
override fun onCheckedCategoriesChanged(checkedIds: Set<Int>) = Unit
override fun onError(e: Throwable) {
private fun onError(e: Throwable) {
Snackbar.make(pager, e.message ?: return, Snackbar.LENGTH_LONG).show()
}
override fun onFavouritesChanged(mangaId: Long) = Unit
override fun onCategoriesChanged() {
presenter.loadAllCategories()
viewModel.loadAllCategories()
}
override fun onTabLongClick(tabView: View, category: FavouriteCategory): Boolean {
@@ -101,15 +102,15 @@ class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites),
}
override fun onDeleteCategory(category: FavouriteCategory) {
presenter.deleteCategory(category.id)
viewModel.deleteCategory(category.id)
}
override fun onRenameCategory(category: FavouriteCategory, newName: String) {
presenter.renameCategory(category.id, newName)
viewModel.renameCategory(category.id, newName)
}
override fun onCreateCategory(name: String) {
presenter.createCategory(name)
viewModel.createCategory(name)
}
companion object {

View File

@@ -1,12 +1,13 @@
package org.koitharu.kotatsu.ui.list.favourites
package org.koitharu.kotatsu.favourites.ui
import android.view.View
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.koitharu.kotatsu.base.ui.list.AdapterUpdater
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.ui.base.list.AdapterUpdater
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
import org.koitharu.kotatsu.utils.ext.replaceWith
class FavouritesPagerAdapter(

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.list.favourites
package org.koitharu.kotatsu.favourites.ui
import android.view.View
import org.koitharu.kotatsu.core.model.FavouriteCategory

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.list.favourites.categories
package org.koitharu.kotatsu.favourites.ui.categories
import android.content.Context
import android.content.Intent
@@ -12,18 +12,18 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_categories.*
import moxy.ktx.moxyPresenter
import org.koin.android.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.list.OnRecyclerItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.ui.base.BaseActivity
import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.showPopupMenu
class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener<FavouriteCategory>,
FavouriteCategoriesView, View.OnClickListener, CategoriesEditDelegate.CategoriesEditCallback {
View.OnClickListener, CategoriesEditDelegate.CategoriesEditCallback {
private val presenter by moxyPresenter(factory = ::FavouriteCategoriesPresenter)
private val viewModel by viewModel<FavouritesCategoriesViewModel>()
private lateinit var adapter: CategoriesAdapter
private lateinit var reorderHelper: ItemTouchHelper
@@ -41,6 +41,9 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener<Favourite
fab_add.setOnClickListener(this)
reorderHelper = ItemTouchHelper(ReorderHelperCallback())
reorderHelper.attachToRecyclerView(recyclerView)
viewModel.categories.observe(this, ::onCategoriesChanged)
viewModel.onError.observe(this, ::onError)
}
override fun onClick(v: View) {
@@ -66,28 +69,26 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener<Favourite
return true
}
override fun onCategoriesChanged(categories: List<FavouriteCategory>) {
private fun onCategoriesChanged(categories: List<FavouriteCategory>) {
adapter.replaceData(categories)
textView_holder.isVisible = categories.isEmpty()
}
override fun onCheckedCategoriesChanged(checkedIds: Set<Int>) = Unit
override fun onError(e: Throwable) {
private fun onError(e: Throwable) {
Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG)
.show()
}
override fun onDeleteCategory(category: FavouriteCategory) {
presenter.deleteCategory(category.id)
viewModel.deleteCategory(category.id)
}
override fun onRenameCategory(category: FavouriteCategory, newName: String) {
presenter.renameCategory(category.id, newName)
viewModel.renameCategory(category.id, newName)
}
override fun onCreateCategory(name: String) {
presenter.createCategory(name)
viewModel.createCategory(name)
}
private inner class ReorderHelperCallback : ItemTouchHelper.SimpleCallback(
@@ -102,7 +103,7 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener<Favourite
val oldPos = viewHolder.bindingAdapterPosition
val newPos = target.bindingAdapterPosition
adapter.moveItem(oldPos, newPos)
presenter.storeCategoriesOrder(adapter.items.map { it.id })
viewModel.storeCategoriesOrder(adapter.items.map { it.id })
return true
}

View File

@@ -1,13 +1,13 @@
package org.koitharu.kotatsu.ui.list.favourites.categories
package org.koitharu.kotatsu.favourites.ui.categories
import android.annotation.SuppressLint
import android.view.MotionEvent
import android.view.ViewGroup
import kotlinx.android.synthetic.main.item_category.*
import org.koitharu.kotatsu.base.ui.list.BaseRecyclerAdapter
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
import org.koitharu.kotatsu.base.ui.list.OnRecyclerItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.ui.base.list.BaseRecyclerAdapter
import org.koitharu.kotatsu.ui.base.list.BaseViewHolder
import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener
class CategoriesAdapter(private val onItemClickListener: OnRecyclerItemClickListener<FavouriteCategory>) :
BaseRecyclerAdapter<FavouriteCategory, Unit>() {

View File

@@ -1,11 +1,11 @@
package org.koitharu.kotatsu.ui.list.favourites.categories
package org.koitharu.kotatsu.favourites.ui.categories
import android.content.Context
import android.text.InputType
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.dialog.TextInputDialog
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.ui.base.dialog.TextInputDialog
class CategoriesEditDelegate(
private val context: Context,

View File

@@ -1,10 +1,10 @@
package org.koitharu.kotatsu.ui.list.favourites.categories
package org.koitharu.kotatsu.favourites.ui.categories
import android.view.ViewGroup
import kotlinx.android.synthetic.main.item_category.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.ui.base.list.BaseViewHolder
class CategoryHolder(parent: ViewGroup) :
BaseViewHolder<FavouriteCategory, Unit>(parent, R.layout.item_category) {

View File

@@ -0,0 +1,77 @@
package org.koitharu.kotatsu.favourites.ui.categories
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.Job
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.utils.ext.mapToSet
class FavouritesCategoriesViewModel(
private val repository: FavouritesRepository
) : BaseViewModel() {
private var reorderJob: Job? = null
val categories = MutableLiveData<List<FavouriteCategory>>()
val mangaCategories = MutableLiveData<Set<Int>>()
init {
loadAllCategories()
}
fun loadAllCategories() {
launchJob {
categories.value = repository.getAllCategories()
}
}
fun loadMangaCategories(manga: Manga) {
launchJob {
val categories = repository.getCategories(manga.id)
mangaCategories.value = categories.mapToSet { it.id.toInt() }
}
}
fun createCategory(name: String) {
launchJob {
repository.addCategory(name)
categories.value = repository.getAllCategories()
}
}
fun renameCategory(id: Long, name: String) {
launchJob {
repository.renameCategory(id, name)
categories.value = repository.getAllCategories()
}
}
fun deleteCategory(id: Long) {
launchJob {
repository.removeCategory(id)
categories.value = repository.getAllCategories()
}
}
fun storeCategoriesOrder(orderedIds: List<Long>) {
val prevJob = reorderJob
reorderJob = launchJob {
prevJob?.join()
repository.reorderCategories(orderedIds)
}
}
fun addToCategory(manga: Manga, categoryId: Long) {
launchJob {
repository.addToCategory(manga, categoryId)
}
}
fun removeFromCategory(manga: Manga, categoryId: Long) {
launchJob {
repository.removeFromCategory(manga, categoryId)
}
}
}

View File

@@ -1,12 +1,12 @@
package org.koitharu.kotatsu.ui.list.favourites.categories.select
package org.koitharu.kotatsu.favourites.ui.categories.select
import android.util.SparseBooleanArray
import android.view.ViewGroup
import android.widget.Checkable
import androidx.core.util.set
import org.koitharu.kotatsu.base.ui.list.BaseRecyclerAdapter
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.ui.base.list.BaseRecyclerAdapter
import org.koitharu.kotatsu.ui.base.list.BaseViewHolder
class CategoriesSelectAdapter(private val listener: OnCategoryCheckListener) :
BaseRecyclerAdapter<FavouriteCategory, Boolean>() {

View File

@@ -1,10 +1,10 @@
package org.koitharu.kotatsu.ui.list.favourites.categories.select
package org.koitharu.kotatsu.favourites.ui.categories.select
import android.view.ViewGroup
import kotlinx.android.synthetic.main.item_category_checkable.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.ui.base.list.BaseViewHolder
class CategoryCheckableHolder(parent: ViewGroup) :
BaseViewHolder<FavouriteCategory, Boolean>(parent, R.layout.item_category_checkable) {

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.list.favourites.categories.select
package org.koitharu.kotatsu.favourites.ui.categories.select
import android.os.Bundle
import android.text.InputType
@@ -6,22 +6,20 @@ import android.view.View
import android.widget.Toast
import androidx.fragment.app.FragmentManager
import kotlinx.android.synthetic.main.dialog_favorite_categories.*
import moxy.ktx.moxyPresenter
import org.koin.android.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
import org.koitharu.kotatsu.base.ui.dialog.TextInputDialog
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.ui.base.BaseBottomSheet
import org.koitharu.kotatsu.ui.base.dialog.TextInputDialog
import org.koitharu.kotatsu.ui.list.favourites.categories.FavouriteCategoriesPresenter
import org.koitharu.kotatsu.ui.list.favourites.categories.FavouriteCategoriesView
import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.withArgs
class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categories),
FavouriteCategoriesView,
OnCategoryCheckListener {
private val presenter by moxyPresenter(factory = ::FavouriteCategoriesPresenter)
private val viewModel by viewModel<FavouritesCategoriesViewModel>()
private val manga get() = arguments?.getParcelable<Manga>(ARG_MANGA)
@@ -38,8 +36,12 @@ class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categ
createCategory()
}
manga?.let {
presenter.loadMangaCategories(it)
viewModel.loadMangaCategories(it)
}
viewModel.categories.observe(viewLifecycleOwner, ::onCategoriesChanged)
viewModel.mangaCategories.observe(viewLifecycleOwner, ::onCheckedCategoriesChanged)
viewModel.onError.observe(viewLifecycleOwner, ::onError)
}
override fun onDestroyView() {
@@ -47,23 +49,23 @@ class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categ
super.onDestroyView()
}
override fun onCategoriesChanged(categories: List<FavouriteCategory>) {
private fun onCategoriesChanged(categories: List<FavouriteCategory>) {
adapter?.replaceData(categories)
}
override fun onCheckedCategoriesChanged(checkedIds: Set<Int>) {
private fun onCheckedCategoriesChanged(checkedIds: Set<Int>) {
adapter?.setCheckedIds(checkedIds)
}
override fun onCategoryChecked(category: FavouriteCategory) {
presenter.addToCategory(manga ?: return, category.id)
viewModel.addToCategory(manga ?: return, category.id)
}
override fun onCategoryUnchecked(category: FavouriteCategory) {
presenter.removeFromCategory(manga ?: return, category.id)
viewModel.removeFromCategory(manga ?: return, category.id)
}
override fun onError(e: Throwable) {
private fun onError(e: Throwable) {
Toast.makeText(context ?: return, e.getDisplayMessage(resources), Toast.LENGTH_SHORT).show()
}
@@ -75,7 +77,7 @@ class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categ
.setInputType(InputType.TYPE_TEXT_VARIATION_PERSON_NAME or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES)
.setNegativeButton(android.R.string.cancel)
.setPositiveButton(R.string.add) { _, name ->
presenter.createCategory(name)
viewModel.createCategory(name)
}.create()
.show()
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.list.favourites.categories.select
package org.koitharu.kotatsu.favourites.ui.categories.select
import org.koitharu.kotatsu.core.model.FavouriteCategory

View File

@@ -1,28 +1,27 @@
package org.koitharu.kotatsu.ui.list.favourites
package org.koitharu.kotatsu.favourites.ui.list
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import kotlinx.android.synthetic.main.fragment_list.*
import moxy.ktx.moxyPresenter
import org.koin.android.ext.android.get
import org.koin.android.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.ui.list.MangaListFragment
import org.koitharu.kotatsu.ui.list.MangaListView
import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.utils.ext.withArgs
class FavouritesListFragment : MangaListFragment<Unit>(), MangaListView<Unit> {
class FavouritesListFragment : MangaListFragment() {
private val presenter by moxyPresenter {
FavouritesListPresenter(categoryId, get())
override val viewModel by viewModel<FavouritesListViewModel> {
parametersOf(categoryId)
}
private val categoryId: Long
get() = arguments?.getLong(ARG_CATEGORY_ID) ?: 0L
override fun onRequestMoreItems(offset: Int) {
presenter.loadList(offset)
viewModel.loadList(offset)
}
override fun setUpEmptyListHolder() {
@@ -41,9 +40,9 @@ class FavouritesListFragment : MangaListFragment<Unit>(), MangaListView<Unit> {
inflater.inflate(R.menu.popup_favourites, menu)
}
override fun onPopupMenuItemSelected(item: MenuItem, data: Manga) = when(item.itemId) {
override fun onPopupMenuItemSelected(item: MenuItem, data: Manga) = when (item.itemId) {
R.id.action_remove -> {
presenter.removeFromFavourites(data)
viewModel.removeFromFavourites(data)
true
}
else -> super.onPopupMenuItemSelected(item, data)

Some files were not shown because too many files have changed in this diff Show More