Merge branch 'devel' into feature/shikimori
This commit is contained in:
@@ -94,6 +94,7 @@ class KotatsuApp : Application() {
|
||||
ReportField.PHONE_MODEL,
|
||||
ReportField.CRASH_CONFIGURATION,
|
||||
ReportField.STACK_TRACE,
|
||||
ReportField.CUSTOM_DATA,
|
||||
ReportField.SHARED_PREFERENCES,
|
||||
)
|
||||
dialog {
|
||||
|
||||
@@ -6,14 +6,12 @@ import androidx.annotation.CallSuper
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
|
||||
import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.settings.SettingsHeadersFragment
|
||||
|
||||
abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
package org.koitharu.kotatsu.base.ui.dialog
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
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 org.koitharu.kotatsu.databinding.DialogInputBinding
|
||||
|
||||
class TextInputDialog private constructor(
|
||||
private val delegate: AlertDialog,
|
||||
) : DialogInterface by delegate {
|
||||
|
||||
fun show() = delegate.show()
|
||||
|
||||
class Builder(context: Context) {
|
||||
|
||||
private val binding = DialogInputBinding.inflate(LayoutInflater.from(context))
|
||||
|
||||
private val delegate = MaterialAlertDialogBuilder(context)
|
||||
.setView(binding.root)
|
||||
|
||||
fun setTitle(@StringRes titleResId: Int): Builder {
|
||||
delegate.setTitle(titleResId)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setTitle(title: CharSequence): Builder {
|
||||
delegate.setTitle(title)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setHint(@StringRes hintResId: Int): Builder {
|
||||
binding.inputEdit.hint = binding.root.context.getString(hintResId)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setMaxLength(maxLength: Int, strict: Boolean): Builder {
|
||||
with(binding.inputLayout) {
|
||||
counterMaxLength = maxLength
|
||||
isCounterEnabled = maxLength > 0
|
||||
}
|
||||
if (strict && maxLength > 0) {
|
||||
binding.inputEdit.filters += InputFilter.LengthFilter(maxLength)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun setInputType(inputType: Int): Builder {
|
||||
binding.inputEdit.inputType = inputType
|
||||
return this
|
||||
}
|
||||
|
||||
fun setText(text: String): Builder {
|
||||
binding.inputEdit.setText(text)
|
||||
binding.inputEdit.setSelection(text.length)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setPositiveButton(
|
||||
@StringRes textId: Int,
|
||||
listener: (DialogInterface, String) -> Unit
|
||||
): Builder {
|
||||
delegate.setPositiveButton(textId) { dialog, _ ->
|
||||
listener(dialog, binding.inputEdit.text?.toString().orEmpty())
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun setNegativeButton(
|
||||
@StringRes textId: Int,
|
||||
listener: DialogInterface.OnClickListener? = null
|
||||
): Builder {
|
||||
delegate.setNegativeButton(textId, listener)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setOnCancelListener(listener: DialogInterface.OnCancelListener): Builder {
|
||||
delegate.setOnCancelListener(listener)
|
||||
return this
|
||||
}
|
||||
|
||||
fun create() =
|
||||
TextInputDialog(delegate.create())
|
||||
|
||||
}
|
||||
}
|
||||
@@ -17,19 +17,28 @@
|
||||
package org.koitharu.kotatsu.base.ui.widgets
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.Button
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.core.view.postDelayed
|
||||
import org.koitharu.kotatsu.R
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.google.android.material.shape.ShapeAppearanceModel
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.koitharu.kotatsu.databinding.FadingSnackbarLayoutBinding
|
||||
import org.koitharu.kotatsu.utils.ext.getThemeColorStateList
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
private const val ENTER_DURATION = 300L
|
||||
private const val EXIT_DURATION = 200L
|
||||
private const val SHORT_DURATION = 1_500L
|
||||
private const val LONG_DURATION = 2_750L
|
||||
private const val SHORT_DURATION_MS = 1_500L
|
||||
private const val LONG_DURATION_MS = 2_750L
|
||||
|
||||
/**
|
||||
* A custom snackbar implementation allowing more control over placement and entry/exit animations.
|
||||
*
|
||||
@@ -40,16 +49,13 @@ private const val LONG_DURATION = 2_750L
|
||||
class FadingSnackbar @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
defStyleAttr: Int = 0,
|
||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val message: TextView
|
||||
private val action: Button
|
||||
private val binding = FadingSnackbarLayoutBinding.inflate(LayoutInflater.from(context), this)
|
||||
|
||||
init {
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.fading_snackbar_layout, this, true)
|
||||
message = view.findViewById(R.id.snackbar_text)
|
||||
action = view.findViewById(R.id.snackbar_action)
|
||||
binding.snackbarLayout.background = createThemedBackground()
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
@@ -62,33 +68,66 @@ class FadingSnackbar @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
fun show(
|
||||
messageText: CharSequence? = null,
|
||||
@StringRes actionId: Int? = null,
|
||||
longDuration: Boolean = true,
|
||||
actionClick: () -> Unit = { dismiss() },
|
||||
dismissListener: () -> Unit = { }
|
||||
messageText: CharSequence?,
|
||||
@StringRes actionId: Int = 0,
|
||||
duration: Int = Snackbar.LENGTH_SHORT,
|
||||
onActionClick: (FadingSnackbar.() -> Unit)? = null,
|
||||
onDismiss: (() -> Unit)? = null,
|
||||
) {
|
||||
message.text = messageText
|
||||
if (actionId != null) {
|
||||
action.run {
|
||||
binding.snackbarText.text = messageText
|
||||
if (actionId != 0) {
|
||||
with(binding.snackbarAction) {
|
||||
visibility = VISIBLE
|
||||
text = context.getString(actionId)
|
||||
setOnClickListener {
|
||||
actionClick()
|
||||
onActionClick?.invoke(this@FadingSnackbar) ?: dismiss()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
action.visibility = GONE
|
||||
binding.snackbarAction.visibility = GONE
|
||||
}
|
||||
alpha = 0f
|
||||
visibility = VISIBLE
|
||||
animate()
|
||||
.alpha(1f)
|
||||
.duration = ENTER_DURATION
|
||||
val showDuration = ENTER_DURATION + if (longDuration) LONG_DURATION else SHORT_DURATION
|
||||
postDelayed(showDuration) {
|
||||
if (duration == Snackbar.LENGTH_INDEFINITE) {
|
||||
return
|
||||
}
|
||||
val durationMs = ENTER_DURATION + if (duration == Snackbar.LENGTH_LONG) LONG_DURATION_MS else SHORT_DURATION_MS
|
||||
postDelayed(durationMs) {
|
||||
dismiss()
|
||||
dismissListener()
|
||||
onDismiss?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createThemedBackground(): Drawable {
|
||||
val backgroundColor = MaterialColors.layer(this, materialR.attr.colorSurface, materialR.attr.colorOnSurface, 1f)
|
||||
val shapeAppearanceModel = ShapeAppearanceModel.builder(
|
||||
context,
|
||||
materialR.style.ShapeAppearance_Material3_Corner_ExtraSmall,
|
||||
0
|
||||
).build()
|
||||
val background = createMaterialShapeDrawableBackground(
|
||||
backgroundColor,
|
||||
shapeAppearanceModel,
|
||||
)
|
||||
val backgroundTint = context.getThemeColorStateList(materialR.attr.colorSurfaceInverse)
|
||||
return if (backgroundTint != null) {
|
||||
val wrappedDrawable = DrawableCompat.wrap(background)
|
||||
DrawableCompat.setTintList(wrappedDrawable, backgroundTint)
|
||||
wrappedDrawable
|
||||
} else {
|
||||
DrawableCompat.wrap(background)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMaterialShapeDrawableBackground(
|
||||
@ColorInt backgroundColor: Int,
|
||||
shapeAppearanceModel: ShapeAppearanceModel,
|
||||
): MaterialShapeDrawable {
|
||||
val background = MaterialShapeDrawable(shapeAppearanceModel)
|
||||
background.fillColor = ColorStateList.valueOf(backgroundColor)
|
||||
return background
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,11 @@ class BackupZipOutput(val file: File) : Closeable {
|
||||
|
||||
private val output = ZipOutput(file, Deflater.BEST_COMPRESSION)
|
||||
|
||||
suspend fun put(entry: BackupEntry) {
|
||||
suspend fun put(entry: BackupEntry) = runInterruptible(Dispatchers.IO) {
|
||||
output.put(entry.name, entry.data.toString(2))
|
||||
}
|
||||
|
||||
suspend fun finish() {
|
||||
suspend fun finish() = runInterruptible(Dispatchers.IO) {
|
||||
output.finish()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package org.koitharu.kotatsu.core.exceptions
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import okio.IOException
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
class CloudFlareProtectedException(
|
||||
val url: String
|
||||
|
||||
@@ -126,6 +126,10 @@ class AppSettings(context: Context) {
|
||||
get() = prefs.getString(KEY_APP_PASSWORD, null)
|
||||
set(value) = prefs.edit { if (value != null) putString(KEY_APP_PASSWORD, value) else remove(KEY_APP_PASSWORD) }
|
||||
|
||||
var isBiometricProtectionEnabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_PROTECT_APP_BIOMETRIC, true)
|
||||
set(value) = prefs.edit { putBoolean(KEY_PROTECT_APP_BIOMETRIC, value) }
|
||||
|
||||
var sourcesOrder: List<String>
|
||||
get() = prefs.getString(KEY_SOURCES_ORDER, null)
|
||||
?.split('|')
|
||||
@@ -286,6 +290,7 @@ class AppSettings(context: Context) {
|
||||
const val KEY_READER_MODE_DETECT = "reader_mode_detect"
|
||||
const val KEY_APP_PASSWORD = "app_password"
|
||||
const val KEY_PROTECT_APP = "protect_app"
|
||||
const val KEY_PROTECT_APP_BIOMETRIC = "protect_app_bio"
|
||||
const val KEY_APP_VERSION = "app_version"
|
||||
const val KEY_ZOOM_MODE = "zoom_mode"
|
||||
const val KEY_BACKUP = "backup"
|
||||
@@ -310,9 +315,6 @@ class AppSettings(context: Context) {
|
||||
const val KEY_APP_UPDATE = "app_update"
|
||||
const val KEY_APP_UPDATE_AUTO = "app_update_auto"
|
||||
const val KEY_APP_TRANSLATION = "about_app_translation"
|
||||
const val KEY_FEEDBACK_4PDA = "about_feedback_4pda"
|
||||
const val KEY_FEEDBACK_DISCORD = "about_feedback_discord"
|
||||
const val KEY_FEEDBACK_GITHUB = "about_feedback_github"
|
||||
|
||||
private const val NETWORK_NEVER = 0
|
||||
private const val NETWORK_ALWAYS = 1
|
||||
|
||||
@@ -21,9 +21,11 @@ import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.commit
|
||||
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.coroutines.launch
|
||||
import org.acra.ktx.sendWithAcra
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
@@ -37,6 +39,7 @@ import org.koitharu.kotatsu.core.os.ShortcutsRepository
|
||||
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
|
||||
import org.koitharu.kotatsu.details.ui.adapter.BranchesAdapter
|
||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||
@@ -82,7 +85,7 @@ class DetailsActivity :
|
||||
viewModel.onMangaRemoved.observe(this, ::onMangaRemoved)
|
||||
viewModel.onError.observe(this, ::onError)
|
||||
viewModel.onShowToast.observe(this) {
|
||||
binding.snackbar.show(messageText = getString(it), longDuration = false)
|
||||
binding.snackbar.show(messageText = getString(it))
|
||||
}
|
||||
|
||||
registerReceiver(downloadReceiver, IntentFilter(DownloadService.ACTION_DOWNLOAD_COMPLETE))
|
||||
@@ -115,6 +118,21 @@ class DetailsActivity :
|
||||
Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show()
|
||||
finishAfterTransition()
|
||||
}
|
||||
e is ParseException || e is IllegalArgumentException || e is IllegalStateException -> {
|
||||
binding.snackbar.show(
|
||||
messageText = e.getDisplayMessage(resources),
|
||||
actionId = R.string.report,
|
||||
duration = if (viewModel.manga.value?.chapters == null) {
|
||||
Snackbar.LENGTH_INDEFINITE
|
||||
} else {
|
||||
Snackbar.LENGTH_LONG
|
||||
},
|
||||
onActionClick = {
|
||||
e.sendWithAcra()
|
||||
dismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
binding.snackbar.show(e.getDisplayMessage(resources))
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asFlow
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.*
|
||||
@@ -32,6 +31,7 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import java.io.IOException
|
||||
|
||||
class DetailsViewModel(
|
||||
intent: MangaIntent,
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.details.ui
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.acra.ACRA
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException
|
||||
@@ -13,6 +14,7 @@ import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import org.koitharu.kotatsu.details.ui.model.toListItem
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
@@ -20,6 +22,7 @@ import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
import org.koitharu.kotatsu.utils.ext.iterator
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.setCurrentManga
|
||||
|
||||
class MangaDetailsDelegate(
|
||||
private val intent: MangaIntent,
|
||||
@@ -32,6 +35,7 @@ class MangaDetailsDelegate(
|
||||
private val mangaData = MutableStateFlow(intent.manga)
|
||||
|
||||
val selectedBranch = MutableStateFlow<String?>(null)
|
||||
|
||||
// Remote manga for saved and saved for remote
|
||||
val relatedManga = MutableStateFlow<Manga?>(null)
|
||||
val manga: StateFlow<Manga?>
|
||||
@@ -41,6 +45,7 @@ class MangaDetailsDelegate(
|
||||
suspend fun doLoad() {
|
||||
var manga = mangaDataRepository.resolveIntent(intent)
|
||||
?: throw MangaNotFoundException("Cannot find manga")
|
||||
ACRA.setCurrentManga(manga)
|
||||
mangaData.value = manga
|
||||
manga = MangaRepository(manga.source).getDetails(manga)
|
||||
// find default branch
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingStatus
|
||||
import org.koitharu.kotatsu.scrobbling.ui.selector.ScrobblingSelectorBottomSheet
|
||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.requireValue
|
||||
|
||||
class ScrobblingInfoBottomSheet :
|
||||
BaseBottomSheet<SheetScrobblingBinding>(),
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class CategoriesEditDelegate(
|
||||
private val context: Context,
|
||||
@@ -11,9 +12,10 @@ class CategoriesEditDelegate(
|
||||
) {
|
||||
|
||||
fun deleteCategory(category: FavouriteCategory) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
MaterialAlertDialogBuilder(context, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered)
|
||||
.setMessage(context.getString(R.string.category_delete_confirm, category.title))
|
||||
.setTitle(R.string.remove_category)
|
||||
.setIcon(R.drawable.ic_delete)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.remove) { _, _ ->
|
||||
callback.onDeleteCategory(category)
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.MenuItem
|
||||
import androidx.core.view.MenuProvider
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.koitharu.kotatsu.R
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class HistoryListMenuProvider(
|
||||
private val context: Context,
|
||||
@@ -19,9 +20,10 @@ class HistoryListMenuProvider(
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||
R.id.action_clear_history -> {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
MaterialAlertDialogBuilder(context, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered)
|
||||
.setTitle(R.string.clear_history)
|
||||
.setMessage(R.string.text_clear_history_prompt)
|
||||
.setIcon(R.drawable.ic_delete)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.clear) { _, _ ->
|
||||
viewModel.clearHistory()
|
||||
|
||||
@@ -9,7 +9,6 @@ import androidx.collection.ArraySet
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isNotEmpty
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
||||
@@ -299,8 +299,9 @@ class MainActivity :
|
||||
}
|
||||
|
||||
override fun onClearSearchHistory() {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
MaterialAlertDialogBuilder(this, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered)
|
||||
.setTitle(R.string.clear_search_history)
|
||||
.setIcon(R.drawable.ic_clear_all)
|
||||
.setMessage(R.string.text_clear_search_history_prompt)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.clear) { _, _ ->
|
||||
|
||||
@@ -96,6 +96,9 @@ class ProtectActivity :
|
||||
}
|
||||
|
||||
private fun useFingerprint(): Boolean {
|
||||
if (!viewModel.isBiometricEnabled) {
|
||||
return false
|
||||
}
|
||||
if (BiometricManager.from(this).canAuthenticate(BIOMETRIC_WEAK) != BIOMETRIC_SUCCESS) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ class ProtectViewModel(
|
||||
|
||||
val onUnlockSuccess = SingleLiveEvent<Unit>()
|
||||
|
||||
val isBiometricEnabled
|
||||
get() = settings.isBiometricProtectionEnabled
|
||||
|
||||
fun tryUnlock(password: String) {
|
||||
if (job?.isActive == true) {
|
||||
return
|
||||
|
||||
@@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.acra.ktx.sendWithAcra
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
@@ -214,6 +215,8 @@ class ReaderActivity :
|
||||
val resolveTextId = ExceptionResolver.getResolveStringId(e)
|
||||
if (resolveTextId != 0) {
|
||||
dialog.setPositiveButton(resolveTextId, listener)
|
||||
} else {
|
||||
dialog.setPositiveButton(R.string.report, listener)
|
||||
}
|
||||
dialog.show()
|
||||
}
|
||||
@@ -368,7 +371,11 @@ class ReaderActivity :
|
||||
override fun onClick(dialog: DialogInterface?, which: Int) {
|
||||
if (which == DialogInterface.BUTTON_POSITIVE) {
|
||||
dialog?.dismiss()
|
||||
tryResolve(exception)
|
||||
if (ExceptionResolver.canResolve(exception)) {
|
||||
tryResolve(exception)
|
||||
} else {
|
||||
exception.sendWithAcra()
|
||||
}
|
||||
} else {
|
||||
onCancel(dialog)
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import java.util.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.acra.ACRA
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
@@ -32,6 +32,8 @@ import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||
import org.koitharu.kotatsu.utils.ext.setCurrentManga
|
||||
import java.util.*
|
||||
|
||||
private const val BOUNDS_PAGE_OFFSET = 2
|
||||
private const val PAGES_TRIM_THRESHOLD = 120
|
||||
@@ -257,6 +259,7 @@ class ReaderViewModel(
|
||||
private fun loadImpl() {
|
||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
||||
var manga = dataRepository.resolveIntent(intent) ?: throw MangaNotFoundException("Cannot find manga")
|
||||
ACRA.setCurrentManga(manga)
|
||||
mangaData.value = manga
|
||||
val repo = MangaRepository(manga.source)
|
||||
manga = repo.getDetails(manga)
|
||||
|
||||
@@ -16,6 +16,7 @@ abstract class BasePageHolder<B : ViewBinding>(
|
||||
exceptionResolver: ExceptionResolver
|
||||
) : RecyclerView.ViewHolder(binding.root), PageHolderDelegate.Callback {
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
protected val delegate = PageHolderDelegate(loader, settings, this, exceptionResolver)
|
||||
protected val bindingInfo = LayoutPageInfoBinding.bind(binding.root)
|
||||
|
||||
|
||||
@@ -11,10 +11,11 @@ import org.koitharu.kotatsu.utils.ext.resetTransformations
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
abstract class BaseReaderAdapter<H : BasePageHolder<*>>(
|
||||
private val loader: PageLoader,
|
||||
private val settings: AppSettings,
|
||||
private val exceptionResolver: ExceptionResolver
|
||||
private val exceptionResolver: ExceptionResolver,
|
||||
) : RecyclerView.Adapter<H>() {
|
||||
|
||||
private val differ = AsyncListDiffer(this, DiffCallback())
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.search.ui
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.graphics.Insets
|
||||
|
||||
@@ -91,7 +91,7 @@ class SettingsActivity :
|
||||
val fm = supportFragmentManager
|
||||
val fragment = fm.fragmentFactory.instantiate(classLoader, pref.fragment ?: return false)
|
||||
fragment.arguments = pref.extras
|
||||
// fragment.setTargetFragment(caller, 0)
|
||||
fragment.setTargetFragment(caller, 0)
|
||||
openFragment(fragment)
|
||||
return true
|
||||
}
|
||||
@@ -122,6 +122,7 @@ class SettingsActivity :
|
||||
ACTION_READER -> ReaderSettingsFragment()
|
||||
ACTION_SUGGESTIONS -> SuggestionsSettingsFragment()
|
||||
ACTION_SHIKIMORI -> ShikimoriSettingsFragment()
|
||||
ACTION_TRACKER -> TrackerSettingsFragment()
|
||||
ACTION_SOURCE -> SourceSettingsFragment.newInstance(
|
||||
intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL
|
||||
)
|
||||
@@ -146,6 +147,7 @@ class SettingsActivity :
|
||||
|
||||
private const val ACTION_READER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_READER_SETTINGS"
|
||||
private const val ACTION_SUGGESTIONS = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SUGGESTIONS"
|
||||
private const val ACTION_TRACKER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_TRACKER"
|
||||
private const val ACTION_SOURCE = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS"
|
||||
private const val ACTION_SHIKIMORI = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SHIKIMORI_SETTINGS"
|
||||
private const val EXTRA_SOURCE = "source"
|
||||
@@ -166,6 +168,10 @@ class SettingsActivity :
|
||||
Intent(context, SettingsActivity::class.java)
|
||||
.setAction(ACTION_SUGGESTIONS)
|
||||
|
||||
fun newTrackerSettingsIntent(context: Context) =
|
||||
Intent(context, SettingsActivity::class.java)
|
||||
.setAction(ACTION_TRACKER)
|
||||
|
||||
fun newSourceSettingsIntent(context: Context, source: MangaSource) =
|
||||
Intent(context, SettingsActivity::class.java)
|
||||
.setAction(ACTION_SOURCE)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.koitharu.kotatsu.settings.protect
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
@@ -7,9 +9,11 @@ import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.TextView
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
@@ -18,7 +22,7 @@ import org.koitharu.kotatsu.databinding.ActivitySetupProtectBinding
|
||||
private const val MIN_PASSWORD_LENGTH = 4
|
||||
|
||||
class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWatcher,
|
||||
View.OnClickListener, TextView.OnEditorActionListener {
|
||||
View.OnClickListener, TextView.OnEditorActionListener, CompoundButton.OnCheckedChangeListener {
|
||||
|
||||
private val viewModel by viewModel<ProtectSetupViewModel>()
|
||||
|
||||
@@ -31,6 +35,9 @@ class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWa
|
||||
binding.buttonNext.setOnClickListener(this)
|
||||
binding.buttonCancel.setOnClickListener(this)
|
||||
|
||||
binding.switchBiometric.isChecked = viewModel.isBiometricEnabled
|
||||
binding.switchBiometric.setOnCheckedChangeListener(this)
|
||||
|
||||
viewModel.isSecondStep.observe(this, this::onStepChanged)
|
||||
viewModel.onPasswordSet.observe(this) {
|
||||
finishAfterTransition()
|
||||
@@ -62,6 +69,10 @@ class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWa
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
|
||||
viewModel.setBiometricEnabled(isChecked)
|
||||
}
|
||||
|
||||
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
|
||||
return if (actionId == EditorInfo.IME_ACTION_DONE && binding.buttonNext.isEnabled) {
|
||||
binding.buttonNext.performClick()
|
||||
@@ -85,6 +96,7 @@ class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWa
|
||||
|
||||
private fun onStepChanged(isSecondStep: Boolean) {
|
||||
binding.buttonCancel.isGone = isSecondStep
|
||||
binding.switchBiometric.isVisible = isSecondStep && isBiometricAvailable()
|
||||
if (isSecondStep) {
|
||||
binding.layoutPassword.helperText = getString(R.string.repeat_password)
|
||||
binding.buttonNext.setText(R.string.confirm)
|
||||
@@ -93,4 +105,9 @@ class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWa
|
||||
binding.buttonNext.setText(R.string.next)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isBiometricAvailable(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,9 @@ class ProtectSetupViewModel(
|
||||
val onPasswordMismatch = SingleLiveEvent<Unit>()
|
||||
val onClearText = SingleLiveEvent<Unit>()
|
||||
|
||||
val isBiometricEnabled
|
||||
get() = settings.isBiometricProtectionEnabled
|
||||
|
||||
fun onNextClick(password: String) {
|
||||
if (firstPassword.value == null) {
|
||||
firstPassword.value = password
|
||||
@@ -35,4 +38,8 @@ class ProtectSetupViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setBiometricEnabled(isEnabled: Boolean) {
|
||||
settings.isBiometricProtectionEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,6 @@ class RingtonePickContract(private val title: String?) : ActivityResultContract<
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
|
||||
return intent?.getParcelableExtra<Uri>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
||||
return intent?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
||||
}
|
||||
}
|
||||
@@ -32,14 +32,14 @@ class SuggestionRepository(
|
||||
suspend fun replace(suggestions: Iterable<MangaSuggestion>) {
|
||||
db.withTransaction {
|
||||
db.suggestionDao.deleteAll()
|
||||
suggestions.forEach { x ->
|
||||
val tags = x.manga.tags.toEntities()
|
||||
suggestions.forEach { (manga, relevance) ->
|
||||
val tags = manga.tags.toEntities()
|
||||
db.tagsDao.upsert(tags)
|
||||
db.mangaDao.upsert(x.manga.toEntity(), tags)
|
||||
db.mangaDao.upsert(manga.toEntity(), tags)
|
||||
db.suggestionDao.upsert(
|
||||
SuggestionEntity(
|
||||
mangaId = x.manga.id,
|
||||
relevance = x.relevance,
|
||||
mangaId = manga.id,
|
||||
relevance = relevance,
|
||||
createdAt = System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.core.view.MenuProvider
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.tracker.work.TrackWorker
|
||||
|
||||
class FeedMenuProvider(
|
||||
@@ -43,6 +44,11 @@ class FeedMenuProvider(
|
||||
}.show()
|
||||
true
|
||||
}
|
||||
R.id.action_settings -> {
|
||||
val intent = SettingsActivity.newTrackerSettingsIntent(context)
|
||||
context.startActivity(intent)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class ScreenOrientationHelper(private val activity: Activity) {
|
||||
isLandscape = !isLandscape
|
||||
}
|
||||
|
||||
fun observeAutoOrientation() = callbackFlow<Boolean> {
|
||||
fun observeAutoOrientation() = callbackFlow {
|
||||
val observer = object : ContentObserver(Handler(activity.mainLooper)) {
|
||||
override fun onChange(selfChange: Boolean) {
|
||||
trySendBlocking(isAutoRotationEnabled)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
import android.view.View
|
||||
import androidx.core.graphics.Insets
|
||||
|
||||
fun Insets.getStart(view: View): Int {
|
||||
return if (view.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
|
||||
right
|
||||
} else {
|
||||
left
|
||||
}
|
||||
}
|
||||
|
||||
fun Insets.getEnd(view: View): Int {
|
||||
return if (view.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
|
||||
left
|
||||
} else {
|
||||
right
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,11 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.liveData
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.koitharu.kotatsu.utils.BufferedObserver
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
fun <T> LiveData<T?>.observeNotNull(owner: LifecycleOwner, observer: Observer<T>) {
|
||||
this.observe(owner) {
|
||||
|
||||
@@ -2,14 +2,16 @@ package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.res.Resources
|
||||
import java.io.FileNotFoundException
|
||||
import java.net.SocketTimeoutException
|
||||
import okio.FileNotFoundException
|
||||
import org.acra.ACRA
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
||||
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
|
||||
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
|
||||
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import java.net.SocketTimeoutException
|
||||
|
||||
fun Throwable.getDisplayMessage(resources: Resources) = when (this) {
|
||||
is AuthRequiredException -> resources.getString(R.string.auth_required)
|
||||
@@ -22,4 +24,6 @@ fun Throwable.getDisplayMessage(resources: Resources) = when (this) {
|
||||
is SocketTimeoutException -> resources.getString(R.string.network_error)
|
||||
is WrongPasswordException -> resources.getString(R.string.wrong_password)
|
||||
else -> localizedMessage ?: resources.getString(R.string.error_occurred)
|
||||
}
|
||||
}
|
||||
|
||||
fun ACRA.setCurrentManga(manga: Manga?) = errorReporter.putCustomData("manga", manga?.publicUrl.toString())
|
||||
@@ -5,8 +5,6 @@ import android.graphics.Rect
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.TooltipCompat
|
||||
import androidx.core.view.children
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
@@ -7,13 +7,15 @@ import android.widget.RemoteViewsService
|
||||
import coil.ImageLoader
|
||||
import coil.executeBlocking
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.Scale
|
||||
import coil.size.Size
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.ext.requireBitmap
|
||||
import java.io.IOException
|
||||
|
||||
class RecentListFactory(
|
||||
private val context: Context,
|
||||
@@ -22,9 +24,15 @@ class RecentListFactory(
|
||||
) : RemoteViewsService.RemoteViewsFactory {
|
||||
|
||||
private val dataSet = ArrayList<Manga>()
|
||||
private val transformation = RoundedCornersTransformation(
|
||||
context.resources.getDimension(R.dimen.appwidget_corner_radius_inner)
|
||||
)
|
||||
private val coverSize = Size(
|
||||
context.resources.getDimensionPixelSize(R.dimen.widget_cover_width),
|
||||
context.resources.getDimensionPixelSize(R.dimen.widget_cover_height),
|
||||
)
|
||||
|
||||
override fun onCreate() {
|
||||
}
|
||||
override fun onCreate() = Unit
|
||||
|
||||
override fun getLoadingView() = null
|
||||
|
||||
@@ -41,14 +49,18 @@ class RecentListFactory(
|
||||
override fun getViewAt(position: Int): RemoteViews {
|
||||
val views = RemoteViews(context.packageName, R.layout.item_recent)
|
||||
val item = dataSet[position]
|
||||
try {
|
||||
val cover = coil.executeBlocking(
|
||||
runCatching {
|
||||
coil.executeBlocking(
|
||||
ImageRequest.Builder(context)
|
||||
.data(item.coverUrl)
|
||||
.size(coverSize)
|
||||
.scale(Scale.FILL)
|
||||
.transformations(transformation)
|
||||
.build()
|
||||
).requireBitmap()
|
||||
}.onSuccess { cover ->
|
||||
views.setImageViewBitmap(R.id.imageView_cover, cover)
|
||||
} catch (e: IOException) {
|
||||
}.onFailure {
|
||||
views.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder)
|
||||
}
|
||||
val intent = Intent()
|
||||
@@ -61,6 +73,5 @@ class RecentListFactory(
|
||||
|
||||
override fun getViewTypeCount() = 1
|
||||
|
||||
override fun onDestroy() {
|
||||
}
|
||||
override fun onDestroy() = Unit
|
||||
}
|
||||
@@ -10,8 +10,6 @@ import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -40,9 +38,6 @@ class ShelfConfigActivity : BaseActivity<ActivityCategoriesBinding>(),
|
||||
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
|
||||
}
|
||||
adapter = CategorySelectAdapter(this)
|
||||
binding.recyclerView.addItemDecoration(
|
||||
MaterialDividerItemDecoration(this, RecyclerView.VERTICAL)
|
||||
)
|
||||
binding.recyclerView.adapter = adapter
|
||||
binding.buttonDone.isVisible = true
|
||||
binding.buttonDone.setOnClickListener(this)
|
||||
|
||||
@@ -7,6 +7,9 @@ import android.widget.RemoteViewsService
|
||||
import coil.ImageLoader
|
||||
import coil.executeBlocking
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.Scale
|
||||
import coil.size.Size
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
@@ -14,20 +17,25 @@ import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.ext.requireBitmap
|
||||
import java.io.IOException
|
||||
|
||||
class ShelfListFactory(
|
||||
private val context: Context,
|
||||
private val favouritesRepository: FavouritesRepository,
|
||||
private val coil: ImageLoader,
|
||||
widgetId: Int
|
||||
widgetId: Int,
|
||||
) : RemoteViewsService.RemoteViewsFactory {
|
||||
|
||||
private val dataSet = ArrayList<Manga>()
|
||||
private val config = AppWidgetConfig(context, widgetId)
|
||||
private val transformation = RoundedCornersTransformation(
|
||||
context.resources.getDimension(R.dimen.appwidget_corner_radius_inner)
|
||||
)
|
||||
private val coverSize = Size(
|
||||
context.resources.getDimensionPixelSize(R.dimen.widget_cover_width),
|
||||
context.resources.getDimensionPixelSize(R.dimen.widget_cover_height),
|
||||
)
|
||||
|
||||
override fun onCreate() {
|
||||
}
|
||||
override fun onCreate() = Unit
|
||||
|
||||
override fun getLoadingView() = null
|
||||
|
||||
@@ -52,14 +60,18 @@ class ShelfListFactory(
|
||||
val views = RemoteViews(context.packageName, R.layout.item_shelf)
|
||||
val item = dataSet[position]
|
||||
views.setTextViewText(R.id.textView_title, item.title)
|
||||
try {
|
||||
val cover = coil.executeBlocking(
|
||||
runCatching {
|
||||
coil.executeBlocking(
|
||||
ImageRequest.Builder(context)
|
||||
.data(item.coverUrl)
|
||||
.size(coverSize)
|
||||
.scale(Scale.FILL)
|
||||
.transformations(transformation)
|
||||
.build()
|
||||
).requireBitmap()
|
||||
}.onSuccess { cover ->
|
||||
views.setImageViewBitmap(R.id.imageView_cover, cover)
|
||||
} catch (e: IOException) {
|
||||
}.onFailure {
|
||||
views.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder)
|
||||
}
|
||||
val intent = Intent()
|
||||
|
||||
Reference in New Issue
Block a user