diff --git a/.travis.yml b/.travis.yml index 134c73e58..714819fb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,9 @@ language: android dist: trusty -jdk: - - oraclejdk8 android: components: - - tools - - platform-tools-30.0.3 - - build-tools-30.0.2 - android-30 - licenses: - - android-sdk-preview-license-.+ - - android-sdk-license-.+ - - google-gdk-license-.+ + - build-tools-30.0.2 + - platform-tools-30.0.3 + - tools script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index fa799eefe..1daeefc60 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,6 +3,7 @@ plugins { id 'kotlin-android' id 'kotlin-android-extensions' id 'kotlin-kapt' + // TODO id 'kotlin-parcelize' } def gitCommits = 'git rev-list --count HEAD'.execute([], rootDir).text.trim().toInteger() @@ -70,6 +71,7 @@ dependencies { implementation 'androidx.activity:activity-ktx:1.2.0-beta01' implementation 'androidx.fragment:fragment-ktx:1.3.0-beta01' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-beta01' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0-beta01' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha06' @@ -84,24 +86,22 @@ dependencies { implementation 'androidx.room:room-ktx:2.2.5' kapt 'androidx.room:room-compiler:2.2.5' - implementation 'com.github.moxy-community:moxy:2.2.0' - implementation 'com.github.moxy-community:moxy-androidx:2.2.0' - implementation 'com.github.moxy-community:moxy-material:2.2.0' - implementation 'com.github.moxy-community:moxy-ktx:2.2.0' - kapt 'com.github.moxy-community:moxy-compiler:2.2.0' - implementation 'com.squareup.okhttp3:okhttp:4.9.0' implementation 'com.squareup.okio:okio:2.9.0' implementation 'org.jsoup:jsoup:1.13.1' + implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.0' + implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-layoutcontainer:4.3.0' + implementation 'org.koin:koin-android:2.2.0' - implementation 'io.coil-kt:coil-base:1.0.0' + implementation 'org.koin:koin-android-viewmodel:2.2.0' + implementation 'io.coil-kt:coil-base:1.1.0' implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0' implementation 'com.tomclaw.cache:cache:1.0' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5' testImplementation 'junit:junit:4.13.1' - testImplementation 'org.json:json:20200518' + testImplementation 'org.json:json:20201115' testImplementation 'org.koin:koin-test:2.2.0-rc-2' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1846957cd..9ed804995 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,7 +23,7 @@ android:theme="@style/AppTheme" android:usesCleartextTraffic="true" tools:ignore="UnusedAttribute"> - + @@ -32,62 +32,62 @@ android:name="android.app.default_searchable" android:value=".ui.search.SearchActivity" /> - + - + - + @@ -111,7 +111,7 @@ android:resource="@xml/widget_shelf" /> diff --git a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt index e8a4f29b4..63a8692b7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt +++ b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt @@ -6,25 +6,30 @@ import androidx.appcompat.app.AppCompatDelegate import org.koin.android.ext.android.get import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin -import org.koin.dsl.module -import org.koitharu.kotatsu.core.backup.BackupRepository -import org.koitharu.kotatsu.core.backup.RestoreRepository +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.db.databaseModule import org.koitharu.kotatsu.core.github.githubModule -import org.koitharu.kotatsu.core.local.PagesCache import org.koitharu.kotatsu.core.network.networkModule -import org.koitharu.kotatsu.core.parser.LocalMangaRepository import org.koitharu.kotatsu.core.parser.parserModule import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.domain.MangaDataRepository -import org.koitharu.kotatsu.domain.MangaLoaderContext -import org.koitharu.kotatsu.domain.MangaSearchRepository -import org.koitharu.kotatsu.domain.favourites.FavouritesRepository -import org.koitharu.kotatsu.domain.history.HistoryRepository -import org.koitharu.kotatsu.domain.tracking.TrackingRepository -import org.koitharu.kotatsu.ui.base.uiModule -import org.koitharu.kotatsu.ui.utils.AppCrashHandler -import org.koitharu.kotatsu.ui.widget.WidgetUpdater +import org.koitharu.kotatsu.core.ui.AppCrashHandler +import org.koitharu.kotatsu.core.ui.uiModule +import org.koitharu.kotatsu.details.detailsModule +import org.koitharu.kotatsu.favourites.domain.FavouritesRepository +import org.koitharu.kotatsu.favourites.favouritesModule +import org.koitharu.kotatsu.history.domain.HistoryRepository +import org.koitharu.kotatsu.history.historyModule +import org.koitharu.kotatsu.local.data.PagesCache +import org.koitharu.kotatsu.local.domain.LocalMangaRepository +import org.koitharu.kotatsu.local.localModule +import org.koitharu.kotatsu.main.mainModule +import org.koitharu.kotatsu.reader.readerModule +import org.koitharu.kotatsu.remotelist.remoteListModule +import org.koitharu.kotatsu.search.searchModule +import org.koitharu.kotatsu.settings.settingsModule +import org.koitharu.kotatsu.tracker.trackerModule +import org.koitharu.kotatsu.widget.WidgetUpdater +import org.koitharu.kotatsu.widget.appWidgetModule class KotatsuApp : Application() { @@ -62,20 +67,19 @@ class KotatsuApp : Application() { networkModule, databaseModule, githubModule, - parserModule, uiModule, - module { - single { FavouritesRepository(get()) } - single { HistoryRepository(get()) } - single { TrackingRepository(get(), get()) } - single { MangaDataRepository(get()) } - single { BackupRepository(get()) } - single { RestoreRepository(get()) } - single { MangaSearchRepository() } - single { MangaLoaderContext() } - single { AppSettings(get()) } - single { PagesCache(get()) } - } + parserModule, + mainModule, + searchModule, + localModule, + favouritesModule, + historyModule, + remoteListModule, + detailsModule, + trackerModule, + settingsModule, + readerModule, + appWidgetModule ) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/MangaDataRepository.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt similarity index 81% rename from app/src/main/java/org/koitharu/kotatsu/domain/MangaDataRepository.kt rename to app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt index 5be65ce1c..06f22090a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/MangaDataRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.domain +package org.koitharu.kotatsu.base.domain import androidx.room.withTransaction import org.koitharu.kotatsu.core.db.MangaDatabase @@ -32,6 +32,12 @@ class MangaDataRepository(private val db: MangaDatabase) { return db.mangaDao.find(mangaId)?.toManga() } + suspend fun resolveIntent(intent: MangaIntent): Manga? = when { + intent.manga != null -> intent.manga + intent.mangaId != MangaIntent.ID_NONE -> db.mangaDao.find(intent.mangaId)?.toManga() + else -> null // TODO resolve uri + } + suspend fun storeManga(manga: Manga) { val tags = manga.tags.map(TagEntity.Companion::fromMangaTag) db.withTransaction { diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaIntent.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaIntent.kt new file mode 100644 index 000000000..98b6522b5 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaIntent.kt @@ -0,0 +1,33 @@ +package org.koitharu.kotatsu.base.domain + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import org.koitharu.kotatsu.core.model.Manga + +data class MangaIntent( + val manga: Manga?, + val mangaId: Long, + val uri: Uri? +) { + + companion object { + + fun from(intent: Intent?) = MangaIntent( + manga = intent?.getParcelableExtra(KEY_MANGA), + mangaId = intent?.getLongExtra(KEY_ID, ID_NONE) ?: ID_NONE, + uri = intent?.data + ) + + fun from(args: Bundle?) = MangaIntent( + manga = args?.getParcelable(KEY_MANGA), + mangaId = args?.getLong(KEY_ID, ID_NONE) ?: ID_NONE, + uri = null + ) + + const val ID_NONE = 0L + + const val KEY_MANGA = "manga" + const val KEY_ID = "id" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/MangaLoaderContext.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaLoaderContext.kt similarity index 97% rename from app/src/main/java/org/koitharu/kotatsu/domain/MangaLoaderContext.kt rename to app/src/main/java/org/koitharu/kotatsu/base/domain/MangaLoaderContext.kt index c2c884026..45f561580 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/MangaLoaderContext.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaLoaderContext.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.domain +package org.koitharu.kotatsu.base.domain import okhttp3.* import org.koin.core.component.KoinComponent diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/MangaProviderFactory.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaProviderFactory.kt similarity index 94% rename from app/src/main/java/org/koitharu/kotatsu/domain/MangaProviderFactory.kt rename to app/src/main/java/org/koitharu/kotatsu/base/domain/MangaProviderFactory.kt index bf0ce25fb..8498cc190 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/MangaProviderFactory.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaProviderFactory.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.domain +package org.koitharu.kotatsu.base.domain import org.koin.core.component.KoinComponent import org.koin.core.component.get diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/MangaUtils.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt similarity index 97% rename from app/src/main/java/org/koitharu/kotatsu/domain/MangaUtils.kt rename to app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt index cf581dc41..cce8023f2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/MangaUtils.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.domain +package org.koitharu.kotatsu.base.domain import android.graphics.BitmapFactory import android.net.Uri diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/AlertDialogFragment.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/AlertDialogFragment.kt similarity index 88% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/AlertDialogFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/AlertDialogFragment.kt index 128800038..08f80ed88 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/AlertDialogFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/AlertDialogFragment.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base +package org.koitharu.kotatsu.base.ui import android.app.Dialog import android.os.Bundle @@ -6,11 +6,11 @@ import android.view.View import androidx.annotation.CallSuper import androidx.annotation.LayoutRes import androidx.appcompat.app.AlertDialog -import moxy.MvpAppCompatDialogFragment +import androidx.fragment.app.DialogFragment abstract class AlertDialogFragment( @LayoutRes private val layoutResId: Int -) : MvpAppCompatDialogFragment() { +) : DialogFragment() { private var rootView: View? = null diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/BaseActivity.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt similarity index 86% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/BaseActivity.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt index eb04d30c8..16710be54 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/BaseActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt @@ -1,15 +1,15 @@ -package org.koitharu.kotatsu.ui.base +package org.koitharu.kotatsu.base.ui import android.os.Bundle import android.view.MenuItem import android.view.View +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar -import moxy.MvpAppCompatActivity import org.koin.android.ext.android.get import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.prefs.AppSettings -abstract class BaseActivity : MvpAppCompatActivity() { +abstract class BaseActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { if (get().isAmoledTheme) { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/BaseBottomSheet.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseBottomSheet.kt similarity index 83% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/BaseBottomSheet.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/BaseBottomSheet.kt index 4faa58305..81eaeb5b7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/BaseBottomSheet.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseBottomSheet.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base +package org.koitharu.kotatsu.base.ui import android.app.Dialog import android.os.Bundle @@ -7,11 +7,11 @@ import android.view.View import android.view.ViewGroup import androidx.annotation.LayoutRes import androidx.appcompat.app.AppCompatDialog -import moxy.MvpBottomSheetDialogFragment +import com.google.android.material.bottomsheet.BottomSheetDialogFragment import org.koitharu.kotatsu.utils.UiUtils abstract class BaseBottomSheet(@LayoutRes private val layoutResId: Int) : - MvpBottomSheetDialogFragment() { + BottomSheetDialogFragment() { final override fun onCreateView( inflater: LayoutInflater, diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/BaseFragment.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt similarity index 78% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/BaseFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt index 9ca17d71f..398f85cf8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/BaseFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt @@ -1,14 +1,14 @@ -package org.koitharu.kotatsu.ui.base +package org.koitharu.kotatsu.base.ui import android.content.Context import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment import coil.ImageLoader -import moxy.MvpAppCompatFragment import org.koin.android.ext.android.inject abstract class BaseFragment( @LayoutRes contentLayoutId: Int -) : MvpAppCompatFragment(contentLayoutId) { +) : Fragment(contentLayoutId) { protected val coil by inject() diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/BaseFullscreenActivity.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFullscreenActivity.kt similarity index 97% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/BaseFullscreenActivity.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFullscreenActivity.kt index 5528576ea..481e54318 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/BaseFullscreenActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFullscreenActivity.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base +package org.koitharu.kotatsu.base.ui import android.graphics.Color import android.os.Build diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/BasePreferenceFragment.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BasePreferenceFragment.kt similarity index 91% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/BasePreferenceFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/BasePreferenceFragment.kt index 81f787f4f..9f02475fd 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/BasePreferenceFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BasePreferenceFragment.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base +package org.koitharu.kotatsu.base.ui import androidx.annotation.StringRes import androidx.preference.PreferenceFragmentCompat diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseService.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseService.kt new file mode 100644 index 000000000..05e07729e --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseService.kt @@ -0,0 +1,5 @@ +package org.koitharu.kotatsu.base.ui + +import androidx.lifecycle.LifecycleService + +abstract class BaseService : LifecycleService() \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/BasePresenter.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseViewModel.kt similarity index 54% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/BasePresenter.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/BaseViewModel.kt index a267efd03..b3df5277a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/BasePresenter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseViewModel.kt @@ -1,35 +1,35 @@ -package org.koitharu.kotatsu.ui.base +package org.koitharu.kotatsu.base.ui +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import kotlinx.coroutines.* -import moxy.MvpPresenter -import moxy.presenterScope -import org.koin.core.component.KoinComponent import org.koitharu.kotatsu.BuildConfig +import org.koitharu.kotatsu.utils.SingleLiveEvent import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -abstract class BasePresenter : MvpPresenter(), KoinComponent { +abstract class BaseViewModel : ViewModel() { + + val onError = SingleLiveEvent() + val isLoading = MutableLiveData(false) protected fun launchJob( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit - ) { - presenterScope.launch(context + createErrorHandler(), start, block) - } + ): Job = viewModelScope.launch(context + createErrorHandler(), start, block) protected fun launchLoadingJob( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit - ) { - presenterScope.launch(context + createErrorHandler(), start) { - viewState.onLoadingStateChanged(isLoading = true) - try { - block() - } finally { - viewState.onLoadingStateChanged(isLoading = false) - } + ): Job = viewModelScope.launch(context + createErrorHandler(), start) { + isLoading.postValue(true) + try { + block() + } finally { + isLoading.postValue(false) } } @@ -38,7 +38,7 @@ abstract class BasePresenter : MvpPresenter(), KoinComponent throwable.printStackTrace() } if (throwable !is CancellationException) { - viewState.onError(throwable) + onError.postCall(throwable) } } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/dialog/CheckBoxAlertDialog.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/CheckBoxAlertDialog.kt similarity index 91% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/dialog/CheckBoxAlertDialog.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/CheckBoxAlertDialog.kt index 3a56aed74..ec9ad0567 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/dialog/CheckBoxAlertDialog.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/CheckBoxAlertDialog.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base.dialog +package org.koitharu.kotatsu.base.ui.dialog import android.annotation.SuppressLint import android.content.Context @@ -8,7 +8,6 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import com.google.android.material.checkbox.MaterialCheckBox -import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.koitharu.kotatsu.R class CheckBoxAlertDialog private constructor(private val delegate: AlertDialog) : @@ -23,7 +22,7 @@ class CheckBoxAlertDialog private constructor(private val delegate: AlertDialog) .inflate(R.layout.dialog_checkbox, null, false) private val checkBox = view.findViewById(android.R.id.checkbox) - private val delegate = MaterialAlertDialogBuilder(context) + private val delegate = AlertDialog.Builder(context) .setView(view) fun setTitle(@StringRes titleResId: Int): Builder { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/dialog/StorageSelectDialog.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/StorageSelectDialog.kt similarity index 91% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/dialog/StorageSelectDialog.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/StorageSelectDialog.kt index bfcc37f58..ae66acca8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/dialog/StorageSelectDialog.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/StorageSelectDialog.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base.dialog +package org.koitharu.kotatsu.base.ui.dialog import android.content.Context import android.content.DialogInterface @@ -7,10 +7,9 @@ import android.view.ViewGroup import android.widget.BaseAdapter import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog -import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.android.synthetic.main.item_storage.view.* import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.parser.LocalMangaRepository +import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.utils.ext.getStorageName import org.koitharu.kotatsu.utils.ext.inflate import org.koitharu.kotatsu.utils.ext.longHashCode @@ -24,7 +23,7 @@ class StorageSelectDialog private constructor(private val delegate: AlertDialog) class Builder(context: Context, defaultValue: File?, listener: OnStorageSelectListener) { private val adapter = VolumesAdapter(context) - private val delegate = MaterialAlertDialogBuilder(context) + private val delegate = AlertDialog.Builder(context) init { if (adapter.isEmpty) { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/dialog/TextInputDialog.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/TextInputDialog.kt similarity index 92% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/dialog/TextInputDialog.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/TextInputDialog.kt index f78ef32c1..159c52786 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/dialog/TextInputDialog.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/TextInputDialog.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base.dialog +package org.koitharu.kotatsu.base.ui.dialog import android.annotation.SuppressLint import android.content.Context @@ -7,7 +7,6 @@ import android.text.InputFilter import android.view.LayoutInflater import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog -import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.android.synthetic.main.dialog_input.view.* import org.koitharu.kotatsu.R @@ -23,7 +22,7 @@ class TextInputDialog private constructor( private val view = LayoutInflater.from(context) .inflate(R.layout.dialog_input, null, false) - private val delegate = MaterialAlertDialogBuilder(context) + private val delegate = AlertDialog.Builder(context) .setView(view) fun setTitle(@StringRes titleResId: Int): Builder { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/AdapterUpdater.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/AdapterUpdater.kt similarity index 92% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/list/AdapterUpdater.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/list/AdapterUpdater.kt index f0e982c4a..9e238487a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/AdapterUpdater.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/AdapterUpdater.kt @@ -1,9 +1,10 @@ -package org.koitharu.kotatsu.ui.base.list +package org.koitharu.kotatsu.base.ui.list import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import java.util.* +@Deprecated("") class AdapterUpdater(oldList: List, newList: List, getId: (T) -> Long) { private val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/BaseViewHolder.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BaseViewHolder.kt similarity index 56% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/list/BaseViewHolder.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/list/BaseViewHolder.kt index 4de7c3f91..0d4b629a5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/BaseViewHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BaseViewHolder.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base.list +package org.koitharu.kotatsu.base.ui.list import android.view.View import android.view.ViewGroup @@ -30,25 +30,7 @@ abstract class BaseViewHolder protected constructor(view: View) : return boundData ?: throw IllegalStateException("Calling requireData() before bind()") } - fun setOnItemClickListener(listener: OnRecyclerItemClickListener?) { - val listenersAdapter = listener?.let { HolderListenersAdapter(it) } - itemView.setOnClickListener(listenersAdapter) - itemView.setOnLongClickListener(listenersAdapter) - } - open fun onRecycled() = Unit abstract fun onBind(data: T, extra: E) - - private inner class HolderListenersAdapter(private val listener: OnRecyclerItemClickListener) : - View.OnClickListener, View.OnLongClickListener { - - override fun onClick(v: View) { - listener.onItemClick(boundData ?: return, bindingAdapterPosition, v) - } - - override fun onLongClick(v: View): Boolean { - return listener.onItemLongClick(boundData ?: return false, bindingAdapterPosition, v) - } - } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/BoundsScrollListener.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BoundsScrollListener.kt similarity index 96% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/list/BoundsScrollListener.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/list/BoundsScrollListener.kt index 79b7f55ca..4a412d081 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/BoundsScrollListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BoundsScrollListener.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base.list +package org.koitharu.kotatsu.base.ui.list import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnListItemClickListener.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnListItemClickListener.kt new file mode 100644 index 000000000..f39b81d14 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnListItemClickListener.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.base.ui.list + +import android.view.View + +interface OnListItemClickListener { + + fun onItemClick(item: I, view: View) + + fun onItemLongClick(item: I, view: View) = false +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/PaginationScrollListener.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/PaginationScrollListener.kt new file mode 100644 index 000000000..5681cae23 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/PaginationScrollListener.kt @@ -0,0 +1,18 @@ +package org.koitharu.kotatsu.base.ui.list + +import androidx.recyclerview.widget.RecyclerView + +class PaginationScrollListener(offset: Int, private val callback: Callback) : + BoundsScrollListener(0, offset) { + + override fun onScrolledToStart(recyclerView: RecyclerView) = Unit + + override fun onScrolledToEnd(recyclerView: RecyclerView) { + callback.onScrolledToEnd() + } + + interface Callback { + + fun onScrolledToEnd() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/ProgressBarHolder.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/ProgressBarHolder.kt similarity index 95% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/list/ProgressBarHolder.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/list/ProgressBarHolder.kt index 87070c7d3..bb940b180 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/ProgressBarHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/ProgressBarHolder.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base.list +package org.koitharu.kotatsu.base.ui.list import android.view.View import android.view.ViewGroup diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/decor/ItemTypeDividerDecoration.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/ItemTypeDividerDecoration.kt similarity index 97% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/list/decor/ItemTypeDividerDecoration.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/ItemTypeDividerDecoration.kt index 2ed17fd9f..a887d8311 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/decor/ItemTypeDividerDecoration.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/ItemTypeDividerDecoration.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base.list.decor +package org.koitharu.kotatsu.base.ui.list.decor import android.content.Context import android.graphics.Canvas diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/decor/SectionItemDecoration.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SectionItemDecoration.kt similarity index 98% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/list/decor/SectionItemDecoration.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SectionItemDecoration.kt index 0fb82c73b..b5ecb78a2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/decor/SectionItemDecoration.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SectionItemDecoration.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base.list.decor +package org.koitharu.kotatsu.base.ui.list.decor import android.graphics.Canvas import android.graphics.Rect diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/decor/SpacingItemDecoration.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SpacingItemDecoration.kt similarity index 94% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/list/decor/SpacingItemDecoration.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SpacingItemDecoration.kt index 1fe6d1a34..2369b1cdb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/decor/SpacingItemDecoration.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SpacingItemDecoration.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base.list.decor +package org.koitharu.kotatsu.base.ui.list.decor import android.graphics.Rect import android.view.View diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/widgets/CheckableImageView.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CheckableImageView.kt similarity index 97% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/widgets/CheckableImageView.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CheckableImageView.kt index 09dc3f97f..472f95a78 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/widgets/CheckableImageView.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CheckableImageView.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base.widgets +package org.koitharu.kotatsu.base.ui.widgets import android.content.Context import android.util.AttributeSet diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/widgets/CoverImageView.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CoverImageView.kt similarity index 97% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/widgets/CoverImageView.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CoverImageView.kt index 4b0e4e1bf..049452f4f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/widgets/CoverImageView.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CoverImageView.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base.widgets +package org.koitharu.kotatsu.base.ui.widgets import android.content.Context import android.util.AttributeSet diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/widgets/SquareLayout.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/SquareLayout.kt similarity index 89% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/widgets/SquareLayout.kt rename to app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/SquareLayout.kt index 3eeb91043..589d75382 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/widgets/SquareLayout.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/SquareLayout.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base.widgets +package org.koitharu.kotatsu.base.ui.widgets import android.content.Context import android.util.AttributeSet diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/browser/BrowserActivity.kt b/app/src/main/java/org/koitharu/kotatsu/browser/BrowserActivity.kt similarity index 96% rename from app/src/main/java/org/koitharu/kotatsu/ui/browser/BrowserActivity.kt rename to app/src/main/java/org/koitharu/kotatsu/browser/BrowserActivity.kt index 5edd7b5ab..5ee23f338 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/browser/BrowserActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/browser/BrowserActivity.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.browser +package org.koitharu.kotatsu.browser import android.annotation.SuppressLint import android.content.ActivityNotFoundException @@ -11,7 +11,7 @@ import android.view.MenuItem import androidx.core.view.isVisible import kotlinx.android.synthetic.main.activity_browser.* import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.ui.base.BaseActivity +import org.koitharu.kotatsu.base.ui.BaseActivity @SuppressLint("SetJavaScriptEnabled") class BrowserActivity : BaseActivity(), BrowserCallback { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/browser/BrowserCallback.kt b/app/src/main/java/org/koitharu/kotatsu/browser/BrowserCallback.kt similarity index 77% rename from app/src/main/java/org/koitharu/kotatsu/ui/browser/BrowserCallback.kt rename to app/src/main/java/org/koitharu/kotatsu/browser/BrowserCallback.kt index 71b1ecfa9..e6f3fae84 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/browser/BrowserCallback.kt +++ b/app/src/main/java/org/koitharu/kotatsu/browser/BrowserCallback.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.browser +package org.koitharu.kotatsu.browser interface BrowserCallback { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/browser/BrowserClient.kt b/app/src/main/java/org/koitharu/kotatsu/browser/BrowserClient.kt similarity index 97% rename from app/src/main/java/org/koitharu/kotatsu/ui/browser/BrowserClient.kt rename to app/src/main/java/org/koitharu/kotatsu/browser/BrowserClient.kt index bda99170d..11ec6827d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/browser/BrowserClient.kt +++ b/app/src/main/java/org/koitharu/kotatsu/browser/BrowserClient.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.browser +package org.koitharu.kotatsu.browser import android.graphics.Bitmap import android.webkit.WebResourceRequest diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/utils/cloudflare/CloudFlareCallback.kt b/app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareCallback.kt similarity index 60% rename from app/src/main/java/org/koitharu/kotatsu/ui/utils/cloudflare/CloudFlareCallback.kt rename to app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareCallback.kt index 6f3c2514d..aedd22605 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/utils/cloudflare/CloudFlareCallback.kt +++ b/app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareCallback.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.utils.cloudflare +package org.koitharu.kotatsu.browser.cloudflare interface CloudFlareCallback { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/utils/cloudflare/CloudFlareClient.kt b/app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareClient.kt similarity index 96% rename from app/src/main/java/org/koitharu/kotatsu/ui/utils/cloudflare/CloudFlareClient.kt rename to app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareClient.kt index be484f6ce..b955ec529 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/utils/cloudflare/CloudFlareClient.kt +++ b/app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareClient.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.utils.cloudflare +package org.koitharu.kotatsu.browser.cloudflare import android.graphics.Bitmap import android.webkit.CookieManager diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/utils/cloudflare/CloudFlareDialog.kt b/app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareDialog.kt similarity index 95% rename from app/src/main/java/org/koitharu/kotatsu/ui/utils/cloudflare/CloudFlareDialog.kt rename to app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareDialog.kt index 0e7d6a6a4..e34ef8ff1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/utils/cloudflare/CloudFlareDialog.kt +++ b/app/src/main/java/org/koitharu/kotatsu/browser/cloudflare/CloudFlareDialog.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.utils.cloudflare +package org.koitharu.kotatsu.browser.cloudflare import android.annotation.SuppressLint import android.os.Bundle @@ -10,8 +10,8 @@ import androidx.core.view.isInvisible import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import kotlinx.android.synthetic.main.fragment_cloudflare.* import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.core.network.UserAgentInterceptor -import org.koitharu.kotatsu.ui.base.AlertDialogFragment import org.koitharu.kotatsu.utils.ext.stringArgument import org.koitharu.kotatsu.utils.ext.withArgs diff --git a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt index c8f40b3bb..c82914ff1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt @@ -4,7 +4,11 @@ import org.json.JSONArray import org.json.JSONObject import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.core.db.MangaDatabase -import org.koitharu.kotatsu.core.db.entity.* +import org.koitharu.kotatsu.core.db.entity.MangaEntity +import org.koitharu.kotatsu.core.db.entity.TagEntity +import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity +import org.koitharu.kotatsu.favourites.data.FavouriteEntity +import org.koitharu.kotatsu.history.data.HistoryEntity class BackupRepository(private val db: MangaDatabase) { diff --git a/app/src/main/java/org/koitharu/kotatsu/core/backup/RestoreRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/backup/RestoreRepository.kt index a8afc1b6e..5f97f93d4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/backup/RestoreRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/backup/RestoreRepository.kt @@ -3,7 +3,11 @@ package org.koitharu.kotatsu.core.backup import androidx.room.withTransaction import org.json.JSONObject import org.koitharu.kotatsu.core.db.MangaDatabase -import org.koitharu.kotatsu.core.db.entity.* +import org.koitharu.kotatsu.core.db.entity.MangaEntity +import org.koitharu.kotatsu.core.db.entity.TagEntity +import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity +import org.koitharu.kotatsu.favourites.data.FavouriteEntity +import org.koitharu.kotatsu.history.data.HistoryEntity import org.koitharu.kotatsu.utils.ext.getStringOrNull import org.koitharu.kotatsu.utils.ext.iterator import org.koitharu.kotatsu.utils.ext.map diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt index 386b9e4ab..b207504f6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt @@ -4,6 +4,12 @@ import androidx.room.Database import androidx.room.RoomDatabase import org.koitharu.kotatsu.core.db.dao.* import org.koitharu.kotatsu.core.db.entity.* +import org.koitharu.kotatsu.favourites.data.FavouriteCategoriesDao +import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity +import org.koitharu.kotatsu.favourites.data.FavouriteEntity +import org.koitharu.kotatsu.favourites.data.FavouritesDao +import org.koitharu.kotatsu.history.data.HistoryDao +import org.koitharu.kotatsu.history.data.HistoryEntity @Database( entities = [ diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackEntity.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackEntity.kt index 561ff2f46..57a01888f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackEntity.kt @@ -15,7 +15,7 @@ import androidx.room.PrimaryKey ) ] ) -data class TrackEntity ( +data class TrackEntity( @PrimaryKey(autoGenerate = false) @ColumnInfo(name = "manga_id") val mangaId: Long, @ColumnInfo(name = "chapters_total") val totalChapters: Int, diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/ParseException.kt b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/ParseException.kt index d7857b761..2a2bb64dc 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/ParseException.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/ParseException.kt @@ -1,3 +1,4 @@ package org.koitharu.kotatsu.core.exceptions -class ParseException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause) \ No newline at end of file +class ParseException(message: String? = null, cause: Throwable? = null) : + RuntimeException(message, cause) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt index 9d1c1baa1..2e430a652 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt @@ -4,9 +4,10 @@ import android.os.Parcelable import kotlinx.android.parcel.Parcelize import org.koin.core.context.GlobalContext import org.koin.core.error.NoBeanDefFoundException -import org.koitharu.kotatsu.core.parser.LocalMangaRepository +import org.koin.core.qualifier.named import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.site.* +import org.koitharu.kotatsu.local.domain.LocalMangaRepository @Suppress("SpellCheckingInspection") @Parcelize @@ -30,6 +31,7 @@ enum class MangaSource( // HENTAILIB("HentaiLib", "ru", HentaiLibRepository::class.java) @get:Throws(NoBeanDefFoundException::class) + @Deprecated("") val repository: MangaRepository - get() = GlobalContext.get().get(cls.kotlin) + get() = GlobalContext.get().get(named(this)) } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaTracking.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaTracking.kt index d5a1ccb39..7a3841c36 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaTracking.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaTracking.kt @@ -5,10 +5,10 @@ import kotlinx.android.parcel.Parcelize import java.util.* @Parcelize -data class MangaTracking ( +data class MangaTracking( val manga: Manga, val knownChaptersCount: Int, val lastChapterId: Long, val lastNotifiedChapterId: Long, val lastCheck: Date? -): Parcelable \ No newline at end of file +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/TrackingLogItem.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/TrackingLogItem.kt index 3f5d50984..de7adaaa7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/TrackingLogItem.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/TrackingLogItem.kt @@ -5,9 +5,9 @@ import kotlinx.android.parcel.Parcelize import java.util.* @Parcelize -data class TrackingLogItem ( +data class TrackingLogItem( val id: Long, val manga: Manga, val chapters: List, val createdAt: Date -): Parcelable \ No newline at end of file +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/MangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/MangaRepository.kt index 27009db46..e9e928028 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/MangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/MangaRepository.kt @@ -6,13 +6,18 @@ interface MangaRepository { val sortOrders: Set - suspend fun getList(offset: Int, query: String? = null, sortOrder: SortOrder? = null, tag: MangaTag? = null): List + suspend fun getList( + offset: Int, + query: String? = null, + sortOrder: SortOrder? = null, + tag: MangaTag? = null + ): List - suspend fun getDetails(manga: Manga) : Manga + suspend fun getDetails(manga: Manga): Manga - suspend fun getPages(chapter: MangaChapter) : List + suspend fun getPages(chapter: MangaChapter): List - suspend fun getPageFullUrl(page: MangaPage) : String + suspend fun getPageFullUrl(page: MangaPage): String suspend fun getTags(): Set } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt index 5497d1aa0..a1b2ae7e1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt @@ -1,22 +1,25 @@ package org.koitharu.kotatsu.core.parser -import org.koin.dsl.bind +import org.koin.core.qualifier.named import org.koin.dsl.module +import org.koitharu.kotatsu.base.domain.MangaLoaderContext +import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.parser.site.* val parserModule get() = module { - single { LocalMangaRepository(get()) } bind MangaRepository::class - factory { ReadmangaRepository(get()) } bind MangaRepository::class - factory { MintMangaRepository(get()) } bind MangaRepository::class - factory { SelfMangaRepository(get()) } bind MangaRepository::class - factory { MangaChanRepository(get()) } bind MangaRepository::class - factory { DesuMeRepository(get()) } bind MangaRepository::class - factory { HenChanRepository(get()) } bind MangaRepository::class - factory { YaoiChanRepository(get()) } bind MangaRepository::class - factory { MangaTownRepository(get()) } bind MangaRepository::class - factory { MangaLibRepository(get()) } bind MangaRepository::class - factory { NudeMoonRepository(get()) } bind MangaRepository::class - factory { MangareadRepository(get()) } bind MangaRepository::class + single { MangaLoaderContext() } + + factory(named(MangaSource.READMANGA_RU)) { ReadmangaRepository(get()) } + factory(named(MangaSource.MINTMANGA)) { MintMangaRepository(get()) } + factory(named(MangaSource.SELFMANGA)) { SelfMangaRepository(get()) } + factory(named(MangaSource.MANGACHAN)) { MangaChanRepository(get()) } + factory(named(MangaSource.DESUME)) { DesuMeRepository(get()) } + factory(named(MangaSource.HENCHAN)) { HenChanRepository(get()) } + factory(named(MangaSource.YAOICHAN)) { YaoiChanRepository(get()) } + factory(named(MangaSource.MANGATOWN)) { MangaTownRepository(get()) } + factory(named(MangaSource.MANGALIB)) { MangaLibRepository(get()) } + factory(named(MangaSource.NUDEMOON)) { NudeMoonRepository(get()) } + factory(named(MangaSource.MANGAREAD)) { MangareadRepository(get()) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt index 5965c8a9f..a5d6a4ae7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt @@ -1,10 +1,10 @@ package org.koitharu.kotatsu.core.parser +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.core.model.SortOrder -import org.koitharu.kotatsu.domain.MangaLoaderContext abstract class RemoteMangaRepository( protected val loaderContext: MangaLoaderContext diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ChanRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ChanRepository.kt index f2f049e99..54fcb38d5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ChanRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ChanRepository.kt @@ -1,11 +1,11 @@ package org.koitharu.kotatsu.core.parser.site import androidx.collection.arraySetOf +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.prefs.SourceSettings -import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.utils.ext.* import java.util.* diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/DesuMeRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/DesuMeRepository.kt index cc807e2cd..aefc070d0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/DesuMeRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/DesuMeRepository.kt @@ -1,11 +1,11 @@ package org.koitharu.kotatsu.core.parser.site import androidx.collection.arraySetOf +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.prefs.SourceSettings -import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.utils.ext.* import java.util.* import kotlin.collections.ArrayList diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/GroupleRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/GroupleRepository.kt index 3ced8edac..53d14a8ec 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/GroupleRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/GroupleRepository.kt @@ -2,11 +2,11 @@ package org.koitharu.kotatsu.core.parser.site import androidx.collection.arraySetOf import androidx.core.net.toUri +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.prefs.SourceSettings -import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.utils.ext.* import java.util.* diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/HenChanRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/HenChanRepository.kt index 2ef8aed61..2373f19b8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/HenChanRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/HenChanRepository.kt @@ -1,8 +1,8 @@ package org.koitharu.kotatsu.core.parser.site +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.model.* -import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.utils.ext.longHashCode import org.koitharu.kotatsu.utils.ext.mapToSet import org.koitharu.kotatsu.utils.ext.parseHtml diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaChanRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaChanRepository.kt index 0d6247169..f1ca82252 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaChanRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaChanRepository.kt @@ -1,7 +1,7 @@ package org.koitharu.kotatsu.core.parser.site +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.domain.MangaLoaderContext class MangaChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(loaderContext) { diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt index d614c0c97..58bd3a19b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt @@ -4,11 +4,11 @@ import androidx.collection.ArraySet import androidx.collection.arraySetOf import org.json.JSONArray import org.json.JSONObject +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.prefs.SourceSettings -import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.utils.ext.* import java.util.* import kotlin.collections.ArrayList diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaTownRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaTownRepository.kt index 6222d1c74..d51f38564 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaTownRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaTownRepository.kt @@ -2,11 +2,11 @@ package org.koitharu.kotatsu.core.parser.site import androidx.collection.arraySetOf import org.intellij.lang.annotations.Language +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.prefs.SourceSettings -import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.utils.ext.* import java.util.* diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangareadRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangareadRepository.kt index 7205aa1fa..6e9a44eff 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangareadRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangareadRepository.kt @@ -1,11 +1,11 @@ package org.koitharu.kotatsu.core.parser.site import androidx.collection.arraySetOf +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.prefs.SourceSettings -import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.utils.ext.* import java.util.* diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MintMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MintMangaRepository.kt index e0383adea..18bc10626 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MintMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MintMangaRepository.kt @@ -1,7 +1,7 @@ package org.koitharu.kotatsu.core.parser.site +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.domain.MangaLoaderContext class MintMangaRepository(loaderContext: MangaLoaderContext) : GroupleRepository(loaderContext) { diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/NudeMoonRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/NudeMoonRepository.kt index e43a37a64..9e45028c0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/NudeMoonRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/NudeMoonRepository.kt @@ -1,11 +1,11 @@ package org.koitharu.kotatsu.core.parser.site import androidx.collection.arraySetOf +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.prefs.SourceSettings -import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.utils.ext.* import java.util.* import java.util.regex.Pattern diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ReadmangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ReadmangaRepository.kt index 02ec673e8..3871a966f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ReadmangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ReadmangaRepository.kt @@ -1,7 +1,7 @@ package org.koitharu.kotatsu.core.parser.site +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.domain.MangaLoaderContext class ReadmangaRepository(loaderContext: MangaLoaderContext) : GroupleRepository(loaderContext) { diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/SelfMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/SelfMangaRepository.kt index 952c29578..d4e194300 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/SelfMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/SelfMangaRepository.kt @@ -1,7 +1,7 @@ package org.koitharu.kotatsu.core.parser.site +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.domain.MangaLoaderContext class SelfMangaRepository(loaderContext: MangaLoaderContext) : GroupleRepository(loaderContext) { diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/YaoiChanRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/YaoiChanRepository.kt index e40a054b8..590555a42 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/YaoiChanRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/YaoiChanRepository.kt @@ -1,10 +1,10 @@ package org.koitharu.kotatsu.core.parser.site +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.utils.ext.longHashCode import org.koitharu.kotatsu.utils.ext.parseHtml import org.koitharu.kotatsu.utils.ext.withDomain diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt index 52409ace7..9cc5460bb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -7,8 +7,11 @@ import androidx.appcompat.app.AppCompatDelegate import androidx.collection.arraySetOf import androidx.core.content.edit import androidx.preference.PreferenceManager +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.channels.sendBlocking +import kotlinx.coroutines.flow.callbackFlow import org.koitharu.kotatsu.core.model.ZoomMode -import org.koitharu.kotatsu.core.parser.LocalMangaRepository +import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.utils.delegates.prefs.* import java.io.File @@ -19,7 +22,7 @@ class AppSettings private constructor(private val prefs: SharedPreferences) : PreferenceManager.getDefaultSharedPreferences(context) ) - var listMode by IntEnumPreferenceDelegate( + var listMode by EnumPreferenceDelegate( ListMode::class.java, KEY_LIST_MODE, ListMode.DETAILED_LIST @@ -38,7 +41,7 @@ class AppSettings private constructor(private val prefs: SharedPreferences) : val isAmoledTheme by BoolPreferenceDelegate(KEY_THEME_AMOLED, defaultValue = false) - val gridSize by IntPreferenceDelegate(KEY_GRID_SIZE, defaultValue = 100) + var gridSize by IntPreferenceDelegate(KEY_GRID_SIZE, defaultValue = 100) val readerPageSwitch by StringSetPreferenceDelegate( KEY_READER_SWITCHERS, @@ -117,6 +120,16 @@ class AppSettings private constructor(private val prefs: SharedPreferences) : prefs.unregisterOnSharedPreferenceChangeListener(listener) } + fun observe() = callbackFlow { + val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + sendBlocking(key) + } + prefs.registerOnSharedPreferenceChangeListener(listener) + awaitClose { + prefs.unregisterOnSharedPreferenceChangeListener(listener) + } + } + companion object { const val PAGE_SWITCH_TAPS = "taps" @@ -125,7 +138,7 @@ class AppSettings private constructor(private val prefs: SharedPreferences) : const val TRACK_HISTORY = "history" const val TRACK_FAVOURITES = "favourites" - const val KEY_LIST_MODE = "list_mode" + const val KEY_LIST_MODE = "list_mode_2" const val KEY_APP_SECTION = "app_section" const val KEY_THEME = "theme" const val KEY_THEME_AMOLED = "amoled_theme" diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppWidgetConfig.kt b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppWidgetConfig.kt index fa4d577e9..312764f24 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppWidgetConfig.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppWidgetConfig.kt @@ -7,7 +7,7 @@ import org.koitharu.kotatsu.utils.delegates.prefs.LongPreferenceDelegate class AppWidgetConfig private constructor( private val prefs: SharedPreferences, val widgetId: Int -) : SharedPreferences by prefs { +) : SharedPreferences by prefs { var categoryId by LongPreferenceDelegate(CATEGORY_ID, 0L) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/utils/AppCrashHandler.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/AppCrashHandler.kt similarity index 95% rename from app/src/main/java/org/koitharu/kotatsu/ui/utils/AppCrashHandler.kt rename to app/src/main/java/org/koitharu/kotatsu/core/ui/AppCrashHandler.kt index 5f7b5964c..002b8b05e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/utils/AppCrashHandler.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/AppCrashHandler.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.utils +package org.koitharu.kotatsu.core.ui import android.content.Context import android.content.Intent diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/ChipsFactory.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/ChipsFactory.kt similarity index 95% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/ChipsFactory.kt rename to app/src/main/java/org/koitharu/kotatsu/core/ui/ChipsFactory.kt index 9120721a5..a4cceb152 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/ChipsFactory.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/ChipsFactory.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.base +package org.koitharu.kotatsu.core.ui import android.content.Context import android.view.View diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/utils/CrashActivity.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/CrashActivity.kt similarity index 95% rename from app/src/main/java/org/koitharu/kotatsu/ui/utils/CrashActivity.kt rename to app/src/main/java/org/koitharu/kotatsu/core/ui/CrashActivity.kt index 3f035c029..d68e6ae00 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/utils/CrashActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/CrashActivity.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.utils +package org.koitharu.kotatsu.core.ui import android.app.Activity import android.content.ActivityNotFoundException @@ -10,7 +10,7 @@ import android.view.MenuItem import android.view.View import kotlinx.android.synthetic.main.activity_crash.* import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.ui.list.MainActivity +import org.koitharu.kotatsu.main.ui.MainActivity import org.koitharu.kotatsu.utils.ShareHelper class CrashActivity : Activity(), View.OnClickListener { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/uiModule.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt similarity index 81% rename from app/src/main/java/org/koitharu/kotatsu/ui/base/uiModule.kt rename to app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt index d4706cc3f..8032d9783 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/uiModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt @@ -1,11 +1,11 @@ -package org.koitharu.kotatsu.ui.base +package org.koitharu.kotatsu.core.ui import coil.ComponentRegistry import coil.ImageLoader import okhttp3.OkHttpClient import org.koin.android.ext.koin.androidContext import org.koin.dsl.module -import org.koitharu.kotatsu.core.local.CbzFetcher +import org.koitharu.kotatsu.local.data.CbzFetcher val uiModule get() = module { diff --git a/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt b/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt new file mode 100644 index 000000000..815358e2c --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.details + +import org.koin.android.viewmodel.dsl.viewModel +import org.koin.dsl.module +import org.koitharu.kotatsu.base.domain.MangaIntent +import org.koitharu.kotatsu.details.ui.DetailsViewModel + +val detailsModule + get() = module { + + viewModel { (intent: MangaIntent) -> + DetailsViewModel(intent, get(), get(), get(), get(), get()) + } + } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt new file mode 100644 index 000000000..789853318 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt @@ -0,0 +1,148 @@ +package org.koitharu.kotatsu.details.ui + +import android.app.ActivityOptions +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ActionMode +import androidx.core.view.isVisible +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.fragment_chapters.* +import org.koin.android.viewmodel.ext.android.sharedViewModel +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BaseFragment +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.model.MangaChapter +import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter +import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration +import org.koitharu.kotatsu.details.ui.model.ChapterListItem +import org.koitharu.kotatsu.download.DownloadService +import org.koitharu.kotatsu.reader.ui.ReaderActivity + +class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), + OnListItemClickListener, ActionMode.Callback { + + private val viewModel by sharedViewModel() + + private var chaptersAdapter: ChaptersAdapter? = null + private var actionMode: ActionMode? = null + private var selectionDecoration: ChaptersSelectionDecoration? = null + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + chaptersAdapter = ChaptersAdapter(this) + selectionDecoration = ChaptersSelectionDecoration(view.context) + with(recyclerView_chapters) { + addItemDecoration(DividerItemDecoration(view.context, RecyclerView.VERTICAL)) + addItemDecoration(selectionDecoration!!) + setHasFixedSize(true) + adapter = chaptersAdapter + } + + viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged) + viewModel.chapters.observe(viewLifecycleOwner, this::onChaptersChanged) + } + + override fun onDestroyView() { + chaptersAdapter = null + selectionDecoration = null + super.onDestroyView() + } + + private fun onChaptersChanged(list: List) { + chaptersAdapter?.items = list + } + + private fun onLoadingStateChanged(isLoading: Boolean) { + progressBar.isVisible = isLoading + } + + override fun onItemClick(item: MangaChapter, view: View) { + if (selectionDecoration?.checkedItemsCount != 0) { + selectionDecoration?.toggleItemChecked(item.id) + if (selectionDecoration?.checkedItemsCount == 0) { + actionMode?.finish() + } else { + actionMode?.invalidate() + recyclerView_chapters.invalidateItemDecorations() + } + return + } + val options = ActivityOptions.makeScaleUpAnimation( + view, + 0, + 0, + view.measuredWidth, + view.measuredHeight + ) + startActivity( + ReaderActivity.newIntent( + context ?: return, + viewModel.manga.value ?: return, + item.id + ), options.toBundle() + ) + } + + override fun onItemLongClick(item: MangaChapter, view: View): Boolean { + if (actionMode == null) { + actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this) + } + return actionMode?.also { + selectionDecoration?.setItemIsChecked(item.id, true) + recyclerView_chapters.invalidateItemDecorations() + it.invalidate() + } != null + } + + override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_save -> { + DownloadService.start( + context ?: return false, + viewModel.manga.value ?: return false, + selectionDecoration?.checkedItemsIds + ) + mode.finish() + true + } + R.id.action_select_all -> { + val ids = chaptersAdapter?.items?.map { it.chapter.id } ?: return false + selectionDecoration?.checkAll(ids) + recyclerView_chapters.invalidateItemDecorations() + mode.invalidate() + true + } + else -> false + } + } + + override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + val manga = viewModel.manga.value + mode.menuInflater.inflate(R.menu.mode_chapters, menu) + menu.findItem(R.id.action_save).isVisible = manga?.source != MangaSource.LOCAL + mode.title = manga?.title + return true + } + + override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + val count = selectionDecoration?.checkedItemsCount ?: return false + mode.subtitle = resources.getQuantityString( + R.plurals.chapters_from_x, + count, + count, + chaptersAdapter?.itemCount ?: 0 + ) + return true + } + + override fun onDestroyActionMode(mode: ActionMode?) { + selectionDecoration?.clearSelection() + recyclerView_chapters.invalidateItemDecorations() + actionMode = null + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt similarity index 70% rename from app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsActivity.kt rename to app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt index a4ed4cfd3..1ee1907f2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.details +package org.koitharu.kotatsu.details.ui import android.content.Context import android.content.Intent @@ -7,70 +7,58 @@ import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.widget.Toast +import androidx.appcompat.app.AlertDialog import androidx.appcompat.view.ActionMode import androidx.core.content.ContextCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.net.toFile import androidx.lifecycle.lifecycleScope -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import kotlinx.android.synthetic.main.activity_details.* import kotlinx.coroutines.launch -import moxy.MvpDelegate -import moxy.ktx.moxyPresenter +import org.koin.android.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.FavouriteCategory +import org.koitharu.kotatsu.base.domain.MangaIntent +import org.koitharu.kotatsu.base.ui.BaseActivity +import org.koitharu.kotatsu.browser.BrowserActivity import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.ui.base.BaseActivity -import org.koitharu.kotatsu.ui.browser.BrowserActivity -import org.koitharu.kotatsu.ui.download.DownloadService +import org.koitharu.kotatsu.download.DownloadService +import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity import org.koitharu.kotatsu.utils.MangaShortcut import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getThemeColor -class MangaDetailsActivity : BaseActivity(), MangaDetailsView, - TabLayoutMediator.TabConfigurationStrategy { +class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrategy { - private val presenter by moxyPresenter { - MangaDetailsPresenter.getInstance(hashCode()) + private val viewModel by viewModel { + parametersOf(MangaIntent.from(intent)) } - private var manga: Manga? = null - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_details) supportActionBar?.setDisplayHomeAsUpEnabled(true) pager.adapter = MangaDetailsAdapter(this) TabLayoutMediator(tabs, pager, this).attach() - if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) { - intent?.getParcelableExtra(EXTRA_MANGA)?.let { - presenter.loadDetails(it, true) - } ?: intent?.getLongExtra(EXTRA_MANGA_ID, 0)?.takeUnless { it == 0L }?.let { - presenter.findMangaById(it) - } ?: finishAfterTransition() - } + + viewModel.manga.observe(this, ::onMangaUpdated) + viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged) + viewModel.onMangaRemoved.observe(this, ::onMangaRemoved) + viewModel.onError.observe(this, ::onError) } - override fun onMangaUpdated(manga: Manga) { - this.manga = manga + private fun onMangaUpdated(manga: Manga) { title = manga.title invalidateOptionsMenu() } - override fun onHistoryChanged(history: MangaHistory?) = Unit - - override fun onFavouriteChanged(categories: List) = Unit - - override fun onLoadingStateChanged(isLoading: Boolean) = Unit - - override fun onMangaRemoved(manga: Manga) { + private fun onMangaRemoved(manga: Manga) { Toast.makeText( this, getString(R.string._s_deleted_from_local_storage, manga.title), Toast.LENGTH_SHORT @@ -78,8 +66,8 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView, finishAfterTransition() } - override fun onError(e: Throwable) { - if (manga == null) { + private fun onError(e: Throwable) { + if (viewModel.manga.value == null) { Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show() finishAfterTransition() } else { @@ -87,7 +75,7 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView, } } - override fun onNewChaptersChanged(newChapters: Int) { + private fun onNewChaptersChanged(newChapters: Int) { val tab = tabs.getTabAt(1) ?: return if (newChapters == 0) { tab.removeBadge() @@ -104,8 +92,9 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView, } override fun onPrepareOptionsMenu(menu: Menu): Boolean { + val manga = viewModel.manga.value menu.findItem(R.id.action_save).isVisible = - manga?.source != null && manga?.source != MangaSource.LOCAL + manga?.source != null && manga.source != MangaSource.LOCAL menu.findItem(R.id.action_delete).isVisible = manga?.source == MangaSource.LOCAL menu.findItem(R.id.action_browser).isVisible = @@ -117,7 +106,7 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView, override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { R.id.action_share -> { - manga?.let { + viewModel.manga.value?.let { if (it.source == MangaSource.LOCAL) { ShareHelper.shareCbz(this, Uri.parse(it.url).toFile()) } else { @@ -127,12 +116,12 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView, true } R.id.action_delete -> { - manga?.let { m -> - MaterialAlertDialogBuilder(this) + viewModel.manga.value?.let { m -> + AlertDialog.Builder(this) .setTitle(R.string.delete_manga) .setMessage(getString(R.string.text_delete_local_manga, m.title)) .setPositiveButton(R.string.delete) { _, _ -> - presenter.deleteLocal(m) + viewModel.deleteLocal(m) } .setNegativeButton(android.R.string.cancel, null) .show() @@ -140,10 +129,10 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView, true } R.id.action_save -> { - manga?.let { + viewModel.manga.value?.let { val chaptersCount = it.chapters?.size ?: 0 if (chaptersCount > 5) { - MaterialAlertDialogBuilder(this) + AlertDialog.Builder(this) .setTitle(R.string.save_manga) .setMessage( getString( @@ -166,15 +155,21 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView, true } R.id.action_browser -> { - manga?.let { + viewModel.manga.value?.let { startActivity(BrowserActivity.newIntent(this, it.url)) } true } + R.id.action_related -> { + viewModel.manga.value?.let { + startActivity(GlobalSearchActivity.newIntent(this, it.title)) + } + true + } R.id.action_shortcut -> { - manga?.let { + viewModel.manga.value?.let { lifecycleScope.launch { - if (!MangaShortcut(it).requestPinShortcut(this@MangaDetailsActivity)) { + if (!MangaShortcut(it).requestPinShortcut(this@DetailsActivity)) { Snackbar.make( pager, R.string.operation_not_supported, @@ -192,7 +187,6 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView, tab.text = when (position) { 0 -> getString(R.string.details) 1 -> getString(R.string.chapters) - 2 -> getString(R.string.related) else -> null } } @@ -211,17 +205,14 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView, companion object { - private const val EXTRA_MANGA = "manga" - const val EXTRA_MANGA_ID = "manga_id" - const val ACTION_MANGA_VIEW = "${BuildConfig.APPLICATION_ID}.action.VIEW_MANGA" fun newIntent(context: Context, manga: Manga) = - Intent(context, MangaDetailsActivity::class.java) - .putExtra(EXTRA_MANGA, manga) + Intent(context, DetailsActivity::class.java) + .putExtra(MangaIntent.KEY_MANGA, manga) fun newIntent(context: Context, mangaId: Long) = - Intent(context, MangaDetailsActivity::class.java) - .putExtra(EXTRA_MANGA_ID, mangaId) + Intent(context, DetailsActivity::class.java) + .putExtra(MangaIntent.KEY_ID, mangaId) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt similarity index 63% rename from app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt index 5bc0a6832..d6b934876 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt @@ -1,5 +1,6 @@ -package org.koitharu.kotatsu.ui.details +package org.koitharu.kotatsu.details.ui +import android.os.Bundle import android.text.Spanned import android.view.View import androidx.core.net.toUri @@ -10,37 +11,36 @@ import kotlinx.android.synthetic.main.fragment_details.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import moxy.ktx.moxyPresenter +import org.koin.android.viewmodel.ext.android.sharedViewModel import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.FavouriteCategory +import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaHistory -import org.koitharu.kotatsu.ui.base.BaseFragment -import org.koitharu.kotatsu.ui.list.favourites.categories.select.FavouriteCategoriesDialog -import org.koitharu.kotatsu.ui.reader.ReaderActivity -import org.koitharu.kotatsu.ui.search.MangaSearchSheet +import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesDialog +import org.koitharu.kotatsu.reader.ui.ReaderActivity +import org.koitharu.kotatsu.search.ui.MangaSearchSheet import org.koitharu.kotatsu.utils.FileSizeUtils import org.koitharu.kotatsu.utils.ext.* import kotlin.math.roundToInt -class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetailsView, - View.OnClickListener, +class DetailsFragment : BaseFragment(R.layout.fragment_details), View.OnClickListener, View.OnLongClickListener { - @Suppress("unused") - private val presenter by moxyPresenter { - MangaDetailsPresenter.getInstance(activity.hashCode()) - } + private val viewModel by sharedViewModel() - private var manga: Manga? = null - private var history: MangaHistory? = null + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.manga.observe(viewLifecycleOwner, ::onMangaUpdated) + viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged) + viewModel.favouriteCategories.observe(viewLifecycleOwner, ::onFavouriteChanged) + viewModel.readingHistory.observe(viewLifecycleOwner, ::onHistoryChanged) + } - override fun onMangaUpdated(manga: Manga) { - this.manga = manga + private fun onMangaUpdated(manga: Manga) { imageView_cover.newImageRequest(manga.largeCoverUrl ?: manga.coverUrl) .fallback(R.drawable.ic_placeholder) .crossfade(true) - .lifecycle(this) + .lifecycle(viewLifecycleOwner) .enqueueWith(coil) textView_title.text = manga.title textView_subtitle.textAndVisible = manga.altTitle @@ -59,7 +59,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai text = it, iconRes = R.drawable.ic_chip_user, tag = it, - onClickListener = this@MangaDetailsFragment + onClickListener = this@DetailsFragment ) } } @@ -68,7 +68,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai text = it.title, iconRes = R.drawable.ic_chip_tag, tag = it, - onClickListener = this@MangaDetailsFragment + onClickListener = this@DetailsFragment ) } manga.url.toUri().toFileOrNull()?.let { f -> @@ -81,7 +81,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai text = FileSizeUtils.formatBytes(context, size), iconRes = R.drawable.ic_chip_storage, tag = it, - onClickListener = this@MangaDetailsFragment + onClickListener = this@DetailsFragment ) } } @@ -89,35 +89,37 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai imageView_favourite.setOnClickListener(this) button_read.setOnClickListener(this) button_read.setOnLongClickListener(this) - updateReadButton() + button_read.isEnabled = !manga.chapters.isNullOrEmpty() } - override fun onHistoryChanged(history: MangaHistory?) { - this.history = history - updateReadButton() + private fun onHistoryChanged(history: MangaHistory?) { + with(button_read) { + if (history == null) { + setText(R.string.read) + setIconResource(R.drawable.ic_read) + } else { + setText(R.string._continue) + setIconResource(R.drawable.ic_play) + } + } } - override fun onFavouriteChanged(categories: List) { + private fun onFavouriteChanged(isFavourite: Boolean) { imageView_favourite.setImageResource( - if (categories.isEmpty()) { - R.drawable.ic_heart_outline - } else { + if (isFavourite) { R.drawable.ic_heart + } else { + R.drawable.ic_heart_outline } ) } - override fun onLoadingStateChanged(isLoading: Boolean) { + private fun onLoadingStateChanged(isLoading: Boolean) { progressBar.isVisible = isLoading } - override fun onError(e: Throwable) = Unit //handled in activity - - override fun onMangaRemoved(manga: Manga) = Unit //handled in activity - - override fun onNewChaptersChanged(newChapters: Int) = Unit - override fun onClick(v: View) { + val manga = viewModel.manga.value when { v.id == R.id.imageView_favourite -> { FavouriteCategoriesDialog.show(childFragmentManager, manga ?: return) @@ -127,7 +129,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai ReaderActivity.newIntent( context ?: return, manga ?: return, - history + viewModel.readingHistory.value ) ) } @@ -146,7 +148,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai override fun onLongClick(v: View): Boolean { when (v.id) { R.id.button_read -> { - if (history == null) { + if (viewModel.readingHistory.value == null) { return false } v.showPopupMenu(R.menu.popup_read) { @@ -155,7 +157,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai startActivity( ReaderActivity.newIntent( context ?: return@showPopupMenu false, - manga ?: return@showPopupMenu false + viewModel.manga.value ?: return@showPopupMenu false ) ) true @@ -168,19 +170,4 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai else -> return false } } - - private fun updateReadButton() { - if (manga?.chapters.isNullOrEmpty()) { - button_read.isEnabled = false - } else { - button_read.isEnabled = true - if (history == null) { - button_read.setText(R.string.read) - button_read.setIconResource(R.drawable.ic_read) - } else { - button_read.setText(R.string._continue) - button_read.setIconResource(R.drawable.ic_play) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt new file mode 100644 index 000000000..23642ba34 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -0,0 +1,101 @@ +package org.koitharu.kotatsu.details.ui + +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.* +import org.koitharu.kotatsu.base.domain.MangaDataRepository +import org.koitharu.kotatsu.base.domain.MangaIntent +import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.details.ui.model.toListItem +import org.koitharu.kotatsu.favourites.domain.FavouritesRepository +import org.koitharu.kotatsu.history.domain.ChapterExtra +import org.koitharu.kotatsu.history.domain.HistoryRepository +import org.koitharu.kotatsu.local.domain.LocalMangaRepository +import org.koitharu.kotatsu.tracker.domain.TrackingRepository +import org.koitharu.kotatsu.utils.SingleLiveEvent +import org.koitharu.kotatsu.utils.ext.safe +import java.io.IOException + +class DetailsViewModel( + intent: MangaIntent, + private val historyRepository: HistoryRepository, + private val favouritesRepository: FavouritesRepository, + private val localMangaRepository: LocalMangaRepository, + private val trackingRepository: TrackingRepository, + private val mangaDataRepository: MangaDataRepository +) : BaseViewModel() { + + private val mangaData = MutableStateFlow(intent.manga) + + private val history = mangaData.mapNotNull { it?.id } + .distinctUntilChanged() + .flatMapLatest { mangaId -> + historyRepository.observeOne(mangaId) + }.stateIn(viewModelScope, SharingStarted.Eagerly, null) + + private val favourite = mangaData.mapNotNull { it?.id } + .distinctUntilChanged() + .flatMapLatest { mangaId -> + favouritesRepository.observeCategoriesIds(mangaId).map { it.isNotEmpty() } + }.stateIn(viewModelScope, SharingStarted.Eagerly, false) + + private val newChapters = mangaData.mapNotNull { it?.id } + .distinctUntilChanged() + .mapLatest { mangaId -> + trackingRepository.getNewChaptersCount(mangaId) + }.stateIn(viewModelScope, SharingStarted.Eagerly, 0) + + val manga = mangaData.filterNotNull() + .asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + val favouriteCategories = favourite + .asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + val newChaptersCount = newChapters + .asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + val readingHistory = history + .asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + + val onMangaRemoved = SingleLiveEvent() + + val chapters = combine( + mangaData.map { it?.chapters.orEmpty() }, + history.map { it?.chapterId }, + newChapters + ) { chapters, currentId, newCount -> + val currentIndex = chapters.indexOfFirst { it.id == currentId } + val firstNewIndex = chapters.size - newCount + chapters.mapIndexed { index, chapter -> + chapter.toListItem( + when { + index >= firstNewIndex -> ChapterExtra.NEW + index == currentIndex -> ChapterExtra.CURRENT + index < currentIndex -> ChapterExtra.READ + else -> ChapterExtra.UNREAD + } + ) + } + }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + + init { + launchLoadingJob(Dispatchers.Default) { + var manga = mangaDataRepository.resolveIntent(intent) + ?: throw MangaNotFoundException("Cannot find manga") + mangaData.value = manga + manga = manga.source.repository.getDetails(manga) + mangaData.value = manga + } + } + + fun deleteLocal(manga: Manga) { + launchLoadingJob(Dispatchers.Default) { + val original = localMangaRepository.getRemoteManga(manga) + localMangaRepository.delete(manga) || throw IOException("Unable to delete file") + safe { + historyRepository.deleteOrSwap(manga, original) + } + onMangaRemoved.postCall(manga) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsAdapter.kt similarity index 62% rename from app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsAdapter.kt rename to app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsAdapter.kt index 2644fd61d..03cf1d76c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsAdapter.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.details +package org.koitharu.kotatsu.details.ui import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity @@ -6,12 +6,11 @@ import androidx.viewpager2.adapter.FragmentStateAdapter class MangaDetailsAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) { - override fun getItemCount() = 3 + override fun getItemCount() = 2 - override fun createFragment(position: Int): Fragment = when(position) { - 0 -> MangaDetailsFragment() + override fun createFragment(position: Int): Fragment = when (position) { + 0 -> DetailsFragment() 1 -> ChaptersFragment() - 2 -> RelatedMangaFragment() else -> throw IndexOutOfBoundsException("No fragment for position $position") } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/ChapterHolder.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt similarity index 55% rename from app/src/main/java/org/koitharu/kotatsu/ui/details/ChapterHolder.kt rename to app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt index 7afbc88e1..afd24ee42 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/ChapterHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt @@ -1,23 +1,29 @@ -package org.koitharu.kotatsu.ui.details +package org.koitharu.kotatsu.details.ui.adapter -import android.graphics.Color -import android.view.ViewGroup -import androidx.core.view.isVisible +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer import kotlinx.android.synthetic.main.item_chapter.* import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.model.MangaChapter -import org.koitharu.kotatsu.domain.history.ChapterExtra -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder +import org.koitharu.kotatsu.details.ui.model.ChapterListItem +import org.koitharu.kotatsu.history.domain.ChapterExtra import org.koitharu.kotatsu.utils.ext.getThemeColor -class ChapterHolder(parent: ViewGroup) : - BaseViewHolder(parent, R.layout.item_chapter) { +fun chapterListItemAD( + clickListener: OnListItemClickListener +) = adapterDelegateLayoutContainer(R.layout.item_chapter) { - override fun onBind(data: MangaChapter, extra: ChapterExtra) { - textView_title.text = data.name - textView_number.text = data.number.toString() - imageView_check.isVisible = extra == ChapterExtra.CHECKED - when (extra) { + itemView.setOnClickListener { + clickListener.onItemClick(item.chapter, it) + } + itemView.setOnLongClickListener { + clickListener.onItemLongClick(item.chapter, it) + } + + bind { payload -> + textView_title.text = item.chapter.name + textView_number.text = item.chapter.number.toString() + when (item.extra) { ChapterExtra.UNREAD -> { textView_number.setBackgroundResource(R.drawable.bg_badge_default) textView_number.setTextColor(context.getThemeColor(android.R.attr.textColorSecondaryInverse)) @@ -34,10 +40,6 @@ class ChapterHolder(parent: ViewGroup) : textView_number.setBackgroundResource(R.drawable.bg_badge_accent) textView_number.setTextColor(context.getThemeColor(android.R.attr.textColorPrimaryInverse)) } - ChapterExtra.CHECKED -> { - textView_number.background = null - textView_number.setTextColor(Color.TRANSPARENT) - } } } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersAdapter.kt new file mode 100644 index 000000000..f032dcc23 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersAdapter.kt @@ -0,0 +1,40 @@ +package org.koitharu.kotatsu.details.ui.adapter + +import androidx.recyclerview.widget.DiffUtil +import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.model.MangaChapter +import org.koitharu.kotatsu.details.ui.model.ChapterListItem +import kotlin.jvm.internal.Intrinsics + +class ChaptersAdapter( + onItemClickListener: OnListItemClickListener +) : AsyncListDifferDelegationAdapter(DiffCallback()) { + + init { + setHasStableIds(true) + delegatesManager.addDelegate(chapterListItemAD(onItemClickListener)) + } + + override fun getItemId(position: Int): Long { + return items[position].chapter.id + } + + private class DiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: ChapterListItem, newItem: ChapterListItem): Boolean { + return oldItem.chapter.id == newItem.chapter.id + } + + override fun areContentsTheSame(oldItem: ChapterListItem, newItem: ChapterListItem): Boolean { + return Intrinsics.areEqual(oldItem, newItem) + } + + override fun getChangePayload(oldItem: ChapterListItem, newItem: ChapterListItem): Any? { + if (oldItem.extra != newItem.extra) { + return newItem.extra + } + return null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt new file mode 100644 index 000000000..69ad32f06 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt @@ -0,0 +1,108 @@ +package org.koitharu.kotatsu.details.ui.adapter + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import androidx.collection.ArraySet +import androidx.core.content.ContextCompat +import androidx.core.view.children +import androidx.recyclerview.widget.RecyclerView +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.utils.ext.getThemeColor +import org.koitharu.kotatsu.utils.ext.resolveDp + +class ChaptersSelectionDecoration(context: Context) : RecyclerView.ItemDecoration() { + + private val icon = ContextCompat.getDrawable(context, R.drawable.ic_check) + private val padding = context.resources.resolveDp(16) + private val bounds = Rect() + private val selection = ArraySet() + private val paint = Paint(Paint.ANTI_ALIAS_FLAG) + + init { + paint.color = context.getThemeColor(com.google.android.material.R.attr.scrimBackground) + paint.style = Paint.Style.FILL + } + + val checkedItemsCount: Int + get() = selection.size + + val checkedItemsIds: Set + get() = selection + + fun toggleItemChecked(id: Long) { + if (!selection.remove(id)) { + selection.add(id) + } + } + + fun setItemIsChecked(id: Long, isChecked: Boolean) { + if (isChecked) { + selection.add(id) + } else { + selection.remove(id) + } + } + + fun checkAll(ids: Collection) { + selection.addAll(ids) + } + + fun clearSelection() { + selection.clear() + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + icon ?: return + canvas.save() + if (parent.clipToPadding) { + canvas.clipRect( + parent.paddingLeft, parent.paddingTop, parent.width - parent.paddingRight, + parent.height - parent.paddingBottom + ) + } + + for (child in parent.children) { + val itemId = parent.getChildItemId(child) + if (itemId in selection) { + parent.getDecoratedBoundsWithMargins(child, bounds) + bounds.offset(child.translationX.toInt(), child.translationY.toInt()) + canvas.drawRect(bounds, paint) + } + } + canvas.restore() + } + + override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + icon ?: return + canvas.save() + val left: Int + val right: Int + if (parent.clipToPadding) { + left = parent.paddingLeft + right = parent.width - parent.paddingRight + canvas.clipRect( + left, parent.paddingTop, right, + parent.height - parent.paddingBottom + ) + } else { + left = 0 + right = parent.width + } + + for (child in parent.children) { + val itemId = parent.getChildItemId(child) + if (itemId in selection) { + parent.getDecoratedBoundsWithMargins(child, bounds) + bounds.offset(child.translationX.toInt(), child.translationY.toInt()) + val hh = (bounds.height() - icon.intrinsicHeight) / 2 + val top: Int = bounds.top + hh + val bottom: Int = bounds.bottom - hh + icon.setBounds(right - icon.intrinsicWidth - padding, top, right - padding, bottom) + icon.draw(canvas) + } + } + canvas.restore() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt new file mode 100644 index 000000000..5fad6e03a --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt @@ -0,0 +1,9 @@ +package org.koitharu.kotatsu.details.ui.model + +import org.koitharu.kotatsu.core.model.MangaChapter +import org.koitharu.kotatsu.history.domain.ChapterExtra + +data class ChapterListItem( + val chapter: MangaChapter, + val extra: ChapterExtra +) diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt new file mode 100644 index 000000000..dc1df8e0f --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt @@ -0,0 +1,9 @@ +package org.koitharu.kotatsu.details.ui.model + +import org.koitharu.kotatsu.core.model.MangaChapter +import org.koitharu.kotatsu.history.domain.ChapterExtra + +fun MangaChapter.toListItem(extra: ChapterExtra) = ChapterListItem( + chapter = this, + extra = extra +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/history/ChapterExtra.kt b/app/src/main/java/org/koitharu/kotatsu/domain/history/ChapterExtra.kt deleted file mode 100644 index 4d6d2b411..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/domain/history/ChapterExtra.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.koitharu.kotatsu.domain.history - -enum class ChapterExtra { - - READ, CURRENT, UNREAD, NEW, CHECKED -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadNotification.kt b/app/src/main/java/org/koitharu/kotatsu/download/DownloadNotification.kt similarity index 95% rename from app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadNotification.kt rename to app/src/main/java/org/koitharu/kotatsu/download/DownloadNotification.kt index 7eaf3c515..387506326 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadNotification.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/DownloadNotification.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.download +package org.koitharu.kotatsu.download import android.app.Notification import android.app.NotificationChannel @@ -12,7 +12,7 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.ui.details.MangaDetailsActivity +import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.utils.ext.getDisplayMessage import kotlin.math.roundToInt @@ -24,7 +24,8 @@ class DownloadNotification(private val context: Context) { init { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O - && manager.getNotificationChannel(CHANNEL_ID) == null) { + && manager.getNotificationChannel(CHANNEL_ID) == null + ) { val channel = NotificationChannel( CHANNEL_ID, context.getString(R.string.downloads), @@ -145,7 +146,7 @@ class DownloadNotification(private val context: Context) { private fun createIntent(context: Context, manga: Manga) = PendingIntent.getActivity( context, manga.hashCode(), - MangaDetailsActivity.newIntent(context, manga), + DetailsActivity.newIntent(context, manga), PendingIntent.FLAG_CANCEL_CURRENT ) } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadService.kt b/app/src/main/java/org/koitharu/kotatsu/download/DownloadService.kt similarity index 93% rename from app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadService.kt rename to app/src/main/java/org/koitharu/kotatsu/download/DownloadService.kt index 5aef01327..b786e34a6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadService.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/DownloadService.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.download +package org.koitharu.kotatsu.download import android.content.Context import android.content.Intent @@ -7,6 +7,7 @@ import android.os.PowerManager import android.webkit.MimeTypeMap import android.widget.Toast import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import coil.ImageLoader import coil.request.ImageRequest import kotlinx.coroutines.* @@ -19,13 +20,13 @@ import org.koin.android.ext.android.inject import org.koin.core.context.GlobalContext import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.local.PagesCache +import org.koitharu.kotatsu.base.ui.BaseService +import org.koitharu.kotatsu.base.ui.dialog.CheckBoxAlertDialog import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.parser.LocalMangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.domain.local.MangaZip -import org.koitharu.kotatsu.ui.base.BaseService -import org.koitharu.kotatsu.ui.base.dialog.CheckBoxAlertDialog +import org.koitharu.kotatsu.local.data.MangaZip +import org.koitharu.kotatsu.local.data.PagesCache +import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.utils.CacheUtils import org.koitharu.kotatsu.utils.ext.* import java.io.File @@ -54,8 +55,9 @@ class DownloadService : BaseService() { .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "kotatsu:downloading") } - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - when (intent?.action) { + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + super.onStartCommand(intent, flags, startId) + when (intent.action) { ACTION_DOWNLOAD_START -> { val manga = intent.getParcelableExtra(EXTRA_MANGA) val chapters = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toArraySet() @@ -77,7 +79,7 @@ class DownloadService : BaseService() { } private fun downloadManga(manga: Manga, chaptersIds: Set?, startId: Int): Job { - return serviceScope.launch(Dispatchers.Default) { + return lifecycleScope.launch(Dispatchers.Default) { mutex.lock() wakeLock.acquire(TimeUnit.HOURS.toMillis(1)) notification.fillFrom(manga) diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/FavouritesModule.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/FavouritesModule.kt new file mode 100644 index 000000000..926202ea6 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/FavouritesModule.kt @@ -0,0 +1,23 @@ +package org.koitharu.kotatsu.favourites + +import org.koin.android.viewmodel.dsl.viewModel +import org.koin.dsl.module +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.favourites.domain.FavouritesRepository +import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel +import org.koitharu.kotatsu.favourites.ui.categories.select.MangaCategoriesViewModel +import org.koitharu.kotatsu.favourites.ui.list.FavouritesListViewModel + +val favouritesModule + get() = module { + + single { FavouritesRepository(get()) } + + viewModel { (categoryId: Long) -> + FavouritesListViewModel(categoryId, get(), get()) + } + viewModel { FavouritesCategoriesViewModel(get()) } + viewModel { (manga: Manga) -> + MangaCategoriesViewModel(manga, get()) + } + } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/FavouriteCategoriesDao.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt similarity index 80% rename from app/src/main/java/org/koitharu/kotatsu/core/db/dao/FavouriteCategoriesDao.kt rename to app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt index 4392261b1..e56cf6999 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/FavouriteCategoriesDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt @@ -1,7 +1,8 @@ -package org.koitharu.kotatsu.core.db.dao +package org.koitharu.kotatsu.favourites.data import androidx.room.* -import org.koitharu.kotatsu.core.db.entity.FavouriteCategoryEntity +import kotlinx.coroutines.flow.Flow +import org.koitharu.kotatsu.core.model.FavouriteCategory @Dao abstract class FavouriteCategoriesDao { @@ -9,6 +10,9 @@ abstract class FavouriteCategoriesDao { @Query("SELECT * FROM favourite_categories ORDER BY sort_key") abstract suspend fun findAll(): List + @Query("SELECT * FROM favourite_categories ORDER BY sort_key") + abstract fun observeAll(): Flow> + @Insert(onConflict = OnConflictStrategy.ABORT) abstract suspend fun insert(category: FavouriteCategoryEntity): Long diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/FavouriteCategoryEntity.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoryEntity.kt similarity index 93% rename from app/src/main/java/org/koitharu/kotatsu/core/db/entity/FavouriteCategoryEntity.kt rename to app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoryEntity.kt index d495a718f..1da715ae5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/FavouriteCategoryEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoryEntity.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.core.db.entity +package org.koitharu.kotatsu.favourites.data import androidx.room.ColumnInfo import androidx.room.Entity diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/FavouriteEntity.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteEntity.kt similarity index 87% rename from app/src/main/java/org/koitharu/kotatsu/core/db/entity/FavouriteEntity.kt rename to app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteEntity.kt index 182afa5e8..95ae66e87 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/FavouriteEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteEntity.kt @@ -1,8 +1,9 @@ -package org.koitharu.kotatsu.core.db.entity +package org.koitharu.kotatsu.favourites.data import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey +import org.koitharu.kotatsu.core.db.entity.MangaEntity @Entity( tableName = "favourites", primaryKeys = ["manga_id", "category_id"], foreignKeys = [ diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/FavouriteManga.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteManga.kt similarity index 71% rename from app/src/main/java/org/koitharu/kotatsu/core/db/entity/FavouriteManga.kt rename to app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteManga.kt index d641b2508..5b74bc08f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/FavouriteManga.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteManga.kt @@ -1,8 +1,11 @@ -package org.koitharu.kotatsu.core.db.entity +package org.koitharu.kotatsu.favourites.data import androidx.room.Embedded import androidx.room.Junction import androidx.room.Relation +import org.koitharu.kotatsu.core.db.entity.MangaEntity +import org.koitharu.kotatsu.core.db.entity.MangaTagsEntity +import org.koitharu.kotatsu.core.db.entity.TagEntity data class FavouriteManga( @Embedded val favourite: FavouriteEntity, diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/FavouritesDao.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt similarity index 71% rename from app/src/main/java/org/koitharu/kotatsu/core/db/dao/FavouritesDao.kt rename to app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt index 52f6018b4..591dda4c9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/FavouritesDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt @@ -1,8 +1,7 @@ -package org.koitharu.kotatsu.core.db.dao +package org.koitharu.kotatsu.favourites.data import androidx.room.* -import org.koitharu.kotatsu.core.db.entity.FavouriteEntity -import org.koitharu.kotatsu.core.db.entity.FavouriteManga +import kotlinx.coroutines.flow.Flow import org.koitharu.kotatsu.core.db.entity.MangaEntity @Dao @@ -12,6 +11,10 @@ abstract class FavouritesDao { @Query("SELECT * FROM favourites GROUP BY manga_id ORDER BY created_at") abstract suspend fun findAll(): List + @Transaction + @Query("SELECT * FROM favourites GROUP BY manga_id ORDER BY created_at") + abstract fun observeAll(): Flow> + @Transaction @Query("SELECT * FROM favourites GROUP BY manga_id ORDER BY created_at LIMIT :limit OFFSET :offset") abstract suspend fun findAll(offset: Int, limit: Int): List @@ -20,6 +23,10 @@ abstract class FavouritesDao { @Query("SELECT * FROM favourites WHERE category_id = :categoryId GROUP BY manga_id ORDER BY created_at") abstract suspend fun findAll(categoryId: Long): List + @Transaction + @Query("SELECT * FROM favourites WHERE category_id = :categoryId GROUP BY manga_id ORDER BY created_at") + abstract fun observeAll(categoryId: Long): Flow> + @Transaction @Query("SELECT * FROM favourites WHERE category_id = :categoryId GROUP BY manga_id ORDER BY created_at LIMIT :limit OFFSET :offset") abstract suspend fun findAll(categoryId: Long, offset: Int, limit: Int): List @@ -31,6 +38,13 @@ abstract class FavouritesDao { @Query("SELECT * FROM favourites WHERE manga_id = :id GROUP BY manga_id") abstract suspend fun find(id: Long): FavouriteManga? + @Transaction + @Query("SELECT * FROM favourites WHERE manga_id = :id GROUP BY manga_id") + abstract fun observe(id: Long): Flow + + @Query("SELECT DISTINCT category_id FROM favourites WHERE manga_id = :id") + abstract fun observeIds(id: Long): Flow> + @Insert(onConflict = OnConflictStrategy.IGNORE) abstract suspend fun insert(favourite: FavouriteEntity) diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/favourites/FavouritesRepository.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt similarity index 77% rename from app/src/main/java/org/koitharu/kotatsu/domain/favourites/FavouritesRepository.kt rename to app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt index f8a5bb3ff..ddd14e256 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/favourites/FavouritesRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt @@ -1,16 +1,19 @@ -package org.koitharu.kotatsu.domain.favourites +package org.koitharu.kotatsu.favourites.domain import androidx.collection.ArraySet import androidx.room.withTransaction import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import org.koitharu.kotatsu.core.db.MangaDatabase -import org.koitharu.kotatsu.core.db.entity.FavouriteCategoryEntity -import org.koitharu.kotatsu.core.db.entity.FavouriteEntity import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.TagEntity import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity +import org.koitharu.kotatsu.favourites.data.FavouriteEntity +import org.koitharu.kotatsu.utils.ext.mapItems import org.koitharu.kotatsu.utils.ext.mapToSet class FavouritesRepository(private val db: MangaDatabase) { @@ -20,6 +23,11 @@ class FavouritesRepository(private val db: MangaDatabase) { return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } } + fun observeAll(): Flow> { + return db.favouritesDao.observeAll() + .mapItems { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } + } + suspend fun getAllManga(offset: Int): List { val entities = db.favouritesDao.findAll(offset, 20) return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } @@ -30,6 +38,11 @@ class FavouritesRepository(private val db: MangaDatabase) { return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } } + fun observeAll(categoryId: Long): Flow> { + return db.favouritesDao.observeAll(categoryId) + .mapItems { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } + } + suspend fun getManga(categoryId: Long, offset: Int): List { val entities = db.favouritesDao.findAll(categoryId, offset, 20) return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } @@ -45,6 +58,22 @@ class FavouritesRepository(private val db: MangaDatabase) { return entities?.map { it.toFavouriteCategory() }.orEmpty() } + fun observeCategories(): Flow> { + return db.favouriteCategoriesDao.observeAll().mapItems { + it.toFavouriteCategory() + } + } + + fun observeCategories(mangaId: Long): Flow> { + return db.favouritesDao.observe(mangaId).map { entity -> + entity?.categories?.map { it.toFavouriteCategory() }.orEmpty() + } + } + + fun observeCategoriesIds(mangaId: Long): Flow> { + return db.favouritesDao.observeIds(mangaId) + } + suspend fun addCategory(title: String): FavouriteCategory { val entity = FavouriteCategoryEntity( title = title, diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/favourites/OnFavouritesChangeListener.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/OnFavouritesChangeListener.kt similarity index 62% rename from app/src/main/java/org/koitharu/kotatsu/domain/favourites/OnFavouritesChangeListener.kt rename to app/src/main/java/org/koitharu/kotatsu/favourites/domain/OnFavouritesChangeListener.kt index 8796b4439..1a9c7b2c1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/favourites/OnFavouritesChangeListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/OnFavouritesChangeListener.kt @@ -1,5 +1,6 @@ -package org.koitharu.kotatsu.domain.favourites +package org.koitharu.kotatsu.favourites.domain +@Deprecated("Use flow") fun interface OnFavouritesChangeListener { fun onFavouritesChanged(mangaId: Long) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesContainerFragment.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt similarity index 64% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesContainerFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt index 3bc872ec4..7f9ace391 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesContainerFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.list.favourites +package org.koitharu.kotatsu.favourites.ui import android.os.Bundle import android.view.Menu @@ -8,25 +8,22 @@ import android.view.View import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayoutMediator import kotlinx.android.synthetic.main.fragment_favourites.* -import moxy.ktx.moxyPresenter +import org.koin.android.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.domain.favourites.FavouritesRepository -import org.koitharu.kotatsu.domain.favourites.OnFavouritesChangeListener -import org.koitharu.kotatsu.ui.base.BaseFragment -import org.koitharu.kotatsu.ui.list.favourites.categories.CategoriesActivity -import org.koitharu.kotatsu.ui.list.favourites.categories.CategoriesEditDelegate -import org.koitharu.kotatsu.ui.list.favourites.categories.FavouriteCategoriesPresenter -import org.koitharu.kotatsu.ui.list.favourites.categories.FavouriteCategoriesView +import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity +import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate +import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel import org.koitharu.kotatsu.utils.ext.showPopupMenu import java.util.* import kotlin.collections.ArrayList class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites), - FavouriteCategoriesView, OnFavouritesChangeListener, FavouritesTabLongClickListener, - CategoriesEditDelegate.CategoriesEditCallback { + FavouritesTabLongClickListener, CategoriesEditDelegate.CategoriesEditCallback { + + private val viewModel by viewModel() - private val presenter by moxyPresenter(factory = ::FavouriteCategoriesPresenter) private val editDelegate by lazy(LazyThreadSafetyMode.NONE) { CategoriesEditDelegate(requireContext(), this) } @@ -41,15 +38,12 @@ class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites), val adapter = FavouritesPagerAdapter(this, this) pager.adapter = adapter TabLayoutMediator(tabs, pager, adapter).attach() - FavouritesRepository.subscribe(this) - } - override fun onDestroyView() { - FavouritesRepository.unsubscribe(this) - super.onDestroyView() + viewModel.categories.observe(viewLifecycleOwner, ::onCategoriesChanged) + viewModel.onError.observe(viewLifecycleOwner, ::onError) } - override fun onCategoriesChanged(categories: List) { + private fun onCategoriesChanged(categories: List) { val data = ArrayList(categories.size + 1) data += FavouriteCategory(0L, getString(R.string.all_favourites), -1, Date()) data += categories @@ -72,21 +66,13 @@ class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites), } override fun getTitle(): CharSequence? { - return getString(R.string.favourites) + return context?.getString(R.string.favourites) } - override fun onCheckedCategoriesChanged(checkedIds: Set) = Unit - - override fun onError(e: Throwable) { + private fun onError(e: Throwable) { Snackbar.make(pager, e.message ?: return, Snackbar.LENGTH_LONG).show() } - override fun onFavouritesChanged(mangaId: Long) = Unit - - override fun onCategoriesChanged() { - presenter.loadAllCategories() - } - override fun onTabLongClick(tabView: View, category: FavouriteCategory): Boolean { val menuRes = if (category.id == 0L) R.menu.popup_category_empty else R.menu.popup_category tabView.showPopupMenu(menuRes) { @@ -101,15 +87,15 @@ class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites), } override fun onDeleteCategory(category: FavouriteCategory) { - presenter.deleteCategory(category.id) + viewModel.deleteCategory(category.id) } override fun onRenameCategory(category: FavouriteCategory, newName: String) { - presenter.renameCategory(category.id, newName) + viewModel.renameCategory(category.id, newName) } override fun onCreateCategory(name: String) { - presenter.createCategory(name) + viewModel.createCategory(name) } companion object { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesPagerAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesPagerAdapter.kt similarity index 88% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesPagerAdapter.kt rename to app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesPagerAdapter.kt index f8db9be9c..8da63e64c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesPagerAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesPagerAdapter.kt @@ -1,12 +1,13 @@ -package org.koitharu.kotatsu.ui.list.favourites +package org.koitharu.kotatsu.favourites.ui import android.view.View import androidx.fragment.app.Fragment import androidx.viewpager2.adapter.FragmentStateAdapter import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator +import org.koitharu.kotatsu.base.ui.list.AdapterUpdater import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.ui.base.list.AdapterUpdater +import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment import org.koitharu.kotatsu.utils.ext.replaceWith class FavouritesPagerAdapter( diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesTabLongClickListener.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesTabLongClickListener.kt similarity index 80% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesTabLongClickListener.kt rename to app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesTabLongClickListener.kt index 63709f134..acc4f89c0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesTabLongClickListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesTabLongClickListener.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.list.favourites +package org.koitharu.kotatsu.favourites.ui import android.view.View import org.koitharu.kotatsu.core.model.FavouriteCategory diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/CategoriesActivity.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoriesActivity.kt similarity index 70% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/CategoriesActivity.kt rename to app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoriesActivity.kt index 2ba84ce7d..d722348a8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/CategoriesActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoriesActivity.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.list.favourites.categories +package org.koitharu.kotatsu.favourites.ui.categories import android.content.Context import android.content.Intent @@ -12,18 +12,18 @@ import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.activity_categories.* -import moxy.ktx.moxyPresenter +import org.koin.android.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BaseActivity +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.ui.base.BaseActivity -import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.showPopupMenu -class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener, - FavouriteCategoriesView, View.OnClickListener, CategoriesEditDelegate.CategoriesEditCallback { +class CategoriesActivity : BaseActivity(), OnListItemClickListener, + View.OnClickListener, CategoriesEditDelegate.CategoriesEditCallback { - private val presenter by moxyPresenter(factory = ::FavouriteCategoriesPresenter) + private val viewModel by viewModel() private lateinit var adapter: CategoriesAdapter private lateinit var reorderHelper: ItemTouchHelper @@ -41,6 +41,9 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener editDelegate.deleteCategory(item) @@ -59,35 +62,33 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener) { - adapter.replaceData(categories) + private fun onCategoriesChanged(categories: List) { + adapter.items = categories textView_holder.isVisible = categories.isEmpty() } - override fun onCheckedCategoriesChanged(checkedIds: Set) = Unit - - override fun onError(e: Throwable) { + private fun onError(e: Throwable) { Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG) .show() } override fun onDeleteCategory(category: FavouriteCategory) { - presenter.deleteCategory(category.id) + viewModel.deleteCategory(category.id) } override fun onRenameCategory(category: FavouriteCategory, newName: String) { - presenter.renameCategory(category.id, newName) + viewModel.renameCategory(category.id, newName) } override fun onCreateCategory(name: String) { - presenter.createCategory(name) + viewModel.createCategory(name) } private inner class ReorderHelperCallback : ItemTouchHelper.SimpleCallback( @@ -101,8 +102,7 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener +) : AsyncListDifferDelegationAdapter(DiffCallback()) { + + init { + delegatesManager.addDelegate(categoryAD(onItemClickListener)) + } + + private class DiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: FavouriteCategory, newItem: FavouriteCategory): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: FavouriteCategory, newItem: FavouriteCategory): Boolean { + return Intrinsics.areEqual(oldItem, newItem) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/CategoriesEditDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoriesEditDelegate.kt similarity index 88% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/CategoriesEditDelegate.kt rename to app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoriesEditDelegate.kt index 1401e2213..e10ec58ee 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/CategoriesEditDelegate.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoriesEditDelegate.kt @@ -1,11 +1,11 @@ -package org.koitharu.kotatsu.ui.list.favourites.categories +package org.koitharu.kotatsu.favourites.ui.categories import android.content.Context import android.text.InputType -import com.google.android.material.dialog.MaterialAlertDialogBuilder +import androidx.appcompat.app.AlertDialog import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.dialog.TextInputDialog import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.ui.base.dialog.TextInputDialog class CategoriesEditDelegate( private val context: Context, @@ -13,7 +13,7 @@ class CategoriesEditDelegate( ) { fun deleteCategory(category: FavouriteCategory) { - MaterialAlertDialogBuilder(context) + AlertDialog.Builder(context) .setMessage(context.getString(R.string.category_delete_confirm, category.title)) .setTitle(R.string.remove_category) .setNegativeButton(android.R.string.cancel, null) diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoryAD.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoryAD.kt new file mode 100644 index 000000000..0bfd98070 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoryAD.kt @@ -0,0 +1,29 @@ +package org.koitharu.kotatsu.favourites.ui.categories + +import android.view.MotionEvent +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer +import kotlinx.android.synthetic.main.item_category.* +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.model.FavouriteCategory + +fun categoryAD( + clickListener: OnListItemClickListener +) = adapterDelegateLayoutContainer(R.layout.item_category) { + + imageView_more.setOnClickListener { + clickListener.onItemClick(item, it) + } + @Suppress("ClickableViewAccessibility") + imageView_handle.setOnTouchListener { v, event -> + if (event.actionMasked == MotionEvent.ACTION_DOWN) { + clickListener.onItemLongClick(item, itemView) + } else { + false + } + } + + bind { + textView_title.text = item.title + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt new file mode 100644 index 000000000..557515cba --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt @@ -0,0 +1,48 @@ +package org.koitharu.kotatsu.favourites.ui.categories + +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.favourites.domain.FavouritesRepository + +class FavouritesCategoriesViewModel( + private val repository: FavouritesRepository +) : BaseViewModel() { + + private var reorderJob: Job? = null + + val categories = repository.observeCategories() + .asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + + fun createCategory(name: String) { + launchJob(Dispatchers.Default) { + repository.addCategory(name) + } + } + + fun renameCategory(id: Long, name: String) { + launchJob(Dispatchers.Default) { + repository.renameCategory(id, name) + } + } + + fun deleteCategory(id: Long) { + launchJob(Dispatchers.Default) { + repository.removeCategory(id) + } + } + + fun reorderCategories(oldPos: Int, newPos: Int) { + val prevJob = reorderJob + reorderJob = launchJob(Dispatchers.Default) { + prevJob?.join() + val items = categories.value ?: error("This should not happen") + val ids = items.mapTo(ArrayList(items.size)) { it.id } + val item = ids.removeAt(oldPos) + ids.add(newPos, item) + repository.reorderCategories(ids) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/FavouriteCategoriesDialog.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/FavouriteCategoriesDialog.kt new file mode 100644 index 000000000..2754a9264 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/FavouriteCategoriesDialog.kt @@ -0,0 +1,85 @@ +package org.koitharu.kotatsu.favourites.ui.categories.select + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.fragment.app.FragmentManager +import kotlinx.android.synthetic.main.dialog_favorite_categories.* +import org.koin.android.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.domain.MangaIntent +import org.koitharu.kotatsu.base.ui.BaseBottomSheet +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.model.FavouriteCategory +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate +import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter +import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem +import org.koitharu.kotatsu.utils.ext.getDisplayMessage +import org.koitharu.kotatsu.utils.ext.withArgs + +class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categories), + OnListItemClickListener, CategoriesEditDelegate.CategoriesEditCallback, + View.OnClickListener { + + private val viewModel by viewModel { + parametersOf(requireNotNull(arguments?.getParcelable(MangaIntent.KEY_MANGA))) + } + + private var adapter: MangaCategoriesAdapter? = null + private val editDelegate by lazy(LazyThreadSafetyMode.NONE) { + CategoriesEditDelegate(requireContext(), this@FavouriteCategoriesDialog) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + adapter = MangaCategoriesAdapter(this) + recyclerView_categories.adapter = adapter + textView_add.setOnClickListener(this) + + viewModel.content.observe(viewLifecycleOwner, this::onContentChanged) + viewModel.onError.observe(viewLifecycleOwner, ::onError) + } + + override fun onDestroyView() { + adapter = null + super.onDestroyView() + } + + override fun onClick(v: View) { + when (v.id) { + R.id.textView_add -> editDelegate.createCategory() + } + } + + override fun onItemClick(item: MangaCategoryItem, view: View) { + viewModel.setChecked(item.id, !item.isChecked) + } + + override fun onDeleteCategory(category: FavouriteCategory) = Unit + + override fun onRenameCategory(category: FavouriteCategory, newName: String) = Unit + + override fun onCreateCategory(name: String) { + viewModel.createCategory(name) + } + + private fun onContentChanged(categories: List) { + adapter?.items = categories + } + + private fun onError(e: Throwable) { + Toast.makeText(context ?: return, e.getDisplayMessage(resources), Toast.LENGTH_SHORT).show() + } + + companion object { + + private const val TAG = "FavouriteCategoriesDialog" + + fun show(fm: FragmentManager, manga: Manga) = FavouriteCategoriesDialog() + .withArgs(1) { + putParcelable(MangaIntent.KEY_MANGA, manga) + }.show(fm, TAG) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/MangaCategoriesViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/MangaCategoriesViewModel.kt new file mode 100644 index 000000000..ced611e9c --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/MangaCategoriesViewModel.kt @@ -0,0 +1,45 @@ +package org.koitharu.kotatsu.favourites.ui.categories.select + +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.combine +import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.favourites.domain.FavouritesRepository +import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem + +class MangaCategoriesViewModel( + private val manga: Manga, + private val favouritesRepository: FavouritesRepository +) : BaseViewModel() { + + val content = combine( + favouritesRepository.observeCategories(), + favouritesRepository.observeCategoriesIds(manga.id) + ) { all, checked -> + all.map { + MangaCategoryItem( + id = it.id, + name = it.title, + isChecked = it.id in checked + ) + } + }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + + fun setChecked(categoryId: Long, isChecked: Boolean) { + launchJob(Dispatchers.Default) { + if (isChecked) { + favouritesRepository.addToCategory(manga, categoryId) + } else { + favouritesRepository.removeFromCategory(manga, categoryId) + } + } + } + + fun createCategory(name: String) { + launchJob(Dispatchers.Default) { + favouritesRepository.addCategory(name) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/MangaCaegoryAD.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/MangaCaegoryAD.kt new file mode 100644 index 000000000..ae84a47d6 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/MangaCaegoryAD.kt @@ -0,0 +1,23 @@ +package org.koitharu.kotatsu.favourites.ui.categories.select.adapter + +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer +import kotlinx.android.synthetic.main.item_category_checkable.* +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem + +fun mangaCategoryAD( + clickListener: OnListItemClickListener +) = adapterDelegateLayoutContainer( + R.layout.item_category_checkable +) { + + itemView.setOnClickListener { + clickListener.onItemClick(item, itemView) + } + + bind { + checkedTextView.text = item.name + checkedTextView.isChecked = item.isChecked + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/MangaCategoriesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/MangaCategoriesAdapter.kt new file mode 100644 index 000000000..df6e54ca0 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/MangaCategoriesAdapter.kt @@ -0,0 +1,37 @@ +package org.koitharu.kotatsu.favourites.ui.categories.select.adapter + +import androidx.recyclerview.widget.DiffUtil +import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem + +class MangaCategoriesAdapter( + clickListener: OnListItemClickListener +) : AsyncListDifferDelegationAdapter(DiffCallback()) { + + init { + delegatesManager.addDelegate(mangaCategoryAD(clickListener)) + } + + private class DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: MangaCategoryItem, + newItem: MangaCategoryItem + ): Boolean = oldItem.id == newItem.id + + override fun areContentsTheSame( + oldItem: MangaCategoryItem, + newItem: MangaCategoryItem + ): Boolean = oldItem == newItem + + override fun getChangePayload( + oldItem: MangaCategoryItem, + newItem: MangaCategoryItem + ): Any? { + if (oldItem.isChecked != newItem.isChecked) { + return newItem.isChecked + } + return super.getChangePayload(oldItem, newItem) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/model/MangaCategoryItem.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/model/MangaCategoryItem.kt new file mode 100644 index 000000000..95447a2af --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/select/model/MangaCategoryItem.kt @@ -0,0 +1,7 @@ +package org.koitharu.kotatsu.favourites.ui.categories.select.model + +data class MangaCategoryItem( + val id: Long, + val name: String, + val isChecked: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt similarity index 68% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesListFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt index a9a2d9816..df3eb9e47 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt @@ -1,29 +1,28 @@ -package org.koitharu.kotatsu.ui.list.favourites +package org.koitharu.kotatsu.favourites.ui.list import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import kotlinx.android.synthetic.main.fragment_list.* -import moxy.ktx.moxyPresenter -import org.koin.android.ext.android.get +import org.koin.android.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.ui.list.MangaListFragment -import org.koitharu.kotatsu.ui.list.MangaListView +import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.utils.ext.withArgs -class FavouritesListFragment : MangaListFragment(), MangaListView { +class FavouritesListFragment : MangaListFragment() { - private val presenter by moxyPresenter { - FavouritesListPresenter(categoryId, get()) + override val viewModel by viewModel { + parametersOf(categoryId) } private val categoryId: Long get() = arguments?.getLong(ARG_CATEGORY_ID) ?: 0L - override fun onRequestMoreItems(offset: Int) { - presenter.loadList(offset) - } + override val isSwipeRefreshEnabled = false + + override fun onScrolledToEnd() = Unit override fun setUpEmptyListHolder() { textView_holder.setText( @@ -41,9 +40,9 @@ class FavouritesListFragment : MangaListFragment(), MangaListView { inflater.inflate(R.menu.popup_favourites, menu) } - override fun onPopupMenuItemSelected(item: MenuItem, data: Manga) = when(item.itemId) { + override fun onPopupMenuItemSelected(item: MenuItem, data: Manga) = when (item.itemId) { R.id.action_remove -> { - presenter.removeFromFavourites(data) + viewModel.removeFromFavourites(data) true } else -> super.onPopupMenuItemSelected(item, data) diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt new file mode 100644 index 000000000..e6daa5a75 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt @@ -0,0 +1,51 @@ +package org.koitharu.kotatsu.favourites.ui.list + +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.ListMode +import org.koitharu.kotatsu.favourites.domain.FavouritesRepository +import org.koitharu.kotatsu.list.ui.MangaListViewModel +import org.koitharu.kotatsu.list.ui.model.toGridModel +import org.koitharu.kotatsu.list.ui.model.toListDetailedModel +import org.koitharu.kotatsu.list.ui.model.toListModel +import org.koitharu.kotatsu.utils.ext.onFirst + +class FavouritesListViewModel( + private val categoryId: Long, + private val repository: FavouritesRepository, + settings: AppSettings +) : MangaListViewModel(settings) { + + override val content = combine( + if (categoryId == 0L) repository.observeAll() else repository.observeAll(categoryId), + createListModeFlow() + ) { list, mode -> + when (mode) { + ListMode.LIST -> list.map { it.toListModel() } + ListMode.DETAILED_LIST -> list.map { it.toListDetailedModel() } + ListMode.GRID -> list.map { it.toGridModel() } + } + }.onEach { + isEmptyState.postValue(it.isEmpty()) + }.onStart { + isLoading.postValue(true) + }.onFirst { + isLoading.postValue(false) + }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + + fun removeFromFavourites(manga: Manga) { + launchJob { + if (categoryId == 0L) { + repository.removeFromFavourites(manga) + } else { + repository.removeFromCategory(manga, categoryId) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/history/HistoryModule.kt b/app/src/main/java/org/koitharu/kotatsu/history/HistoryModule.kt new file mode 100644 index 000000000..fe15db497 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/history/HistoryModule.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.history + +import org.koin.android.ext.koin.androidContext +import org.koin.android.viewmodel.dsl.viewModel +import org.koin.dsl.module +import org.koitharu.kotatsu.history.domain.HistoryRepository +import org.koitharu.kotatsu.history.ui.HistoryListViewModel + +val historyModule + get() = module { + + single { HistoryRepository(get()) } + viewModel { HistoryListViewModel(get(), androidContext(), get()) } + } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/HistoryDao.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt similarity index 81% rename from app/src/main/java/org/koitharu/kotatsu/core/db/dao/HistoryDao.kt rename to app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt index ca768f982..9ee2642fb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/HistoryDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt @@ -1,8 +1,7 @@ -package org.koitharu.kotatsu.core.db.dao +package org.koitharu.kotatsu.history.data import androidx.room.* -import org.koitharu.kotatsu.core.db.entity.HistoryEntity -import org.koitharu.kotatsu.core.db.entity.HistoryWithManga +import kotlinx.coroutines.flow.Flow import org.koitharu.kotatsu.core.db.entity.MangaEntity @@ -16,12 +15,19 @@ abstract class HistoryDao { @Query("SELECT * FROM history ORDER BY updated_at DESC LIMIT :limit OFFSET :offset") abstract suspend fun findAll(offset: Int, limit: Int): List + @Transaction + @Query("SELECT * FROM history ORDER BY updated_at DESC") + abstract fun observeAll(): Flow> + @Query("SELECT * FROM manga WHERE manga_id IN (SELECT manga_id FROM history)") abstract suspend fun findAllManga(): List @Query("SELECT * FROM history WHERE manga_id = :id") abstract suspend fun find(id: Long): HistoryEntity? + @Query("SELECT * FROM history WHERE manga_id = :id") + abstract fun observe(id: Long): Flow + @Query("DELETE FROM history") abstract suspend fun clear() diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/HistoryEntity.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt similarity index 90% rename from app/src/main/java/org/koitharu/kotatsu/core/db/entity/HistoryEntity.kt rename to app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt index 43d865aad..34a75b6a5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/HistoryEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt @@ -1,9 +1,10 @@ -package org.koitharu.kotatsu.core.db.entity +package org.koitharu.kotatsu.history.data import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey +import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.model.MangaHistory import java.util.* diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/HistoryWithManga.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryWithManga.kt similarity index 65% rename from app/src/main/java/org/koitharu/kotatsu/core/db/entity/HistoryWithManga.kt rename to app/src/main/java/org/koitharu/kotatsu/history/data/HistoryWithManga.kt index 76d1edb6a..d1c8ee2b0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/HistoryWithManga.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryWithManga.kt @@ -1,8 +1,11 @@ -package org.koitharu.kotatsu.core.db.entity +package org.koitharu.kotatsu.history.data import androidx.room.Embedded import androidx.room.Junction import androidx.room.Relation +import org.koitharu.kotatsu.core.db.entity.MangaEntity +import org.koitharu.kotatsu.core.db.entity.MangaTagsEntity +import org.koitharu.kotatsu.core.db.entity.TagEntity data class HistoryWithManga( @Embedded val history: HistoryEntity, diff --git a/app/src/main/java/org/koitharu/kotatsu/history/domain/ChapterExtra.kt b/app/src/main/java/org/koitharu/kotatsu/history/domain/ChapterExtra.kt new file mode 100644 index 000000000..32178c19d --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/history/domain/ChapterExtra.kt @@ -0,0 +1,6 @@ +package org.koitharu.kotatsu.history.domain + +enum class ChapterExtra { + + READ, CURRENT, UNREAD, NEW +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/history/HistoryRepository.kt b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt similarity index 81% rename from app/src/main/java/org/koitharu/kotatsu/domain/history/HistoryRepository.kt rename to app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt index 47efb59ac..76f98590a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/history/HistoryRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt @@ -1,18 +1,21 @@ -package org.koitharu.kotatsu.domain.history +package org.koitharu.kotatsu.history.domain import androidx.collection.ArraySet import androidx.room.withTransaction import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent import org.koin.core.component.inject import org.koitharu.kotatsu.core.db.MangaDatabase -import org.koitharu.kotatsu.core.db.entity.HistoryEntity import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.TagEntity import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaHistory -import org.koitharu.kotatsu.domain.tracking.TrackingRepository +import org.koitharu.kotatsu.history.data.HistoryEntity +import org.koitharu.kotatsu.tracker.domain.TrackingRepository +import org.koitharu.kotatsu.utils.ext.mapItems import org.koitharu.kotatsu.utils.ext.mapToSet class HistoryRepository(private val db: MangaDatabase) : KoinComponent { @@ -24,6 +27,18 @@ class HistoryRepository(private val db: MangaDatabase) : KoinComponent { return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } } + fun observeAll(): Flow> { + return db.historyDao.observeAll().mapItems { + it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) + } + } + + fun observeOne(id: Long): Flow { + return db.historyDao.observe(id).map { + it?.toMangaHistory() + } + } + suspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int, scroll: Int) { val tags = manga.tags.map(TagEntity.Companion::fromMangaTag) db.withTransaction { diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/history/OnHistoryChangeListener.kt b/app/src/main/java/org/koitharu/kotatsu/history/domain/OnHistoryChangeListener.kt similarity index 59% rename from app/src/main/java/org/koitharu/kotatsu/domain/history/OnHistoryChangeListener.kt rename to app/src/main/java/org/koitharu/kotatsu/history/domain/OnHistoryChangeListener.kt index ee70791a9..f612318a3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/history/OnHistoryChangeListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/domain/OnHistoryChangeListener.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.domain.history +package org.koitharu.kotatsu.history.domain fun interface OnHistoryChangeListener { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/history/HistoryListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt similarity index 69% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/history/HistoryListFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt index 79723bf5c..2dcc7cefb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/history/HistoryListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt @@ -1,28 +1,31 @@ -package org.koitharu.kotatsu.ui.list.history +package org.koitharu.kotatsu.history.ui +import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem -import com.google.android.material.dialog.MaterialAlertDialogBuilder +import android.view.View +import androidx.appcompat.app.AlertDialog import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_list.* -import moxy.ktx.moxyPresenter +import org.koin.android.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaHistory -import org.koitharu.kotatsu.ui.list.MangaListFragment -import org.koitharu.kotatsu.ui.list.MangaListView +import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.utils.ext.ellipsize -class HistoryListFragment : MangaListFragment(), - MangaListView { +class HistoryListFragment : MangaListFragment() { - private val presenter by moxyPresenter(factory = ::HistoryListPresenter) + override val viewModel by viewModel() + override val isSwipeRefreshEnabled = false - override fun onRequestMoreItems(offset: Int) { - presenter.loadList(offset) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.onItemRemoved.observe(viewLifecycleOwner, ::onItemRemoved) } + override fun onScrolledToEnd() = Unit + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.opt_history, menu) super.onCreateOptionsMenu(menu, inflater) @@ -31,12 +34,12 @@ class HistoryListFragment : MangaListFragment(), override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.action_clear_history -> { - MaterialAlertDialogBuilder(context ?: return false) + AlertDialog.Builder(context ?: return false) .setTitle(R.string.clear_history) .setMessage(R.string.text_clear_history_prompt) .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(R.string.clear) { _, _ -> - presenter.clearHistory() + viewModel.clearHistory() }.show() true } @@ -61,15 +64,14 @@ class HistoryListFragment : MangaListFragment(), override fun onPopupMenuItemSelected(item: MenuItem, data: Manga): Boolean { return when (item.itemId) { R.id.action_remove -> { - presenter.removeFromHistory(data) + viewModel.removeFromHistory(data) true } else -> super.onPopupMenuItemSelected(item, data) } } - override fun onItemRemoved(item: Manga) { - super.onItemRemoved(item) + fun onItemRemoved(item: Manga) { Snackbar.make( recyclerView, getString( R.string._s_removed_from_history, diff --git a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt new file mode 100644 index 000000000..d31ccfa0b --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt @@ -0,0 +1,67 @@ +package org.koitharu.kotatsu.history.ui + +import android.content.Context +import android.os.Build +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.ListMode +import org.koitharu.kotatsu.history.domain.HistoryRepository +import org.koitharu.kotatsu.list.ui.MangaListViewModel +import org.koitharu.kotatsu.list.ui.model.toGridModel +import org.koitharu.kotatsu.list.ui.model.toListDetailedModel +import org.koitharu.kotatsu.list.ui.model.toListModel +import org.koitharu.kotatsu.utils.MangaShortcut +import org.koitharu.kotatsu.utils.SingleLiveEvent +import org.koitharu.kotatsu.utils.ext.onFirst + +class HistoryListViewModel( + private val repository: HistoryRepository, + private val context: Context //todo create ShortcutRepository + , settings: AppSettings +) : MangaListViewModel(settings) { + + val onItemRemoved = SingleLiveEvent() + + override val content = combine( + repository.observeAll(), + createListModeFlow() + ) { list, mode -> + when (mode) { + ListMode.LIST -> list.map { it.toListModel() } + ListMode.DETAILED_LIST -> list.map { it.toListDetailedModel() } + ListMode.GRID -> list.map { it.toGridModel() } + } + }.onEach { + isEmptyState.postValue(it.isEmpty()) + }.onStart { + isLoading.postValue(true) + }.onFirst { + isLoading.postValue(false) + }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + + fun clearHistory() { + launchLoadingJob { + repository.clear() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + MangaShortcut.clearAppShortcuts(context) + } + } + } + + fun removeFromHistory(manga: Manga) { + launchJob { + repository.delete(manga) + onItemRemoved.call(manga) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + MangaShortcut(manga).removeAppShortcut(context) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/ListModeSelectDialog.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/ListModeSelectDialog.kt similarity index 65% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/ListModeSelectDialog.kt rename to app/src/main/java/org/koitharu/kotatsu/list/ui/ListModeSelectDialog.kt index ac4d29fc3..78491def3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/ListModeSelectDialog.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/ListModeSelectDialog.kt @@ -1,29 +1,34 @@ -package org.koitharu.kotatsu.ui.list +package org.koitharu.kotatsu.list.ui import android.os.Bundle import android.view.View +import android.widget.SeekBar import androidx.appcompat.app.AlertDialog import androidx.fragment.app.FragmentManager import kotlinx.android.synthetic.main.dialog_list_mode.* import org.koin.android.ext.android.inject import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ListMode -import org.koitharu.kotatsu.ui.base.AlertDialogFragment -class ListModeSelectDialog : AlertDialogFragment(R.layout.dialog_list_mode), View.OnClickListener { +class ListModeSelectDialog : AlertDialogFragment(R.layout.dialog_list_mode), View.OnClickListener, + SeekBar.OnSeekBarChangeListener { private val settings by inject() - private lateinit var mode: ListMode + private var mode: ListMode = ListMode.GRID + private var pendingGridSize: Int = 100 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mode = settings.listMode + pendingGridSize = settings.gridSize } override fun onBuildDialog(builder: AlertDialog.Builder) { builder.setTitle(R.string.list_mode) + .setPositiveButton(R.string.done, null) .setCancelable(true) } @@ -33,22 +38,33 @@ class ListModeSelectDialog : AlertDialogFragment(R.layout.dialog_list_mode), Vie button_list_detailed.isChecked = mode == ListMode.DETAILED_LIST button_grid.isChecked = mode == ListMode.GRID - button_ok.setOnClickListener(this) + with(seekbar_grid) { + progress = pendingGridSize - 50 + setOnSeekBarChangeListener(this@ListModeSelectDialog) + } + button_list.setOnClickListener(this) button_grid.setOnClickListener(this) button_list_detailed.setOnClickListener(this) } + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + pendingGridSize = progress + 50 + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit + + override fun onStopTrackingTouch(seekBar: SeekBar?) { + settings.gridSize = pendingGridSize + } + override fun onClick(v: View) { when (v.id) { - R.id.button_ok -> { - settings.listMode = mode - dismiss() - } R.id.button_list -> mode = ListMode.LIST R.id.button_list_detailed -> mode = ListMode.DETAILED_LIST R.id.button_grid -> mode = ListMode.GRID } + settings.listMode = mode } companion object { diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaFilterConfig.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaFilterConfig.kt new file mode 100644 index 000000000..e2147aa97 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaFilterConfig.kt @@ -0,0 +1,11 @@ +package org.koitharu.kotatsu.list.ui + +import org.koitharu.kotatsu.core.model.MangaFilter +import org.koitharu.kotatsu.core.model.MangaTag +import org.koitharu.kotatsu.core.model.SortOrder + +data class MangaFilterConfig( + val sortOrders: List, + val tags: List, + val currentFilter: MangaFilter? +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt new file mode 100644 index 000000000..1ea0a3398 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt @@ -0,0 +1,286 @@ +package org.koitharu.kotatsu.list.ui + +import android.os.Bundle +import android.view.* +import androidx.annotation.CallSuper +import androidx.appcompat.widget.PopupMenu +import androidx.core.view.GravityCompat +import androidx.core.view.isGone +import androidx.core.view.isVisible +import androidx.drawerlayout.widget.DrawerLayout +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.google.android.material.snackbar.Snackbar +import kotlinx.android.synthetic.main.fragment_list.* +import org.koin.android.ext.android.get +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BaseFragment +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener +import org.koitharu.kotatsu.base.ui.list.decor.ItemTypeDividerDecoration +import org.koitharu.kotatsu.base.ui.list.decor.SectionItemDecoration +import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration +import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog +import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.model.MangaFilter +import org.koitharu.kotatsu.core.prefs.ListMode +import org.koitharu.kotatsu.details.ui.DetailsActivity +import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter +import org.koitharu.kotatsu.list.ui.filter.FilterAdapter +import org.koitharu.kotatsu.list.ui.filter.OnFilterChangedListener +import org.koitharu.kotatsu.utils.ext.clearItemDecorations +import org.koitharu.kotatsu.utils.ext.getDisplayMessage +import org.koitharu.kotatsu.utils.ext.hasItems +import org.koitharu.kotatsu.utils.ext.toggleDrawer + +abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), + PaginationScrollListener.Callback, OnListItemClickListener, OnFilterChangedListener, + SectionItemDecoration.Callback, SwipeRefreshLayout.OnRefreshListener { + + private var adapter: MangaListAdapter? = null + private var paginationListener: PaginationScrollListener? = null + private val spanResolver = MangaListSpanResolver() + private val spanSizeLookup = SpanSizeLookup() + open val isSwipeRefreshEnabled = true + + protected abstract val viewModel: MangaListViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + drawer?.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) + adapter = MangaListAdapter(get(), this) + paginationListener = PaginationScrollListener(4, this) + recyclerView.setHasFixedSize(true) + recyclerView.adapter = adapter + recyclerView.addOnScrollListener(paginationListener!!) + swipeRefreshLayout.setOnRefreshListener(this) + swipeRefreshLayout.isEnabled = isSwipeRefreshEnabled + recyclerView_filter.setHasFixedSize(true) + recyclerView_filter.addItemDecoration(ItemTypeDividerDecoration(view.context)) + recyclerView_filter.addItemDecoration(SectionItemDecoration(false, this)) + + viewModel.content.observe(viewLifecycleOwner, ::onListChanged) + viewModel.filter.observe(viewLifecycleOwner, ::onInitFilter) + viewModel.onError.observe(viewLifecycleOwner, ::onError) + viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged) + viewModel.listMode.observe(viewLifecycleOwner, ::onListModeChanged) + viewModel.gridScale.observe(viewLifecycleOwner, ::onGridScaleChanged) + viewModel.isEmptyState.observe(viewLifecycleOwner, ::onEmptyStateChanged) + } + + override fun onDestroyView() { + adapter = null + paginationListener = null + spanSizeLookup.invalidateCache() + super.onDestroyView() + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.opt_list, menu) + super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.action_list_mode -> { + ListModeSelectDialog.show(childFragmentManager) + true + } + R.id.action_filter -> { + drawer?.toggleDrawer(GravityCompat.END) + true + } + else -> super.onOptionsItemSelected(item) + } + + override fun onPrepareOptionsMenu(menu: Menu) { + menu.findItem(R.id.action_filter).isVisible = drawer != null && + drawer?.getDrawerLockMode(GravityCompat.END) != DrawerLayout.LOCK_MODE_LOCKED_CLOSED + super.onPrepareOptionsMenu(menu) + } + + override fun onItemClick(item: Manga, view: View) { + startActivity(DetailsActivity.newIntent(context ?: return, item)) + } + + override fun onItemLongClick(item: Manga, view: View): Boolean { + val menu = PopupMenu(context ?: return false, view) + onCreatePopupMenu(menu.menuInflater, menu.menu, item) + return if (menu.menu.hasVisibleItems()) { + menu.setOnMenuItemClickListener { + onPopupMenuItemSelected(it, item) + } + menu.gravity = GravityCompat.END or Gravity.TOP + menu.show() + true + } else { + false + } + } + + @CallSuper + override fun onRefresh() { + swipeRefreshLayout.isRefreshing = true + } + + private fun onListChanged(list: List) { + spanSizeLookup.invalidateCache() + adapter?.items = list + } + + private fun onError(e: Throwable) { + if (e is CloudFlareProtectedException) { + CloudFlareDialog.newInstance(e.url).show(childFragmentManager, CloudFlareDialog.TAG) + } + if (viewModel.isEmptyState.value == true) { + textView_holder.text = e.getDisplayMessage(resources) + textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds( + 0, + R.drawable.ic_error_large, + 0, + 0 + ) + layout_holder.isVisible = true + } else { + Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT) + .show() + } + } + + @CallSuper + protected open fun onLoadingStateChanged(isLoading: Boolean) { + val hasItems = recyclerView.hasItems + progressBar.isVisible = isLoading && !hasItems && viewModel.isEmptyState.value != true + swipeRefreshLayout.isEnabled = isSwipeRefreshEnabled && !progressBar.isVisible + if (!isLoading) { + swipeRefreshLayout.isRefreshing = false + } + } + + private fun onEmptyStateChanged(isEmpty: Boolean) { + if (isEmpty) { + setUpEmptyListHolder() + } + layout_holder.isVisible = isEmpty + } + + protected fun onInitFilter(config: MangaFilterConfig) { + recyclerView_filter.adapter = FilterAdapter( + sortOrders = config.sortOrders, + tags = config.tags, + state = config.currentFilter, + listener = this + ) + drawer?.setDrawerLockMode( + if (config.sortOrders.isEmpty() && config.tags.isEmpty()) { + DrawerLayout.LOCK_MODE_LOCKED_CLOSED + } else { + DrawerLayout.LOCK_MODE_UNLOCKED + } + ) ?: divider_filter?.let { + it.isGone = config.sortOrders.isEmpty() && config.tags.isEmpty() + recyclerView_filter.isVisible = it.isVisible + } + activity?.invalidateOptionsMenu() + } + + @CallSuper + override fun onFilterChanged(filter: MangaFilter) { + drawer?.closeDrawers() + } + + protected open fun setUpEmptyListHolder() { + textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null) + textView_holder.setText(R.string.nothing_found) + } + + private fun onGridScaleChanged(scale: Float) { + spanSizeLookup.invalidateCache() + spanResolver.setGridSize(scale, recyclerView) + } + + private fun onListModeChanged(mode: ListMode) { + spanSizeLookup.invalidateCache() + with(recyclerView) { + clearItemDecorations() + removeOnLayoutChangeListener(spanResolver) + when (mode) { + ListMode.LIST -> { + layoutManager = LinearLayoutManager(context) + addItemDecoration( + DividerItemDecoration( + context, + RecyclerView.VERTICAL + ) + ) + } + ListMode.DETAILED_LIST -> { + layoutManager = LinearLayoutManager(context) + addItemDecoration( + SpacingItemDecoration( + resources.getDimensionPixelOffset(R.dimen.grid_spacing) + ) + ) + } + ListMode.GRID -> { + layoutManager = GridLayoutManager(context, spanResolver.spanCount).also { + it.spanSizeLookup = spanSizeLookup + } + addItemDecoration( + SpacingItemDecoration( + resources.getDimensionPixelOffset(R.dimen.grid_spacing) + ) + ) + addOnLayoutChangeListener(spanResolver) + } + } + } + } + + final override fun isSection(position: Int): Boolean { + return position == 0 || recyclerView_filter.adapter?.run { + getItemViewType(position) != getItemViewType(position - 1) + } ?: false + } + + final override fun getSectionTitle(position: Int): CharSequence? { + return when (recyclerView_filter.adapter?.getItemViewType(position)) { + FilterAdapter.VIEW_TYPE_SORT -> getString(R.string.sort_order) + FilterAdapter.VIEW_TYPE_TAG -> getString(R.string.genre) + else -> null + } + } + + protected open fun onCreatePopupMenu(inflater: MenuInflater, menu: Menu, data: Manga) = Unit + + protected open fun onPopupMenuItemSelected(item: MenuItem, data: Manga) = false + + private inner class SpanSizeLookup : GridLayoutManager.SpanSizeLookup() { + + init { + isSpanIndexCacheEnabled = true + isSpanGroupIndexCacheEnabled = true + } + + override fun getSpanSize(position: Int): Int { + val total = (recyclerView.layoutManager as? GridLayoutManager)?.spanCount ?: return 1 + return when (adapter?.getItemViewType(position)) { + MangaListAdapter.ITEM_TYPE_PROGRESS -> total + else -> 1 + } + } + + fun invalidateCache() { + invalidateSpanGroupIndexCache() + invalidateSpanIndexCache() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListSheet.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListSheet.kt similarity index 63% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListSheet.kt rename to app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListSheet.kt index e2a932451..77f27887a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListSheet.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListSheet.kt @@ -1,53 +1,47 @@ -package org.koitharu.kotatsu.ui.list +package org.koitharu.kotatsu.list.ui import android.content.SharedPreferences import android.os.Bundle import android.view.MenuItem import android.view.View import androidx.appcompat.widget.Toolbar -import androidx.core.view.isGone import androidx.core.view.isVisible -import androidx.recyclerview.widget.* +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.sheet_list.* -import moxy.MvpDelegate +import org.koin.android.ext.android.get import org.koin.android.ext.android.inject import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BaseBottomSheet +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener +import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaFilter -import org.koitharu.kotatsu.core.model.MangaTag -import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ListMode -import org.koitharu.kotatsu.ui.base.BaseBottomSheet -import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener -import org.koitharu.kotatsu.ui.base.list.PaginationScrollListener -import org.koitharu.kotatsu.ui.base.list.ProgressBarAdapter -import org.koitharu.kotatsu.ui.base.list.decor.SpacingItemDecoration -import org.koitharu.kotatsu.ui.details.MangaDetailsActivity +import org.koitharu.kotatsu.details.ui.DetailsActivity +import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter import org.koitharu.kotatsu.utils.UiUtils import org.koitharu.kotatsu.utils.ext.* -abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list), - MangaListView, - PaginationScrollListener.Callback, OnRecyclerItemClickListener, +abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list), + PaginationScrollListener.Callback, OnListItemClickListener, SharedPreferences.OnSharedPreferenceChangeListener, Toolbar.OnMenuItemClickListener { private val settings by inject() - private val adapterConfig = ConcatAdapter.Config.Builder() - .setIsolateViewTypes(true) - .setStableIdMode(ConcatAdapter.Config.StableIdMode.SHARED_STABLE_IDS) - .build() private var adapter: MangaListAdapter? = null - private var progressAdapter: ProgressBarAdapter? = null + + protected abstract val viewModel: MangaListViewModel override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - adapter = MangaListAdapter(this) - progressAdapter = ProgressBarAdapter() + adapter = MangaListAdapter(get(), this) initListMode(settings.listMode) recyclerView.adapter = adapter recyclerView.addOnScrollListener(PaginationScrollListener(4, this)) @@ -62,15 +56,18 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list), textView_title.isVisible = false appbar.elevation = resources.getDimension(R.dimen.elevation_large) } - if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) { - onRequestMoreItems(0) + if (savedInstanceState == null) { + onScrolledToEnd() } + viewModel.content.observe(viewLifecycleOwner, ::onListChanged) + viewModel.onError.observe(viewLifecycleOwner, ::onError) + viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged) + viewModel.listMode.observe(viewLifecycleOwner, ::initListMode) } override fun onDestroyView() { settings.unsubscribe(this) adapter = null - progressAdapter = null super.onDestroyView() } @@ -121,50 +118,21 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list), } } - override fun onItemClick(item: Manga, position: Int, view: View) { - startActivity(MangaDetailsActivity.newIntent(context ?: return, item)) + override fun onItemClick(item: Manga, view: View) { + startActivity(DetailsActivity.newIntent(context ?: return, item)) } - override fun onListChanged(list: List) { - adapter?.replaceData(list) + private fun onListChanged(list: List) { + adapter?.items = list textView_holder.isVisible = list.isEmpty() - progressAdapter?.isProgressVisible = list.isNotEmpty() recyclerView.callOnScrollListeners() } - override fun onListAppended(list: List) { - adapter?.appendData(list) - if (list.isNotEmpty()) { - textView_holder.isVisible = false - } - progressAdapter?.isProgressVisible = list.isNotEmpty() - recyclerView.callOnScrollListeners() - } - - override fun onListError(e: Throwable) { - Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show() - } - - override fun getItemsCount() = adapter?.itemCount ?: 0 - - override fun onInitFilter( - sortOrders: List, - tags: List, - currentFilter: MangaFilter? - ) = Unit - - override fun onItemRemoved(item: Manga) { - adapter?.let { - it.removeItem(item) - textView_holder.isGone = it.hasItems - } - } - - override fun onError(e: Throwable) { + private fun onError(e: Throwable) { Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show() } - override fun onLoadingStateChanged(isLoading: Boolean) { + private fun onLoadingStateChanged(isLoading: Boolean) { progressBar.isVisible = isLoading && !recyclerView.hasItems if (isLoading) { textView_holder.isVisible = false @@ -174,23 +142,20 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list), private fun initListMode(mode: ListMode) { val ctx = context ?: return val position = recyclerView.firstItem - recyclerView.adapter = null recyclerView.layoutManager = null recyclerView.clearItemDecorations() recyclerView.removeOnLayoutChangeListener(UiUtils.SpanCountResolver) - adapter?.listMode = mode recyclerView.layoutManager = when (mode) { ListMode.GRID -> { GridLayoutManager(ctx, UiUtils.resolveGridSpanCount(ctx)).apply { spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { - override fun getSpanSize(position: Int) = if (position < getItemsCount()) + override fun getSpanSize(position: Int) = if (position < TODO() as Int) 1 else this@apply.spanCount } } } else -> LinearLayoutManager(ctx) } - recyclerView.adapter = ConcatAdapter(adapterConfig, adapter, progressAdapter) recyclerView.addItemDecoration( when (mode) { ListMode.LIST -> DividerItemDecoration(ctx, RecyclerView.VERTICAL) diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListSpanResolver.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListSpanResolver.kt new file mode 100644 index 000000000..1e58b2065 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListSpanResolver.kt @@ -0,0 +1,62 @@ +package org.koitharu.kotatsu.list.ui + +import android.view.View +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import org.koitharu.kotatsu.R +import kotlin.math.abs +import kotlin.math.roundToInt + +class MangaListSpanResolver : View.OnLayoutChangeListener { + + var spanCount = 3 + private set + + private var gridWidth = -1f + private var cellWidth = -1f + + override fun onLayoutChange( + v: View?, + left: Int, + top: Int, + right: Int, + bottom: Int, + oldLeft: Int, + oldTop: Int, + oldRight: Int, + oldBottom: Int + ) { + if (cellWidth <= 0f) { + return + } + val rv = v as? RecyclerView ?: return + if (gridWidth < 0f) { + gridWidth = rv.resources.getDimension(R.dimen.preferred_grid_width) + } + val width = abs(right - left) + if (width == 0) { + return + } + resolveGridSpanCount(width) + (rv.layoutManager as? GridLayoutManager)?.spanCount = spanCount + } + + fun setGridSize(scaleFactor: Float, rv: RecyclerView?) { + if (gridWidth < 0f) { + gridWidth = (rv ?: return).resources.getDimension(R.dimen.preferred_grid_width) + } + cellWidth = gridWidth * scaleFactor + if (rv != null) { + val width = rv.width + if (width != 0) { + resolveGridSpanCount(width) + (rv.layoutManager as? GridLayoutManager)?.spanCount = spanCount + } + } + } + + private fun resolveGridSpanCount(width: Int) { + val estimatedCount = (width / cellWidth).roundToInt() + spanCount = estimatedCount.coerceAtLeast(2) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt new file mode 100644 index 000000000..4b9b1603f --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt @@ -0,0 +1,33 @@ +package org.koitharu.kotatsu.list.ui + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.* +import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.ListMode + +abstract class MangaListViewModel( + private val settings: AppSettings +) : BaseViewModel() { + + abstract val content: LiveData> + val isEmptyState = MutableLiveData(false) + val filter = MutableLiveData() + val listMode = MutableLiveData() + val gridScale = settings.observe() + .filter { it == AppSettings.KEY_GRID_SIZE } + .map { settings.gridSize / 100f } + .onStart { emit(settings.gridSize / 100f) } + .asLiveData(viewModelScope.coroutineContext + Dispatchers.IO) + + protected fun createListModeFlow() = settings.observe() + .filter { it == AppSettings.KEY_LIST_MODE } + .map { settings.listMode } + .onStart { emit(settings.listMode) } + .distinctUntilChanged() + .onEach { listMode.postValue(it) } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/IndeterminateProgressAD.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/IndeterminateProgressAD.kt new file mode 100644 index 000000000..1b7a3fd17 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/IndeterminateProgressAD.kt @@ -0,0 +1,8 @@ +package org.koitharu.kotatsu.list.ui.adapter + +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.list.ui.model.IndeterminateProgress + +fun indeterminateProgressAD() = adapterDelegate(R.layout.item_progress) { +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt new file mode 100644 index 000000000..85639a746 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt @@ -0,0 +1,42 @@ +package org.koitharu.kotatsu.list.ui.adapter + +import coil.ImageLoader +import coil.request.Disposable +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer +import kotlinx.android.synthetic.main.item_manga_list.* +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.list.ui.model.MangaGridModel +import org.koitharu.kotatsu.utils.ext.enqueueWith +import org.koitharu.kotatsu.utils.ext.newImageRequest + +fun mangaGridItemAD( + coil: ImageLoader, + clickListener: OnListItemClickListener +) = adapterDelegateLayoutContainer(R.layout.item_manga_grid) { + + var imageRequest: Disposable? = null + + itemView.setOnClickListener { + clickListener.onItemClick(item.manga, it) + } + itemView.setOnLongClickListener { + clickListener.onItemLongClick(item.manga, it) + } + + bind { + textView_title.text = item.title + imageRequest?.dispose() + imageRequest = imageView_cover.newImageRequest(item.coverUrl) + .placeholder(R.drawable.ic_placeholder) + .fallback(R.drawable.ic_placeholder) + .error(R.drawable.ic_placeholder) + .enqueueWith(coil) + } + + onViewRecycled { + imageRequest?.dispose() + imageView_cover.setImageDrawable(null) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt new file mode 100644 index 000000000..343e691ad --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt @@ -0,0 +1,59 @@ +package org.koitharu.kotatsu.list.ui.adapter + +import androidx.recyclerview.widget.DiffUtil +import coil.ImageLoader +import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.list.ui.model.IndeterminateProgress +import org.koitharu.kotatsu.list.ui.model.MangaGridModel +import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel +import org.koitharu.kotatsu.list.ui.model.MangaListModel +import kotlin.jvm.internal.Intrinsics + +class MangaListAdapter( + coil: ImageLoader, + clickListener: OnListItemClickListener +) : AsyncListDifferDelegationAdapter(DiffCallback()) { + + init { + delegatesManager.addDelegate(ITEM_TYPE_MANGA_LIST, mangaListItemAD(coil, clickListener)) + .addDelegate( + ITEM_TYPE_MANGA_LIST_DETAILED, + mangaListDetailedItemAD(coil, clickListener) + ) + .addDelegate(ITEM_TYPE_MANGA_GRID, mangaGridItemAD(coil, clickListener)) + .addDelegate(ITEM_TYPE_PROGRESS, indeterminateProgressAD()) + } + + private class DiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: Any, newItem: Any) = when { + oldItem is MangaListModel && newItem is MangaListModel -> { + oldItem.id == newItem.id + } + oldItem is MangaListDetailedModel && newItem is MangaListDetailedModel -> { + oldItem.id == newItem.id + } + oldItem is MangaGridModel && newItem is MangaGridModel -> { + oldItem.id == newItem.id + } + oldItem == IndeterminateProgress && newItem == IndeterminateProgress -> { + true + } + else -> false + } + + override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean { + return Intrinsics.areEqual(oldItem, newItem) + } + } + + companion object { + + const val ITEM_TYPE_MANGA_LIST = 0 + const val ITEM_TYPE_MANGA_LIST_DETAILED = 1 + const val ITEM_TYPE_MANGA_GRID = 2 + const val ITEM_TYPE_PROGRESS = 3 + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt new file mode 100644 index 000000000..00ec8ab56 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt @@ -0,0 +1,46 @@ +package org.koitharu.kotatsu.list.ui.adapter + +import coil.ImageLoader +import coil.request.Disposable +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer +import kotlinx.android.synthetic.main.item_manga_list_details.* +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel +import org.koitharu.kotatsu.utils.ext.enqueueWith +import org.koitharu.kotatsu.utils.ext.newImageRequest +import org.koitharu.kotatsu.utils.ext.textAndVisible + +fun mangaListDetailedItemAD( + coil: ImageLoader, + clickListener: OnListItemClickListener +) = adapterDelegateLayoutContainer(R.layout.item_manga_list_details) { + + var imageRequest: Disposable? = null + + itemView.setOnClickListener { + clickListener.onItemClick(item.manga, it) + } + itemView.setOnLongClickListener { + clickListener.onItemLongClick(item.manga, it) + } + + bind { + imageRequest?.dispose() + textView_title.text = item.title + textView_subtitle.textAndVisible = item.subtitle + imageRequest = imageView_cover.newImageRequest(item.coverUrl) + .placeholder(R.drawable.ic_placeholder) + .fallback(R.drawable.ic_placeholder) + .error(R.drawable.ic_placeholder) + .enqueueWith(coil) + textView_rating.textAndVisible = item.rating + textView_tags.text = item.tags + } + + onViewRecycled { + imageRequest?.dispose() + imageView_cover.setImageDrawable(null) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt new file mode 100644 index 000000000..9529c9a8c --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt @@ -0,0 +1,44 @@ +package org.koitharu.kotatsu.list.ui.adapter + +import coil.ImageLoader +import coil.request.Disposable +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer +import kotlinx.android.synthetic.main.item_manga_list.* +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.list.ui.model.MangaListModel +import org.koitharu.kotatsu.utils.ext.enqueueWith +import org.koitharu.kotatsu.utils.ext.newImageRequest +import org.koitharu.kotatsu.utils.ext.textAndVisible + +fun mangaListItemAD( + coil: ImageLoader, + clickListener: OnListItemClickListener +) = adapterDelegateLayoutContainer(R.layout.item_manga_list) { + + var imageRequest: Disposable? = null + + itemView.setOnClickListener { + clickListener.onItemClick(item.manga, it) + } + itemView.setOnLongClickListener { + clickListener.onItemLongClick(item.manga, it) + } + + bind { + imageRequest?.dispose() + textView_title.text = item.title + textView_subtitle.textAndVisible = item.subtitle + imageRequest = imageView_cover.newImageRequest(item.coverUrl) + .placeholder(R.drawable.ic_placeholder) + .fallback(R.drawable.ic_placeholder) + .error(R.drawable.ic_placeholder) + .enqueueWith(coil) + } + + onViewRecycled { + imageRequest?.dispose() + imageView_cover.setImageDrawable(null) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/filter/FilterAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterAdapter.kt similarity index 96% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/filter/FilterAdapter.kt rename to app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterAdapter.kt index da8de8fa4..cc8464f0e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/filter/FilterAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterAdapter.kt @@ -1,11 +1,11 @@ -package org.koitharu.kotatsu.ui.list.filter +package org.koitharu.kotatsu.list.ui.filter import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView +import org.koitharu.kotatsu.base.ui.list.BaseViewHolder import org.koitharu.kotatsu.core.model.MangaFilter import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.core.model.SortOrder -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder import java.util.* import kotlin.collections.ArrayList diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/filter/FilterSortHolder.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterSortHolder.kt similarity index 80% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/filter/FilterSortHolder.kt rename to app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterSortHolder.kt index 9b6d5e8c3..9abeab8eb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/filter/FilterSortHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterSortHolder.kt @@ -1,10 +1,10 @@ -package org.koitharu.kotatsu.ui.list.filter +package org.koitharu.kotatsu.list.ui.filter import android.view.ViewGroup import kotlinx.android.synthetic.main.item_checkable_single.* import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.BaseViewHolder import org.koitharu.kotatsu.core.model.SortOrder -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder class FilterSortHolder(parent: ViewGroup) : BaseViewHolder(parent, R.layout.item_checkable_single) { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/filter/FilterTagHolder.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterTagHolder.kt similarity index 81% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/filter/FilterTagHolder.kt rename to app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterTagHolder.kt index d1fcd95d4..ed8ef8489 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/filter/FilterTagHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterTagHolder.kt @@ -1,10 +1,10 @@ -package org.koitharu.kotatsu.ui.list.filter +package org.koitharu.kotatsu.list.ui.filter import android.view.ViewGroup import kotlinx.android.synthetic.main.item_checkable_single.* import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.BaseViewHolder import org.koitharu.kotatsu.core.model.MangaTag -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder class FilterTagHolder(parent: ViewGroup) : BaseViewHolder(parent, R.layout.item_checkable_single) { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/filter/OnFilterChangedListener.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/OnFilterChangedListener.kt similarity index 75% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/filter/OnFilterChangedListener.kt rename to app/src/main/java/org/koitharu/kotatsu/list/ui/filter/OnFilterChangedListener.kt index 8a2d93978..93a1b7db5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/filter/OnFilterChangedListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/OnFilterChangedListener.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.list.filter +package org.koitharu.kotatsu.list.ui.filter import org.koitharu.kotatsu.core.model.MangaFilter diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/IndeterminateProgress.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/IndeterminateProgress.kt new file mode 100644 index 000000000..cb739762d --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/IndeterminateProgress.kt @@ -0,0 +1,3 @@ +package org.koitharu.kotatsu.list.ui.model + +object IndeterminateProgress \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt new file mode 100644 index 000000000..2a73c09c0 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt @@ -0,0 +1,29 @@ +package org.koitharu.kotatsu.list.ui.model + +import org.koitharu.kotatsu.core.model.Manga +import kotlin.math.roundToInt + +fun Manga.toListModel() = MangaListModel( + id = id, + title = title, + subtitle = tags.joinToString(", ") { it.title }, + coverUrl = coverUrl, + manga = this +) + +fun Manga.toListDetailedModel() = MangaListDetailedModel( + id = id, + title = title, + subtitle = altTitle, + rating = "${(rating * 10).roundToInt()}/10", + tags = tags.joinToString(", ") { it.title }, + coverUrl = coverUrl, + manga = this +) + +fun Manga.toGridModel() = MangaGridModel( + id = id, + title = title, + coverUrl = coverUrl, + manga = this +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/MangaGridModel.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/MangaGridModel.kt new file mode 100644 index 000000000..601bc9304 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/MangaGridModel.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.list.ui.model + +import org.koitharu.kotatsu.core.model.Manga + +data class MangaGridModel( + val id: Long, + val title: String, + val coverUrl: String, + val manga: Manga +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/MangaListDetailedModel.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/MangaListDetailedModel.kt new file mode 100644 index 000000000..6dac0ec99 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/MangaListDetailedModel.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.list.ui.model + +import org.koitharu.kotatsu.core.model.Manga + +data class MangaListDetailedModel( + val id: Long, + val title: String, + val subtitle: String?, + val tags: String, + val coverUrl: String, + val rating: String?, + val manga: Manga +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/MangaListModel.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/MangaListModel.kt new file mode 100644 index 000000000..20384aaa1 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/MangaListModel.kt @@ -0,0 +1,11 @@ +package org.koitharu.kotatsu.list.ui.model + +import org.koitharu.kotatsu.core.model.Manga + +data class MangaListModel( + val id: Long, + val title: String, + val subtitle: String, + val coverUrl: String, + val manga: Manga +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/local/LocalModule.kt b/app/src/main/java/org/koitharu/kotatsu/local/LocalModule.kt new file mode 100644 index 000000000..bfdbc21f5 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/local/LocalModule.kt @@ -0,0 +1,19 @@ +package org.koitharu.kotatsu.local + +import org.koin.android.ext.koin.androidContext +import org.koin.android.viewmodel.dsl.viewModel +import org.koin.core.qualifier.named +import org.koin.dsl.module +import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.parser.MangaRepository +import org.koitharu.kotatsu.local.domain.LocalMangaRepository +import org.koitharu.kotatsu.local.ui.LocalListViewModel + +val localModule + get() = module { + + single { LocalMangaRepository(androidContext()) } + factory(named(MangaSource.LOCAL)) { get() } + + viewModel { LocalListViewModel(get(), get(), get(), androidContext()) } + } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/local/Cache.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/Cache.kt similarity index 65% rename from app/src/main/java/org/koitharu/kotatsu/core/local/Cache.kt rename to app/src/main/java/org/koitharu/kotatsu/local/data/Cache.kt index 1a16d23fd..d675fbc2a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/local/Cache.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/Cache.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.core.local +package org.koitharu.kotatsu.local.data enum class Cache(val dir: String) { diff --git a/app/src/main/java/org/koitharu/kotatsu/core/local/CbzFetcher.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt similarity index 95% rename from app/src/main/java/org/koitharu/kotatsu/core/local/CbzFetcher.kt rename to app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt index a3bbb8cf2..f74c023e1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/local/CbzFetcher.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.core.local +package org.koitharu.kotatsu.local.data import android.net.Uri import android.webkit.MimeTypeMap diff --git a/app/src/main/java/org/koitharu/kotatsu/core/local/CbzFilter.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFilter.kt similarity index 87% rename from app/src/main/java/org/koitharu/kotatsu/core/local/CbzFilter.kt rename to app/src/main/java/org/koitharu/kotatsu/local/data/CbzFilter.kt index 3080f1fb9..98f4e73fc 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/local/CbzFilter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFilter.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.core.local +package org.koitharu.kotatsu.local.data import java.io.File import java.io.FilenameFilter diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/local/MangaIndex.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/MangaIndex.kt similarity index 98% rename from app/src/main/java/org/koitharu/kotatsu/domain/local/MangaIndex.kt rename to app/src/main/java/org/koitharu/kotatsu/local/data/MangaIndex.kt index 4ce4dffd9..255c1a5c6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/local/MangaIndex.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/MangaIndex.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.domain.local +package org.koitharu.kotatsu.local.data import org.json.JSONArray import org.json.JSONObject diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/local/MangaZip.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/MangaZip.kt similarity index 94% rename from app/src/main/java/org/koitharu/kotatsu/domain/local/MangaZip.kt rename to app/src/main/java/org/koitharu/kotatsu/local/data/MangaZip.kt index b6672aa31..2904910d6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/local/MangaZip.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/MangaZip.kt @@ -1,8 +1,7 @@ -package org.koitharu.kotatsu.domain.local +package org.koitharu.kotatsu.local.data import androidx.annotation.CheckResult import androidx.annotation.WorkerThread -import org.koitharu.kotatsu.core.local.WritableCbzFile import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.utils.ext.takeIfReadable diff --git a/app/src/main/java/org/koitharu/kotatsu/core/local/PagesCache.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt similarity index 83% rename from app/src/main/java/org/koitharu/kotatsu/core/local/PagesCache.kt rename to app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt index 63c842d2c..8630ed688 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/local/PagesCache.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.core.local +package org.koitharu.kotatsu.local.data import android.content.Context import com.tomclaw.cache.DiskLruCache @@ -12,7 +12,8 @@ import java.io.OutputStream class PagesCache(context: Context) { private val cacheDir = context.externalCacheDir ?: context.cacheDir - private val lruCache = DiskLruCache.create(cacheDir.sub(Cache.PAGES.dir), FileSizeUtils.mbToBytes(200)) + private val lruCache = + DiskLruCache.create(cacheDir.sub(Cache.PAGES.dir), FileSizeUtils.mbToBytes(200)) operator fun get(url: String): File? { return lruCache.get(url)?.takeIfReadable() diff --git a/app/src/main/java/org/koitharu/kotatsu/core/local/WritableCbzFile.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/WritableCbzFile.kt similarity index 98% rename from app/src/main/java/org/koitharu/kotatsu/core/local/WritableCbzFile.kt rename to app/src/main/java/org/koitharu/kotatsu/local/data/WritableCbzFile.kt index 7ea9a2523..5a591740f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/local/WritableCbzFile.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/WritableCbzFile.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.core.local +package org.koitharu.kotatsu.local.data import androidx.annotation.CheckResult import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt similarity index 92% rename from app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt rename to app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt index 3f3fb0567..9fe679199 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/LocalMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.core.parser +package org.koitharu.kotatsu.local.domain import android.annotation.SuppressLint import android.content.Context @@ -7,10 +7,11 @@ import android.webkit.MimeTypeMap import androidx.collection.ArraySet import androidx.core.net.toFile import androidx.core.net.toUri -import org.koitharu.kotatsu.core.local.CbzFilter import org.koitharu.kotatsu.core.model.* -import org.koitharu.kotatsu.domain.local.MangaIndex -import org.koitharu.kotatsu.domain.local.MangaZip +import org.koitharu.kotatsu.core.parser.MangaRepository +import org.koitharu.kotatsu.local.data.CbzFilter +import org.koitharu.kotatsu.local.data.MangaIndex +import org.koitharu.kotatsu.local.data.MangaZip import org.koitharu.kotatsu.utils.AlphanumComparator import org.koitharu.kotatsu.utils.ext.longHashCode import org.koitharu.kotatsu.utils.ext.readText @@ -23,14 +24,19 @@ import java.util.zip.ZipFile class LocalMangaRepository(private val context: Context) : MangaRepository { + private val filenameFilter = CbzFilter() + override suspend fun getList( offset: Int, query: String?, sortOrder: SortOrder?, tag: MangaTag? ): List { + require(offset == 0) { + "LocalMangaRepository does not support pagination" + } val files = getAvailableStorageDirs(context) - .flatMap { x -> x.listFiles(CbzFilter())?.toList().orEmpty() } + .flatMap { x -> x.listFiles(filenameFilter)?.toList().orEmpty() } return files.mapNotNull { x -> safe { getFromFile(x) } } } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/local/LocalListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt similarity index 74% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/local/LocalListFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt index 85fac3dc3..36da2d8aa 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/local/LocalListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt @@ -1,35 +1,44 @@ -package org.koitharu.kotatsu.ui.list.local +package org.koitharu.kotatsu.local.ui import android.content.ActivityNotFoundException import android.net.Uri +import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import android.view.View import androidx.activity.result.ActivityResultCallback import androidx.activity.result.contract.ActivityResultContracts -import com.google.android.material.dialog.MaterialAlertDialogBuilder +import androidx.appcompat.app.AlertDialog import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_list.* -import moxy.ktx.moxyPresenter +import org.koin.android.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.ui.list.MangaListFragment +import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.utils.ext.ellipsize -import java.io.File -class LocalListFragment : MangaListFragment(), ActivityResultCallback { +class LocalListFragment : MangaListFragment(), ActivityResultCallback { - private val presenter by moxyPresenter(factory = ::LocalListPresenter) + override val viewModel by viewModel() private val importCall = registerForActivityResult( ActivityResultContracts.OpenDocument(), this ) - override fun onRequestMoreItems(offset: Int) { - presenter.loadList(offset) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.onMangaRemoved.observe(viewLifecycleOwner, ::onItemRemoved) } + override fun onRefresh() { + super.onRefresh() + viewModel.onRefresh() + } + + override fun onScrolledToEnd() = Unit + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.opt_local, menu) super.onCreateOptionsMenu(menu, inflater) @@ -65,7 +74,7 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback override fun onActivityResult(result: Uri?) { if (result != null) { - presenter.importFile(context?.applicationContext ?: return, result) + viewModel.importFile(result) } } @@ -77,11 +86,11 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback override fun onPopupMenuItemSelected(item: MenuItem, data: Manga): Boolean { return when (item.itemId) { R.id.action_delete -> { - MaterialAlertDialogBuilder(context ?: return false) + AlertDialog.Builder(context ?: return false) .setTitle(R.string.delete_manga) .setMessage(getString(R.string.text_delete_local_manga, data.title)) .setPositiveButton(R.string.delete) { _, _ -> - presenter.delete(data) + viewModel.delete(data) } .setNegativeButton(android.R.string.cancel, null) .show() @@ -91,8 +100,7 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback } } - override fun onItemRemoved(item: Manga) { - super.onItemRemoved(item) + private fun onItemRemoved(item: Manga) { Snackbar.make( recyclerView, getString( R.string._s_deleted_from_local_storage, diff --git a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt new file mode 100644 index 000000000..f1eaf8dae --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt @@ -0,0 +1,97 @@ +package org.koitharu.kotatsu.local.ui + +import android.content.Context +import android.net.Uri +import android.os.Build +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.withContext +import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.ListMode +import org.koitharu.kotatsu.history.domain.HistoryRepository +import org.koitharu.kotatsu.list.ui.MangaListViewModel +import org.koitharu.kotatsu.list.ui.model.toGridModel +import org.koitharu.kotatsu.list.ui.model.toListDetailedModel +import org.koitharu.kotatsu.list.ui.model.toListModel +import org.koitharu.kotatsu.local.domain.LocalMangaRepository +import org.koitharu.kotatsu.utils.MangaShortcut +import org.koitharu.kotatsu.utils.MediaStoreCompat +import org.koitharu.kotatsu.utils.SingleLiveEvent +import org.koitharu.kotatsu.utils.ext.safe +import org.koitharu.kotatsu.utils.ext.sub +import java.io.IOException + +class LocalListViewModel( + private val repository: LocalMangaRepository, + private val historyRepository: HistoryRepository, + private val settings: AppSettings, + private val context: Context +) : MangaListViewModel(settings) { + + val onMangaRemoved = SingleLiveEvent() + private val mangaList = MutableStateFlow>(emptyList()) + + override val content = combine(mangaList, createListModeFlow()) { list, mode -> + when (mode) { + ListMode.LIST -> list.map { it.toListModel() } + ListMode.DETAILED_LIST -> list.map { it.toListDetailedModel() } + ListMode.GRID -> list.map { it.toGridModel() } + } + }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + + init { + onRefresh() + } + + fun onRefresh() { + launchLoadingJob { + withContext(Dispatchers.Default) { + val list = repository.getList(0) + mangaList.value = list + isEmptyState.postValue(list.isEmpty()) + } + } + } + + fun importFile(uri: Uri) { + launchLoadingJob { + val contentResolver = context.contentResolver + withContext(Dispatchers.Default) { + val name = MediaStoreCompat.getName(contentResolver, uri) + ?: throw IOException("Cannot fetch name from uri: $uri") + if (!LocalMangaRepository.isFileSupported(name)) { + throw UnsupportedFileException("Unsupported file on $uri") + } + val dest = settings.getStorageDir(context)?.sub(name) + ?: throw IOException("External files dir unavailable") + contentResolver.openInputStream(uri)?.use { source -> + dest.outputStream().use { output -> + source.copyTo(output) + } + } ?: throw IOException("Cannot open input stream: $uri") + } + onRefresh() + } + } + + fun delete(manga: Manga) { + launchJob { + withContext(Dispatchers.Default) { + val original = repository.getRemoteManga(manga) + repository.delete(manga) || throw IOException("Unable to delete file") + safe { + historyRepository.deleteOrSwap(manga, original) + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + MangaShortcut(manga).removeAppShortcut(context) + } + onMangaRemoved.call(manga) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/main/MainModule.kt b/app/src/main/java/org/koitharu/kotatsu/main/MainModule.kt new file mode 100644 index 000000000..53f0204ad --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/main/MainModule.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.main + +import org.koin.android.viewmodel.dsl.viewModel +import org.koin.dsl.module +import org.koitharu.kotatsu.main.ui.MainViewModel +import org.koitharu.kotatsu.main.ui.protect.ProtectViewModel + +val mainModule + get() = module { + viewModel { MainViewModel(get()) } + viewModel { ProtectViewModel(get()) } + } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt similarity index 82% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt rename to app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt index a2396d703..1b0e62717 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.list +package org.koitharu.kotatsu.main.ui import android.app.ActivityOptions import android.content.SharedPreferences @@ -17,34 +17,34 @@ import androidx.swiperefreshlayout.widget.CircularProgressDrawable import com.google.android.material.navigation.NavigationView import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.activity_main.* -import moxy.ktx.moxyPresenter import org.koin.android.ext.android.inject +import org.koin.android.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.domain.MangaProviderFactory +import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.prefs.AppSection import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.domain.MangaProviderFactory -import org.koitharu.kotatsu.ui.base.BaseActivity -import org.koitharu.kotatsu.ui.list.favourites.FavouritesContainerFragment -import org.koitharu.kotatsu.ui.list.feed.FeedFragment -import org.koitharu.kotatsu.ui.list.history.HistoryListFragment -import org.koitharu.kotatsu.ui.list.local.LocalListFragment -import org.koitharu.kotatsu.ui.list.remote.RemoteListFragment -import org.koitharu.kotatsu.ui.reader.ReaderActivity -import org.koitharu.kotatsu.ui.reader.ReaderState -import org.koitharu.kotatsu.ui.search.SearchHelper -import org.koitharu.kotatsu.ui.settings.AppUpdateChecker -import org.koitharu.kotatsu.ui.settings.SettingsActivity -import org.koitharu.kotatsu.ui.tracker.TrackWorker -import org.koitharu.kotatsu.ui.utils.protect.AppProtectHelper +import org.koitharu.kotatsu.favourites.ui.FavouritesContainerFragment +import org.koitharu.kotatsu.history.ui.HistoryListFragment +import org.koitharu.kotatsu.local.ui.LocalListFragment +import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper +import org.koitharu.kotatsu.reader.ui.ReaderActivity +import org.koitharu.kotatsu.reader.ui.ReaderState +import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment +import org.koitharu.kotatsu.search.ui.SearchHelper +import org.koitharu.kotatsu.settings.AppUpdateChecker +import org.koitharu.kotatsu.settings.SettingsActivity +import org.koitharu.kotatsu.tracker.ui.FeedFragment +import org.koitharu.kotatsu.tracker.work.TrackWorker import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.resolveDp import java.io.Closeable class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener, - SharedPreferences.OnSharedPreferenceChangeListener, MainView, View.OnClickListener { + SharedPreferences.OnSharedPreferenceChangeListener, View.OnClickListener { - private val presenter by moxyPresenter(factory = ::MainPresenter) + private val viewModel by viewModel() private val settings by inject() private lateinit var drawerToggle: ActionBarDrawerToggle @@ -77,6 +77,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList } TrackWorker.setup(applicationContext) AppUpdateChecker(this).launchIfNeeded() + + viewModel.onOpenReader.observe(this, ::onOpenReader) + viewModel.onError.observe(this, ::onError) + viewModel.isLoading.observe(this, ::onLoadingStateChanged) } override fun onDestroy() { @@ -121,7 +125,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList override fun onClick(v: View) { when (v.id) { - R.id.fab -> presenter.openLastReader() + R.id.fab -> viewModel.openLastReader() } } @@ -156,7 +160,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList return true } - override fun onOpenReader(state: ReaderState) { + private fun onOpenReader(state: ReaderState) { val options = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { ActivityOptions.makeClipRevealAnimation( fab, 0, 0, fab.measuredWidth, fab.measuredHeight @@ -169,11 +173,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList startActivity(ReaderActivity.newIntent(this, state), options?.toBundle()) } - override fun onError(e: Throwable) { + private fun onError(e: Throwable) { Snackbar.make(container, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show() } - override fun onLoadingStateChanged(isLoading: Boolean) { + private fun onLoadingStateChanged(isLoading: Boolean) { fab.isEnabled = !isLoading if (isLoading) { fab.setImageDrawable(CircularProgressDrawable(this).also { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MainPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainViewModel.kt similarity index 50% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/MainPresenter.kt rename to app/src/main/java/org/koitharu/kotatsu/main/ui/MainViewModel.kt index 4bcd8b9b6..6b4e8d799 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MainPresenter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainViewModel.kt @@ -1,16 +1,16 @@ -package org.koitharu.kotatsu.ui.list +package org.koitharu.kotatsu.main.ui -import moxy.InjectViewState -import org.koin.core.component.inject +import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException -import org.koitharu.kotatsu.domain.history.HistoryRepository -import org.koitharu.kotatsu.ui.base.BasePresenter -import org.koitharu.kotatsu.ui.reader.ReaderState +import org.koitharu.kotatsu.history.domain.HistoryRepository +import org.koitharu.kotatsu.reader.ui.ReaderState +import org.koitharu.kotatsu.utils.SingleLiveEvent -@InjectViewState -class MainPresenter : BasePresenter() { +class MainViewModel( + private val historyRepository: HistoryRepository +) : BaseViewModel() { - private val historyRepository by inject() + val onOpenReader = SingleLiveEvent() fun openLastReader() { launchLoadingJob { @@ -21,7 +21,7 @@ class MainPresenter : BasePresenter() { manga.source.repository.getDetails(manga), history.chapterId, history.page, history.scroll ) - viewState.onOpenReader(state) + onOpenReader.call(state) } } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/AppProtectHelper.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/protect/AppProtectHelper.kt similarity index 89% rename from app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/AppProtectHelper.kt rename to app/src/main/java/org/koitharu/kotatsu/main/ui/protect/AppProtectHelper.kt index 9b92cacb4..be8ba3cff 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/AppProtectHelper.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/protect/AppProtectHelper.kt @@ -1,11 +1,11 @@ -package org.koitharu.kotatsu.ui.utils.protect +package org.koitharu.kotatsu.main.ui.protect import android.app.Activity import android.content.Intent import org.koin.core.component.KoinComponent import org.koin.core.component.inject import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.ui.list.MainActivity +import org.koitharu.kotatsu.main.ui.MainActivity object AppProtectHelper : KoinComponent { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectActivity.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt similarity index 70% rename from app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectActivity.kt rename to app/src/main/java/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt index 5fa104455..f98e013d1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.utils.protect +package org.koitharu.kotatsu.main.ui.protect import android.content.Context import android.content.Intent @@ -11,14 +11,14 @@ import android.view.MenuItem import android.view.inputmethod.EditorInfo import android.widget.TextView import kotlinx.android.synthetic.main.activity_protect.* -import moxy.ktx.moxyPresenter +import org.koin.android.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.ui.base.BaseActivity +import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.utils.ext.getDisplayMessage -class ProtectActivity : BaseActivity(), ProtectView, TextView.OnEditorActionListener, TextWatcher { +class ProtectActivity : BaseActivity(), TextView.OnEditorActionListener, TextWatcher { - private val presenter by moxyPresenter(factory = ::ProtectPresenter) + private val viewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -29,6 +29,10 @@ class ProtectActivity : BaseActivity(), ProtectView, TextView.OnEditorActionList setDisplayHomeAsUpEnabled(true) setHomeAsUpIndicator(R.drawable.ic_cross) } + + viewModel.onError.observe(this, this::onError) + viewModel.isLoading.observe(this, this::onLoadingStateChanged) + viewModel.onUnlockSuccess.observe(this, this::onUnlockSuccess) } override fun onCreateOptionsMenu(menu: Menu?): Boolean { @@ -38,7 +42,7 @@ class ProtectActivity : BaseActivity(), ProtectView, TextView.OnEditorActionList override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { R.id.action_done -> { - presenter.tryUnlock(edit_password.text?.toString().orEmpty()) + viewModel.tryUnlock(edit_password.text?.toString().orEmpty()) true } else -> super.onOptionsItemSelected(item) @@ -46,7 +50,7 @@ class ProtectActivity : BaseActivity(), ProtectView, TextView.OnEditorActionList override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean { return if (actionId == EditorInfo.IME_ACTION_DONE) { - presenter.tryUnlock(edit_password.text?.toString().orEmpty()) + viewModel.tryUnlock(edit_password.text?.toString().orEmpty()) true } else { false @@ -61,15 +65,15 @@ class ProtectActivity : BaseActivity(), ProtectView, TextView.OnEditorActionList layout_password.error = null } - override fun onUnlockSuccess() { + private fun onUnlockSuccess(unit: Unit) { AppProtectHelper.unlock(this) } - override fun onError(e: Throwable) { + private fun onError(e: Throwable) { layout_password.error = e.getDisplayMessage(resources) } - override fun onLoadingStateChanged(isLoading: Boolean) { + private fun onLoadingStateChanged(isLoading: Boolean) { layout_password.isEnabled = !isLoading } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/protect/ProtectViewModel.kt similarity index 64% rename from app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectPresenter.kt rename to app/src/main/java/org/koitharu/kotatsu/main/ui/protect/ProtectViewModel.kt index 51d404adb..3957fa797 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectPresenter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/protect/ProtectViewModel.kt @@ -1,22 +1,24 @@ -package org.koitharu.kotatsu.ui.utils.protect +package org.koitharu.kotatsu.main.ui.protect import kotlinx.coroutines.delay -import org.koin.core.component.inject +import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.core.exceptions.WrongPasswordException import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.ui.base.BasePresenter +import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.md5 -class ProtectPresenter : BasePresenter() { +class ProtectViewModel( + private val settings: AppSettings +) : BaseViewModel() { - private val settings by inject() + val onUnlockSuccess = SingleLiveEvent() fun tryUnlock(password: String) { launchLoadingJob { val passwordHash = password.md5() val appPasswordHash = settings.appPassword if (passwordHash == appPasswordHash) { - viewState.onUnlockSuccess() + onUnlockSuccess.call(Unit) } else { delay(PASSWORD_COMPARE_DELAY) throw WrongPasswordException() diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ReaderModule.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ReaderModule.kt new file mode 100644 index 000000000..e3b8b5eb1 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ReaderModule.kt @@ -0,0 +1,16 @@ +package org.koitharu.kotatsu.reader + +import org.koin.android.viewmodel.dsl.viewModel +import org.koin.dsl.module +import org.koitharu.kotatsu.base.domain.MangaDataRepository +import org.koitharu.kotatsu.local.data.PagesCache +import org.koitharu.kotatsu.reader.ui.ReaderViewModel + +val readerModule + get() = module { + + single { MangaDataRepository(get()) } + single { PagesCache(get()) } + + viewModel { ReaderViewModel(get(), get()) } + } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ChaptersDialog.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersDialog.kt similarity index 64% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/ChaptersDialog.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersDialog.kt index c99be2c1b..d2e8caf28 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ChaptersDialog.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersDialog.kt @@ -1,22 +1,21 @@ -package org.koitharu.kotatsu.ui.reader +package org.koitharu.kotatsu.reader.ui import android.os.Bundle import android.view.View import androidx.appcompat.app.AlertDialog import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.dialog_chapters.* import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.AlertDialogFragment +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.model.MangaChapter -import org.koitharu.kotatsu.ui.base.AlertDialogFragment -import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener -import org.koitharu.kotatsu.ui.details.ChaptersAdapter +import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter import org.koitharu.kotatsu.utils.ext.withArgs class ChaptersDialog : AlertDialogFragment(R.layout.dialog_chapters), - OnRecyclerItemClickListener { + OnListItemClickListener { override fun onBuildDialog(builder: AlertDialog.Builder) { builder.setTitle(R.string.chapters) @@ -32,22 +31,12 @@ class ChaptersDialog : AlertDialogFragment(R.layout.dialog_chapters), ) ) recyclerView_chapters.adapter = ChaptersAdapter(this).apply { - arguments?.getParcelableArrayList(ARG_CHAPTERS)?.let(this::replaceData) - currentChapterId = arguments?.getLong(ARG_CURRENT_ID, 0L)?.takeUnless { it == 0L } + // arguments?.getParcelableArrayList(ARG_CHAPTERS)?.let(this::setItems) + // currentChapterId = arguments?.getLong(ARG_CURRENT_ID, 0L)?.takeUnless { it == 0L } } } - override fun onResume() { - super.onResume() - val pos = (recyclerView_chapters.adapter as? ChaptersAdapter)?.currentChapterPosition - ?: RecyclerView.NO_POSITION - if (pos != RecyclerView.NO_POSITION) { - (recyclerView_chapters.layoutManager as? LinearLayoutManager) - ?.scrollToPositionWithOffset(pos, 100) - } - } - - override fun onItemClick(item: MangaChapter, position: Int, view: View) { + override fun onItemClick(item: MangaChapter, view: View) { ((parentFragment as? OnChapterChangeListener) ?: (activity as? OnChapterChangeListener))?.let { dismiss() diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/PageLoader.kt similarity index 96% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/PageLoader.kt index d369c5618..7c42c05da 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/PageLoader.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader +package org.koitharu.kotatsu.reader.ui import android.graphics.Bitmap import android.graphics.BitmapFactory @@ -11,7 +11,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import org.koin.core.component.KoinComponent import org.koin.core.component.inject -import org.koitharu.kotatsu.core.local.PagesCache +import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.utils.CacheUtils import org.koitharu.kotatsu.utils.ext.await import java.io.File diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt similarity index 91% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt index adce552bc..7bde0b214 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt @@ -1,7 +1,6 @@ -package org.koitharu.kotatsu.ui.reader +package org.koitharu.kotatsu.reader.ui import android.Manifest -import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.SharedPreferences @@ -13,12 +12,12 @@ import android.view.* import android.widget.Toast import androidx.activity.result.ActivityResultCallback import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.graphics.Insets import androidx.core.view.* import androidx.fragment.app.commit import androidx.lifecycle.lifecycleScope -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.activity_reader.* import kotlinx.coroutines.Dispatchers @@ -26,23 +25,22 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import moxy.MvpDelegate -import moxy.ktx.moxyPresenter import org.koin.android.ext.android.inject +import org.koin.android.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BaseFullscreenActivity import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ReaderMode -import org.koitharu.kotatsu.ui.base.BaseFullscreenActivity -import org.koitharu.kotatsu.ui.reader.base.AbstractReader -import org.koitharu.kotatsu.ui.reader.reversed.ReversedReaderFragment -import org.koitharu.kotatsu.ui.reader.standard.PagerReaderFragment -import org.koitharu.kotatsu.ui.reader.thumbnails.OnPageSelectListener -import org.koitharu.kotatsu.ui.reader.thumbnails.PagesThumbnailsSheet -import org.koitharu.kotatsu.ui.reader.wetoon.WebtoonReaderFragment +import org.koitharu.kotatsu.reader.ui.base.AbstractReader +import org.koitharu.kotatsu.reader.ui.reversed.ReversedReaderFragment +import org.koitharu.kotatsu.reader.ui.standard.PagerReaderFragment +import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener +import org.koitharu.kotatsu.reader.ui.thumbnails.PagesThumbnailsSheet +import org.koitharu.kotatsu.reader.ui.wetoon.WebtoonReaderFragment import org.koitharu.kotatsu.utils.GridTouchHelper import org.koitharu.kotatsu.utils.MangaShortcut import org.koitharu.kotatsu.utils.ScreenOrientationHelper @@ -50,12 +48,12 @@ import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.anim.Motion import org.koitharu.kotatsu.utils.ext.* -class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnChapterChangeListener, +class ReaderActivity : BaseFullscreenActivity(), ChaptersDialog.OnChapterChangeListener, GridTouchHelper.OnGridTouchListener, OnPageSelectListener, ReaderConfigDialog.Callback, ReaderListener, SharedPreferences.OnSharedPreferenceChangeListener, ActivityResultCallback, OnApplyWindowInsetsListener { - private val presenter by moxyPresenter(factory = ::ReaderPresenter) + private val viewModel by viewModel() private val settings by inject() lateinit var state: ReaderState @@ -102,17 +100,21 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh toolbar_bottom.menu.findItem(R.id.action_screen_rotate).isVisible = !it }.launchIn(lifecycleScope) - if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) { - presenter.init(state.manga) + if (savedInstanceState == null) { + viewModel.init(state.manga) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { GlobalScope.launch(Dispatchers.Main + IgnoreErrors) { MangaShortcut(state.manga).addAppShortcut(applicationContext) } } } + + viewModel.onError.observe(this, this::onError) + viewModel.reader.observe(this) { (manga, mode) -> onInitReader(manga, mode) } + viewModel.onPageSaved.observe(this, this::onPageSaved) } - override fun onInitReader(manga: Manga, mode: ReaderMode) { + private fun onInitReader(manga: Manga, mode: ReaderMode) { val currentReader = reader when (mode) { ReaderMode.WEBTOON -> if (currentReader !is WebtoonReaderFragment) { @@ -226,7 +228,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh override fun onActivityResult(result: Boolean) { if (result) { - presenter.savePage( + viewModel.savePage( resolver = contentResolver, page = reader?.currentPage ?: return ) @@ -235,7 +237,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh override fun saveState(chapterId: Long, page: Int, scroll: Int) { state = state.copy(chapterId = chapterId, page = page, scroll = scroll) - ReaderPresenter.saveState(state) + ReaderViewModel.saveState(state) } override fun onLoadingStateChanged(isLoading: Boolean) { @@ -245,7 +247,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh } override fun onError(e: Throwable) { - val dialog = MaterialAlertDialogBuilder(this) + val dialog = AlertDialog.Builder(this) .setTitle(R.string.error_occurred) .setMessage(e.message) .setPositiveButton(R.string.close, null) @@ -343,11 +345,10 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh override fun onReaderModeChanged(mode: ReaderMode) { //TODO save state - presenter.setMode(state.manga, mode) + viewModel.setMode(state.manga, mode) } - @SuppressLint("ShowToast") - override fun onPageSaved(uri: Uri?) { + private fun onPageSaved(uri: Uri?) { if (uri != null) { Snackbar.make(container, R.string.page_saved, Snackbar.LENGTH_LONG) .setAnchorView(appbar_bottom) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderConfigDialog.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderConfigDialog.kt similarity index 92% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderConfigDialog.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderConfigDialog.kt index d0c9f4265..9902293ac 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderConfigDialog.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderConfigDialog.kt @@ -1,14 +1,13 @@ -package org.koitharu.kotatsu.ui.reader +package org.koitharu.kotatsu.reader.ui import android.os.Bundle import android.view.View import androidx.appcompat.app.AlertDialog import androidx.fragment.app.FragmentManager -import kotlinx.android.synthetic.main.dialog_list_mode.button_ok import kotlinx.android.synthetic.main.dialog_reader_config.* import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.core.prefs.ReaderMode -import org.koitharu.kotatsu.ui.base.AlertDialogFragment import org.koitharu.kotatsu.utils.ext.withArgs class ReaderConfigDialog : AlertDialogFragment(R.layout.dialog_reader_config), diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderListener.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderListener.kt similarity index 52% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderListener.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderListener.kt index 6a98189fc..777bd8c48 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderListener.kt @@ -1,11 +1,14 @@ -package org.koitharu.kotatsu.ui.reader +package org.koitharu.kotatsu.reader.ui import org.koitharu.kotatsu.core.model.MangaChapter -import org.koitharu.kotatsu.ui.base.BaseMvpView -interface ReaderListener : BaseMvpView { +interface ReaderListener { fun onPageChanged(chapter: MangaChapter, page: Int) fun saveState(chapterId: Long, page: Int, scroll: Int) + + fun onLoadingStateChanged(isLoading: Boolean) + + fun onError(error: Throwable) } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderState.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderState.kt similarity index 91% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderState.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderState.kt index fe7c1edaa..f3ebae266 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderState.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderState.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader +package org.koitharu.kotatsu.reader.ui import android.os.Parcelable import kotlinx.android.parcel.IgnoredOnParcel diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt new file mode 100644 index 000000000..4c24311b0 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -0,0 +1,119 @@ +package org.koitharu.kotatsu.reader.ui + +import android.content.ContentResolver +import android.net.Uri +import android.webkit.URLUtil +import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import org.koin.core.component.KoinComponent +import org.koin.core.component.get +import org.koitharu.kotatsu.base.domain.MangaDataRepository +import org.koitharu.kotatsu.base.domain.MangaUtils +import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.model.MangaPage +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.ReaderMode +import org.koitharu.kotatsu.history.domain.HistoryRepository +import org.koitharu.kotatsu.utils.MediaStoreCompat +import org.koitharu.kotatsu.utils.SingleLiveEvent +import org.koitharu.kotatsu.utils.ext.* + +class ReaderViewModel( + private val dataRepository: MangaDataRepository, + private val settings: AppSettings +) : BaseViewModel() { + + val reader = MutableLiveData>() + val onPageSaved = SingleLiveEvent() + + fun init(manga: Manga) { + launchLoadingJob { + val mode = withContext(Dispatchers.Default) { + val repo = manga.source.repository + val chapter = + (manga.chapters ?: throw RuntimeException("Chapters is null")).random() + var mode = dataRepository.getReaderMode(manga.id) + if (mode == null) { + val pages = repo.getPages(chapter) + val isWebtoon = MangaUtils.determineMangaIsWebtoon(pages) + mode = getReaderMode(isWebtoon) + if (isWebtoon != null) { + dataRepository.savePreferences( + manga = manga, + mode = mode + ) + } + } + mode + } + reader.value = manga to mode + } + } + + fun setMode(manga: Manga, mode: ReaderMode) { + launchJob { + dataRepository.savePreferences( + manga = manga, + mode = mode + ) + reader.value = manga to mode + } + } + + fun savePage(resolver: ContentResolver, page: MangaPage) { + launchJob { + withContext(Dispatchers.Default) { + try { + val repo = page.source.repository + val url = repo.getPageFullUrl(page) + val request = Request.Builder() + .url(url) + .get() + .build() + val uri = get().newCall(request).await().use { response -> + val fileName = + URLUtil.guessFileName( + url, + response.contentDisposition, + response.mimeType + ) + MediaStoreCompat.insertImage(resolver, fileName) { + response.body!!.byteStream().copyTo(it) + } + } + onPageSaved.postCall(uri) + } catch (e: CancellationException) { + } catch (e: Exception) { + onPageSaved.postCall(null) + } + } + } + } + + private fun getReaderMode(isWebtoon: Boolean?) = when { + isWebtoon == true -> ReaderMode.WEBTOON + settings.isPreferRtlReader -> ReaderMode.REVERSED + else -> ReaderMode.STANDARD + } + + companion object : KoinComponent { + + fun saveState(state: ReaderState) { + processLifecycleScope.launch(Dispatchers.Default + IgnoreErrors) { + get().addOrUpdate( + manga = state.manga, + chapterId = state.chapterId, + page = state.page, + scroll = state.scroll + ) + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/SimpleSettingsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/SimpleSettingsActivity.kt similarity index 77% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/SimpleSettingsActivity.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/SimpleSettingsActivity.kt index 1b832cd9f..ea02b4c0c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/SimpleSettingsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/SimpleSettingsActivity.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader +package org.koitharu.kotatsu.reader.ui import android.content.Context import android.content.Intent @@ -6,10 +6,10 @@ import android.os.Bundle import androidx.fragment.app.commit import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.ui.base.BaseActivity -import org.koitharu.kotatsu.ui.settings.MainSettingsFragment -import org.koitharu.kotatsu.ui.settings.NetworkSettingsFragment -import org.koitharu.kotatsu.ui.settings.ReaderSettingsFragment +import org.koitharu.kotatsu.base.ui.BaseActivity +import org.koitharu.kotatsu.settings.MainSettingsFragment +import org.koitharu.kotatsu.settings.NetworkSettingsFragment +import org.koitharu.kotatsu.settings.ReaderSettingsFragment class SimpleSettingsActivity : BaseActivity() { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/AbstractReader.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/base/AbstractReader.kt similarity index 96% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/base/AbstractReader.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/base/AbstractReader.kt index 1421b80ef..33678367a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/AbstractReader.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/base/AbstractReader.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.base +package org.koitharu.kotatsu.reader.ui.base import android.content.Context import android.os.Bundle @@ -10,13 +10,13 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaPage -import org.koitharu.kotatsu.ui.base.BaseFragment -import org.koitharu.kotatsu.ui.reader.PageLoader -import org.koitharu.kotatsu.ui.reader.ReaderListener -import org.koitharu.kotatsu.ui.reader.ReaderState +import org.koitharu.kotatsu.reader.ui.PageLoader +import org.koitharu.kotatsu.reader.ui.ReaderListener +import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.utils.ext.associateByLong import org.koitharu.kotatsu.utils.ext.viewLifecycleScope diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/BaseReaderAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/base/BaseReaderAdapter.kt similarity index 92% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/base/BaseReaderAdapter.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/base/BaseReaderAdapter.kt index b90e752ca..67a54c62d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/BaseReaderAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/base/BaseReaderAdapter.kt @@ -1,8 +1,8 @@ -package org.koitharu.kotatsu.ui.reader.base +package org.koitharu.kotatsu.reader.ui.base import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder +import org.koitharu.kotatsu.base.ui.list.BaseViewHolder abstract class BaseReaderAdapter(protected val pages: List) : RecyclerView.Adapter>() { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/OnBoundsScrollListener.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/base/OnBoundsScrollListener.kt similarity index 65% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/base/OnBoundsScrollListener.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/base/OnBoundsScrollListener.kt index 03054e3ef..44afcd6a2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/OnBoundsScrollListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/base/OnBoundsScrollListener.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.base +package org.koitharu.kotatsu.reader.ui.base interface OnBoundsScrollListener { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/PageHolderDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/base/PageHolderDelegate.kt similarity index 96% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/base/PageHolderDelegate.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/base/PageHolderDelegate.kt index 5d98f5abd..5c96fb65e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/PageHolderDelegate.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/base/PageHolderDelegate.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.base +package org.koitharu.kotatsu.reader.ui.base import android.net.Uri import androidx.core.net.toUri @@ -8,7 +8,7 @@ import org.koin.core.component.inject import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.ui.reader.PageLoader +import org.koitharu.kotatsu.reader.ui.PageLoader import org.koitharu.kotatsu.utils.ext.launchAfter import org.koitharu.kotatsu.utils.ext.launchInstead import java.io.File diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/ReaderPage.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/base/ReaderPage.kt similarity index 93% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/base/ReaderPage.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/base/ReaderPage.kt index 5f1807f68..b73387360 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/ReaderPage.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/base/ReaderPage.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.base +package org.koitharu.kotatsu.reader.ui.base import android.os.Parcelable import kotlinx.android.parcel.Parcelize diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedPageAnimTransformer.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/reversed/ReversedPageAnimTransformer.kt similarity index 93% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedPageAnimTransformer.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/reversed/ReversedPageAnimTransformer.kt index 3404f0529..f572c4616 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedPageAnimTransformer.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/reversed/ReversedPageAnimTransformer.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.reversed +package org.koitharu.kotatsu.reader.ui.reversed import android.view.View import androidx.viewpager2.widget.ViewPager2 diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedPageHolder.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/reversed/ReversedPageHolder.kt similarity index 89% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedPageHolder.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/reversed/ReversedPageHolder.kt index c21253cd2..233ab81ef 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedPageHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/reversed/ReversedPageHolder.kt @@ -1,12 +1,12 @@ -package org.koitharu.kotatsu.ui.reader.reversed +package org.koitharu.kotatsu.reader.ui.reversed import android.graphics.PointF import android.view.ViewGroup import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import kotlinx.android.synthetic.main.item_page.* import org.koitharu.kotatsu.core.model.ZoomMode -import org.koitharu.kotatsu.ui.reader.PageLoader -import org.koitharu.kotatsu.ui.reader.standard.PageHolder +import org.koitharu.kotatsu.reader.ui.PageLoader +import org.koitharu.kotatsu.reader.ui.standard.PageHolder class ReversedPageHolder(parent: ViewGroup, loader: PageLoader) : PageHolder(parent, loader) { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedPagesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/reversed/ReversedPagesAdapter.kt similarity index 78% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedPagesAdapter.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/reversed/ReversedPagesAdapter.kt index c4de634fd..620957714 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedPagesAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/reversed/ReversedPagesAdapter.kt @@ -1,10 +1,10 @@ -package org.koitharu.kotatsu.ui.reader.reversed +package org.koitharu.kotatsu.reader.ui.reversed import android.view.ViewGroup -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder -import org.koitharu.kotatsu.ui.reader.PageLoader -import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter -import org.koitharu.kotatsu.ui.reader.base.ReaderPage +import org.koitharu.kotatsu.base.ui.list.BaseViewHolder +import org.koitharu.kotatsu.reader.ui.PageLoader +import org.koitharu.kotatsu.reader.ui.base.BaseReaderAdapter +import org.koitharu.kotatsu.reader.ui.base.ReaderPage class ReversedPagesAdapter( pages: List, diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedReaderFragment.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/reversed/ReversedReaderFragment.kt similarity index 86% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedReaderFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/reversed/ReversedReaderFragment.kt index 2bed0925a..5fce5dd4c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedReaderFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/reversed/ReversedReaderFragment.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.reversed +package org.koitharu.kotatsu.reader.ui.reversed import android.content.Context import android.content.SharedPreferences @@ -8,12 +8,12 @@ import kotlinx.android.synthetic.main.fragment_reader_standard.* import org.koin.android.ext.android.inject import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.ui.reader.ReaderState -import org.koitharu.kotatsu.ui.reader.base.AbstractReader -import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter -import org.koitharu.kotatsu.ui.reader.base.ReaderPage -import org.koitharu.kotatsu.ui.reader.standard.PageAnimTransformer -import org.koitharu.kotatsu.ui.reader.standard.PagerPaginationListener +import org.koitharu.kotatsu.reader.ui.ReaderState +import org.koitharu.kotatsu.reader.ui.base.AbstractReader +import org.koitharu.kotatsu.reader.ui.base.BaseReaderAdapter +import org.koitharu.kotatsu.reader.ui.base.ReaderPage +import org.koitharu.kotatsu.reader.ui.standard.PageAnimTransformer +import org.koitharu.kotatsu.reader.ui.standard.PagerPaginationListener import org.koitharu.kotatsu.utils.ext.doOnPageChanged import org.koitharu.kotatsu.utils.ext.swapAdapter import org.koitharu.kotatsu.utils.ext.withArgs diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PageAnimTransformer.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PageAnimTransformer.kt similarity index 93% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PageAnimTransformer.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PageAnimTransformer.kt index 584f7cf64..6b8405327 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PageAnimTransformer.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PageAnimTransformer.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.standard +package org.koitharu.kotatsu.reader.ui.standard import android.view.View import androidx.viewpager2.widget.ViewPager2 diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PageHolder.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PageHolder.kt similarity index 90% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PageHolder.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PageHolder.kt index 4d127639c..75725a5b5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PageHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PageHolder.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.standard +package org.koitharu.kotatsu.reader.ui.standard import android.graphics.PointF import android.net.Uri @@ -9,11 +9,11 @@ import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import kotlinx.android.synthetic.main.item_page.* import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.BaseViewHolder import org.koitharu.kotatsu.core.model.ZoomMode -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder -import org.koitharu.kotatsu.ui.reader.PageLoader -import org.koitharu.kotatsu.ui.reader.base.PageHolderDelegate -import org.koitharu.kotatsu.ui.reader.base.ReaderPage +import org.koitharu.kotatsu.reader.ui.PageLoader +import org.koitharu.kotatsu.reader.ui.base.PageHolderDelegate +import org.koitharu.kotatsu.reader.ui.base.ReaderPage import org.koitharu.kotatsu.utils.ext.getDisplayMessage open class PageHolder(parent: ViewGroup, loader: PageLoader) : diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerPaginationListener.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PagerPaginationListener.kt similarity index 87% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerPaginationListener.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PagerPaginationListener.kt index 7bb129036..d8bad45b1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerPaginationListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PagerPaginationListener.kt @@ -1,8 +1,8 @@ -package org.koitharu.kotatsu.ui.reader.standard +package org.koitharu.kotatsu.reader.ui.standard import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 -import org.koitharu.kotatsu.ui.reader.base.OnBoundsScrollListener +import org.koitharu.kotatsu.reader.ui.base.OnBoundsScrollListener class PagerPaginationListener( private val adapter: RecyclerView.Adapter<*>, diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerReaderFragment.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PagerReaderFragment.kt similarity index 89% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerReaderFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PagerReaderFragment.kt index b327bef84..2ce317e4b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerReaderFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PagerReaderFragment.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.standard +package org.koitharu.kotatsu.reader.ui.standard import android.content.Context import android.content.SharedPreferences @@ -8,10 +8,10 @@ import kotlinx.android.synthetic.main.fragment_reader_standard.* import org.koin.android.ext.android.inject import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.ui.reader.ReaderState -import org.koitharu.kotatsu.ui.reader.base.AbstractReader -import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter -import org.koitharu.kotatsu.ui.reader.base.ReaderPage +import org.koitharu.kotatsu.reader.ui.ReaderState +import org.koitharu.kotatsu.reader.ui.base.AbstractReader +import org.koitharu.kotatsu.reader.ui.base.BaseReaderAdapter +import org.koitharu.kotatsu.reader.ui.base.ReaderPage import org.koitharu.kotatsu.utils.ext.doOnPageChanged import org.koitharu.kotatsu.utils.ext.swapAdapter import org.koitharu.kotatsu.utils.ext.withArgs diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PagesAdapter.kt similarity index 51% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagesAdapter.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PagesAdapter.kt index 084f057c6..0ba2ec6bd 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagesAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/standard/PagesAdapter.kt @@ -1,9 +1,9 @@ -package org.koitharu.kotatsu.ui.reader.standard +package org.koitharu.kotatsu.reader.ui.standard import android.view.ViewGroup -import org.koitharu.kotatsu.ui.reader.PageLoader -import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter -import org.koitharu.kotatsu.ui.reader.base.ReaderPage +import org.koitharu.kotatsu.reader.ui.PageLoader +import org.koitharu.kotatsu.reader.ui.base.BaseReaderAdapter +import org.koitharu.kotatsu.reader.ui.base.ReaderPage class PagesAdapter( pages: List, diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/OnPageSelectListener.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/OnPageSelectListener.kt similarity index 71% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/OnPageSelectListener.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/OnPageSelectListener.kt index f99bf76bf..57eb3645c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/OnPageSelectListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/OnPageSelectListener.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.thumbnails +package org.koitharu.kotatsu.reader.ui.thumbnails import org.koitharu.kotatsu.core.model.MangaPage diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PageThumbnailHolder.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/PageThumbnailHolder.kt similarity index 91% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PageThumbnailHolder.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/PageThumbnailHolder.kt index 47f7a1527..71048d6f0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PageThumbnailHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/PageThumbnailHolder.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.thumbnails +package org.koitharu.kotatsu.reader.ui.thumbnails import android.view.ViewGroup import androidx.core.net.toUri @@ -10,9 +10,9 @@ import kotlinx.android.synthetic.main.item_page_thumb.* import kotlinx.coroutines.* import org.koin.core.component.inject import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.local.PagesCache +import org.koitharu.kotatsu.base.ui.list.BaseViewHolder import org.koitharu.kotatsu.core.model.MangaPage -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder +import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.utils.ext.IgnoreErrors class PageThumbnailHolder(parent: ViewGroup, private val scope: CoroutineScope) : diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PagesThumbnailsSheet.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsSheet.kt similarity index 82% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PagesThumbnailsSheet.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsSheet.kt index f47d85fdc..d88fb63f8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PagesThumbnailsSheet.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/PagesThumbnailsSheet.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.thumbnails +package org.koitharu.kotatsu.reader.ui.thumbnails import android.os.Bundle import android.view.View @@ -8,30 +8,32 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import kotlinx.android.synthetic.main.sheet_pages.* import kotlinx.coroutines.DisposableHandle +import org.koin.android.ext.android.get import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BaseBottomSheet +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.core.model.MangaPage -import org.koitharu.kotatsu.ui.base.BaseBottomSheet -import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener -import org.koitharu.kotatsu.ui.base.list.decor.SpacingItemDecoration +import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter import org.koitharu.kotatsu.utils.UiUtils import org.koitharu.kotatsu.utils.ext.resolveDp +import org.koitharu.kotatsu.utils.ext.viewLifecycleScope import org.koitharu.kotatsu.utils.ext.withArgs class PagesThumbnailsSheet : BaseBottomSheet(R.layout.sheet_pages), - OnRecyclerItemClickListener { + OnListItemClickListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) recyclerView.addItemDecoration(SpacingItemDecoration(view.resources.resolveDp(8))) val pages = arguments?.getParcelableArrayList(ARG_PAGES) - if (pages != null) { - recyclerView.adapter = PagesThumbnailsAdapter(this).apply { - replaceData(pages) - } - } else { + if (pages == null) { dismissAllowingStateLoss() return } + recyclerView.adapter = PageThumbnailAdapter(get(), viewLifecycleScope, get(), this).apply { + items = pages + } val title = arguments?.getString(ARG_TITLE) toolbar.title = title toolbar.setNavigationOnClickListener { dismiss() } @@ -74,7 +76,7 @@ class PagesThumbnailsSheet : BaseBottomSheet(R.layout.sheet_pages), super.onDestroyView() } - override fun onItemClick(item: MangaPage, position: Int, view: View) { + override fun onItemClick(item: MangaPage, view: View) { ((parentFragment as? OnPageSelectListener) ?: (activity as? OnPageSelectListener))?.run { onPageSelected(item) diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt new file mode 100644 index 000000000..bd89a5b16 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt @@ -0,0 +1,59 @@ +package org.koitharu.kotatsu.reader.ui.thumbnails.adapter + +import androidx.core.net.toUri +import coil.ImageLoader +import coil.request.ImageRequest +import coil.size.PixelSize +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer +import kotlinx.android.synthetic.main.item_page_thumb.* +import kotlinx.coroutines.* +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.model.MangaPage +import org.koitharu.kotatsu.local.data.PagesCache +import org.koitharu.kotatsu.utils.ext.IgnoreErrors + +fun pageThumbnailAD( + coil: ImageLoader, + scope: CoroutineScope, + cache: PagesCache, + clickListener: OnListItemClickListener +) = adapterDelegateLayoutContainer(R.layout.item_page_thumb) { + + var job: Job? = null + val gridWidth = itemView.context.resources.getDimensionPixelSize(R.dimen.preferred_grid_width) + val thumbSize = PixelSize( + width = gridWidth, + height = (gridWidth * 13f / 18f).toInt() + ) + + handle.setOnClickListener { + clickListener.onItemClick(item, itemView) + } + + bind { + job?.cancel() + imageView_thumb.setImageDrawable(null) + textView_number.text = (bindingAdapterPosition + 1).toString() + job = scope.launch(Dispatchers.Default + IgnoreErrors) { + val url = item.preview ?: item.url.let { + val pageUrl = item.source.repository.getPageFullUrl(item) + cache[pageUrl]?.toUri()?.toString() ?: pageUrl + } + val drawable = coil.execute( + ImageRequest.Builder(context) + .data(url) + .size(thumbSize) + .build() + ).drawable + withContext(Dispatchers.Main) { + imageView_thumb.setImageDrawable(drawable) + } + } + } + + onViewRecycled { + job?.cancel() + imageView_thumb.setImageDrawable(null) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAdapter.kt new file mode 100644 index 000000000..4533910f0 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAdapter.kt @@ -0,0 +1,20 @@ +package org.koitharu.kotatsu.reader.ui.thumbnails.adapter + +import coil.ImageLoader +import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter +import kotlinx.coroutines.CoroutineScope +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.model.MangaPage +import org.koitharu.kotatsu.local.data.PagesCache + +class PageThumbnailAdapter( + coil: ImageLoader, + scope: CoroutineScope, + cache: PagesCache, + clickListener: OnListItemClickListener +) : ListDelegationAdapter>() { + + init { + delegatesManager.addDelegate(pageThumbnailAD(coil, scope, cache, clickListener)) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/ListPaginationListener.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/ListPaginationListener.kt similarity index 90% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/ListPaginationListener.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/ListPaginationListener.kt index 23d5ed3d2..7c1daccf3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/ListPaginationListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/ListPaginationListener.kt @@ -1,8 +1,8 @@ -package org.koitharu.kotatsu.ui.reader.wetoon +package org.koitharu.kotatsu.reader.ui.wetoon import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import org.koitharu.kotatsu.ui.reader.base.OnBoundsScrollListener +import org.koitharu.kotatsu.reader.ui.base.OnBoundsScrollListener class ListPaginationListener( private val offset: Int, diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonAdapter.kt similarity index 51% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonAdapter.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonAdapter.kt index 7972b9d8c..52d8efc7e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonAdapter.kt @@ -1,9 +1,9 @@ -package org.koitharu.kotatsu.ui.reader.wetoon +package org.koitharu.kotatsu.reader.ui.wetoon import android.view.ViewGroup -import org.koitharu.kotatsu.ui.reader.PageLoader -import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter -import org.koitharu.kotatsu.ui.reader.base.ReaderPage +import org.koitharu.kotatsu.reader.ui.PageLoader +import org.koitharu.kotatsu.reader.ui.base.BaseReaderAdapter +import org.koitharu.kotatsu.reader.ui.base.ReaderPage class WebtoonAdapter( pages: List, diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonFrameLayout.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonFrameLayout.kt similarity index 92% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonFrameLayout.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonFrameLayout.kt index a6f1ec906..506fe322f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonFrameLayout.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonFrameLayout.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.wetoon +package org.koitharu.kotatsu.reader.ui.wetoon import android.content.Context import android.util.AttributeSet diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonHolder.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonHolder.kt similarity index 88% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonHolder.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonHolder.kt index acbc8c4a5..98d4ebe0e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonHolder.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.wetoon +package org.koitharu.kotatsu.reader.ui.wetoon import android.net.Uri import android.view.View @@ -8,11 +8,11 @@ import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import kotlinx.android.synthetic.main.item_page_webtoon.* import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.BaseViewHolder import org.koitharu.kotatsu.core.model.ZoomMode -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder -import org.koitharu.kotatsu.ui.reader.PageLoader -import org.koitharu.kotatsu.ui.reader.base.PageHolderDelegate -import org.koitharu.kotatsu.ui.reader.base.ReaderPage +import org.koitharu.kotatsu.reader.ui.PageLoader +import org.koitharu.kotatsu.reader.ui.base.PageHolderDelegate +import org.koitharu.kotatsu.reader.ui.base.ReaderPage import org.koitharu.kotatsu.utils.ext.getDisplayMessage diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonImageView.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonImageView.kt similarity index 96% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonImageView.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonImageView.kt index 9414818a6..fd8c21226 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonImageView.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonImageView.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.wetoon +package org.koitharu.kotatsu.reader.ui.wetoon import android.content.Context import android.graphics.PointF diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonReaderFragment.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonReaderFragment.kt similarity index 89% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonReaderFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonReaderFragment.kt index bf5ba37d9..d78cf8fd9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonReaderFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonReaderFragment.kt @@ -1,14 +1,14 @@ -package org.koitharu.kotatsu.ui.reader.wetoon +package org.koitharu.kotatsu.reader.ui.wetoon import android.os.Bundle import android.view.View import android.view.animation.AccelerateDecelerateInterpolator import kotlinx.android.synthetic.main.fragment_reader_webtoon.* import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.ui.reader.ReaderState -import org.koitharu.kotatsu.ui.reader.base.AbstractReader -import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter -import org.koitharu.kotatsu.ui.reader.base.ReaderPage +import org.koitharu.kotatsu.reader.ui.ReaderState +import org.koitharu.kotatsu.reader.ui.base.AbstractReader +import org.koitharu.kotatsu.reader.ui.base.BaseReaderAdapter +import org.koitharu.kotatsu.reader.ui.base.ReaderPage import org.koitharu.kotatsu.utils.ext.doOnCurrentItemChanged import org.koitharu.kotatsu.utils.ext.findCenterViewPosition import org.koitharu.kotatsu.utils.ext.firstItem diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonRecyclerView.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonRecyclerView.kt similarity index 88% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonRecyclerView.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonRecyclerView.kt index e898f7637..1b9a42a29 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonRecyclerView.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/wetoon/WebtoonRecyclerView.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader.wetoon +package org.koitharu.kotatsu.reader.ui.wetoon import android.content.Context import android.util.AttributeSet @@ -48,7 +48,8 @@ class WebtoonRecyclerView @JvmOverloads constructor( if (consumedByChild < dy) { if (childCount > 1) { val nextChild = getChildAt(1) as WebtoonFrameLayout - val unconsumed = dy - consumedByChild - nextChild.top //will be consumed by scroll + val unconsumed = + dy - consumedByChild - nextChild.top //will be consumed by scroll if (unconsumed > 0) { consumedByChild += nextChild.dispatchVerticalScroll(unconsumed) } @@ -62,7 +63,8 @@ class WebtoonRecyclerView @JvmOverloads constructor( if (consumedByChild > dy) { if (childCount > 1) { val nextChild = getChildAt(childCount - 2) as WebtoonFrameLayout - val unconsumed = dy - consumedByChild + (height - nextChild.bottom) //will be consumed by scroll + val unconsumed = + dy - consumedByChild + (height - nextChild.bottom) //will be consumed by scroll if (unconsumed < 0) { consumedByChild += nextChild.dispatchVerticalScroll(unconsumed) } diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/RemoteListModule.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/RemoteListModule.kt new file mode 100644 index 000000000..f36bf7a69 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/RemoteListModule.kt @@ -0,0 +1,15 @@ +package org.koitharu.kotatsu.remotelist + +import org.koin.android.viewmodel.dsl.viewModel +import org.koin.core.qualifier.named +import org.koin.dsl.module +import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel + +val remoteListModule + get() = module { + + viewModel { (source: MangaSource) -> + RemoteListViewModel(get(named(source)), get()) + } + } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/remote/RemoteListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt similarity index 67% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/remote/RemoteListFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt index 698e5105a..91f95c0a3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/remote/RemoteListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt @@ -1,27 +1,33 @@ -package org.koitharu.kotatsu.ui.list.remote +package org.koitharu.kotatsu.remotelist.ui import android.view.Menu import android.view.MenuInflater import android.view.MenuItem -import moxy.ktx.moxyPresenter +import org.koin.android.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.MangaFilter import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.ui.list.MangaListFragment -import org.koitharu.kotatsu.ui.search.SearchActivity +import org.koitharu.kotatsu.list.ui.MangaListFragment +import org.koitharu.kotatsu.search.ui.SearchActivity import org.koitharu.kotatsu.utils.ext.parcelableArgument import org.koitharu.kotatsu.utils.ext.withArgs -class RemoteListFragment : MangaListFragment() { +class RemoteListFragment : MangaListFragment() { - private val presenter by moxyPresenter { - RemoteListPresenter(source) + override val viewModel by viewModel { + parametersOf(source) } private val source by parcelableArgument(ARG_SOURCE) - override fun onRequestMoreItems(offset: Int) { - presenter.loadList(offset) + override fun onRefresh() { + super.onRefresh() + viewModel.loadList(append = false) + } + + override fun onScrolledToEnd() { + viewModel.loadList(append = true) } override fun getTitle(): CharSequence? { @@ -29,7 +35,7 @@ class RemoteListFragment : MangaListFragment() { } override fun onFilterChanged(filter: MangaFilter) { - presenter.applyFilter(filter) + viewModel.applyFilter(filter) super.onFilterChanged(filter) } diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt new file mode 100644 index 000000000..07469a896 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt @@ -0,0 +1,95 @@ +package org.koitharu.kotatsu.remotelist.ui + +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.withContext +import org.koitharu.kotatsu.BuildConfig +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.model.MangaFilter +import org.koitharu.kotatsu.core.parser.MangaRepository +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.ListMode +import org.koitharu.kotatsu.list.ui.MangaFilterConfig +import org.koitharu.kotatsu.list.ui.MangaListViewModel +import org.koitharu.kotatsu.list.ui.model.IndeterminateProgress +import org.koitharu.kotatsu.list.ui.model.toGridModel +import org.koitharu.kotatsu.list.ui.model.toListDetailedModel +import org.koitharu.kotatsu.list.ui.model.toListModel + +class RemoteListViewModel( + private val repository: MangaRepository, + settings: AppSettings +) : MangaListViewModel(settings) { + + private val mangaList = MutableStateFlow>(emptyList()) + private val hasNextPage = MutableStateFlow(false) + private var appliedFilter: MangaFilter? = null + private var loadingJob: Job? = null + + override val content = combine(mangaList.drop(1), createListModeFlow()) { list, mode -> + when (mode) { + ListMode.LIST -> list.map { it.toListModel() } + ListMode.DETAILED_LIST -> list.map { it.toListDetailedModel() } + ListMode.GRID -> list.map { it.toGridModel() } + } + }.onEach { + isEmptyState.postValue(it.isEmpty()) + }.combine(hasNextPage) { list, isHasNextPage -> + if (isHasNextPage && list.isNotEmpty()) list + IndeterminateProgress else list + }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + + init { + loadList(false) + loadFilter() + } + + fun loadList(append: Boolean) { + if (loadingJob?.isActive == true) { + return + } + loadingJob = launchLoadingJob { + withContext(Dispatchers.Default) { + val list = repository.getList( + offset = if (append) mangaList.value.size else 0, + sortOrder = appliedFilter?.sortOrder, + tag = appliedFilter?.tag + ) + if (!append) { + mangaList.value = list + } else if (list.isNotEmpty()) { + mangaList.value += list + } + hasNextPage.value = list.isNotEmpty() + } + } + } + + fun applyFilter(newFilter: MangaFilter) { + appliedFilter = newFilter + mangaList.value = emptyList() + hasNextPage.value = false + loadList(false) + } + + private fun loadFilter() { + launchJob { + try { + val (sorts, tags) = withContext(Dispatchers.Default) { + repository.sortOrders.sortedBy { it.ordinal } to repository.getTags() + .sortedBy { it.title } + } + filter.value = MangaFilterConfig(sorts, tags, appliedFilter) + } catch (e: Exception) { + if (BuildConfig.DEBUG) { + e.printStackTrace() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/SearchModule.kt b/app/src/main/java/org/koitharu/kotatsu/search/SearchModule.kt new file mode 100644 index 000000000..b1fb6f120 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/search/SearchModule.kt @@ -0,0 +1,18 @@ +package org.koitharu.kotatsu.search + +import org.koin.android.viewmodel.dsl.viewModel +import org.koin.core.qualifier.named +import org.koin.dsl.module +import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.search.domain.MangaSearchRepository +import org.koitharu.kotatsu.search.ui.SearchViewModel +import org.koitharu.kotatsu.search.ui.global.GlobalSearchViewModel + +val searchModule + get() = module { + + single { MangaSearchRepository() } + + viewModel { (source: MangaSource) -> SearchViewModel(get(named(source)), get()) } + viewModel { GlobalSearchViewModel(get(), get()) } + } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/MangaSearchRepository.kt b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt similarity index 90% rename from app/src/main/java/org/koitharu/kotatsu/domain/MangaSearchRepository.kt rename to app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt index 5e205c0e8..ad41053bb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/MangaSearchRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt @@ -1,7 +1,8 @@ -package org.koitharu.kotatsu.domain +package org.koitharu.kotatsu.search.domain import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import org.koitharu.kotatsu.base.domain.MangaProviderFactory import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.SortOrder diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaSearchSheet.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaSearchSheet.kt new file mode 100644 index 000000000..bb3941707 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaSearchSheet.kt @@ -0,0 +1,49 @@ +package org.koitharu.kotatsu.search.ui + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.FragmentManager +import org.koin.android.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.list.ui.MangaListSheet +import org.koitharu.kotatsu.utils.ext.parcelableArgument +import org.koitharu.kotatsu.utils.ext.stringArgument +import org.koitharu.kotatsu.utils.ext.withArgs + +class MangaSearchSheet : MangaListSheet() { + + override val viewModel by viewModel { + parametersOf(source) + } + + private val query by stringArgument(ARG_QUERY) + private val source by parcelableArgument(ARG_SOURCE) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setTitle(query.orEmpty()) + setSubtitle(getString(R.string.search_results_on_s, source.title)) + } + + override fun onScrolledToEnd() { + viewModel.loadList(query.orEmpty(), append = true) + } + + companion object { + + private const val ARG_SOURCE = "source" + private const val ARG_QUERY = "query" + + private const val TAG = "MangaSearchSheet" + + fun show(fm: FragmentManager, source: MangaSource, query: String) { + MangaSearchSheet().withArgs(2) { + putParcelable(ARG_SOURCE, source) + putString(ARG_QUERY, query) + }.show(fm, TAG) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/search/MangaSuggestionsProvider.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaSuggestionsProvider.kt similarity index 98% rename from app/src/main/java/org/koitharu/kotatsu/ui/search/MangaSuggestionsProvider.kt rename to app/src/main/java/org/koitharu/kotatsu/search/ui/MangaSuggestionsProvider.kt index 81627a063..2889a24cf 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/search/MangaSuggestionsProvider.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaSuggestionsProvider.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.search +package org.koitharu.kotatsu.search.ui import android.app.SearchManager import android.content.ContentResolver diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchActivity.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchActivity.kt similarity index 95% rename from app/src/main/java/org/koitharu/kotatsu/ui/search/SearchActivity.kt rename to app/src/main/java/org/koitharu/kotatsu/search/ui/SearchActivity.kt index 89ea79c3d..33a19d383 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchActivity.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.search +package org.koitharu.kotatsu.search.ui import android.content.Context import android.content.Intent @@ -7,8 +7,8 @@ import android.os.Parcelable import androidx.appcompat.widget.SearchView import kotlinx.android.synthetic.main.activity_search.* import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.ui.base.BaseActivity import org.koitharu.kotatsu.utils.ext.showKeyboard class SearchActivity : BaseActivity(), SearchView.OnQueryTextListener { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchFragment.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt similarity index 56% rename from app/src/main/java/org/koitharu/kotatsu/ui/search/SearchFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt index 1a796624f..ac6e22c25 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt @@ -1,21 +1,29 @@ -package org.koitharu.kotatsu.ui.search +package org.koitharu.kotatsu.search.ui -import moxy.ktx.moxyPresenter +import org.koin.android.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.ui.list.MangaListFragment +import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.utils.ext.parcelableArgument import org.koitharu.kotatsu.utils.ext.stringArgument import org.koitharu.kotatsu.utils.ext.withArgs -class SearchFragment : MangaListFragment() { +class SearchFragment : MangaListFragment() { - private val presenter by moxyPresenter(factory = ::SearchPresenter) + override val viewModel by viewModel { + parametersOf(source) + } private val query by stringArgument(ARG_QUERY) private val source by parcelableArgument(ARG_SOURCE) - override fun onRequestMoreItems(offset: Int) { - presenter.loadList(source, query.orEmpty(), offset) + override fun onRefresh() { + super.onRefresh() + viewModel.loadList(query.orEmpty(), append = false) + } + + override fun onScrolledToEnd() { + viewModel.loadList(query.orEmpty(), append = true) } override fun getTitle(): CharSequence? { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchHelper.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchHelper.kt similarity index 94% rename from app/src/main/java/org/koitharu/kotatsu/ui/search/SearchHelper.kt rename to app/src/main/java/org/koitharu/kotatsu/search/ui/SearchHelper.kt index e408fdc7d..7cbdad906 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchHelper.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchHelper.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.search +package org.koitharu.kotatsu.search.ui import android.app.SearchManager import android.content.Context @@ -6,7 +6,7 @@ import android.database.Cursor import android.view.MenuItem import androidx.appcompat.widget.SearchView import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.ui.search.global.GlobalSearchActivity +import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity import org.koitharu.kotatsu.utils.ext.safe import java.io.Closeable diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt new file mode 100644 index 000000000..6d6b8b783 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt @@ -0,0 +1,29 @@ +package org.koitharu.kotatsu.search.ui + +import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.koitharu.kotatsu.core.parser.MangaRepository +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.list.ui.MangaListViewModel + +class SearchViewModel( + private val repository: MangaRepository, + settings: AppSettings +) : MangaListViewModel(settings) { + + override val content = MutableLiveData>() + + fun loadList(query: String, append: Boolean) { + launchLoadingJob { + val list = withContext(Dispatchers.Default) { + repository.getList(TODO(), query = query) + } + if (!append) { + content.value = list + } else { + content.value = content.value.orEmpty() + list + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/search/global/GlobalSearchActivity.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchActivity.kt similarity index 90% rename from app/src/main/java/org/koitharu/kotatsu/ui/search/global/GlobalSearchActivity.kt rename to app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchActivity.kt index 1adb63af6..acddcca71 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/search/global/GlobalSearchActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchActivity.kt @@ -1,10 +1,10 @@ -package org.koitharu.kotatsu.ui.search.global +package org.koitharu.kotatsu.search.ui.global import android.content.Context import android.content.Intent import android.os.Bundle import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.ui.base.BaseActivity +import org.koitharu.kotatsu.base.ui.BaseActivity class GlobalSearchActivity : BaseActivity() { diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt new file mode 100644 index 000000000..63a116b4e --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt @@ -0,0 +1,34 @@ +package org.koitharu.kotatsu.search.ui.global + +import org.koin.android.viewmodel.ext.android.viewModel +import org.koitharu.kotatsu.list.ui.MangaListFragment +import org.koitharu.kotatsu.utils.ext.stringArgument +import org.koitharu.kotatsu.utils.ext.withArgs + + +class GlobalSearchFragment : MangaListFragment() { + + override val viewModel by viewModel() + + private val query by stringArgument(ARG_QUERY) + + override fun onRefresh() { + super.onRefresh() + viewModel.startSearch(query.orEmpty()) + } + + override fun onScrolledToEnd() = Unit + + override fun getTitle(): CharSequence? { + return query + } + + companion object { + + private const val ARG_QUERY = "query" + + fun newInstance(query: String) = GlobalSearchFragment().withArgs(1) { + putString(ARG_QUERY, query) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchViewModel.kt new file mode 100644 index 000000000..d4b965628 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchViewModel.kt @@ -0,0 +1,43 @@ +package org.koitharu.kotatsu.search.ui.global + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.* +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.list.ui.MangaListViewModel +import org.koitharu.kotatsu.search.domain.MangaSearchRepository +import org.koitharu.kotatsu.utils.ext.onFirst +import java.io.IOException + +class GlobalSearchViewModel( + private val repository: MangaSearchRepository, + settings: AppSettings +) : MangaListViewModel(settings) { + + override val content = MutableLiveData>() + private var searchJob: Job? = null + + fun startSearch(query: String) { + isLoading.value = true + searchJob?.cancel() + searchJob = repository.globalSearch(query) + .flowOn(Dispatchers.Default) + .catch { e -> + if (e is IOException) { + onError.call(e) + } + }.filterNot { x -> x.isEmpty() } + .onEmpty { + content.value = emptyList() + isLoading.value = false + }.onCompletion { + // TODO + }.onFirst { + isLoading.value = false + }.onEach { + content.value = content.value.orEmpty() + it + }.launchIn(viewModelScope) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateChecker.kt b/app/src/main/java/org/koitharu/kotatsu/settings/AppUpdateChecker.kt similarity index 96% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateChecker.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/AppUpdateChecker.kt index 1988467bf..f1e4446fa 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateChecker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/AppUpdateChecker.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.settings +package org.koitharu.kotatsu.settings import android.annotation.SuppressLint import android.content.Context @@ -6,8 +6,8 @@ import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import androidx.activity.ComponentActivity +import androidx.appcompat.app.AlertDialog import androidx.lifecycle.lifecycleScope -import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -68,7 +68,7 @@ class AppUpdateChecker(private val activity: ComponentActivity) { } private fun showUpdateDialog(version: AppVersion) { - MaterialAlertDialogBuilder(activity) + AlertDialog.Builder(activity) .setTitle(R.string.app_update_available) .setMessage(buildString { append(activity.getString(R.string.new_version_s, version.name)) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/HistorySettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/HistorySettingsFragment.kt similarity index 93% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/HistorySettingsFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/HistorySettingsFragment.kt index 0343ac7e1..45abfe9d2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/HistorySettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/HistorySettingsFragment.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.settings +package org.koitharu.kotatsu.settings import android.os.Bundle import android.view.View @@ -9,11 +9,11 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koin.android.ext.android.inject import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.local.Cache +import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.domain.tracking.TrackingRepository -import org.koitharu.kotatsu.ui.base.BasePreferenceFragment -import org.koitharu.kotatsu.ui.search.MangaSuggestionsProvider +import org.koitharu.kotatsu.local.data.Cache +import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider +import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.utils.CacheUtils import org.koitharu.kotatsu.utils.FileSizeUtils import org.koitharu.kotatsu.utils.ext.getDisplayMessage diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt similarity index 85% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt index 1a298b532..4306615e7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.settings +package org.koitharu.kotatsu.settings import android.content.DialogInterface import android.content.Intent @@ -9,22 +9,20 @@ import android.provider.Settings import android.text.InputType import android.view.View import androidx.appcompat.app.AppCompatDelegate -import androidx.collection.arrayMapOf import androidx.preference.* import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.launch import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BasePreferenceFragment +import org.koitharu.kotatsu.base.ui.dialog.StorageSelectDialog +import org.koitharu.kotatsu.base.ui.dialog.TextInputDialog import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ListMode -import org.koitharu.kotatsu.ui.base.BasePreferenceFragment -import org.koitharu.kotatsu.ui.base.dialog.StorageSelectDialog -import org.koitharu.kotatsu.ui.base.dialog.TextInputDialog -import org.koitharu.kotatsu.ui.list.ListModeSelectDialog -import org.koitharu.kotatsu.ui.settings.utils.MultiSummaryProvider -import org.koitharu.kotatsu.ui.tracker.TrackWorker +import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider +import org.koitharu.kotatsu.tracker.work.TrackWorker import org.koitharu.kotatsu.utils.ext.getStorageName import org.koitharu.kotatsu.utils.ext.md5 import org.koitharu.kotatsu.utils.ext.names @@ -38,12 +36,6 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_main) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - findPreference(AppSettings.KEY_LIST_MODE)?.summary = - LIST_MODES[settings.listMode]?.let(::getString) findPreference(AppSettings.KEY_GRID_SIZE)?.run { summary = "%d%%".format(value) setOnPreferenceChangeListener { preference, newValue -> @@ -55,6 +47,18 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), MultiSummaryProvider(R.string.gestures_only) findPreference(AppSettings.KEY_TRACK_SOURCES)?.summaryProvider = MultiSummaryProvider(R.string.dont_check) + findPreference(AppSettings.KEY_ZOOM_MODE)?.run { + entryValues = ZoomMode.values().names() + setDefaultValue(ZoomMode.FIT_CENTER.name) + } + findPreference(AppSettings.KEY_LIST_MODE)?.run { + entryValues = ListMode.values().names() + setDefaultValue(ListMode.GRID.name) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) findPreference(AppSettings.KEY_APP_UPDATE_AUTO)?.run { isVisible = AppUpdateChecker.isUpdateSupported(context) } @@ -62,10 +66,6 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), summary = settings.getStorageDir(context)?.getStorageName(context) ?: getString(R.string.not_available) } - findPreference(AppSettings.KEY_ZOOM_MODE)?.let { - it.entryValues = ZoomMode.values().names() - it.setDefaultValue(ZoomMode.FIT_CENTER.name) - } findPreference(AppSettings.KEY_PROTECT_APP)?.isChecked = !settings.appPassword.isNullOrEmpty() findPreference(AppSettings.KEY_APP_VERSION)?.run { @@ -82,8 +82,6 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { when (key) { - AppSettings.KEY_LIST_MODE -> findPreference(key)?.summary = - LIST_MODES[settings.listMode]?.let(::getString) AppSettings.KEY_THEME -> { AppCompatDelegate.setDefaultNightMode(settings.theme) } @@ -111,10 +109,6 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), override fun onPreferenceTreeClick(preference: Preference?): Boolean { return when (preference?.key) { - AppSettings.KEY_LIST_MODE -> { - ListModeSelectDialog.show(childFragmentManager) - true - } AppSettings.KEY_NOTIFICATIONS_SETTINGS -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) @@ -224,13 +218,4 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), } } } - - private companion object { - - val LIST_MODES = arrayMapOf( - ListMode.DETAILED_LIST to R.string.detailed_list, - ListMode.GRID to R.string.grid, - ListMode.LIST to R.string.list - ) - } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/NetworkSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/NetworkSettingsFragment.kt similarity index 74% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/NetworkSettingsFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/NetworkSettingsFragment.kt index 435702315..8bfb2c3d6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/NetworkSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/NetworkSettingsFragment.kt @@ -1,8 +1,8 @@ -package org.koitharu.kotatsu.ui.settings +package org.koitharu.kotatsu.settings import android.os.Bundle import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.ui.base.BasePreferenceFragment +import org.koitharu.kotatsu.base.ui.BasePreferenceFragment class NetworkSettingsFragment : BasePreferenceFragment(R.string.settings) { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/NotificationSettingsLegacyFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/NotificationSettingsLegacyFragment.kt similarity index 89% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/NotificationSettingsLegacyFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/NotificationSettingsLegacyFragment.kt index 599c14fb0..8643fae82 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/NotificationSettingsLegacyFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/NotificationSettingsLegacyFragment.kt @@ -1,13 +1,13 @@ -package org.koitharu.kotatsu.ui.settings +package org.koitharu.kotatsu.settings import android.media.RingtoneManager import android.os.Bundle import android.view.View import androidx.preference.Preference import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.ui.base.BasePreferenceFragment -import org.koitharu.kotatsu.ui.settings.utils.RingtonePickContract +import org.koitharu.kotatsu.settings.utils.RingtonePickContract import org.koitharu.kotatsu.utils.ext.toUriOrNull class NotificationSettingsLegacyFragment : BasePreferenceFragment(R.string.notifications) { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/ReaderSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt similarity index 72% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/ReaderSettingsFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt index 98d0e29ce..486b7d177 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/ReaderSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt @@ -1,24 +1,19 @@ -package org.koitharu.kotatsu.ui.settings +package org.koitharu.kotatsu.settings import android.os.Bundle -import android.view.View import androidx.preference.ListPreference import androidx.preference.MultiSelectListPreference import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.ui.base.BasePreferenceFragment -import org.koitharu.kotatsu.ui.settings.utils.MultiSummaryProvider +import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider import org.koitharu.kotatsu.utils.ext.names class ReaderSettingsFragment : BasePreferenceFragment(R.string.reader_settings) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_reader) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) findPreference(AppSettings.KEY_READER_SWITCHERS)?.let { it.summaryProvider = MultiSummaryProvider(R.string.gestures_only) } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/SettingsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt similarity index 94% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/SettingsActivity.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt index 8154fc47d..e052e4109 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/SettingsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.settings +package org.koitharu.kotatsu.settings import android.content.Context import android.content.Intent @@ -8,8 +8,8 @@ import androidx.fragment.app.commit import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.ui.base.BaseActivity class SettingsActivity : BaseActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt new file mode 100644 index 000000000..0a8b077f2 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt @@ -0,0 +1,22 @@ +package org.koitharu.kotatsu.settings + +import android.net.Uri +import org.koin.android.ext.koin.androidContext +import org.koin.android.viewmodel.dsl.viewModel +import org.koin.dsl.module +import org.koitharu.kotatsu.core.backup.BackupRepository +import org.koitharu.kotatsu.core.backup.RestoreRepository +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.settings.backup.BackupViewModel +import org.koitharu.kotatsu.settings.backup.RestoreViewModel + +val settingsModule + get() = module { + + single { BackupRepository(get()) } + single { RestoreRepository(get()) } + single { AppSettings(androidContext()) } + + viewModel { BackupViewModel(get(), androidContext()) } + viewModel { (uri: Uri?) -> RestoreViewModel(uri, get(), androidContext()) } + } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/SourceSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt similarity index 93% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/SourceSettingsFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt index 0864b5170..a4fa0e443 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/SourceSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.settings +package org.koitharu.kotatsu.settings import android.os.Bundle import android.view.View @@ -8,7 +8,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.prefs.SourceSettings -import org.koitharu.kotatsu.ui.settings.utils.EditTextSummaryProvider +import org.koitharu.kotatsu.settings.utils.EditTextSummaryProvider import org.koitharu.kotatsu.utils.ext.withArgs class SourceSettingsFragment : PreferenceFragmentCompat() { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/BackupDialogFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupDialogFragment.kt similarity index 70% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/BackupDialogFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupDialogFragment.kt index 9ee572019..3b1380a4b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/BackupDialogFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupDialogFragment.kt @@ -1,30 +1,30 @@ -package org.koitharu.kotatsu.ui.settings.backup +package org.koitharu.kotatsu.settings.backup import android.os.Bundle import android.view.View import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import kotlinx.android.synthetic.main.dialog_progress.* -import moxy.ktx.moxyPresenter -import org.koin.android.ext.android.get +import org.koin.android.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.ui.base.AlertDialogFragment +import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.progress.Progress import java.io.File -class BackupDialogFragment : AlertDialogFragment(R.layout.dialog_progress), BackupView { +class BackupDialogFragment : AlertDialogFragment(R.layout.dialog_progress) { - @Suppress("unused") - private val presenter by moxyPresenter { - BackupPresenter(get()) - } + private val viewModel by viewModel() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) textView_title.setText(R.string.create_backup) textView_subtitle.setText(R.string.processing_) + + viewModel.progress.observe(viewLifecycleOwner, this::onProgressChanged) + viewModel.onBackupDone.observe(viewLifecycleOwner, this::onBackupDone) + viewModel.onError.observe(viewLifecycleOwner, this::onError) } override fun onBuildDialog(builder: AlertDialog.Builder) { @@ -32,7 +32,7 @@ class BackupDialogFragment : AlertDialogFragment(R.layout.dialog_progress), Back .setNegativeButton(android.R.string.cancel, null) } - override fun onError(e: Throwable) { + private fun onError(e: Throwable) { AlertDialog.Builder(context ?: return) .setNegativeButton(R.string.close, null) .setTitle(R.string.error) @@ -41,9 +41,7 @@ class BackupDialogFragment : AlertDialogFragment(R.layout.dialog_progress), Back dismiss() } - override fun onLoadingStateChanged(isLoading: Boolean) = Unit - - override fun onProgressChanged(progress: Progress?) { + private fun onProgressChanged(progress: Progress?) { with(progressBar) { isVisible = true isIndeterminate = progress == null @@ -54,7 +52,7 @@ class BackupDialogFragment : AlertDialogFragment(R.layout.dialog_progress), Back } } - override fun onBackupDone(file: File) { + private fun onBackupDone(file: File) { ShareHelper.shareBackup(context ?: return, file) dismiss() } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/BackupSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupSettingsFragment.kt similarity index 93% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/BackupSettingsFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupSettingsFragment.kt index 02ebc35eb..b7cc6ec68 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/BackupSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupSettingsFragment.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.settings.backup +package org.koitharu.kotatsu.settings.backup import android.content.ActivityNotFoundException import android.net.Uri @@ -10,8 +10,8 @@ import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_list.* import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.ui.base.BasePreferenceFragment class BackupSettingsFragment : BasePreferenceFragment(R.string.backup_restore), ActivityResultCallback { diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupViewModel.kt new file mode 100644 index 000000000..930a0a714 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupViewModel.kt @@ -0,0 +1,41 @@ +package org.koitharu.kotatsu.settings.backup + +import android.content.Context +import androidx.lifecycle.MutableLiveData +import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.core.backup.BackupArchive +import org.koitharu.kotatsu.core.backup.BackupRepository +import org.koitharu.kotatsu.utils.SingleLiveEvent +import org.koitharu.kotatsu.utils.progress.Progress +import java.io.File + +class BackupViewModel( + private val repository: BackupRepository, + private val context: Context +) : BaseViewModel() { + + val progress = MutableLiveData(null) + val onBackupDone = SingleLiveEvent() + + init { + launchLoadingJob { + val backup = BackupArchive.createNew(context) + backup.put(repository.createIndex()) + + progress.value = Progress(0, 3) + backup.put(repository.dumpHistory()) + + progress.value = Progress(1, 3) + backup.put(repository.dumpCategories()) + + progress.value = Progress(2, 3) + backup.put(repository.dumpFavourites()) + + progress.value = Progress(3, 3) + backup.flush() + progress.value = null + backup.cleanup() + onBackupDone.call(backup.file) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/RestoreDialogFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/backup/RestoreDialogFragment.kt similarity index 74% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/RestoreDialogFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/backup/RestoreDialogFragment.kt index f31037539..84e85dd8f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/RestoreDialogFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/backup/RestoreDialogFragment.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.settings.backup +package org.koitharu.kotatsu.settings.backup import android.net.Uri import android.os.Bundle @@ -6,34 +6,37 @@ import android.view.View import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import kotlinx.android.synthetic.main.dialog_progress.* -import moxy.ktx.moxyPresenter -import org.koin.android.ext.android.get +import org.koin.android.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.core.backup.CompositeResult -import org.koitharu.kotatsu.ui.base.AlertDialogFragment import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.toUriOrNull import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.progress.Progress -class RestoreDialogFragment : AlertDialogFragment(R.layout.dialog_progress), RestoreView { +class RestoreDialogFragment : AlertDialogFragment(R.layout.dialog_progress) { - @Suppress("unused") - private val presenter by moxyPresenter { - RestorePresenter(arguments?.getString(ARG_FILE)?.toUriOrNull(), get()) + private val viewModel by viewModel { + parametersOf(arguments?.getString(ARG_FILE)?.toUriOrNull()) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) textView_title.setText(R.string.restore_backup) textView_subtitle.setText(R.string.preparing_) + + viewModel.progress.observe(viewLifecycleOwner, this::onProgressChanged) + viewModel.onRestoreDone.observe(viewLifecycleOwner, this::onRestoreDone) + viewModel.onError.observe(viewLifecycleOwner, this::onError) } override fun onBuildDialog(builder: AlertDialog.Builder) { builder.setCancelable(false) } - override fun onError(e: Throwable) { + private fun onError(e: Throwable) { AlertDialog.Builder(context ?: return) .setNegativeButton(R.string.close, null) .setTitle(R.string.error) @@ -42,9 +45,7 @@ class RestoreDialogFragment : AlertDialogFragment(R.layout.dialog_progress), Res dismiss() } - override fun onLoadingStateChanged(isLoading: Boolean) = Unit - - override fun onProgressChanged(progress: Progress?) { + private fun onProgressChanged(progress: Progress?) { with(progressBar) { isVisible = true isIndeterminate = progress == null @@ -55,7 +56,7 @@ class RestoreDialogFragment : AlertDialogFragment(R.layout.dialog_progress), Res } } - override fun onRestoreDone(result: CompositeResult) { + private fun onRestoreDone(result: CompositeResult) { val builder = AlertDialog.Builder(context ?: return) when { result.isAllSuccess -> builder.setTitle(R.string.data_restored) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/RestorePresenter.kt b/app/src/main/java/org/koitharu/kotatsu/settings/backup/RestoreViewModel.kt similarity index 67% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/RestorePresenter.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/backup/RestoreViewModel.kt index 3c9ce9d37..1948b6585 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/RestorePresenter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/backup/RestoreViewModel.kt @@ -1,33 +1,36 @@ -package org.koitharu.kotatsu.ui.settings.backup +package org.koitharu.kotatsu.settings.backup import android.content.Context import android.net.Uri +import androidx.lifecycle.MutableLiveData import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.withContext -import org.koin.core.component.get +import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.core.backup.BackupArchive import org.koitharu.kotatsu.core.backup.BackupEntry import org.koitharu.kotatsu.core.backup.CompositeResult import org.koitharu.kotatsu.core.backup.RestoreRepository -import org.koitharu.kotatsu.ui.base.BasePresenter +import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.progress.Progress import java.io.File import java.io.FileNotFoundException -class RestorePresenter( - private val uri: Uri?, - private val repository: RestoreRepository -) : BasePresenter() { +class RestoreViewModel( + uri: Uri?, + private val repository: RestoreRepository, + private val context: Context +) : BaseViewModel() { - override fun onFirstViewAttach() { - super.onFirstViewAttach() + val progress = MutableLiveData(null) + val onRestoreDone = SingleLiveEvent() + + init { launchLoadingJob { - viewState.onProgressChanged(null) if (uri == null) { throw FileNotFoundException() } - val contentResolver = get().contentResolver + val contentResolver = context.contentResolver @Suppress("BlockingMethodInNonBlockingContext") val backup = withContext(Dispatchers.IO) { @@ -44,17 +47,17 @@ class RestorePresenter( backup.unpack() val result = CompositeResult() - viewState.onProgressChanged(Progress(0, 3)) + progress.value = Progress(0, 3) result += repository.upsertHistory(backup.getEntry(BackupEntry.HISTORY)) - viewState.onProgressChanged(Progress(1, 3)) + progress.value = Progress(1, 3) result += repository.upsertCategories(backup.getEntry(BackupEntry.CATEGORIES)) - viewState.onProgressChanged(Progress(2, 3)) + progress.value = Progress(2, 3) result += repository.upsertFavourites(backup.getEntry(BackupEntry.FAVOURITES)) - viewState.onProgressChanged(Progress(3, 3)) - viewState.onRestoreDone(result) + progress.value = Progress(3, 3) + onRestoreDone.call(result) } finally { withContext(NonCancellable) { backup.cleanup() diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/sources/SourceViewHolder.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourceViewHolder.kt similarity index 80% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/sources/SourceViewHolder.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/sources/SourceViewHolder.kt index dccb053b8..5039e1bc9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/sources/SourceViewHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourceViewHolder.kt @@ -1,10 +1,10 @@ -package org.koitharu.kotatsu.ui.settings.sources +package org.koitharu.kotatsu.settings.sources import android.view.ViewGroup import kotlinx.android.synthetic.main.item_source_config.* import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.BaseViewHolder import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder class SourceViewHolder(parent: ViewGroup) : BaseViewHolder(parent, R.layout.item_source_config) { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/sources/SourcesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesAdapter.kt similarity index 74% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/sources/SourcesAdapter.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesAdapter.kt index 9bf9c6fa0..3b12c1798 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/sources/SourcesAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesAdapter.kt @@ -1,24 +1,23 @@ -package org.koitharu.kotatsu.ui.settings.sources +package org.koitharu.kotatsu.settings.sources import android.annotation.SuppressLint import android.view.MotionEvent import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.item_source_config.* -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import org.koitharu.kotatsu.base.domain.MangaProviderFactory +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.domain.MangaProviderFactory -import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener import org.koitharu.kotatsu.utils.ext.mapToSet import org.koitharu.kotatsu.utils.ext.safe -class SourcesAdapter(private val onItemClickListener: OnRecyclerItemClickListener) : - RecyclerView.Adapter(), KoinComponent { +class SourcesAdapter( + private val settings: AppSettings, + private val onItemClickListener: OnListItemClickListener, +) : RecyclerView.Adapter() { private val dataSet = MangaProviderFactory.getSources(includeHidden = true).toMutableList() - private val settings by inject() private val hiddenItems = settings.hiddenSources.mapNotNull { safe { MangaSource.valueOf(it) @@ -48,14 +47,13 @@ class SourcesAdapter(private val onItemClickListener: OnRecyclerItemClickListene settings.hiddenSources = hiddenItems.mapToSet { x -> x.name } } holder.imageView_config.setOnClickListener { v -> - onItemClickListener.onItemClick(holder.requireData(), holder.bindingAdapterPosition, v) + onItemClickListener.onItemClick(holder.requireData(), v) } holder.imageView_handle.setOnTouchListener { v, event -> if (event.actionMasked == MotionEvent.ACTION_DOWN) { onItemClickListener.onItemLongClick( holder.requireData(), - holder.bindingAdapterPosition, - v + holder.itemView ) } else { false diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/sources/SourcesReorderCallback.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesReorderCallback.kt similarity index 78% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/sources/SourcesReorderCallback.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesReorderCallback.kt index e6b2b8cbb..066a43a6f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/sources/SourcesReorderCallback.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesReorderCallback.kt @@ -1,9 +1,10 @@ -package org.koitharu.kotatsu.ui.settings.sources +package org.koitharu.kotatsu.settings.sources import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView -class SourcesReorderCallback : ItemTouchHelper.SimpleCallback(ItemTouchHelper.DOWN or ItemTouchHelper.UP, 0) { +class SourcesReorderCallback : + ItemTouchHelper.SimpleCallback(ItemTouchHelper.DOWN or ItemTouchHelper.UP, 0) { override fun onMove( recyclerView: RecyclerView, diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/sources/SourcesSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt similarity index 69% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/sources/SourcesSettingsFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt index ba39fa748..51d1ac860 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/sources/SourcesSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.settings.sources +package org.koitharu.kotatsu.settings.sources import android.os.Bundle import android.view.View @@ -6,14 +6,15 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.fragment_settings_sources.* +import org.koin.android.ext.android.get import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BaseFragment +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.ui.base.BaseFragment -import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener -import org.koitharu.kotatsu.ui.settings.SettingsActivity +import org.koitharu.kotatsu.settings.SettingsActivity class SourcesSettingsFragment : BaseFragment(R.layout.fragment_settings_sources), - OnRecyclerItemClickListener { + OnListItemClickListener { private lateinit var reorderHelper: ItemTouchHelper @@ -30,7 +31,7 @@ class SourcesSettingsFragment : BaseFragment(R.layout.fragment_settings_sources) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) recyclerView.addItemDecoration(DividerItemDecoration(view.context, RecyclerView.VERTICAL)) - recyclerView.adapter = SourcesAdapter(this) + recyclerView.adapter = SourcesAdapter(get(), this) reorderHelper.attachToRecyclerView(recyclerView) } @@ -39,13 +40,13 @@ class SourcesSettingsFragment : BaseFragment(R.layout.fragment_settings_sources) super.onDestroyView() } - override fun onItemClick(item: MangaSource, position: Int, view: View) { + override fun onItemClick(item: MangaSource, view: View) { (activity as? SettingsActivity)?.openMangaSourceSettings(item) } - override fun onItemLongClick(item: MangaSource, position: Int, view: View): Boolean { + override fun onItemLongClick(item: MangaSource, view: View): Boolean { reorderHelper.startDrag( - recyclerView.findViewHolderForAdapterPosition(position) ?: return false + recyclerView.findContainingViewHolder(view) ?: return false ) return true } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/utils/EditTextSummaryProvider.kt b/app/src/main/java/org/koitharu/kotatsu/settings/utils/EditTextSummaryProvider.kt similarity index 90% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/utils/EditTextSummaryProvider.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/utils/EditTextSummaryProvider.kt index 847c46960..1e2976dc6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/utils/EditTextSummaryProvider.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/utils/EditTextSummaryProvider.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.settings.utils +package org.koitharu.kotatsu.settings.utils import androidx.annotation.StringRes import androidx.preference.EditTextPreference diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/utils/MultiSummaryProvider.kt b/app/src/main/java/org/koitharu/kotatsu/settings/utils/MultiSummaryProvider.kt similarity index 92% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/utils/MultiSummaryProvider.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/utils/MultiSummaryProvider.kt index 0705820e7..039dbbb37 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/utils/MultiSummaryProvider.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/utils/MultiSummaryProvider.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.settings.utils +package org.koitharu.kotatsu.settings.utils import androidx.annotation.StringRes import androidx.preference.MultiSelectListPreference diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/utils/RingtonePickContract.kt b/app/src/main/java/org/koitharu/kotatsu/settings/utils/RingtonePickContract.kt similarity index 95% rename from app/src/main/java/org/koitharu/kotatsu/ui/settings/utils/RingtonePickContract.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/utils/RingtonePickContract.kt index 8d5b0d04f..1bf8b7856 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/utils/RingtonePickContract.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/utils/RingtonePickContract.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.settings.utils +package org.koitharu.kotatsu.settings.utils import android.content.Context import android.content.Intent diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/TrackerModule.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/TrackerModule.kt new file mode 100644 index 000000000..2d7a2ab7e --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/TrackerModule.kt @@ -0,0 +1,15 @@ +package org.koitharu.kotatsu.tracker + +import org.koin.android.ext.koin.androidContext +import org.koin.android.viewmodel.dsl.viewModel +import org.koin.dsl.module +import org.koitharu.kotatsu.tracker.domain.TrackingRepository +import org.koitharu.kotatsu.tracker.ui.FeedViewModel + +val trackerModule + get() = module { + + single { TrackingRepository(get(), get()) } + + viewModel { FeedViewModel(androidContext(), get()) } + } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/tracking/TrackingRepository.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt similarity index 96% rename from app/src/main/java/org/koitharu/kotatsu/domain/tracking/TrackingRepository.kt rename to app/src/main/java/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt index 2b19a48dc..4ff9b74bb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/tracking/TrackingRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt @@ -1,11 +1,11 @@ -package org.koitharu.kotatsu.domain.tracking +package org.koitharu.kotatsu.tracker.domain import androidx.room.withTransaction import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.entity.TrackEntity import org.koitharu.kotatsu.core.db.entity.TrackLogEntity import org.koitharu.kotatsu.core.model.* -import org.koitharu.kotatsu.core.parser.LocalMangaRepository +import org.koitharu.kotatsu.local.domain.LocalMangaRepository import java.util.* class TrackingRepository( diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedAdapter.kt new file mode 100644 index 000000000..4334e929b --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedAdapter.kt @@ -0,0 +1,46 @@ +package org.koitharu.kotatsu.tracker.ui + +import androidx.recyclerview.widget.DiffUtil +import coil.ImageLoader +import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.list.ui.adapter.indeterminateProgressAD +import org.koitharu.kotatsu.list.ui.model.IndeterminateProgress +import org.koitharu.kotatsu.tracker.ui.adapter.feedItemAD +import org.koitharu.kotatsu.tracker.ui.model.FeedItem +import kotlin.jvm.internal.Intrinsics + +class FeedAdapter( + coil: ImageLoader, + clickListener: OnListItemClickListener +) : AsyncListDifferDelegationAdapter(DiffCallback()) { + + init { + delegatesManager.addDelegate(ITEM_TYPE_FEED, feedItemAD(coil, clickListener)) + .addDelegate(ITEM_TYPE_PROGRESS, indeterminateProgressAD()) + } + + private class DiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: Any, newItem: Any) = when { + oldItem is FeedItem && newItem is FeedItem -> { + oldItem.id == newItem.id + } + oldItem == IndeterminateProgress && newItem == IndeterminateProgress -> { + true + } + else -> false + } + + override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean { + return Intrinsics.areEqual(oldItem, newItem) + } + } + + companion object { + + const val ITEM_TYPE_FEED = 0 + const val ITEM_TYPE_PROGRESS = 1 + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedFragment.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt similarity index 56% rename from app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt index e9078831c..13b416f64 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.list.feed +package org.koitharu.kotatsu.tracker.ui import android.os.Bundle import android.view.Menu @@ -8,24 +8,23 @@ import android.view.View import androidx.core.view.isVisible import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_tracklogs.* -import moxy.MvpDelegate -import moxy.ktx.moxyPresenter +import org.koin.android.ext.android.get +import org.koin.android.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.TrackingLogItem -import org.koitharu.kotatsu.ui.base.BaseFragment -import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener -import org.koitharu.kotatsu.ui.base.list.PaginationScrollListener -import org.koitharu.kotatsu.ui.base.list.decor.SpacingItemDecoration -import org.koitharu.kotatsu.ui.details.MangaDetailsActivity -import org.koitharu.kotatsu.ui.tracker.TrackWorker -import org.koitharu.kotatsu.utils.ext.callOnScrollListeners +import org.koitharu.kotatsu.base.ui.BaseFragment +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener +import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.details.ui.DetailsActivity +import org.koitharu.kotatsu.tracker.work.TrackWorker import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.hasItems -class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), FeedView, - PaginationScrollListener.Callback, OnRecyclerItemClickListener { +class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), PaginationScrollListener.Callback, + OnListItemClickListener { - private val presenter by moxyPresenter(factory = ::FeedPresenter) + private val viewModel by viewModel() private var adapter: FeedAdapter? = null @@ -38,16 +37,18 @@ class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), FeedView, override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - adapter = FeedAdapter(this) + adapter = FeedAdapter(get(), this) recyclerView.adapter = adapter recyclerView.addItemDecoration( SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing)) ) recyclerView.setHasFixedSize(true) recyclerView.addOnScrollListener(PaginationScrollListener(4, this)) - if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) { - onRequestMoreItems(0) - } + + viewModel.content.observe(viewLifecycleOwner, this::onListChanged) + viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged) + viewModel.onError.observe(viewLifecycleOwner, this::onError) + viewModel.isEmptyState.observe(viewLifecycleOwner, this::onEmptyStateChanged) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -69,23 +70,11 @@ class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), FeedView, super.onDestroyView() } - override fun onListChanged(list: List) { - adapter?.replaceData(list) - if (list.isEmpty()) { - setUpEmptyListHolder() - layout_holder.isVisible = true - } else { - layout_holder.isVisible = false - } - recyclerView.callOnScrollListeners() + private fun onListChanged(list: List) { + adapter?.items = list } - override fun onListAppended(list: List) { - adapter?.appendData(list) - recyclerView.callOnScrollListeners() - } - - override fun onListError(e: Throwable) { + private fun onError(e: Throwable) { if (recyclerView.hasItems) { Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT) .show() @@ -101,28 +90,24 @@ class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), FeedView, } } - override fun onError(e: Throwable) { - Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show() - } - - override fun onLoadingStateChanged(isLoading: Boolean) { + private fun onLoadingStateChanged(isLoading: Boolean) { val hasItems = recyclerView.hasItems progressBar.isVisible = isLoading && !hasItems - if (isLoading) { - layout_holder.isVisible = false - } } - override fun getItemsCount(): Int { - return adapter?.itemCount ?: 0 + private fun onEmptyStateChanged(isEmpty: Boolean) { + if (isEmpty) { + setUpEmptyListHolder() + } + layout_holder.isVisible = isEmpty } - override fun onRequestMoreItems(offset: Int) { - presenter.loadList(offset) + override fun onScrolledToEnd() { + viewModel.loadList(append = true) } - override fun onItemClick(item: TrackingLogItem, position: Int, view: View) { - startActivity(MangaDetailsActivity.newIntent(context ?: return, item.manga)) + override fun onItemClick(item: Manga, view: View) { + startActivity(DetailsActivity.newIntent(context ?: return, item)) } private fun setUpEmptyListHolder() { diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt new file mode 100644 index 000000000..c3da6366b --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt @@ -0,0 +1,58 @@ +package org.koitharu.kotatsu.tracker.ui + +import android.content.Context +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.drop +import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.core.model.TrackingLogItem +import org.koitharu.kotatsu.list.ui.model.IndeterminateProgress +import org.koitharu.kotatsu.tracker.domain.TrackingRepository +import org.koitharu.kotatsu.tracker.ui.model.toFeedItem +import org.koitharu.kotatsu.utils.ext.mapItems + +class FeedViewModel( + context: Context, + private val repository: TrackingRepository +) : BaseViewModel() { + + private val logList = MutableStateFlow>(emptyList()) + private val hasNextPage = MutableStateFlow(false) + private var loadingJob: Job? = null + + val isEmptyState = MutableLiveData(false) + val content = combine( + logList.drop(1).mapItems { + it.toFeedItem(context.resources) + }, + hasNextPage + ) { list, isHasNextPage -> + if (isHasNextPage && list.isNotEmpty()) list + IndeterminateProgress else list + }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + + init { + loadList(append = false) + } + + fun loadList(append: Boolean) { + if (loadingJob?.isActive == true) { + return + } + loadingJob = launchLoadingJob(Dispatchers.Default) { + val offset = if (append) logList.value.size else 0 + val list = repository.getTrackingLog(offset, 20) + if (!append) { + logList.value = list + isEmptyState.postValue(list.isEmpty()) + } else if (list.isNotEmpty()) { + logList.value += list + } + hasNextPage.value = list.isNotEmpty() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedItemAD.kt new file mode 100644 index 000000000..a4b01a155 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedItemAD.kt @@ -0,0 +1,41 @@ +package org.koitharu.kotatsu.tracker.ui.adapter + +import coil.ImageLoader +import coil.request.Disposable +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer +import kotlinx.android.synthetic.main.item_tracklog.* +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.tracker.ui.model.FeedItem +import org.koitharu.kotatsu.utils.ext.enqueueWith +import org.koitharu.kotatsu.utils.ext.newImageRequest + +fun feedItemAD( + coil: ImageLoader, + clickListener: OnListItemClickListener +) = adapterDelegateLayoutContainer(R.layout.item_tracklog) { + + var imageRequest: Disposable? = null + + itemView.setOnClickListener { + clickListener.onItemClick(item.manga, it) + } + + bind { + imageRequest?.dispose() + imageRequest = imageView_cover.newImageRequest(item.imageUrl) + .placeholder(R.drawable.ic_placeholder) + .fallback(R.drawable.ic_placeholder) + .error(R.drawable.ic_placeholder) + .enqueueWith(coil) + textView_title.text = item.title + textView_subtitle.text = item.subtitle + textView_chapters.text = item.chapters + } + + onViewRecycled { + imageRequest?.dispose() + imageView_cover.setImageDrawable(null) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/FeedItem.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/FeedItem.kt new file mode 100644 index 000000000..887f41db3 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/FeedItem.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.tracker.ui.model + +import org.koitharu.kotatsu.core.model.Manga + +data class FeedItem( + val id: Long, + val imageUrl: String, + val title: String, + val subtitle: String, + val chapters: CharSequence, + val manga: Manga +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/ListModelConversionExt.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/ListModelConversionExt.kt new file mode 100644 index 000000000..d1d6694de --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/ListModelConversionExt.kt @@ -0,0 +1,26 @@ +package org.koitharu.kotatsu.tracker.ui.model + +import android.content.res.Resources +import android.text.format.DateUtils +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.model.TrackingLogItem +import org.koitharu.kotatsu.utils.ext.formatRelative + +fun TrackingLogItem.toFeedItem(resources: Resources) = FeedItem( + id = id, + imageUrl = manga.coverUrl, + title = manga.title, + subtitle = buildString { + append(createdAt.formatRelative(DateUtils.DAY_IN_MILLIS)) + append(" ") + append( + resources.getQuantityString( + R.plurals.new_chapters, + chapters.size, + chapters.size + ) + ) + }, + chapters = chapters.joinToString("\n"), + manga = manga +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/tracker/TrackWorker.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt similarity index 97% rename from app/src/main/java/org/koitharu/kotatsu/ui/tracker/TrackWorker.kt rename to app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt index 03b6a7dd5..9efae09da 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/tracker/TrackWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.tracker +package org.koitharu.kotatsu.tracker.work import android.app.NotificationChannel import android.app.NotificationManager @@ -19,8 +19,8 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.domain.tracking.TrackingRepository -import org.koitharu.kotatsu.ui.details.MangaDetailsActivity +import org.koitharu.kotatsu.details.ui.DetailsActivity +import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.utils.ext.safe import org.koitharu.kotatsu.utils.ext.toBitmapOrNull import org.koitharu.kotatsu.utils.ext.toUriOrNull @@ -162,7 +162,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) : style.setSummaryText(manga.title) style.setBigContentTitle(summary) setStyle(style) - val intent = MangaDetailsActivity.newIntent(applicationContext, manga) + val intent = DetailsActivity.newIntent(applicationContext, manga) setContentIntent( PendingIntent.getActivity( applicationContext, id, diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/BaseMvpView.kt b/app/src/main/java/org/koitharu/kotatsu/ui/base/BaseMvpView.kt deleted file mode 100644 index da4a75439..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/BaseMvpView.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.koitharu.kotatsu.ui.base - -import moxy.MvpView -import moxy.viewstate.strategy.alias.AddToEndSingle -import moxy.viewstate.strategy.alias.OneExecution - -interface BaseMvpView : MvpView { - - @OneExecution - fun onError(e: Throwable) - - @AddToEndSingle - fun onLoadingStateChanged(isLoading: Boolean) -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/BaseService.kt b/app/src/main/java/org/koitharu/kotatsu/ui/base/BaseService.kt deleted file mode 100644 index b9ab215da..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/BaseService.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.koitharu.kotatsu.ui.base - -import android.app.Service -import android.content.Intent -import android.os.IBinder -import androidx.annotation.CallSuper -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancel - -abstract class BaseService : Service() { - - @Suppress("MemberVisibilityCanBePrivate") - val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) - - @CallSuper - override fun onDestroy() { - serviceScope.cancel() - super.onDestroy() - } - - override fun onBind(intent: Intent?): IBinder? = null -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/SharedPresenterHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/base/SharedPresenterHolder.kt deleted file mode 100644 index 130008862..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/SharedPresenterHolder.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.koitharu.kotatsu.ui.base - -import android.util.ArrayMap -import moxy.MvpPresenter -import java.lang.ref.WeakReference - -abstract class SharedPresenterHolder> { - - private val cache = ArrayMap>(3) - - fun getInstance(key: Int): T { - var instance = cache[key]?.get() - if (instance == null) { - instance = onCreatePresenter(key) - cache[key] = WeakReference(instance) - } - return instance - } - - fun clear(key: Int) { - cache.remove(key) - } - - protected abstract fun onCreatePresenter(key: Int): T -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/BaseRecyclerAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/base/list/BaseRecyclerAdapter.kt deleted file mode 100644 index ca8245603..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/BaseRecyclerAdapter.kt +++ /dev/null @@ -1,109 +0,0 @@ -package org.koitharu.kotatsu.ui.base.list - -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import org.koin.core.component.KoinComponent -import org.koitharu.kotatsu.utils.ext.replaceWith - -abstract class BaseRecyclerAdapter(private val onItemClickListener: OnRecyclerItemClickListener? = null) : - RecyclerView.Adapter>(), - KoinComponent { - - protected val dataSet = ArrayList() //TODO make private - - val items get() = dataSet as List - - val hasItems get() = dataSet.isNotEmpty() - - init { - @Suppress("LeakingThis") - setHasStableIds(true) - } - - override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { - val item = dataSet[position] - holder.bind(item, getExtra(item, position)) - } - - fun getItem(position: Int) = dataSet[position] - - override fun getItemId(position: Int) = onGetItemId(dataSet[position]) - - protected fun findItemById(id: Long) = dataSet.find { x -> onGetItemId(x) == id } - - protected fun findItemPositionById(id: Long) = - dataSet.indexOfFirst { x -> onGetItemId(x) == id } - - fun replaceData(newData: List) { - val updater = AdapterUpdater(dataSet, newData, this::onGetItemId) - dataSet.replaceWith(newData) - updater(this) - onDataSetChanged() - } - - fun appendData(newData: List) { - val pos = dataSet.size - dataSet.addAll(newData) - notifyItemRangeInserted(pos, newData.size) - onDataSetChanged() - } - - fun prependData(newData: List) { - dataSet.addAll(0, newData) - notifyItemRangeInserted(0, newData.size) - onDataSetChanged() - } - - fun appendItem(newItem: T) { - dataSet.add(newItem) - notifyItemInserted(dataSet.lastIndex) - onDataSetChanged() - } - - fun removeItem(item: T) { - removeItemAt(dataSet.indexOf(item)) - onDataSetChanged() - } - - fun removeItemAt(position: Int) { - if (position in dataSet.indices) { - dataSet.removeAt(position) - notifyItemRemoved(position) - } - onDataSetChanged() - } - - fun clearData() { - dataSet.clear() - notifyDataSetChanged() - onDataSetChanged() - } - - override fun onViewRecycled(holder: BaseViewHolder) { - holder.onRecycled() - } - - final override fun getItemCount() = dataSet.size - - final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { - return onCreateViewHolder(parent) - } - - override fun onViewDetachedFromWindow(holder: BaseViewHolder) { - holder.setOnItemClickListener(null) - super.onViewDetachedFromWindow(holder) - } - - override fun onViewAttachedToWindow(holder: BaseViewHolder) { - super.onViewAttachedToWindow(holder) - holder.setOnItemClickListener(onItemClickListener) - } - - protected open fun onDataSetChanged() = Unit - - protected abstract fun getExtra(item: T, position: Int): E - - protected abstract fun onCreateViewHolder(parent: ViewGroup): BaseViewHolder - - protected abstract fun onGetItemId(item: T): Long -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/OnRecyclerItemClickListener.kt b/app/src/main/java/org/koitharu/kotatsu/ui/base/list/OnRecyclerItemClickListener.kt deleted file mode 100644 index 6d6f659d1..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/OnRecyclerItemClickListener.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.koitharu.kotatsu.ui.base.list - -import android.view.View - -interface OnRecyclerItemClickListener { - - fun onItemClick(item: I, position: Int, view: View) - - fun onItemLongClick(item: I, position: Int, view: View) = false -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/PaginationScrollListener.kt b/app/src/main/java/org/koitharu/kotatsu/ui/base/list/PaginationScrollListener.kt deleted file mode 100644 index 4395da3fa..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/PaginationScrollListener.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.koitharu.kotatsu.ui.base.list - -import androidx.recyclerview.widget.RecyclerView - -class PaginationScrollListener(offset: Int, private val callback: Callback) : - BoundsScrollListener(0, offset) { - - private var lastTotalCount = 0 - - override fun onScrolledToStart(recyclerView: RecyclerView) = Unit - - override fun onScrolledToEnd(recyclerView: RecyclerView) { - val total = callback.getItemsCount() - if (total > lastTotalCount) { - lastTotalCount = total - callback.onRequestMoreItems(total) - } else if (total < lastTotalCount) { - lastTotalCount = total - } - } - - fun reset() { - lastTotalCount = 0 - } - - interface Callback { - - fun onRequestMoreItems(offset: Int) - - fun getItemsCount(): Int - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/ProgressBarAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/base/list/ProgressBarAdapter.kt deleted file mode 100644 index a589c92c7..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/base/list/ProgressBarAdapter.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.koitharu.kotatsu.ui.base.list - -import android.view.ViewGroup - -class ProgressBarAdapter : BaseRecyclerAdapter() { - - var isProgressVisible: Boolean - get() = dataSet.isNotEmpty() - set(value) { - if (value == dataSet.isEmpty()) { - if (value) { - appendItem(true) - } else { - removeItemAt(0) - } - } - } - - override fun getExtra(item: Boolean, position: Int) = Unit - - override fun onCreateViewHolder(parent: ViewGroup) = ProgressBarHolder(parent) - - override fun onGetItemId(item: Boolean) = -1L -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/ChaptersAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/details/ChaptersAdapter.kt deleted file mode 100644 index 6748b539d..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/ChaptersAdapter.kt +++ /dev/null @@ -1,93 +0,0 @@ -package org.koitharu.kotatsu.ui.details - -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import org.koitharu.kotatsu.core.model.MangaChapter -import org.koitharu.kotatsu.domain.history.ChapterExtra -import org.koitharu.kotatsu.ui.base.list.BaseRecyclerAdapter -import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener - -class ChaptersAdapter(onItemClickListener: OnRecyclerItemClickListener) : - BaseRecyclerAdapter(onItemClickListener) { - - private val checkedIds = HashSet() - - val checkedItemsCount: Int - get() = checkedIds.size - - val checkedItemsIds: Set - get() = checkedIds - - var currentChapterId: Long? = null - set(value) { - field = value - updateCurrentPosition() - } - - var newChaptersCount: Int = 0 - set(value) { - val updated = maxOf(field, value) - field = value - notifyItemRangeChanged(itemCount - updated, updated) - } - - var currentChapterPosition = RecyclerView.NO_POSITION - private set - - fun clearChecked() { - checkedIds.clear() - notifyDataSetChanged() - } - - fun checkAll() { - for (item in dataSet) { - checkedIds.add(item.id) - } - notifyDataSetChanged() - } - - fun setItemIsChecked(itemId: Long, isChecked: Boolean) { - if ((isChecked && checkedIds.add(itemId)) || (!isChecked && checkedIds.remove(itemId))) { - val pos = findItemPositionById(itemId) - if (pos != RecyclerView.NO_POSITION) { - notifyItemChanged(pos) - } - } - } - - fun toggleItemChecked(itemId: Long) { - setItemIsChecked(itemId, itemId !in checkedIds) - } - - override fun onCreateViewHolder(parent: ViewGroup) = ChapterHolder(parent) - - override fun onGetItemId(item: MangaChapter) = item.id - - override fun getExtra(item: MangaChapter, position: Int): ChapterExtra = when { - item.id in checkedIds -> ChapterExtra.CHECKED - currentChapterPosition == RecyclerView.NO_POSITION - || currentChapterPosition < position -> if (position >= itemCount - newChaptersCount) { - ChapterExtra.NEW - } else { - ChapterExtra.UNREAD - } - currentChapterPosition == position -> ChapterExtra.CURRENT - currentChapterPosition > position -> ChapterExtra.READ - else -> ChapterExtra.UNREAD - } - - override fun onDataSetChanged() { - super.onDataSetChanged() - updateCurrentPosition() - } - - private fun updateCurrentPosition() { - val pos = currentChapterId?.let { - dataSet.indexOfFirst { x -> x.id == it } - } ?: RecyclerView.NO_POSITION - if (pos != currentChapterPosition) { - currentChapterPosition = pos - notifyDataSetChanged() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/ChaptersFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/details/ChaptersFragment.kt deleted file mode 100644 index 305ee35ce..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/ChaptersFragment.kt +++ /dev/null @@ -1,162 +0,0 @@ -package org.koitharu.kotatsu.ui.details - -import android.app.ActivityOptions -import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import android.view.View -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.view.ActionMode -import androidx.core.view.isVisible -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.synthetic.main.fragment_chapters.* -import moxy.ktx.moxyPresenter -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.* -import org.koitharu.kotatsu.ui.base.BaseFragment -import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener -import org.koitharu.kotatsu.ui.download.DownloadService -import org.koitharu.kotatsu.ui.reader.ReaderActivity -import org.koitharu.kotatsu.utils.ext.resolveDp - -class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), MangaDetailsView, - OnRecyclerItemClickListener, ActionMode.Callback { - - @Suppress("unused") - private val presenter by moxyPresenter { - MangaDetailsPresenter.getInstance(activity.hashCode()) - } - - private var manga: Manga? = null - - private lateinit var adapter: ChaptersAdapter - private var actionMode: ActionMode? = null - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - adapter = ChaptersAdapter(this) - recyclerView_chapters.addItemDecoration( - DividerItemDecoration( - view.context, - RecyclerView.VERTICAL - ) - ) - recyclerView_chapters.setHasFixedSize(true) - recyclerView_chapters.adapter = adapter - } - - override fun onMangaUpdated(manga: Manga) { - this.manga = manga - adapter.replaceData(manga.chapters.orEmpty()) - scrollToCurrent() - } - - override fun onLoadingStateChanged(isLoading: Boolean) { - progressBar.isVisible = isLoading - } - - override fun onError(e: Throwable) = Unit //handled in activity - - override fun onMangaRemoved(manga: Manga) = Unit //handled in activity - - override fun onHistoryChanged(history: MangaHistory?) { - adapter.currentChapterId = history?.chapterId - scrollToCurrent() - } - - override fun onNewChaptersChanged(newChapters: Int) { - adapter.newChaptersCount = newChapters - } - - override fun onFavouriteChanged(categories: List) = Unit - - override fun onItemClick(item: MangaChapter, position: Int, view: View) { - if (adapter.checkedItemsCount != 0) { - adapter.toggleItemChecked(item.id) - if (adapter.checkedItemsCount == 0) { - actionMode?.finish() - } else { - actionMode?.invalidate() - } - return - } - val options = ActivityOptions.makeScaleUpAnimation( - view, - 0, - 0, - view.measuredWidth, - view.measuredHeight - ) - startActivity( - ReaderActivity.newIntent( - context ?: return, - manga ?: return, - item.id - ), options.toBundle() - ) - } - - override fun onItemLongClick(item: MangaChapter, position: Int, view: View): Boolean { - if (actionMode == null) { - actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this) - } - return actionMode?.also { - adapter.setItemIsChecked(item.id, true) - it.invalidate() - } != null - } - - private fun scrollToCurrent() { - val pos = (recyclerView_chapters.adapter as? ChaptersAdapter)?.currentChapterPosition - ?: RecyclerView.NO_POSITION - if (pos != RecyclerView.NO_POSITION) { - (recyclerView_chapters.layoutManager as? LinearLayoutManager) - ?.scrollToPositionWithOffset(pos, resources.resolveDp(40)) - } - } - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.action_save -> { - DownloadService.start( - context ?: return false, - manga ?: return false, - adapter.checkedItemsIds - ) - mode.finish() - true - } - R.id.action_select_all -> { - adapter.checkAll() - mode.invalidate() - true - } - else -> false - } - } - - override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { - mode.menuInflater.inflate(R.menu.mode_chapters, menu) - menu.findItem(R.id.action_save).isVisible = manga?.source != MangaSource.LOCAL - mode.title = manga?.title - return true - } - - override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { - val count = adapter.checkedItemsCount - mode.subtitle = resources.getQuantityString( - R.plurals.chapters_from_x, - count, - count, - adapter.itemCount - ) - return true - } - - override fun onDestroyActionMode(mode: ActionMode?) { - adapter.clearChecked() - actionMode = null - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsPresenter.kt deleted file mode 100644 index 8e6aac244..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsPresenter.kt +++ /dev/null @@ -1,203 +0,0 @@ -package org.koitharu.kotatsu.ui.details - -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import moxy.InjectViewState -import moxy.presenterScope -import org.koin.core.component.get -import org.koin.core.component.inject -import org.koitharu.kotatsu.BuildConfig -import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.parser.LocalMangaRepository -import org.koitharu.kotatsu.domain.MangaDataRepository -import org.koitharu.kotatsu.domain.MangaSearchRepository -import org.koitharu.kotatsu.domain.favourites.FavouritesRepository -import org.koitharu.kotatsu.domain.favourites.OnFavouritesChangeListener -import org.koitharu.kotatsu.domain.history.HistoryRepository -import org.koitharu.kotatsu.domain.history.OnHistoryChangeListener -import org.koitharu.kotatsu.domain.tracking.TrackingRepository -import org.koitharu.kotatsu.ui.base.BasePresenter -import org.koitharu.kotatsu.ui.base.SharedPresenterHolder -import org.koitharu.kotatsu.utils.ext.safe -import java.io.IOException - -@InjectViewState -class MangaDetailsPresenter private constructor(private val key: Int) : - BasePresenter(), OnHistoryChangeListener, OnFavouritesChangeListener { - - private val historyRepository by inject() - private val favouritesRepository by inject() - private val trackingRepository by inject() - private val searchRepository by inject() - private val mangaDataRepository by inject() - - private var manga: Manga? = null - - override fun onFirstViewAttach() { - super.onFirstViewAttach() - HistoryRepository.subscribe(this) - FavouritesRepository.subscribe(this) - } - - fun findMangaById(id: Long) { - presenterScope.launch { - viewState.onLoadingStateChanged(true) - try { - val manga = withContext(Dispatchers.IO) { - mangaDataRepository.findMangaById(id) - } ?: throw MangaNotFoundException("Cannot find manga by id") - viewState.onMangaUpdated(manga) - loadDetails(manga, true) - } catch (_: CancellationException) { - } catch (e: Throwable) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - viewState.onError(e) - } finally { - viewState.onLoadingStateChanged(false) - } - } - } - - fun loadDetails(manga: Manga, force: Boolean = false) { - if (!force && this.manga == manga) { - return - } - loadHistory(manga) - viewState.onMangaUpdated(manga) - loadFavourite(manga) - presenterScope.launch { - try { - viewState.onLoadingStateChanged(true) - val data = withContext(Dispatchers.Default) { - manga.source.repository.getDetails(manga) - } - viewState.onMangaUpdated(data) - this@MangaDetailsPresenter.manga = data - viewState.onNewChaptersChanged(trackingRepository.getNewChaptersCount(manga.id)) - } catch (_: CancellationException) { - } catch (e: Throwable) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - viewState.onError(e) - } finally { - viewState.onLoadingStateChanged(false) - } - } - } - - fun deleteLocal(manga: Manga) { - presenterScope.launch { - viewState.onLoadingStateChanged(true) - try { - withContext(Dispatchers.IO) { - val repository = get() - val original = repository.getRemoteManga(manga) - repository.delete(manga) || throw IOException("Unable to delete file") - safe { - historyRepository.deleteOrSwap(manga, original) - } - } - viewState.onMangaRemoved(manga) - } catch (e: CancellationException) { - } catch (e: Exception) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - viewState.onError(e) - } finally { - viewState.onLoadingStateChanged(false) - } - } - } - - private fun loadHistory(manga: Manga) { - presenterScope.launch { - try { - val history = withContext(Dispatchers.IO) { - historyRepository.getOne(manga) - } - viewState.onHistoryChanged(history) - } catch (e: Exception) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - } - } - } - - private fun loadFavourite(manga: Manga) { - presenterScope.launch { - try { - val categories = withContext(Dispatchers.IO) { - favouritesRepository.getCategories(manga.id) - } - viewState.onFavouriteChanged(categories) - } catch (e: Exception) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - } - } - } - - fun loadRelated() { - val manga = this.manga ?: return - presenterScope.launch { - viewState.onLoadingStateChanged(isLoading = true) - var isFirstCall = true - 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) { - viewState.onError(e) - } - } - .onEmpty { - viewState.onListChanged(emptyList()) - viewState.onLoadingStateChanged(isLoading = false) - }.onCompletion { - viewState.onListAppended(emptyList()) - }.collect { - if (isFirstCall) { - isFirstCall = false - viewState.onListChanged(it) - viewState.onLoadingStateChanged(isLoading = false) - } else { - viewState.onListAppended(it) - } - } - } - } - - override fun onHistoryChanged() { - loadHistory(manga ?: return) - } - - override fun onFavouritesChanged(mangaId: Long) { - if (mangaId == manga?.id) { - loadFavourite(manga!!) - } - } - - override fun onDestroy() { - HistoryRepository.unsubscribe(this) - FavouritesRepository.unsubscribe(this) - clear(key) - super.onDestroy() - } - - companion object Holder : SharedPresenterHolder() { - - override fun onCreatePresenter(key: Int) = MangaDetailsPresenter(key) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsView.kt b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsView.kt deleted file mode 100644 index be9143067..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsView.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.koitharu.kotatsu.ui.details - -import moxy.viewstate.strategy.AddToEndSingleTagStrategy -import moxy.viewstate.strategy.AddToEndStrategy -import moxy.viewstate.strategy.StateStrategyType -import moxy.viewstate.strategy.alias.AddToEndSingle -import moxy.viewstate.strategy.alias.SingleState -import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaHistory -import org.koitharu.kotatsu.ui.base.BaseMvpView - -interface MangaDetailsView : BaseMvpView { - - @AddToEndSingle - fun onMangaUpdated(manga: Manga) - - @AddToEndSingle - fun onHistoryChanged(history: MangaHistory?) - - @AddToEndSingle - fun onFavouriteChanged(categories: List) - - @SingleState - fun onMangaRemoved(manga: Manga) - - @AddToEndSingle - fun onNewChaptersChanged(newChapters: Int) - - @StateStrategyType(AddToEndSingleTagStrategy::class, tag = "content") - fun onListChanged(list: List) = Unit - - @StateStrategyType(AddToEndStrategy::class, tag = "content") - fun onListAppended(list: List) = Unit - - @StateStrategyType(AddToEndSingleTagStrategy::class, tag = "content") - fun onListError(e: Throwable) = Unit -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/RelatedMangaFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/details/RelatedMangaFragment.kt deleted file mode 100644 index 1a5fddd9b..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/RelatedMangaFragment.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.koitharu.kotatsu.ui.details - -import android.os.Bundle -import android.view.View -import moxy.ktx.moxyPresenter -import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaHistory -import org.koitharu.kotatsu.ui.list.MangaListFragment - -class RelatedMangaFragment : MangaListFragment(), MangaDetailsView { - - private val presenter by moxyPresenter { - MangaDetailsPresenter.getInstance(activity.hashCode()) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - isSwipeRefreshEnabled = false - } - - override fun onRequestMoreItems(offset: Int) { - if (offset == 0) { - presenter.loadRelated() - } - } - - override fun onMangaUpdated(manga: Manga) = Unit - - override fun onHistoryChanged(history: MangaHistory?) = Unit - - override fun onFavouriteChanged(categories: List) = Unit - - override fun onMangaRemoved(manga: Manga) = Unit - - override fun onNewChaptersChanged(newChapters: Int) = Unit - - override fun onListChanged(list: List) = super.onListChanged(list) - - override fun onListAppended(list: List) = super.onListAppended(list) - - override fun onListError(e: Throwable) = super.onListError(e) -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MainView.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/MainView.kt deleted file mode 100644 index f41fcd03f..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MainView.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.koitharu.kotatsu.ui.list - -import moxy.viewstate.strategy.alias.OneExecution -import org.koitharu.kotatsu.ui.base.BaseMvpView -import org.koitharu.kotatsu.ui.reader.ReaderState - -interface MainView : BaseMvpView { - - @OneExecution - fun onOpenReader(state: ReaderState) -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaGridHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaGridHolder.kt deleted file mode 100644 index 45486b144..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaGridHolder.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.koitharu.kotatsu.ui.list - -import android.view.ViewGroup -import coil.ImageLoader -import coil.request.Disposable -import kotlinx.android.synthetic.main.item_manga_grid.* -import org.koin.core.component.inject -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaHistory -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder -import org.koitharu.kotatsu.utils.ext.enqueueWith -import org.koitharu.kotatsu.utils.ext.newImageRequest - -class MangaGridHolder(parent: ViewGroup) : - BaseViewHolder(parent, R.layout.item_manga_grid) { - - private val coil by inject() - private var imageRequest: Disposable? = null - - override fun onBind(data: Manga, extra: MangaHistory?) { - textView_title.text = data.title - imageRequest?.dispose() - imageRequest = imageView_cover.newImageRequest(data.coverUrl) - .placeholder(R.drawable.ic_placeholder) - .fallback(R.drawable.ic_placeholder) - .error(R.drawable.ic_placeholder) - .enqueueWith(coil) - } - - override fun onRecycled() { - imageRequest?.dispose() - imageView_cover.setImageDrawable(null) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListAdapter.kt deleted file mode 100644 index 0f8ad99b2..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListAdapter.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.koitharu.kotatsu.ui.list - -import android.view.ViewGroup -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaHistory -import org.koitharu.kotatsu.core.prefs.ListMode -import org.koitharu.kotatsu.ui.base.list.BaseRecyclerAdapter -import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener - -class MangaListAdapter(onItemClickListener: OnRecyclerItemClickListener) : - BaseRecyclerAdapter(onItemClickListener) { - - var listMode: ListMode = ListMode.LIST - - override fun onCreateViewHolder(parent: ViewGroup) = when (listMode) { - ListMode.LIST -> MangaListHolder(parent) - ListMode.DETAILED_LIST -> MangaListDetailsHolder( - parent - ) - ListMode.GRID -> MangaGridHolder(parent) - } - - override fun onGetItemId(item: Manga) = item.id - - override fun getExtra(item: Manga, position: Int): MangaHistory? = null -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListDetailsHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListDetailsHolder.kt deleted file mode 100644 index 3cf72c697..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListDetailsHolder.kt +++ /dev/null @@ -1,51 +0,0 @@ -package org.koitharu.kotatsu.ui.list - -import android.annotation.SuppressLint -import android.view.ViewGroup -import androidx.core.view.isVisible -import coil.ImageLoader -import coil.request.Disposable -import kotlinx.android.synthetic.main.item_manga_list_details.* -import org.koin.core.component.inject -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaHistory -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder -import org.koitharu.kotatsu.utils.ext.enqueueWith -import org.koitharu.kotatsu.utils.ext.newImageRequest -import org.koitharu.kotatsu.utils.ext.textAndVisible -import kotlin.math.roundToInt - -class MangaListDetailsHolder( - parent: ViewGroup -) : BaseViewHolder(parent, R.layout.item_manga_list_details) { - - private val coil by inject() - private var imageRequest: Disposable? = null - - @SuppressLint("SetTextI18n") - override fun onBind(data: Manga, extra: MangaHistory?) { - imageRequest?.dispose() - textView_title.text = data.title - textView_subtitle.textAndVisible = data.altTitle - imageRequest = imageView_cover.newImageRequest(data.coverUrl) - .placeholder(R.drawable.ic_placeholder) - .fallback(R.drawable.ic_placeholder) - .error(R.drawable.ic_placeholder) - .enqueueWith(coil) - if (data.rating == Manga.NO_RATING) { - textView_rating.isVisible = false - } else { - textView_rating.text = "${(data.rating * 10).roundToInt()}/10" - textView_rating.isVisible = true - } - textView_tags.text = data.tags.joinToString(", ") { - it.title - } - } - - override fun onRecycled() { - imageRequest?.dispose() - imageView_cover.setImageDrawable(null) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListFragment.kt deleted file mode 100644 index 3a314f37d..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListFragment.kt +++ /dev/null @@ -1,301 +0,0 @@ -package org.koitharu.kotatsu.ui.list - -import android.content.SharedPreferences -import android.os.Bundle -import android.view.* -import androidx.annotation.CallSuper -import androidx.appcompat.widget.PopupMenu -import androidx.core.view.GravityCompat -import androidx.core.view.isGone -import androidx.core.view.isVisible -import androidx.drawerlayout.widget.DrawerLayout -import androidx.recyclerview.widget.* -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.fragment_list.* -import moxy.MvpDelegate -import org.koin.android.ext.android.inject -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaFilter -import org.koitharu.kotatsu.core.model.MangaTag -import org.koitharu.kotatsu.core.model.SortOrder -import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.core.prefs.ListMode -import org.koitharu.kotatsu.ui.base.BaseFragment -import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener -import org.koitharu.kotatsu.ui.base.list.PaginationScrollListener -import org.koitharu.kotatsu.ui.base.list.ProgressBarAdapter -import org.koitharu.kotatsu.ui.base.list.decor.ItemTypeDividerDecoration -import org.koitharu.kotatsu.ui.base.list.decor.SectionItemDecoration -import org.koitharu.kotatsu.ui.base.list.decor.SpacingItemDecoration -import org.koitharu.kotatsu.ui.details.MangaDetailsActivity -import org.koitharu.kotatsu.ui.list.filter.FilterAdapter -import org.koitharu.kotatsu.ui.list.filter.OnFilterChangedListener -import org.koitharu.kotatsu.ui.utils.cloudflare.CloudFlareDialog -import org.koitharu.kotatsu.utils.UiUtils -import org.koitharu.kotatsu.utils.ext.* - -abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), - MangaListView, PaginationScrollListener.Callback, OnRecyclerItemClickListener, - SharedPreferences.OnSharedPreferenceChangeListener, OnFilterChangedListener, - SectionItemDecoration.Callback, SwipeRefreshLayout.OnRefreshListener { - - private val settings by inject() - private val adapterConfig = ConcatAdapter.Config.Builder() - .setIsolateViewTypes(true) - .setStableIdMode(ConcatAdapter.Config.StableIdMode.SHARED_STABLE_IDS) - .build() - - private var adapter: MangaListAdapter? = null - private var progressAdapter: ProgressBarAdapter? = null - private var paginationListener: PaginationScrollListener? = null - protected var isSwipeRefreshEnabled = true - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setHasOptionsMenu(true) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - drawer?.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) - adapter = MangaListAdapter(this) - progressAdapter = ProgressBarAdapter() - paginationListener = PaginationScrollListener(4, this) - recyclerView.setHasFixedSize(true) - initListMode(settings.listMode) - recyclerView.addOnScrollListener(paginationListener!!) - swipeRefreshLayout.setOnRefreshListener(this) - recyclerView_filter.setHasFixedSize(true) - recyclerView_filter.addItemDecoration(ItemTypeDividerDecoration(view.context)) - recyclerView_filter.addItemDecoration(SectionItemDecoration(false, this)) - settings.subscribe(this) - if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) { - onRequestMoreItems(0) - } - } - - override fun onDestroyView() { - adapter = null - progressAdapter = null - paginationListener = null - settings.unsubscribe(this) - super.onDestroyView() - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.opt_list, menu) - super.onCreateOptionsMenu(menu, inflater) - } - - override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { - R.id.action_list_mode -> { - ListModeSelectDialog.show(childFragmentManager) - true - } - R.id.action_filter -> { - drawer?.toggleDrawer(GravityCompat.END) - true - } - else -> super.onOptionsItemSelected(item) - } - - override fun onPrepareOptionsMenu(menu: Menu) { - menu.findItem(R.id.action_filter).isVisible = drawer != null && - drawer?.getDrawerLockMode(GravityCompat.END) != DrawerLayout.LOCK_MODE_LOCKED_CLOSED - super.onPrepareOptionsMenu(menu) - } - - override fun onItemClick(item: Manga, position: Int, view: View) { - startActivity(MangaDetailsActivity.newIntent(context ?: return, item)) - } - - override fun onItemLongClick(item: Manga, position: Int, view: View): Boolean { - val menu = PopupMenu(context ?: return false, view) - onCreatePopupMenu(menu.menuInflater, menu.menu, item) - return if (menu.menu.hasVisibleItems()) { - menu.setOnMenuItemClickListener { - onPopupMenuItemSelected(it, item) - } - menu.gravity = GravityCompat.END or Gravity.TOP - menu.show() - true - } else { - false - } - } - - final override fun onRefresh() { - swipeRefreshLayout.isRefreshing = true - onRequestMoreItems(0) - } - - override fun onListChanged(list: List) { - paginationListener?.reset() - adapter?.replaceData(list) - if (list.isEmpty()) { - setUpEmptyListHolder() - layout_holder.isVisible = true - } else { - layout_holder.isVisible = false - } - progressAdapter?.isProgressVisible = list.isNotEmpty() - recyclerView.callOnScrollListeners() - } - - override fun onListAppended(list: List) { - adapter?.appendData(list) - progressAdapter?.isProgressVisible = list.isNotEmpty() - if (list.isNotEmpty()) { - layout_holder.isVisible = false - } - recyclerView.callOnScrollListeners() - } - - @CallSuper - override fun onItemRemoved(item: Manga) { - adapter?.let { - it.removeItem(item) - if (it.itemCount == 0) { - setUpEmptyListHolder() - layout_holder.isVisible = true - } else { - layout_holder.isVisible = false - } - } - } - - override fun onListError(e: Throwable) { - if (e is CloudFlareProtectedException) { - CloudFlareDialog.newInstance(e.url).show(childFragmentManager, CloudFlareDialog.TAG) - } - if (recyclerView.hasItems) { - Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT) - .show() - } else { - textView_holder.text = e.getDisplayMessage(resources) - textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds( - 0, - R.drawable.ic_error_large, - 0, - 0 - ) - layout_holder.isVisible = true - } - } - - override fun onError(e: Throwable) { - Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show() - } - - @CallSuper - override fun onLoadingStateChanged(isLoading: Boolean) { - val hasItems = recyclerView.hasItems - progressBar.isVisible = isLoading && !hasItems - swipeRefreshLayout.isEnabled = isSwipeRefreshEnabled && !progressBar.isVisible - if (isLoading) { - layout_holder.isVisible = false - } else { - swipeRefreshLayout.isRefreshing = false - } - } - - @CallSuper - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { - if (context == null) { - return - } - when (key) { - AppSettings.KEY_LIST_MODE -> initListMode(settings.listMode) - AppSettings.KEY_GRID_SIZE -> UiUtils.SpanCountResolver.update(recyclerView) - } - } - - override fun onInitFilter( - sortOrders: List, - tags: List, - currentFilter: MangaFilter? - ) { - recyclerView_filter.adapter = FilterAdapter(sortOrders, tags, currentFilter, this) - drawer?.setDrawerLockMode( - if (sortOrders.isEmpty() && tags.isEmpty()) { - DrawerLayout.LOCK_MODE_LOCKED_CLOSED - } else { - DrawerLayout.LOCK_MODE_UNLOCKED - } - ) ?: divider_filter?.let { - it.isGone = sortOrders.isEmpty() && tags.isEmpty() - recyclerView_filter.isVisible = it.isVisible - } - activity?.invalidateOptionsMenu() - } - - @CallSuper - override fun onFilterChanged(filter: MangaFilter) { - drawer?.closeDrawers() - } - - protected open fun setUpEmptyListHolder() { - textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null) - textView_holder.setText(R.string.nothing_found) - } - - private fun initListMode(mode: ListMode) { - val ctx = context ?: return - val position = recyclerView.firstItem - recyclerView.adapter = null - recyclerView.layoutManager = null - recyclerView.clearItemDecorations() - recyclerView.removeOnLayoutChangeListener(UiUtils.SpanCountResolver) - adapter?.listMode = mode - recyclerView.layoutManager = when (mode) { - ListMode.GRID -> { - GridLayoutManager(ctx, UiUtils.resolveGridSpanCount(ctx)).apply { - spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { - override fun getSpanSize(position: Int) = if (position < getItemsCount()) - 1 else this@apply.spanCount - } - } - } - else -> LinearLayoutManager(ctx) - } - recyclerView.recycledViewPool.clear() - recyclerView.adapter = ConcatAdapter(adapterConfig, adapter, progressAdapter) - recyclerView.addItemDecoration( - when (mode) { - ListMode.LIST -> DividerItemDecoration(ctx, RecyclerView.VERTICAL) - ListMode.DETAILED_LIST, - ListMode.GRID -> SpacingItemDecoration( - resources.getDimensionPixelOffset(R.dimen.grid_spacing) - ) - } - ) - if (mode == ListMode.GRID) { - recyclerView.addOnLayoutChangeListener(UiUtils.SpanCountResolver) - } - adapter?.notifyDataSetChanged() - recyclerView.firstItem = position - } - - override fun getItemsCount() = adapter?.itemCount ?: 0 - - final override fun isSection(position: Int): Boolean { - return position == 0 || recyclerView_filter.adapter?.run { - getItemViewType(position) != getItemViewType(position - 1) - } ?: false - } - - final override fun getSectionTitle(position: Int): CharSequence? { - return when (recyclerView_filter.adapter?.getItemViewType(position)) { - FilterAdapter.VIEW_TYPE_SORT -> getString(R.string.sort_order) - FilterAdapter.VIEW_TYPE_TAG -> getString(R.string.genre) - else -> null - } - } - - protected open fun onCreatePopupMenu(inflater: MenuInflater, menu: Menu, data: Manga) = Unit - - protected open fun onPopupMenuItemSelected(item: MenuItem, data: Manga) = false -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListHolder.kt deleted file mode 100644 index 55ea6b8ab..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListHolder.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.koitharu.kotatsu.ui.list - -import android.view.ViewGroup -import coil.ImageLoader -import coil.request.Disposable -import kotlinx.android.synthetic.main.item_manga_list.* -import org.koin.core.component.inject -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaHistory -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder -import org.koitharu.kotatsu.utils.ext.enqueueWith -import org.koitharu.kotatsu.utils.ext.newImageRequest -import org.koitharu.kotatsu.utils.ext.textAndVisible - -class MangaListHolder( - parent: ViewGroup -) : BaseViewHolder(parent, R.layout.item_manga_list) { - - private val coil by inject() - private var imageRequest: Disposable? = null - - override fun onBind(data: Manga, extra: MangaHistory?) { - imageRequest?.dispose() - textView_title.text = data.title - textView_subtitle.textAndVisible = data.tags.joinToString(", ") { it.title } - imageRequest = imageView_cover.newImageRequest(data.coverUrl) - .placeholder(R.drawable.ic_placeholder) - .fallback(R.drawable.ic_placeholder) - .error(R.drawable.ic_placeholder) - .enqueueWith(coil) - } - - override fun onRecycled() { - imageRequest?.dispose() - imageView_cover.setImageDrawable(null) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListView.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListView.kt deleted file mode 100644 index 5954d9369..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MangaListView.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.koitharu.kotatsu.ui.list - -import moxy.viewstate.strategy.AddToEndSingleTagStrategy -import moxy.viewstate.strategy.AddToEndStrategy -import moxy.viewstate.strategy.StateStrategyType -import moxy.viewstate.strategy.alias.AddToEnd -import moxy.viewstate.strategy.alias.AddToEndSingle -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaFilter -import org.koitharu.kotatsu.core.model.MangaTag -import org.koitharu.kotatsu.core.model.SortOrder -import org.koitharu.kotatsu.ui.base.BaseMvpView - -interface MangaListView : BaseMvpView { - - @StateStrategyType(AddToEndSingleTagStrategy::class, tag = "content") - fun onListChanged(list: List) - - @StateStrategyType(AddToEndStrategy::class, tag = "content") - fun onListAppended(list: List) - - @StateStrategyType(AddToEndSingleTagStrategy::class, tag = "content") - fun onListError(e: Throwable) - - @AddToEndSingle - fun onInitFilter(sortOrders: List, tags: List, currentFilter: MangaFilter?) - - @AddToEnd - fun onItemRemoved(item: Manga) -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesListPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesListPresenter.kt deleted file mode 100644 index 1cc777a00..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/FavouritesListPresenter.kt +++ /dev/null @@ -1,59 +0,0 @@ -package org.koitharu.kotatsu.ui.list.favourites - -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.launch -import moxy.InjectViewState -import moxy.presenterScope -import org.koitharu.kotatsu.BuildConfig -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.domain.favourites.FavouritesRepository -import org.koitharu.kotatsu.ui.base.BasePresenter -import org.koitharu.kotatsu.ui.list.MangaListView - -@InjectViewState -class FavouritesListPresenter( - private val categoryId: Long, - private val repository: FavouritesRepository -) : BasePresenter>() { - - fun loadList(offset: Int) { - presenterScope.launch { - viewState.onLoadingStateChanged(true) - try { - val list = if (categoryId == 0L) { - repository.getAllManga(offset = offset) - } else { - repository.getManga(categoryId = categoryId, offset = offset) - } - if (offset == 0) { - viewState.onListChanged(list) - } else { - viewState.onListAppended(list) - } - } catch (e: CancellationException) { - } catch (e: Throwable) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - if (offset == 0) { - viewState.onListError(e) - } else { - viewState.onError(e) - } - } finally { - viewState.onLoadingStateChanged(false) - } - } - } - - fun removeFromFavourites(manga: Manga) { - launchJob { - if (categoryId == 0L) { - repository.removeFromFavourites(manga) - } else { - repository.removeFromCategory(manga, categoryId) - } - viewState.onItemRemoved(manga) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/CategoriesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/CategoriesAdapter.kt deleted file mode 100644 index 7228265e4..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/CategoriesAdapter.kt +++ /dev/null @@ -1,49 +0,0 @@ -package org.koitharu.kotatsu.ui.list.favourites.categories - -import android.annotation.SuppressLint -import android.view.MotionEvent -import android.view.ViewGroup -import kotlinx.android.synthetic.main.item_category.* -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) : - BaseRecyclerAdapter() { - - override fun onCreateViewHolder(parent: ViewGroup) = CategoryHolder(parent) - - override fun onGetItemId(item: FavouriteCategory) = item.id - - override fun getExtra(item: FavouriteCategory, position: Int) = Unit - - @SuppressLint("ClickableViewAccessibility") - override fun onViewAttachedToWindow(holder: BaseViewHolder) { - holder.imageView_more.setOnClickListener { v -> - onItemClickListener.onItemClick(holder.requireData(), holder.bindingAdapterPosition, v) - } - holder.imageView_handle.setOnTouchListener { v, event -> - if (event.actionMasked == MotionEvent.ACTION_DOWN) { - onItemClickListener.onItemLongClick( - holder.requireData(), - holder.bindingAdapterPosition, - v - ) - } else { - false - } - } - } - - override fun onViewDetachedFromWindow(holder: BaseViewHolder) { - holder.imageView_more.setOnClickListener(null) - holder.imageView_handle.setOnTouchListener(null) - } - - fun moveItem(oldPos: Int, newPos: Int) { - val item = dataSet.removeAt(oldPos) - dataSet.add(newPos, item) - notifyItemMoved(oldPos, newPos) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/CategoryHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/CategoryHolder.kt deleted file mode 100644 index b70fc3ddd..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/CategoryHolder.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.koitharu.kotatsu.ui.list.favourites.categories - -import android.view.ViewGroup -import kotlinx.android.synthetic.main.item_category.* -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder - -class CategoryHolder(parent: ViewGroup) : - BaseViewHolder(parent, R.layout.item_category) { - - override fun onBind(data: FavouriteCategory, extra: Unit) { - textView_title.text = data.title - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/FavouriteCategoriesPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/FavouriteCategoriesPresenter.kt deleted file mode 100644 index 02a966f3d..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/FavouriteCategoriesPresenter.kt +++ /dev/null @@ -1,78 +0,0 @@ -package org.koitharu.kotatsu.ui.list.favourites.categories - -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import moxy.InjectViewState -import org.koin.core.component.get -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.domain.favourites.FavouritesRepository -import org.koitharu.kotatsu.ui.base.BasePresenter -import org.koitharu.kotatsu.utils.ext.mapToSet - -@InjectViewState -class FavouriteCategoriesPresenter : BasePresenter() { - - private val repository = get() - private val reorderMutex by lazy(LazyThreadSafetyMode.NONE) { Mutex() } - - override fun onFirstViewAttach() { - super.onFirstViewAttach() - loadAllCategories() - } - - fun loadAllCategories() { - launchJob { - val categories = repository.getAllCategories() - viewState.onCategoriesChanged(categories) - } - } - - fun loadMangaCategories(manga: Manga) { - launchJob { - val categories = repository.getCategories(manga.id) - viewState.onCheckedCategoriesChanged(categories.mapToSet { it.id.toInt() }) - } - } - - fun createCategory(name: String) { - launchJob { - repository.addCategory(name) - val categories = repository.getAllCategories() - viewState.onCategoriesChanged(categories) - } - } - - fun renameCategory(id: Long, name: String) { - launchJob { - repository.renameCategory(id, name) - } - } - - fun deleteCategory(id: Long) { - launchJob { - repository.removeCategory(id) - val categories = repository.getAllCategories() - viewState.onCategoriesChanged(categories) - } - } - - fun storeCategoriesOrder(orderedIds: List) { - launchJob { - reorderMutex.withLock { - 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) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/FavouriteCategoriesView.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/FavouriteCategoriesView.kt deleted file mode 100644 index b4eb1de8f..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/FavouriteCategoriesView.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.koitharu.kotatsu.ui.list.favourites.categories - -import moxy.viewstate.strategy.AddToEndSingleStrategy -import moxy.viewstate.strategy.StateStrategyType -import moxy.viewstate.strategy.alias.AddToEndSingle -import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.ui.base.BaseMvpView - -interface FavouriteCategoriesView : BaseMvpView { - - @StateStrategyType(AddToEndSingleStrategy::class) - fun onCategoriesChanged(categories: List) - - @StateStrategyType(AddToEndSingleStrategy::class) - fun onCheckedCategoriesChanged(checkedIds: Set) - - @AddToEndSingle - override fun onLoadingStateChanged(isLoading: Boolean) = Unit -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/select/CategoriesSelectAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/select/CategoriesSelectAdapter.kt deleted file mode 100644 index b79143f92..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/select/CategoriesSelectAdapter.kt +++ /dev/null @@ -1,49 +0,0 @@ -package org.koitharu.kotatsu.ui.list.favourites.categories.select - -import android.util.SparseBooleanArray -import android.view.ViewGroup -import android.widget.Checkable -import androidx.core.util.set -import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.ui.base.list.BaseRecyclerAdapter -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder - -class CategoriesSelectAdapter(private val listener: OnCategoryCheckListener) : - BaseRecyclerAdapter() { - - private val checkedIds = SparseBooleanArray() - - fun setCheckedIds(ids: Iterable) { - checkedIds.clear() - ids.forEach { - checkedIds[it] = true - } - notifyDataSetChanged() - } - - override fun getExtra(item: FavouriteCategory, position: Int) = - checkedIds.get(item.id.toInt(), false) - - override fun onCreateViewHolder(parent: ViewGroup) = - CategoryCheckableHolder( - parent - ) - - override fun onGetItemId(item: FavouriteCategory) = item.id - - override fun onViewDetachedFromWindow(holder: BaseViewHolder) { - holder.itemView.setOnClickListener(null) - } - - override fun onViewAttachedToWindow(holder: BaseViewHolder) { - holder.itemView.setOnClickListener { - if (it !is Checkable) return@setOnClickListener - it.toggle() - if (it.isChecked) { - listener.onCategoryChecked(holder.requireData()) - } else { - listener.onCategoryUnchecked(holder.requireData()) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/select/CategoryCheckableHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/select/CategoryCheckableHolder.kt deleted file mode 100644 index a02d7ebed..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/select/CategoryCheckableHolder.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.koitharu.kotatsu.ui.list.favourites.categories.select - -import android.view.ViewGroup -import kotlinx.android.synthetic.main.item_category_checkable.* -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder - -class CategoryCheckableHolder(parent: ViewGroup) : - BaseViewHolder(parent, R.layout.item_category_checkable) { - - override fun onBind(data: FavouriteCategory, extra: Boolean) { - checkedTextView.text = data.title - checkedTextView.isChecked = extra - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/select/FavouriteCategoriesDialog.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/select/FavouriteCategoriesDialog.kt deleted file mode 100644 index 31ea5d817..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/select/FavouriteCategoriesDialog.kt +++ /dev/null @@ -1,96 +0,0 @@ -package org.koitharu.kotatsu.ui.list.favourites.categories.select - -import android.os.Bundle -import android.text.InputType -import android.view.View -import android.widget.Toast -import androidx.fragment.app.FragmentManager -import kotlinx.android.synthetic.main.dialog_favorite_categories.* -import moxy.ktx.moxyPresenter -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.ui.base.BaseBottomSheet -import org.koitharu.kotatsu.ui.base.dialog.TextInputDialog -import org.koitharu.kotatsu.ui.list.favourites.categories.FavouriteCategoriesPresenter -import org.koitharu.kotatsu.ui.list.favourites.categories.FavouriteCategoriesView -import org.koitharu.kotatsu.utils.ext.getDisplayMessage -import org.koitharu.kotatsu.utils.ext.withArgs - -class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categories), - FavouriteCategoriesView, - OnCategoryCheckListener { - - private val presenter by moxyPresenter(factory = ::FavouriteCategoriesPresenter) - - private val manga get() = arguments?.getParcelable(ARG_MANGA) - - private var adapter: CategoriesSelectAdapter? = null - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - adapter = - CategoriesSelectAdapter( - this - ) - recyclerView_categories.adapter = adapter - textView_add.setOnClickListener { - createCategory() - } - manga?.let { - presenter.loadMangaCategories(it) - } - } - - override fun onDestroyView() { - adapter = null - super.onDestroyView() - } - - override fun onCategoriesChanged(categories: List) { - adapter?.replaceData(categories) - } - - override fun onCheckedCategoriesChanged(checkedIds: Set) { - adapter?.setCheckedIds(checkedIds) - } - - override fun onCategoryChecked(category: FavouriteCategory) { - presenter.addToCategory(manga ?: return, category.id) - } - - override fun onCategoryUnchecked(category: FavouriteCategory) { - presenter.removeFromCategory(manga ?: return, category.id) - } - - override fun onError(e: Throwable) { - Toast.makeText(context ?: return, e.getDisplayMessage(resources), Toast.LENGTH_SHORT).show() - } - - private fun createCategory() { - TextInputDialog.Builder(context ?: return) - .setTitle(R.string.add_new_category) - .setHint(R.string.enter_category_name) - .setMaxLength(12, false) - .setInputType(InputType.TYPE_TEXT_VARIATION_PERSON_NAME or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) - .setNegativeButton(android.R.string.cancel) - .setPositiveButton(R.string.add) { _, name -> - presenter.createCategory(name) - }.create() - .show() - } - - companion object { - - private const val ARG_MANGA = "manga" - private const val TAG = "FavouriteCategoriesDialog" - - fun show(fm: FragmentManager, manga: Manga) = FavouriteCategoriesDialog() - .withArgs(1) { - putParcelable(ARG_MANGA, manga) - }.show( - fm, - TAG - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/select/OnCategoryCheckListener.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/select/OnCategoryCheckListener.kt deleted file mode 100644 index f9aa985da..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/favourites/categories/select/OnCategoryCheckListener.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.koitharu.kotatsu.ui.list.favourites.categories.select - -import org.koitharu.kotatsu.core.model.FavouriteCategory - -interface OnCategoryCheckListener { - - fun onCategoryChecked(category: FavouriteCategory) - - fun onCategoryUnchecked(category: FavouriteCategory) -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedAdapter.kt deleted file mode 100644 index 35f5c2581..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedAdapter.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.koitharu.kotatsu.ui.list.feed - -import android.view.ViewGroup -import org.koitharu.kotatsu.core.model.TrackingLogItem -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 FeedAdapter(onItemClickListener: OnRecyclerItemClickListener? = null) : - BaseRecyclerAdapter(onItemClickListener) { - - override fun onCreateViewHolder(parent: ViewGroup): BaseViewHolder { - return FeedHolder(parent) - } - - override fun onGetItemId(item: TrackingLogItem) = item.id - - override fun getExtra(item: TrackingLogItem, position: Int) = Unit -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedHolder.kt deleted file mode 100644 index 3ec2eb4c9..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedHolder.kt +++ /dev/null @@ -1,48 +0,0 @@ -package org.koitharu.kotatsu.ui.list.feed - -import android.text.format.DateUtils -import android.view.ViewGroup -import coil.ImageLoader -import coil.request.Disposable -import kotlinx.android.synthetic.main.item_tracklog.* -import org.koin.core.component.inject -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.TrackingLogItem -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder -import org.koitharu.kotatsu.utils.ext.enqueueWith -import org.koitharu.kotatsu.utils.ext.formatRelative -import org.koitharu.kotatsu.utils.ext.newImageRequest - -class FeedHolder(parent: ViewGroup) : - BaseViewHolder(parent, R.layout.item_tracklog) { - - private val coil by inject() - private var imageRequest: Disposable? = null - - override fun onBind(data: TrackingLogItem, extra: Unit) { - imageRequest?.dispose() - imageRequest = imageView_cover.newImageRequest(data.manga.coverUrl) - .placeholder(R.drawable.ic_placeholder) - .fallback(R.drawable.ic_placeholder) - .error(R.drawable.ic_placeholder) - .enqueueWith(coil) - textView_title.text = data.manga.title - textView_subtitle.text = buildString { - append(data.createdAt.formatRelative(DateUtils.DAY_IN_MILLIS)) - append(" ") - append( - context.resources.getQuantityString( - R.plurals.new_chapters, - data.chapters.size, - data.chapters.size - ) - ) - } - textView_chapters.text = data.chapters.joinToString("\n") - } - - override fun onRecycled() { - imageRequest?.dispose() - imageView_cover.setImageDrawable(null) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedPresenter.kt deleted file mode 100644 index c9d7e5a34..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedPresenter.kt +++ /dev/null @@ -1,44 +0,0 @@ -package org.koitharu.kotatsu.ui.list.feed - -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import moxy.presenterScope -import org.koin.core.component.inject -import org.koitharu.kotatsu.BuildConfig -import org.koitharu.kotatsu.domain.tracking.TrackingRepository -import org.koitharu.kotatsu.ui.base.BasePresenter - -class FeedPresenter : BasePresenter() { - - private val repository by inject() - - fun loadList(offset: Int) { - presenterScope.launch { - viewState.onLoadingStateChanged(true) - try { - val list = withContext(Dispatchers.IO) { - repository.getTrackingLog(offset, 20) - } - if (offset == 0) { - viewState.onListChanged(list) - } else { - viewState.onListAppended(list) - } - } catch (e: CancellationException) { - } catch (e: Throwable) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - if (offset == 0) { - viewState.onListError(e) - } else { - viewState.onError(e) - } - } finally { - viewState.onLoadingStateChanged(false) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedView.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedView.kt deleted file mode 100644 index 8ff33007e..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/feed/FeedView.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.koitharu.kotatsu.ui.list.feed - -import moxy.viewstate.strategy.AddToEndSingleTagStrategy -import moxy.viewstate.strategy.AddToEndStrategy -import moxy.viewstate.strategy.StateStrategyType -import org.koitharu.kotatsu.core.model.TrackingLogItem -import org.koitharu.kotatsu.ui.base.BaseMvpView - -interface FeedView : BaseMvpView { - - @StateStrategyType(AddToEndSingleTagStrategy::class, tag = "content") - fun onListChanged(list: List) - - @StateStrategyType(AddToEndStrategy::class, tag = "content") - fun onListAppended(list: List) - - @StateStrategyType(AddToEndSingleTagStrategy::class, tag = "content") - fun onListError(e: Throwable) -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/history/HistoryListPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/history/HistoryListPresenter.kt deleted file mode 100644 index fa403185b..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/history/HistoryListPresenter.kt +++ /dev/null @@ -1,71 +0,0 @@ -package org.koitharu.kotatsu.ui.list.history - -import android.os.Build -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import moxy.InjectViewState -import moxy.presenterScope -import org.koin.core.component.get -import org.koitharu.kotatsu.BuildConfig -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaHistory -import org.koitharu.kotatsu.domain.history.HistoryRepository -import org.koitharu.kotatsu.ui.base.BasePresenter -import org.koitharu.kotatsu.ui.list.MangaListView -import org.koitharu.kotatsu.utils.MangaShortcut - -@InjectViewState -class HistoryListPresenter : BasePresenter>() { - - private val repository = get() - - fun loadList(offset: Int) { - presenterScope.launch { - viewState.onLoadingStateChanged(true) - try { - val list = withContext(Dispatchers.IO) { - repository.getList(offset = offset) - } - if (offset == 0) { - viewState.onListChanged(list) - } else { - viewState.onListAppended(list) - } - } catch (_: CancellationException) { - } catch (e: Throwable) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - if (offset == 0) { - viewState.onListError(e) - } else { - viewState.onError(e) - } - } finally { - viewState.onLoadingStateChanged(false) - } - } - } - - fun clearHistory() { - launchLoadingJob { - repository.clear() - viewState.onListChanged(emptyList()) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { - MangaShortcut.clearAppShortcuts(get()) - } - } - } - - fun removeFromHistory(manga: Manga) { - launchJob { - repository.delete(manga) - viewState.onItemRemoved(manga) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { - MangaShortcut(manga).removeAppShortcut(get()) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/local/LocalListPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/local/LocalListPresenter.kt deleted file mode 100644 index 45f6d1ff2..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/local/LocalListPresenter.kt +++ /dev/null @@ -1,94 +0,0 @@ -package org.koitharu.kotatsu.ui.list.local - -import android.content.Context -import android.net.Uri -import android.os.Build -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import moxy.InjectViewState -import moxy.presenterScope -import org.koin.core.component.get -import org.koin.core.component.inject -import org.koitharu.kotatsu.BuildConfig -import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.parser.LocalMangaRepository -import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.domain.history.HistoryRepository -import org.koitharu.kotatsu.ui.base.BasePresenter -import org.koitharu.kotatsu.ui.list.MangaListView -import org.koitharu.kotatsu.utils.MangaShortcut -import org.koitharu.kotatsu.utils.MediaStoreCompat -import org.koitharu.kotatsu.utils.ext.safe -import org.koitharu.kotatsu.utils.ext.sub -import java.io.File -import java.io.IOException - -@InjectViewState -class LocalListPresenter : BasePresenter>() { - - private val repository by inject() - - fun loadList(offset: Int) { - presenterScope.launch { - if (offset != 0) { - viewState.onListAppended(emptyList()) - return@launch - } - viewState.onLoadingStateChanged(true) - try { - val list = withContext(Dispatchers.IO) { - repository.getList(0) - } - viewState.onListChanged(list) - } catch (e: CancellationException) { - } catch (e: Throwable) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - viewState.onListError(e) - } finally { - viewState.onLoadingStateChanged(false) - } - } - } - - fun importFile(context: Context, uri: Uri) { - launchJob { - val list = withContext(Dispatchers.IO) { - val name = MediaStoreCompat.getName(context, uri) - ?: throw IOException("Cannot fetch name from uri: $uri") - if (!LocalMangaRepository.isFileSupported(name)) { - throw UnsupportedFileException("Unsupported file on $uri") - } - val dest = get().getStorageDir(context)?.sub(name) - ?: throw IOException("External files dir unavailable") - context.contentResolver.openInputStream(uri)?.use { source -> - dest.outputStream().use { output -> - source.copyTo(output) - } - } ?: throw IOException("Cannot open input stream: $uri") - repository.getList(0) - } - viewState.onListChanged(list) - } - } - - fun delete(manga: Manga) { - launchJob { - withContext(Dispatchers.IO) { - val original = repository.getRemoteManga(manga) - repository.delete(manga) || throw IOException("Unable to delete file") - safe { - get().deleteOrSwap(manga, original) - } - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { - MangaShortcut(manga).removeAppShortcut(get()) - } - viewState.onItemRemoved(manga) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/remote/RemoteListPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/remote/RemoteListPresenter.kt deleted file mode 100644 index 6b90a9a52..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/list/remote/RemoteListPresenter.kt +++ /dev/null @@ -1,87 +0,0 @@ -package org.koitharu.kotatsu.ui.list.remote - -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import moxy.InjectViewState -import moxy.presenterScope -import org.koitharu.kotatsu.BuildConfig -import org.koitharu.kotatsu.core.model.MangaFilter -import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.ui.base.BasePresenter -import org.koitharu.kotatsu.ui.list.MangaListView - -@InjectViewState -class RemoteListPresenter(source: MangaSource) : BasePresenter>() { - - private val repository by lazy(LazyThreadSafetyMode.PUBLICATION) { - source.repository - } - private var isFilterInitialized = false - private var filter: MangaFilter? = null - - override fun onFirstViewAttach() { - super.onFirstViewAttach() - loadFilter() - } - - fun loadList(offset: Int) { - presenterScope.launch { - viewState.onLoadingStateChanged(true) - try { - val list = withContext(Dispatchers.Default) { - repository.getList( - offset = offset, - sortOrder = filter?.sortOrder, - tag = filter?.tag - ) - } - if (offset == 0) { - viewState.onListChanged(list) - } else { - viewState.onListAppended(list) - } - } catch (_: CancellationException) { - } catch (e: Throwable) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - if (offset == 0) { - viewState.onListError(e) - } else { - viewState.onError(e) - } - } finally { - viewState.onLoadingStateChanged(false) - } - } - if (!isFilterInitialized) { - loadFilter() - } - } - - fun applyFilter(filter: MangaFilter) { - this.filter = filter - viewState.onListChanged(emptyList()) - loadList(0) - } - - private fun loadFilter() { - isFilterInitialized = true - launchJob { - try { - val (sorts, tags) = withContext(Dispatchers.Default) { - repository.sortOrders.sortedBy { it.ordinal } to repository.getTags() - .sortedBy { it.title } - } - viewState.onInitFilter(sorts, tags, filter) - } catch (e: Exception) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - isFilterInitialized = false - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderPresenter.kt deleted file mode 100644 index ebb9a8290..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderPresenter.kt +++ /dev/null @@ -1,127 +0,0 @@ -package org.koitharu.kotatsu.ui.reader - -import android.content.ContentResolver -import android.webkit.URLUtil -import kotlinx.coroutines.* -import moxy.InjectViewState -import moxy.presenterScope -import okhttp3.OkHttpClient -import okhttp3.Request -import org.koin.core.component.KoinComponent -import org.koin.core.component.get -import org.koin.core.component.inject -import org.koitharu.kotatsu.BuildConfig -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaPage -import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.core.prefs.ReaderMode -import org.koitharu.kotatsu.domain.MangaDataRepository -import org.koitharu.kotatsu.domain.MangaUtils -import org.koitharu.kotatsu.domain.history.HistoryRepository -import org.koitharu.kotatsu.ui.base.BasePresenter -import org.koitharu.kotatsu.utils.MediaStoreCompat -import org.koitharu.kotatsu.utils.ext.IgnoreErrors -import org.koitharu.kotatsu.utils.ext.await -import org.koitharu.kotatsu.utils.ext.contentDisposition -import org.koitharu.kotatsu.utils.ext.mimeType - -@InjectViewState -class ReaderPresenter : BasePresenter() { - - private val dataRepository by inject() - private val appSettings by inject() - - fun init(manga: Manga) { - presenterScope.launch { - viewState.onLoadingStateChanged(isLoading = true) - try { - val mode = withContext(Dispatchers.Default) { - val repo = manga.source.repository - val chapter = - (manga.chapters ?: throw RuntimeException("Chapters is null")).random() - var mode = dataRepository.getReaderMode(manga.id) - if (mode == null) { - val pages = repo.getPages(chapter) - val isWebtoon = MangaUtils.determineMangaIsWebtoon(pages) - mode = getReaderMode(isWebtoon) - if (isWebtoon != null) { - dataRepository.savePreferences( - manga = manga, - mode = mode - ) - } - } - mode - } - viewState.onInitReader(manga, mode) - } catch (_: CancellationException) { - } catch (e: Throwable) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - viewState.onError(e) - } finally { - viewState.onLoadingStateChanged(isLoading = false) - } - } - } - - fun setMode(manga: Manga, mode: ReaderMode) { - presenterScope.launch { - dataRepository.savePreferences( - manga = manga, - mode = mode - ) - } - viewState.onInitReader(manga, mode) - } - - fun savePage(resolver: ContentResolver, page: MangaPage) { - presenterScope.launch(Dispatchers.IO) { - try { - val repo = page.source.repository - val url = repo.getPageFullUrl(page) - val request = Request.Builder() - .url(url) - .get() - .build() - val uri = get().newCall(request).await().use { response -> - val fileName = - URLUtil.guessFileName(url, response.contentDisposition, response.mimeType) - MediaStoreCompat.insertImage(resolver, fileName) { - response.body!!.byteStream().copyTo(it) - } - } - withContext(Dispatchers.Main) { - viewState.onPageSaved(uri) - } - } catch (e: CancellationException) { - } catch (e: Exception) { - withContext(Dispatchers.Main) { - viewState.onPageSaved(null) - } - } - } - } - - private fun getReaderMode(isWebtoon: Boolean?) = when { - isWebtoon == true -> ReaderMode.WEBTOON - appSettings.isPreferRtlReader -> ReaderMode.REVERSED - else -> ReaderMode.STANDARD - } - - companion object : KoinComponent { - - fun saveState(state: ReaderState) { - GlobalScope.launch(Dispatchers.Default + IgnoreErrors) { - get().addOrUpdate( - manga = state.manga, - chapterId = state.chapterId, - page = state.page, - scroll = state.scroll - ) - } - } - - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderView.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderView.kt deleted file mode 100644 index fb28407cb..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderView.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.koitharu.kotatsu.ui.reader - -import android.net.Uri -import moxy.viewstate.strategy.alias.AddToEndSingle -import moxy.viewstate.strategy.alias.OneExecution -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.prefs.ReaderMode -import org.koitharu.kotatsu.ui.base.BaseMvpView - -interface ReaderView : BaseMvpView { - - @AddToEndSingle - fun onInitReader(manga: Manga, mode: ReaderMode) - - @OneExecution - fun onPageSaved(uri: Uri?) -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PagesThumbnailsAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PagesThumbnailsAdapter.kt deleted file mode 100644 index ea488ba02..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PagesThumbnailsAdapter.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.koitharu.kotatsu.ui.reader.thumbnails - -import android.view.ViewGroup -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.DisposableHandle -import kotlinx.coroutines.SupervisorJob -import org.koin.core.component.inject -import org.koitharu.kotatsu.core.local.PagesCache -import org.koitharu.kotatsu.core.model.MangaPage -import org.koitharu.kotatsu.ui.base.list.BaseRecyclerAdapter -import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener -import kotlin.coroutines.CoroutineContext - -class PagesThumbnailsAdapter(onItemClickListener: OnRecyclerItemClickListener?) : - BaseRecyclerAdapter(onItemClickListener), CoroutineScope, - DisposableHandle { - - private val job = SupervisorJob() - private val cache by inject() - - override val coroutineContext: CoroutineContext - get() = Dispatchers.Main.immediate + job - - override fun dispose() { - job.cancel() - } - - override fun getExtra(item: MangaPage, position: Int) = cache - - override fun onCreateViewHolder(parent: ViewGroup) = PageThumbnailHolder(parent, this) - - override fun onGetItemId(item: MangaPage) = item.id -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/search/MangaSearchSheet.kt b/app/src/main/java/org/koitharu/kotatsu/ui/search/MangaSearchSheet.kt deleted file mode 100644 index e92821ebf..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/search/MangaSearchSheet.kt +++ /dev/null @@ -1,50 +0,0 @@ -package org.koitharu.kotatsu.ui.search - -import android.os.Bundle -import android.view.View -import androidx.fragment.app.FragmentManager -import moxy.ktx.moxyPresenter -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.ui.list.MangaListSheet -import org.koitharu.kotatsu.utils.ext.withArgs - -class MangaSearchSheet : MangaListSheet() { - - private val presenter by moxyPresenter(factory = ::SearchPresenter) - - private lateinit var source: MangaSource - private lateinit var query: String - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - source = requireArguments().getParcelable(ARG_SOURCE)!! - query = requireArguments().getString(ARG_QUERY).orEmpty() - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setTitle(query) - setSubtitle(getString(R.string.search_results_on_s, source.title)) - } - - override fun onRequestMoreItems(offset: Int) { - presenter.loadList(source, query, offset) - } - - companion object { - - private const val ARG_SOURCE = "source" - private const val ARG_QUERY = "query" - - private const val TAG = "MangaSearchSheet" - - fun show(fm: FragmentManager, source: MangaSource, query: String) { - MangaSearchSheet().withArgs(2) { - putParcelable(ARG_SOURCE, source) - putString(ARG_QUERY, query) - }.show(fm, TAG) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchPresenter.kt deleted file mode 100644 index 713bb2bf1..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/search/SearchPresenter.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.koitharu.kotatsu.ui.search - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import moxy.InjectViewState -import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.ui.base.BasePresenter -import org.koitharu.kotatsu.ui.list.MangaListView - -@InjectViewState -class SearchPresenter : BasePresenter>() { - - fun loadList(source: MangaSource, query: String, offset: Int) { - launchLoadingJob { - val list = withContext(Dispatchers.Default) { - source.repository.getList(offset, query = query) - } - if (offset == 0) { - viewState.onListChanged(list) - } else { - viewState.onListAppended(list) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/search/global/GlobalSearchFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/search/global/GlobalSearchFragment.kt deleted file mode 100644 index bf05ceff3..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/search/global/GlobalSearchFragment.kt +++ /dev/null @@ -1,33 +0,0 @@ -package org.koitharu.kotatsu.ui.search.global - -import moxy.ktx.moxyPresenter -import org.koitharu.kotatsu.ui.list.MangaListFragment -import org.koitharu.kotatsu.utils.ext.stringArgument -import org.koitharu.kotatsu.utils.ext.withArgs - - -class GlobalSearchFragment : MangaListFragment() { - - private val presenter by moxyPresenter(factory = ::GlobalSearchPresenter) - - private val query by stringArgument(ARG_QUERY) - - override fun onRequestMoreItems(offset: Int) { - if (offset == 0) { - presenter.startSearch(query.orEmpty()) - } - } - - override fun getTitle(): CharSequence? { - return query - } - - companion object { - - private const val ARG_QUERY = "query" - - fun newInstance(query: String) = GlobalSearchFragment().withArgs(1) { - putString(ARG_QUERY, query) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/search/global/GlobalSearchPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/search/global/GlobalSearchPresenter.kt deleted file mode 100644 index 3addb3e3e..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/search/global/GlobalSearchPresenter.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.koitharu.kotatsu.ui.search.global - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.plus -import moxy.presenterScope -import org.koin.core.component.get -import org.koitharu.kotatsu.domain.MangaSearchRepository -import org.koitharu.kotatsu.ui.base.BasePresenter -import org.koitharu.kotatsu.ui.list.MangaListView -import java.io.IOException - -class GlobalSearchPresenter : BasePresenter>() { - - private val repository = get() - - fun startSearch(query: String) { - viewState.onLoadingStateChanged(isLoading = true) - var isFirstCall = true - repository.globalSearch(query) - .catch { e -> - if (e is IOException) { - viewState.onError(e) - } - }.filterNot { x -> x.isEmpty() } - .onEmpty { - viewState.onListChanged(emptyList()) - viewState.onLoadingStateChanged(isLoading = false) - }.onCompletion { - viewState.onListAppended(emptyList()) - }.onEach { - if (isFirstCall) { - isFirstCall = false - viewState.onListChanged(it) - viewState.onLoadingStateChanged(isLoading = false) - } else { - viewState.onListAppended(it) - } - }.launchIn(presenterScope + Dispatchers.Default) - } - -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/BackupPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/BackupPresenter.kt deleted file mode 100644 index 9f2854c38..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/BackupPresenter.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.koitharu.kotatsu.ui.settings.backup - -import org.koin.core.component.get -import org.koitharu.kotatsu.core.backup.BackupArchive -import org.koitharu.kotatsu.core.backup.BackupRepository -import org.koitharu.kotatsu.ui.base.BasePresenter -import org.koitharu.kotatsu.utils.progress.Progress - -class BackupPresenter( - private val repository: BackupRepository -) : BasePresenter() { - - override fun onFirstViewAttach() { - super.onFirstViewAttach() - launchLoadingJob { - viewState.onProgressChanged(null) - val backup = BackupArchive.createNew(get()) - backup.put(repository.createIndex()) - - viewState.onProgressChanged(Progress(0, 3)) - backup.put(repository.dumpHistory()) - - viewState.onProgressChanged(Progress(1, 3)) - backup.put(repository.dumpCategories()) - - viewState.onProgressChanged(Progress(2, 3)) - backup.put(repository.dumpFavourites()) - - viewState.onProgressChanged(Progress(3, 3)) - backup.flush() - viewState.onProgressChanged(null) - backup.cleanup() - viewState.onBackupDone(backup.file) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/BackupView.kt b/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/BackupView.kt deleted file mode 100644 index 180eef1ff..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/BackupView.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.koitharu.kotatsu.ui.settings.backup - -import moxy.viewstate.strategy.alias.AddToEndSingle -import moxy.viewstate.strategy.alias.SingleState -import org.koitharu.kotatsu.ui.base.BaseMvpView -import org.koitharu.kotatsu.utils.progress.Progress -import java.io.File - -interface BackupView : BaseMvpView { - - @AddToEndSingle - fun onProgressChanged(progress: Progress?) - - @SingleState - fun onBackupDone(file: File) -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/RestoreView.kt b/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/RestoreView.kt deleted file mode 100644 index e81c411de..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/backup/RestoreView.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.koitharu.kotatsu.ui.settings.backup - -import moxy.viewstate.strategy.alias.AddToEndSingle -import moxy.viewstate.strategy.alias.SingleState -import org.koitharu.kotatsu.core.backup.CompositeResult -import org.koitharu.kotatsu.ui.base.BaseMvpView -import org.koitharu.kotatsu.utils.progress.Progress - -interface RestoreView : BaseMvpView { - - @AddToEndSingle - fun onProgressChanged(progress: Progress?) - - @SingleState - fun onRestoreDone(result: CompositeResult) -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectView.kt b/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectView.kt deleted file mode 100644 index 8ab4781d1..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectView.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.koitharu.kotatsu.ui.utils.protect - -import moxy.viewstate.strategy.alias.SingleState -import org.koitharu.kotatsu.ui.base.BaseMvpView - -interface ProtectView : BaseMvpView { - - @SingleState - fun onUnlockSuccess() -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/CategorySelectAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/CategorySelectAdapter.kt deleted file mode 100644 index f9b7dfcf1..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/CategorySelectAdapter.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.koitharu.kotatsu.ui.widget.shelf - -import android.view.ViewGroup -import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.ui.base.list.BaseRecyclerAdapter -import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener - -class CategorySelectAdapter(onItemClickListener: OnRecyclerItemClickListener? = null) : - BaseRecyclerAdapter(onItemClickListener) { - - var checkedItemId = 0L - private set - - fun setCheckedId(id: Long) { - val oldId = checkedItemId - checkedItemId = id - val oldPos = findItemPositionById(oldId) - val newPos = findItemPositionById(id) - if (newPos != -1) { - notifyItemChanged(newPos) - } - if (oldPos != -1) { - notifyItemChanged(oldPos) - } - } - - override fun getExtra(item: FavouriteCategory, position: Int) = - checkedItemId == item.id - - override fun onCreateViewHolder(parent: ViewGroup) = CategorySelectHolder( - parent - ) - - override fun onGetItemId(item: FavouriteCategory) = item.id -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/CategorySelectHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/CategorySelectHolder.kt deleted file mode 100644 index 3e1871a5e..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/CategorySelectHolder.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.koitharu.kotatsu.ui.widget.shelf - -import android.view.ViewGroup -import kotlinx.android.synthetic.main.item_category_checkable.* -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.ui.base.list.BaseViewHolder - -class CategorySelectHolder(parent: ViewGroup) : - BaseViewHolder(parent, R.layout.item_category_checkable_single) { - - override fun onBind(data: FavouriteCategory, extra: Boolean) { - checkedTextView.text = data.title - checkedTextView.isChecked = extra - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/MangaShortcut.kt b/app/src/main/java/org/koitharu/kotatsu/utils/MangaShortcut.kt index b1d712b2e..7a5b0a308 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/MangaShortcut.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/MangaShortcut.kt @@ -17,9 +17,9 @@ import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent import org.koin.core.component.inject import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.domain.MangaDataRepository import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.domain.MangaDataRepository -import org.koitharu.kotatsu.ui.details.MangaDetailsActivity +import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.utils.ext.requireBitmap import org.koitharu.kotatsu.utils.ext.safe @@ -86,8 +86,8 @@ class MangaShortcut(private val manga: Manga) : KoinComponent { IconCompat.createWithAdaptiveBitmap(it) } ?: IconCompat.createWithResource(context, R.drawable.ic_shortcut_default)) .setIntent( - MangaDetailsActivity.newIntent(context, manga.id) - .setAction(MangaDetailsActivity.ACTION_MANGA_VIEW) + DetailsActivity.newIntent(context, manga.id) + .setAction(DetailsActivity.ACTION_MANGA_VIEW) ) } diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/MediaStoreCompat.kt b/app/src/main/java/org/koitharu/kotatsu/utils/MediaStoreCompat.kt index c21ef04f4..0d0127a03 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/MediaStoreCompat.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/MediaStoreCompat.kt @@ -2,7 +2,6 @@ package org.koitharu.kotatsu.utils import android.content.ContentResolver import android.content.ContentValues -import android.content.Context import android.net.Uri import android.os.Build import android.provider.MediaStore @@ -55,16 +54,16 @@ object MediaStoreCompat { return uri } - @JvmStatic - fun getName(context: Context, uri: Uri): String? = (if (uri.scheme == "content") { - context.contentResolver.query(uri, null, null, null, null)?.use { - if (it.moveToFirst()) { - it.getStringOrNull(it.getColumnIndex(OpenableColumns.DISPLAY_NAME)) - } else { - null + fun getName(contentResolver: ContentResolver, uri: Uri): String? = + (if (uri.scheme == "content") { + contentResolver.query(uri, null, null, null, null)?.use { + if (it.moveToFirst()) { + it.getStringOrNull(it.getColumnIndex(OpenableColumns.DISPLAY_NAME)) + } else { + null + } } - } - } else { - null - }) ?: uri.path?.substringAfterLast('/') + } else { + null + }) ?: uri.path?.substringAfterLast('/') } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/SelectionController.kt b/app/src/main/java/org/koitharu/kotatsu/utils/SelectionController.kt new file mode 100644 index 000000000..f3274af2e --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/utils/SelectionController.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.utils + +import kotlinx.coroutines.flow.MutableStateFlow + +class SelectionController { + + private val state = MutableStateFlow(emptySet()) + + +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/SingleLiveEvent.kt b/app/src/main/java/org/koitharu/kotatsu/utils/SingleLiveEvent.kt new file mode 100644 index 000000000..b8f982f33 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/utils/SingleLiveEvent.kt @@ -0,0 +1,36 @@ +package org.koitharu.kotatsu.utils + +import androidx.annotation.AnyThread +import androidx.annotation.MainThread +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import java.util.concurrent.atomic.AtomicBoolean + +class SingleLiveEvent : LiveData() { + + private val pending = AtomicBoolean(false) + + override fun observe(owner: LifecycleOwner, observer: Observer) { + super.observe(owner) { + if (pending.compareAndSet(true, false)) { + observer.onChanged(it) + } + } + } + + override fun setValue(value: T) { + pending.set(true) + super.setValue(value) + } + + @MainThread + fun call(newValue: T) { + setValue(newValue) + } + + @AnyThread + fun postCall(newValue: T) { + postValue(newValue) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/delegates/ParcelableArgumentDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/utils/delegates/ParcelableArgumentDelegate.kt index 544b09119..113637107 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/delegates/ParcelableArgumentDelegate.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/delegates/ParcelableArgumentDelegate.kt @@ -5,7 +5,8 @@ import androidx.fragment.app.Fragment import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty -class ParcelableArgumentDelegate(private val name: String) : ReadOnlyProperty { +class ParcelableArgumentDelegate(private val name: String) : + ReadOnlyProperty { override fun getValue(thisRef: Fragment, property: KProperty<*>): T { return thisRef.requireArguments().getParcelable(name)!! diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/delegates/prefs/NullableStringPreferenceDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/utils/delegates/prefs/NullableStringPreferenceDelegate.kt index e49dd1798..0b96447bc 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/delegates/prefs/NullableStringPreferenceDelegate.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/delegates/prefs/NullableStringPreferenceDelegate.kt @@ -5,7 +5,8 @@ import androidx.core.content.edit import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -class NullableStringPreferenceDelegate(private val key: String) : ReadWriteProperty { +class NullableStringPreferenceDelegate(private val key: String) : + ReadWriteProperty { override fun getValue(thisRef: SharedPreferences, property: KProperty<*>): String? { return thisRef.getString(key, null) diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/delegates/prefs/StringSetPreferenceDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/utils/delegates/prefs/StringSetPreferenceDelegate.kt index 79adf259c..e1e0ed011 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/delegates/prefs/StringSetPreferenceDelegate.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/delegates/prefs/StringSetPreferenceDelegate.kt @@ -5,7 +5,10 @@ import androidx.core.content.edit import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -class StringSetPreferenceDelegate(private val key: String, private val defValue: Set = emptySet()) : +class StringSetPreferenceDelegate( + private val key: String, + private val defValue: Set = emptySet() +) : ReadWriteProperty> { override fun getValue(thisRef: SharedPreferences, property: KProperty<*>): Set { diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CoroutineExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CoroutineExt.kt index 50a29db9f..2ef2d1074 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CoroutineExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CoroutineExt.kt @@ -1,8 +1,8 @@ package org.koitharu.kotatsu.utils.ext +import androidx.lifecycle.ProcessLifecycleOwner +import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.* -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.onEach import okhttp3.Call import okhttp3.Callback import okhttp3.Response @@ -32,16 +32,6 @@ suspend fun Call.await() = suspendCancellableCoroutine { cont -> } } -fun Flow.onFirst(action: suspend (T) -> Unit): Flow { - var isFirstCall = true - return onEach { - if (isFirstCall) { - action(it) - isFirstCall = false - } - } -} - fun CoroutineScope.launchAfter( job: Job?, context: CoroutineContext = EmptyCoroutineContext, @@ -79,4 +69,7 @@ val IgnoreErrors if (BuildConfig.DEBUG) { e.printStackTrace() } - } \ No newline at end of file + } + +val processLifecycleScope: CoroutineScope + inline get() = ProcessLifecycleOwner.get().lifecycleScope \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FlowExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/FlowExt.kt new file mode 100644 index 000000000..a3de68094 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/FlowExt.kt @@ -0,0 +1,19 @@ +package org.koitharu.kotatsu.utils.ext + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach + +fun Flow.onFirst(action: suspend (T) -> Unit): Flow { + var isFirstCall = true + return onEach { + if (isFirstCall) { + action(it) + isFirstCall = false + } + } +} + +inline fun Flow>.mapItems(crossinline transform: (T) -> R): Flow> { + return map { list -> list.map(transform) } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/LiveDataExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/LiveDataExt.kt new file mode 100644 index 000000000..e19382be5 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/LiveDataExt.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.utils.ext + +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer + +fun LiveData.observeNotNull(owner: LifecycleOwner, observer: Observer) { + this.observe(owner) { + if (it != null) { + observer.onChanged(it) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt index 8f31764f8..f4e581995 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt @@ -22,7 +22,7 @@ import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipGroup -import org.koitharu.kotatsu.ui.base.ChipsFactory +import org.koitharu.kotatsu.core.ui.ChipsFactory fun View.hideKeyboard() { val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/AppWidgetModule.kt b/app/src/main/java/org/koitharu/kotatsu/widget/AppWidgetModule.kt new file mode 100644 index 000000000..41a45f413 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/widget/AppWidgetModule.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.widget + +import org.koin.android.viewmodel.dsl.viewModel +import org.koin.dsl.module +import org.koitharu.kotatsu.widget.shelf.ShelfConfigViewModel + +val appWidgetModule + get() = module { + viewModel { ShelfConfigViewModel(get()) } + } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/widget/WidgetUpdater.kt b/app/src/main/java/org/koitharu/kotatsu/widget/WidgetUpdater.kt similarity index 72% rename from app/src/main/java/org/koitharu/kotatsu/ui/widget/WidgetUpdater.kt rename to app/src/main/java/org/koitharu/kotatsu/widget/WidgetUpdater.kt index ac762dc3c..319e7a236 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/widget/WidgetUpdater.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/WidgetUpdater.kt @@ -1,13 +1,13 @@ -package org.koitharu.kotatsu.ui.widget +package org.koitharu.kotatsu.widget import android.appwidget.AppWidgetManager import android.content.ComponentName import android.content.Context import android.content.Intent -import org.koitharu.kotatsu.domain.favourites.OnFavouritesChangeListener -import org.koitharu.kotatsu.domain.history.OnHistoryChangeListener -import org.koitharu.kotatsu.ui.widget.recent.RecentWidgetProvider -import org.koitharu.kotatsu.ui.widget.shelf.ShelfWidgetProvider +import org.koitharu.kotatsu.favourites.domain.OnFavouritesChangeListener +import org.koitharu.kotatsu.history.domain.OnHistoryChangeListener +import org.koitharu.kotatsu.widget.recent.RecentWidgetProvider +import org.koitharu.kotatsu.widget.shelf.ShelfWidgetProvider class WidgetUpdater(private val context: Context) : OnFavouritesChangeListener, OnHistoryChangeListener { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/widget/recent/RecentListFactory.kt b/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt similarity index 87% rename from app/src/main/java/org/koitharu/kotatsu/ui/widget/recent/RecentListFactory.kt rename to app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt index 45ae59b7c..673bca9c0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/widget/recent/RecentListFactory.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.widget.recent +package org.koitharu.kotatsu.widget.recent import android.content.Context import android.content.Intent @@ -9,9 +9,9 @@ import coil.executeBlocking import coil.request.ImageRequest import kotlinx.coroutines.runBlocking import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.domain.history.HistoryRepository -import org.koitharu.kotatsu.ui.details.MangaDetailsActivity +import org.koitharu.kotatsu.history.domain.HistoryRepository import org.koitharu.kotatsu.utils.ext.requireBitmap import java.io.IOException @@ -52,7 +52,7 @@ class RecentListFactory( views.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder) } val intent = Intent() - intent.putExtra(MangaDetailsActivity.EXTRA_MANGA_ID, item.id) + intent.putExtra(MangaIntent.KEY_ID, item.id) views.setOnClickFillInIntent(R.id.imageView_cover, intent) return views } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/widget/recent/RecentWidgetProvider.kt b/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentWidgetProvider.kt similarity index 71% rename from app/src/main/java/org/koitharu/kotatsu/ui/widget/recent/RecentWidgetProvider.kt rename to app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentWidgetProvider.kt index 25f2623e3..733885853 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/widget/recent/RecentWidgetProvider.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentWidgetProvider.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.widget.recent +package org.koitharu.kotatsu.widget.recent import android.app.PendingIntent import android.appwidget.AppWidgetManager @@ -8,7 +8,7 @@ import android.content.Intent import android.net.Uri import android.widget.RemoteViews import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.ui.details.MangaDetailsActivity +import org.koitharu.kotatsu.details.ui.DetailsActivity class RecentWidgetProvider : AppWidgetProvider() { @@ -23,14 +23,16 @@ class RecentWidgetProvider : AppWidgetProvider() { adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id) adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME)) views.setRemoteAdapter(R.id.stackView, adapter) - val intent = Intent(context, MangaDetailsActivity::class.java) - intent.action = MangaDetailsActivity.ACTION_MANGA_VIEW - views.setPendingIntentTemplate(R.id.stackView, PendingIntent.getActivity( - context, - 0, - intent, - PendingIntent.FLAG_UPDATE_CURRENT - )) + val intent = Intent(context, DetailsActivity::class.java) + intent.action = DetailsActivity.ACTION_MANGA_VIEW + views.setPendingIntentTemplate( + R.id.stackView, PendingIntent.getActivity( + context, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT + ) + ) views.setEmptyView(R.id.stackView, R.id.textView_holder) appWidgetManager.updateAppWidget(id, views) } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/widget/recent/RecentWidgetService.kt b/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentWidgetService.kt similarity index 85% rename from app/src/main/java/org/koitharu/kotatsu/ui/widget/recent/RecentWidgetService.kt rename to app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentWidgetService.kt index 230c9e1f3..74e24e159 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/widget/recent/RecentWidgetService.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentWidgetService.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.widget.recent +package org.koitharu.kotatsu.widget.recent import android.content.Intent import android.widget.RemoteViewsService diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfConfigActivity.kt b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfConfigActivity.kt similarity index 65% rename from app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfConfigActivity.kt rename to app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfConfigActivity.kt index bd217cc04..011996af9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfConfigActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfConfigActivity.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.widget.shelf +package org.koitharu.kotatsu.widget.shelf import android.app.Activity import android.appwidget.AppWidgetManager @@ -14,22 +14,18 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.activity_categories.* -import moxy.ktx.moxyPresenter +import org.koin.android.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.FavouriteCategory +import org.koitharu.kotatsu.base.ui.BaseActivity +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.prefs.AppWidgetConfig -import org.koitharu.kotatsu.ui.base.BaseActivity -import org.koitharu.kotatsu.ui.base.list.OnRecyclerItemClickListener -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 java.util.* -import kotlin.collections.ArrayList +import org.koitharu.kotatsu.widget.shelf.adapter.CategorySelectAdapter +import org.koitharu.kotatsu.widget.shelf.model.CategoryItem -class ShelfConfigActivity : BaseActivity(), FavouriteCategoriesView, - OnRecyclerItemClickListener { +class ShelfConfigActivity : BaseActivity(), OnListItemClickListener { - private val presenter by moxyPresenter(factory = ::FavouriteCategoriesPresenter) + private val viewModel by viewModel() private lateinit var adapter: CategorySelectAdapter private lateinit var config: AppWidgetConfig @@ -52,7 +48,10 @@ class ShelfConfigActivity : BaseActivity(), FavouriteCategoriesView, return } config = AppWidgetConfig.getInstance(this, appWidgetId) - adapter.setCheckedId(config.categoryId) + viewModel.checkedId = config.categoryId + + viewModel.content.observe(this, this::onContentChanged) + viewModel.onError.observe(this, this::onError) } override fun onCreateOptionsMenu(menu: Menu?): Boolean { @@ -62,7 +61,7 @@ class ShelfConfigActivity : BaseActivity(), FavouriteCategoriesView, override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { R.id.action_done -> { - config.categoryId = adapter.checkedItemId + config.categoryId = viewModel.checkedId updateWidget() setResult( Activity.RESULT_OK, @@ -74,20 +73,15 @@ class ShelfConfigActivity : BaseActivity(), FavouriteCategoriesView, else -> super.onOptionsItemSelected(item) } - override fun onItemClick(item: FavouriteCategory, position: Int, view: View) { - adapter.setCheckedId(item.id) + override fun onItemClick(item: CategoryItem, view: View) { + viewModel.checkedId = item.id } - override fun onCategoriesChanged(categories: List) { - val data = ArrayList(categories.size + 1) - data += FavouriteCategory(0L, getString(R.string.all_favourites), -1, Date()) - data += categories - adapter.replaceData(data) + private fun onContentChanged(categories: List) { + adapter.items = categories } - override fun onCheckedCategoriesChanged(checkedIds: Set) = Unit - - override fun onError(e: Throwable) { + private fun onError(e: Throwable) { Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG) .show() } diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfConfigViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfConfigViewModel.kt new file mode 100644 index 000000000..c6fc30181 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfConfigViewModel.kt @@ -0,0 +1,32 @@ +package org.koitharu.kotatsu.widget.shelf + +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.favourites.domain.FavouritesRepository +import org.koitharu.kotatsu.widget.shelf.model.CategoryItem +import java.util.* + +class ShelfConfigViewModel( + favouritesRepository: FavouritesRepository +) : BaseViewModel() { + + private val selectedCategoryId = MutableStateFlow(0L) + + val content = combine( + favouritesRepository.observeCategories(), + selectedCategoryId + ) { categories, selectedId -> + val list = ArrayList(categories.size + 1) + list += CategoryItem(0L, null, selectedId == 0L) + categories.mapTo(list) { + CategoryItem(it.id, it.title, selectedId == it.id) + } + list + }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + + var checkedId: Long by selectedCategoryId::value +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfListFactory.kt b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt similarity index 89% rename from app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfListFactory.kt rename to app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt index f3b5b1e09..dfe531baa 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfListFactory.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.widget.shelf +package org.koitharu.kotatsu.widget.shelf import android.content.Context import android.content.Intent @@ -9,10 +9,10 @@ import coil.executeBlocking import coil.request.ImageRequest import kotlinx.coroutines.runBlocking import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.prefs.AppWidgetConfig -import org.koitharu.kotatsu.domain.favourites.FavouritesRepository -import org.koitharu.kotatsu.ui.details.MangaDetailsActivity +import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.utils.ext.requireBitmap import java.io.IOException @@ -63,7 +63,7 @@ class ShelfListFactory( views.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder) } val intent = Intent() - intent.putExtra(MangaDetailsActivity.EXTRA_MANGA_ID, item.id) + intent.putExtra(MangaIntent.KEY_ID, item.id) views.setOnClickFillInIntent(R.id.rootLayout, intent) return views } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfWidgetProvider.kt b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfWidgetProvider.kt similarity index 71% rename from app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfWidgetProvider.kt rename to app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfWidgetProvider.kt index d84dcd882..5b92d7b8e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfWidgetProvider.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfWidgetProvider.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.widget.shelf +package org.koitharu.kotatsu.widget.shelf import android.app.PendingIntent import android.appwidget.AppWidgetManager @@ -8,7 +8,7 @@ import android.content.Intent import android.net.Uri import android.widget.RemoteViews import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.ui.details.MangaDetailsActivity +import org.koitharu.kotatsu.details.ui.DetailsActivity class ShelfWidgetProvider : AppWidgetProvider() { @@ -23,14 +23,16 @@ class ShelfWidgetProvider : AppWidgetProvider() { adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id) adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME)) views.setRemoteAdapter(R.id.gridView, adapter) - val intent = Intent(context, MangaDetailsActivity::class.java) - intent.action = MangaDetailsActivity.ACTION_MANGA_VIEW - views.setPendingIntentTemplate(R.id.gridView, PendingIntent.getActivity( - context, - 0, - intent, - PendingIntent.FLAG_UPDATE_CURRENT - )) + val intent = Intent(context, DetailsActivity::class.java) + intent.action = DetailsActivity.ACTION_MANGA_VIEW + views.setPendingIntentTemplate( + R.id.gridView, PendingIntent.getActivity( + context, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT + ) + ) views.setEmptyView(R.id.gridView, R.id.textView_holder) appWidgetManager.updateAppWidget(id, views) } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfWidgetService.kt b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfWidgetService.kt similarity index 90% rename from app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfWidgetService.kt rename to app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfWidgetService.kt index 1ad3032b5..3f590235a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/widget/shelf/ShelfWidgetService.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfWidgetService.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.widget.shelf +package org.koitharu.kotatsu.widget.shelf import android.appwidget.AppWidgetManager import android.content.Intent diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/adapter/CategorySelectAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/adapter/CategorySelectAdapter.kt new file mode 100644 index 000000000..39f90c22e --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/adapter/CategorySelectAdapter.kt @@ -0,0 +1,33 @@ +package org.koitharu.kotatsu.widget.shelf.adapter + +import androidx.recyclerview.widget.DiffUtil +import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.widget.shelf.model.CategoryItem + +class CategorySelectAdapter( + clickListener: OnListItemClickListener +) : AsyncListDifferDelegationAdapter(DiffCallback()) { + + init { + delegatesManager.addDelegate(categorySelectItemAD(clickListener)) + } + + private class DiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: CategoryItem, newItem: CategoryItem): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: CategoryItem, newItem: CategoryItem): Boolean { + return oldItem == newItem + } + + override fun getChangePayload(oldItem: CategoryItem, newItem: CategoryItem): Any? { + if (oldItem.isSelected != newItem.isSelected) { + return newItem.isSelected + } + return super.getChangePayload(oldItem, newItem) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/adapter/CategorySelectItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/adapter/CategorySelectItemAD.kt new file mode 100644 index 000000000..b71cff6f3 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/adapter/CategorySelectItemAD.kt @@ -0,0 +1,23 @@ +package org.koitharu.kotatsu.widget.shelf.adapter + +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer +import kotlinx.android.synthetic.main.item_category_checkable.* +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.widget.shelf.model.CategoryItem + +fun categorySelectItemAD( + clickListener: OnListItemClickListener +) = adapterDelegateLayoutContainer( + R.layout.item_category_checkable_single +) { + + itemView.setOnClickListener { + clickListener.onItemClick(item, it) + } + + bind { + checkedTextView.text = item.name ?: getString(R.string.all_favourites) + checkedTextView.isChecked = item.isSelected + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/model/CategoryItem.kt b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/model/CategoryItem.kt new file mode 100644 index 000000000..0407dd18a --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/model/CategoryItem.kt @@ -0,0 +1,7 @@ +package org.koitharu.kotatsu.widget.shelf.model + +data class CategoryItem( + val id: Long, + val name: String?, + val isSelected: Boolean +) \ No newline at end of file diff --git a/app/src/main/res/layout-w600dp/fragment_details.xml b/app/src/main/res/layout-w600dp/fragment_details.xml index 508f13a08..cc251c658 100644 --- a/app/src/main/res/layout-w600dp/fragment_details.xml +++ b/app/src/main/res/layout-w600dp/fragment_details.xml @@ -118,6 +118,7 @@ android:justificationMode="inter_word" android:lineSpacingMultiplier="1.2" android:padding="12dp" + android:textIsSelectable="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/imageView_cover" app:layout_constraintTop_toBottomOf="@id/divider_top" diff --git a/app/src/main/res/layout/activity_details.xml b/app/src/main/res/layout/activity_details.xml index 52d7c02cb..7141a1c98 100644 --- a/app/src/main/res/layout/activity_details.xml +++ b/app/src/main/res/layout/activity_details.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".ui.details.MangaDetailsActivity"> + tools:context=".details.ui.DetailsActivity"> @@ -40,12 +41,21 @@ - + android:paddingLeft="?attr/dialogPreferredPadding" + android:paddingRight="?attr/dialogPreferredPadding" + android:singleLine="true" + android:text="@string/grid_size" /> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index b449f07ea..90cd6ce1a 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -129,6 +129,7 @@ android:justificationMode="inter_word" android:lineSpacingMultiplier="1.2" android:padding="12dp" + android:textIsSelectable="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/chips_tags" diff --git a/app/src/main/res/layout/fragment_reader_webtoon.xml b/app/src/main/res/layout/fragment_reader_webtoon.xml index 89d17f537..1f08901c7 100644 --- a/app/src/main/res/layout/fragment_reader_webtoon.xml +++ b/app/src/main/res/layout/fragment_reader_webtoon.xml @@ -1,5 +1,5 @@ - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_progress.xml b/app/src/main/res/layout/item_progress.xml index d060af2fb..2ca64e856 100644 --- a/app/src/main/res/layout/item_progress.xml +++ b/app/src/main/res/layout/item_progress.xml @@ -9,7 +9,6 @@ android:id="@+id/progressBar" style="@style/Widget.AppCompat.ProgressBar" android:layout_width="wrap_content" - android:visibility="invisible" android:layout_height="wrap_content" android:layout_gravity="center" /> diff --git a/app/src/main/res/layout/item_source_config.xml b/app/src/main/res/layout/item_source_config.xml index 4d11f7735..b6a5b0e51 100644 --- a/app/src/main/res/layout/item_source_config.xml +++ b/app/src/main/res/layout/item_source_config.xml @@ -28,7 +28,7 @@ android:textColor="?android:attr/textColorPrimary" tools:text="@tools:sample/lorem[1]" /> - - + + @string/favourites @string/history + + @string/list + @string/detailed_list + @string/grid + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index c67cfeafb..15caea8b4 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -8,6 +8,13 @@ 10dp + +