Migrate to MVVM

pull/26/head
Koitharu 6 years ago
parent eaac271143
commit 7d24286c55

@ -84,17 +84,12 @@ dependencies {
implementation 'androidx.room:room-ktx:2.2.5' implementation 'androidx.room:room-ktx:2.2.5'
kapt 'androidx.room:room-compiler: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.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.okio:okio:2.9.0' implementation 'com.squareup.okio:okio:2.9.0'
implementation 'org.jsoup:jsoup:1.13.1' implementation 'org.jsoup:jsoup:1.13.1'
implementation 'org.koin:koin-android:2.2.0' 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 'io.coil-kt:coil-base:1.0.0'
implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0' implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0'
implementation 'com.tomclaw.cache:cache:1.0' implementation 'com.tomclaw.cache:cache:1.0'

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

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

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

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

@ -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.KoinComponent
import org.koin.core.component.get import org.koin.core.component.get

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

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

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

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

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

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

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

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

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

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

@ -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.Context
import android.content.DialogInterface import android.content.DialogInterface
@ -10,7 +10,7 @@ import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.android.synthetic.main.item_storage.view.* import kotlinx.android.synthetic.main.item_storage.view.*
import org.koitharu.kotatsu.R 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.getStorageName
import org.koitharu.kotatsu.utils.ext.inflate import org.koitharu.kotatsu.utils.ext.inflate
import org.koitharu.kotatsu.utils.ext.longHashCode import org.koitharu.kotatsu.utils.ext.longHashCode

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

@ -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.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView

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

@ -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.View
import android.view.ViewGroup import android.view.ViewGroup

@ -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.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView

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

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

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

@ -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.View
import android.view.ViewGroup import android.view.ViewGroup

@ -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.content.Context
import android.graphics.Canvas import android.graphics.Canvas

@ -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.Canvas
import android.graphics.Rect import android.graphics.Rect

@ -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.graphics.Rect
import android.view.View import android.view.View

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

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

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

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

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

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

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

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

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

@ -4,7 +4,11 @@ import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.db.MangaDatabase 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) { class BackupRepository(private val db: MangaDatabase) {

@ -3,7 +3,11 @@ package org.koitharu.kotatsu.core.backup
import androidx.room.withTransaction import androidx.room.withTransaction
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.core.db.MangaDatabase 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.getStringOrNull
import org.koitharu.kotatsu.utils.ext.iterator import org.koitharu.kotatsu.utils.ext.iterator
import org.koitharu.kotatsu.utils.ext.map import org.koitharu.kotatsu.utils.ext.map

@ -4,6 +4,12 @@ import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import org.koitharu.kotatsu.core.db.dao.* import org.koitharu.kotatsu.core.db.dao.*
import org.koitharu.kotatsu.core.db.entity.* 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( @Database(
entities = [ entities = [

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

@ -1,3 +1,4 @@
package org.koitharu.kotatsu.core.exceptions 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)

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

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

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

@ -6,13 +6,18 @@ interface MangaRepository {
val sortOrders: Set<SortOrder> 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> suspend fun getTags(): Set<MangaTag>
} }

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

@ -1,10 +1,10 @@
package org.koitharu.kotatsu.core.parser 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.MangaPage
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.domain.MangaLoaderContext
abstract class RemoteMangaRepository( abstract class RemoteMangaRepository(
protected val loaderContext: MangaLoaderContext protected val loaderContext: MangaLoaderContext

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

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

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

@ -1,8 +1,8 @@
package org.koitharu.kotatsu.core.parser.site 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.exceptions.ParseException
import org.koitharu.kotatsu.core.model.* 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.longHashCode
import org.koitharu.kotatsu.utils.ext.mapToSet import org.koitharu.kotatsu.utils.ext.mapToSet
import org.koitharu.kotatsu.utils.ext.parseHtml import org.koitharu.kotatsu.utils.ext.parseHtml

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.parser.site 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.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class MangaChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(loaderContext) { class MangaChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(loaderContext) {

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

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

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

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.parser.site 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.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class MintMangaRepository(loaderContext: MangaLoaderContext) : GroupleRepository(loaderContext) { class MintMangaRepository(loaderContext: MangaLoaderContext) : GroupleRepository(loaderContext) {

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

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.parser.site 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.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class ReadmangaRepository(loaderContext: MangaLoaderContext) : GroupleRepository(loaderContext) { class ReadmangaRepository(loaderContext: MangaLoaderContext) : GroupleRepository(loaderContext) {

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.parser.site 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.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class SelfMangaRepository(loaderContext: MangaLoaderContext) : GroupleRepository(loaderContext) { class SelfMangaRepository(loaderContext: MangaLoaderContext) : GroupleRepository(loaderContext) {

@ -1,10 +1,10 @@
package org.koitharu.kotatsu.core.parser.site 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.exceptions.ParseException
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.core.model.MangaSource 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.longHashCode
import org.koitharu.kotatsu.utils.ext.parseHtml import org.koitharu.kotatsu.utils.ext.parseHtml
import org.koitharu.kotatsu.utils.ext.withDomain import org.koitharu.kotatsu.utils.ext.withDomain

@ -8,7 +8,7 @@ import androidx.collection.arraySetOf
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import org.koitharu.kotatsu.core.model.ZoomMode 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 org.koitharu.kotatsu.utils.delegates.prefs.*
import java.io.File import java.io.File

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

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

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

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

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

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

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

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

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

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

@ -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.text.Spanned
import android.view.View import android.view.View
import androidx.core.net.toUri import androidx.core.net.toUri
@ -10,32 +11,36 @@ import kotlinx.android.synthetic.main.fragment_details.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext 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.R
import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.ui.base.BaseFragment import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesDialog
import org.koitharu.kotatsu.ui.list.favourites.categories.select.FavouriteCategoriesDialog import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.ui.reader.ReaderActivity import org.koitharu.kotatsu.search.ui.MangaSearchSheet
import org.koitharu.kotatsu.ui.search.MangaSearchSheet
import org.koitharu.kotatsu.utils.FileSizeUtils import org.koitharu.kotatsu.utils.FileSizeUtils
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
import kotlin.math.roundToInt import kotlin.math.roundToInt
class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetailsView, class DetailsFragment : BaseFragment(R.layout.fragment_details), View.OnClickListener,
View.OnClickListener,
View.OnLongClickListener { View.OnLongClickListener {
@Suppress("unused") private val viewModel by sharedViewModel<DetailsViewModel>()
private val presenter by moxyPresenter {
MangaDetailsPresenter.getInstance(activity.hashCode())
}
private var manga: Manga? = null private var manga: Manga? = null
private var history: MangaHistory? = 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 this.manga = manga
imageView_cover.newImageRequest(manga.largeCoverUrl ?: manga.coverUrl) imageView_cover.newImageRequest(manga.largeCoverUrl ?: manga.coverUrl)
.fallback(R.drawable.ic_placeholder) .fallback(R.drawable.ic_placeholder)
@ -59,7 +64,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
text = it, text = it,
iconRes = R.drawable.ic_chip_user, iconRes = R.drawable.ic_chip_user,
tag = it, tag = it,
onClickListener = this@MangaDetailsFragment onClickListener = this@DetailsFragment
) )
} }
} }
@ -68,7 +73,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
text = it.title, text = it.title,
iconRes = R.drawable.ic_chip_tag, iconRes = R.drawable.ic_chip_tag,
tag = it, tag = it,
onClickListener = this@MangaDetailsFragment onClickListener = this@DetailsFragment
) )
} }
manga.url.toUri().toFileOrNull()?.let { f -> manga.url.toUri().toFileOrNull()?.let { f ->
@ -81,7 +86,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
text = FileSizeUtils.formatBytes(context, size), text = FileSizeUtils.formatBytes(context, size),
iconRes = R.drawable.ic_chip_storage, iconRes = R.drawable.ic_chip_storage,
tag = it, tag = it,
onClickListener = this@MangaDetailsFragment onClickListener = this@DetailsFragment
) )
} }
} }
@ -92,12 +97,12 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
updateReadButton() updateReadButton()
} }
override fun onHistoryChanged(history: MangaHistory?) { private fun onHistoryChanged(history: MangaHistory?) {
this.history = history this.history = history
updateReadButton() updateReadButton()
} }
override fun onFavouriteChanged(categories: List<FavouriteCategory>) { private fun onFavouriteChanged(categories: List<FavouriteCategory>) {
imageView_favourite.setImageResource( imageView_favourite.setImageResource(
if (categories.isEmpty()) { if (categories.isEmpty()) {
R.drawable.ic_heart_outline 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 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) { override fun onClick(v: View) {
when { when {
v.id == R.id.imageView_favourite -> { v.id == R.id.imageView_favourite -> {

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

@ -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.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
@ -8,8 +8,8 @@ class MangaDetailsAdapter(activity: FragmentActivity) : FragmentStateAdapter(act
override fun getItemCount() = 3 override fun getItemCount() = 3
override fun createFragment(position: Int): Fragment = when(position) { override fun createFragment(position: Int): Fragment = when (position) {
0 -> MangaDetailsFragment() 0 -> DetailsFragment()
1 -> ChaptersFragment() 1 -> ChaptersFragment()
2 -> RelatedMangaFragment() 2 -> RelatedMangaFragment()
else -> throw IndexOutOfBoundsException("No fragment for position $position") else -> throw IndexOutOfBoundsException("No fragment for position $position")

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

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

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

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

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

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

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

@ -1,8 +1,11 @@
package org.koitharu.kotatsu.core.db.entity package org.koitharu.kotatsu.favourites.data
import androidx.room.Embedded import androidx.room.Embedded
import androidx.room.Junction import androidx.room.Junction
import androidx.room.Relation 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( data class FavouriteManga(
@Embedded val favourite: FavouriteEntity, @Embedded val favourite: FavouriteEntity,

@ -1,8 +1,6 @@
package org.koitharu.kotatsu.core.db.dao package org.koitharu.kotatsu.favourites.data
import androidx.room.* 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 import org.koitharu.kotatsu.core.db.entity.MangaEntity
@Dao @Dao

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

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

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

@ -1,12 +1,13 @@
package org.koitharu.kotatsu.ui.list.favourites package org.koitharu.kotatsu.favourites.ui
import android.view.View import android.view.View
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator 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.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 import org.koitharu.kotatsu.utils.ext.replaceWith
class FavouritesPagerAdapter( class FavouritesPagerAdapter(

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

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

@ -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.annotation.SuppressLint
import android.view.MotionEvent import android.view.MotionEvent
import android.view.ViewGroup import android.view.ViewGroup
import kotlinx.android.synthetic.main.item_category.* 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.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>) : class CategoriesAdapter(private val onItemClickListener: OnRecyclerItemClickListener<FavouriteCategory>) :
BaseRecyclerAdapter<FavouriteCategory, Unit>() { BaseRecyclerAdapter<FavouriteCategory, Unit>() {

@ -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.content.Context
import android.text.InputType import android.text.InputType
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.dialog.TextInputDialog
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.ui.base.dialog.TextInputDialog
class CategoriesEditDelegate( class CategoriesEditDelegate(
private val context: Context, private val context: Context,

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

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

@ -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.util.SparseBooleanArray
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Checkable import android.widget.Checkable
import androidx.core.util.set 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.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) : class CategoriesSelectAdapter(private val listener: OnCategoryCheckListener) :
BaseRecyclerAdapter<FavouriteCategory, Boolean>() { BaseRecyclerAdapter<FavouriteCategory, Boolean>() {

@ -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 android.view.ViewGroup
import kotlinx.android.synthetic.main.item_category_checkable.* import kotlinx.android.synthetic.main.item_category_checkable.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.ui.base.list.BaseViewHolder
class CategoryCheckableHolder(parent: ViewGroup) : class CategoryCheckableHolder(parent: ViewGroup) :
BaseViewHolder<FavouriteCategory, Boolean>(parent, R.layout.item_category_checkable) { BaseViewHolder<FavouriteCategory, Boolean>(parent, R.layout.item_category_checkable) {

@ -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.os.Bundle
import android.text.InputType import android.text.InputType
@ -6,22 +6,20 @@ import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import kotlinx.android.synthetic.main.dialog_favorite_categories.* 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.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.FavouriteCategory
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.ui.base.BaseBottomSheet import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel
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.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categories), class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categories),
FavouriteCategoriesView,
OnCategoryCheckListener { OnCategoryCheckListener {
private val presenter by moxyPresenter(factory = ::FavouriteCategoriesPresenter) private val viewModel by viewModel<FavouritesCategoriesViewModel>()
private val manga get() = arguments?.getParcelable<Manga>(ARG_MANGA) private val manga get() = arguments?.getParcelable<Manga>(ARG_MANGA)
@ -38,8 +36,12 @@ class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categ
createCategory() createCategory()
} }
manga?.let { 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() { override fun onDestroyView() {
@ -47,23 +49,23 @@ class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categ
super.onDestroyView() super.onDestroyView()
} }
override fun onCategoriesChanged(categories: List<FavouriteCategory>) { private fun onCategoriesChanged(categories: List<FavouriteCategory>) {
adapter?.replaceData(categories) adapter?.replaceData(categories)
} }
override fun onCheckedCategoriesChanged(checkedIds: Set<Int>) { private fun onCheckedCategoriesChanged(checkedIds: Set<Int>) {
adapter?.setCheckedIds(checkedIds) adapter?.setCheckedIds(checkedIds)
} }
override fun onCategoryChecked(category: FavouriteCategory) { override fun onCategoryChecked(category: FavouriteCategory) {
presenter.addToCategory(manga ?: return, category.id) viewModel.addToCategory(manga ?: return, category.id)
} }
override fun onCategoryUnchecked(category: FavouriteCategory) { 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() 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) .setInputType(InputType.TYPE_TEXT_VARIATION_PERSON_NAME or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES)
.setNegativeButton(android.R.string.cancel) .setNegativeButton(android.R.string.cancel)
.setPositiveButton(R.string.add) { _, name -> .setPositiveButton(R.string.add) { _, name ->
presenter.createCategory(name) viewModel.createCategory(name)
}.create() }.create()
.show() .show()
} }

@ -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 import org.koitharu.kotatsu.core.model.FavouriteCategory

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

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

Loading…
Cancel
Save