2023-01-26 23:53:24 +01:00
|
|
|
package tachiyomi.presentation.widget
|
2022-07-31 17:31:40 +02:00
|
|
|
|
|
|
|
import android.app.Application
|
|
|
|
import android.content.Intent
|
|
|
|
import android.graphics.Bitmap
|
|
|
|
import android.os.Build
|
|
|
|
import androidx.compose.runtime.Composable
|
|
|
|
import androidx.compose.ui.unit.DpSize
|
|
|
|
import androidx.compose.ui.unit.dp
|
|
|
|
import androidx.compose.ui.unit.sp
|
|
|
|
import androidx.core.graphics.drawable.toBitmap
|
|
|
|
import androidx.glance.GlanceModifier
|
|
|
|
import androidx.glance.Image
|
|
|
|
import androidx.glance.ImageProvider
|
|
|
|
import androidx.glance.LocalContext
|
|
|
|
import androidx.glance.LocalSize
|
|
|
|
import androidx.glance.action.clickable
|
|
|
|
import androidx.glance.appwidget.CircularProgressIndicator
|
|
|
|
import androidx.glance.appwidget.GlanceAppWidget
|
|
|
|
import androidx.glance.appwidget.GlanceAppWidgetManager
|
|
|
|
import androidx.glance.appwidget.SizeMode
|
|
|
|
import androidx.glance.appwidget.action.actionStartActivity
|
|
|
|
import androidx.glance.appwidget.appWidgetBackground
|
|
|
|
import androidx.glance.appwidget.updateAll
|
|
|
|
import androidx.glance.background
|
|
|
|
import androidx.glance.layout.Alignment
|
|
|
|
import androidx.glance.layout.Box
|
|
|
|
import androidx.glance.layout.Column
|
|
|
|
import androidx.glance.layout.ContentScale
|
|
|
|
import androidx.glance.layout.Row
|
|
|
|
import androidx.glance.layout.fillMaxSize
|
|
|
|
import androidx.glance.layout.fillMaxWidth
|
|
|
|
import androidx.glance.layout.padding
|
|
|
|
import androidx.glance.layout.size
|
|
|
|
import androidx.glance.text.Text
|
|
|
|
import androidx.glance.text.TextAlign
|
|
|
|
import androidx.glance.text.TextStyle
|
|
|
|
import androidx.glance.unit.ColorProvider
|
|
|
|
import coil.executeBlocking
|
|
|
|
import coil.imageLoader
|
|
|
|
import coil.request.CachePolicy
|
|
|
|
import coil.request.ImageRequest
|
|
|
|
import coil.size.Precision
|
|
|
|
import coil.size.Scale
|
|
|
|
import coil.transform.RoundedCornersTransformation
|
2022-09-18 19:07:48 +02:00
|
|
|
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
2022-07-31 17:31:40 +02:00
|
|
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
|
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
|
|
|
import kotlinx.coroutines.MainScope
|
2023-01-21 16:37:07 +01:00
|
|
|
import tachiyomi.data.DatabaseHandler
|
2023-01-22 16:37:13 +01:00
|
|
|
import tachiyomi.domain.manga.model.MangaCover
|
2023-01-21 16:37:07 +01:00
|
|
|
import tachiyomi.view.UpdatesView
|
2022-07-31 17:31:40 +02:00
|
|
|
import uy.kohesive.injekt.Injekt
|
|
|
|
import uy.kohesive.injekt.api.get
|
|
|
|
import uy.kohesive.injekt.injectLazy
|
|
|
|
import java.util.Calendar
|
|
|
|
import java.util.Date
|
|
|
|
|
|
|
|
class UpdatesGridGlanceWidget : GlanceAppWidget() {
|
|
|
|
private val app: Application by injectLazy()
|
2022-09-18 19:07:48 +02:00
|
|
|
private val preferences: SecurityPreferences by injectLazy()
|
2022-07-31 17:31:40 +02:00
|
|
|
|
|
|
|
private val coroutineScope = MainScope()
|
|
|
|
|
|
|
|
var data: List<Pair<Long, Bitmap?>>? = null
|
|
|
|
|
|
|
|
override val sizeMode = SizeMode.Exact
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
override fun Content() {
|
|
|
|
// App lock enabled, don't do anything
|
|
|
|
if (preferences.useAuthenticator().get()) {
|
|
|
|
WidgetNotAvailable()
|
|
|
|
} else {
|
|
|
|
UpdatesWidget()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun WidgetNotAvailable() {
|
2023-01-26 23:53:24 +01:00
|
|
|
val clazz = Class.forName("eu.kanade.tachiyomi.ui.main.MainActivity")
|
|
|
|
val intent = Intent(LocalContext.current, clazz).apply {
|
2022-07-31 17:31:40 +02:00
|
|
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
|
|
}
|
|
|
|
Box(
|
|
|
|
modifier = GlanceModifier
|
|
|
|
.clickable(actionStartActivity(intent))
|
|
|
|
.then(ContainerModifier)
|
|
|
|
.padding(8.dp),
|
|
|
|
contentAlignment = Alignment.Center,
|
|
|
|
) {
|
|
|
|
Text(
|
2022-08-26 15:16:26 +02:00
|
|
|
text = stringResource(R.string.appwidget_unavailable_locked),
|
2022-07-31 17:31:40 +02:00
|
|
|
style = TextStyle(
|
|
|
|
color = ColorProvider(R.color.appwidget_on_secondary_container),
|
|
|
|
fontSize = 12.sp,
|
|
|
|
textAlign = TextAlign.Center,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun UpdatesWidget() {
|
|
|
|
val (rowCount, columnCount) = LocalSize.current.calculateRowAndColumnCount()
|
|
|
|
Column(
|
|
|
|
modifier = ContainerModifier,
|
|
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
|
|
horizontalAlignment = Alignment.CenterHorizontally,
|
|
|
|
) {
|
|
|
|
val inData = data
|
|
|
|
if (inData == null) {
|
|
|
|
CircularProgressIndicator()
|
|
|
|
} else if (inData.isEmpty()) {
|
2022-08-26 15:16:26 +02:00
|
|
|
Text(text = stringResource(R.string.information_no_recent))
|
2022-07-31 17:31:40 +02:00
|
|
|
} else {
|
|
|
|
(0 until rowCount).forEach { i ->
|
|
|
|
val coverRow = (0 until columnCount).mapNotNull { j ->
|
|
|
|
inData.getOrNull(j + (i * columnCount))
|
|
|
|
}
|
|
|
|
if (coverRow.isNotEmpty()) {
|
|
|
|
Row(
|
|
|
|
modifier = GlanceModifier
|
|
|
|
.padding(vertical = 4.dp)
|
|
|
|
.fillMaxWidth(),
|
|
|
|
horizontalAlignment = Alignment.CenterHorizontally,
|
|
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
|
|
) {
|
|
|
|
coverRow.forEach { (mangaId, cover) ->
|
|
|
|
Box(
|
|
|
|
modifier = GlanceModifier
|
|
|
|
.padding(horizontal = 3.dp),
|
|
|
|
contentAlignment = Alignment.Center,
|
|
|
|
) {
|
2023-01-26 23:53:24 +01:00
|
|
|
val intent = Intent(LocalContext.current, Class.forName("eu.kanade.tachiyomi.ui.main.MainActivity")).apply {
|
|
|
|
action = "eu.kanade.tachiyomi.SHOW_MANGA"
|
|
|
|
putExtra("manga", mangaId)
|
2022-07-31 17:31:40 +02:00
|
|
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
|
|
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
|
|
|
|
|
|
|
// https://issuetracker.google.com/issues/238793260
|
|
|
|
addCategory(mangaId.toString())
|
|
|
|
}
|
|
|
|
Cover(
|
|
|
|
modifier = GlanceModifier.clickable(actionStartActivity(intent)),
|
|
|
|
cover = cover,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun Cover(
|
|
|
|
modifier: GlanceModifier = GlanceModifier,
|
|
|
|
cover: Bitmap?,
|
|
|
|
) {
|
|
|
|
Box(
|
|
|
|
modifier = modifier
|
|
|
|
.size(width = CoverWidth, height = CoverHeight)
|
2022-08-08 15:20:45 +02:00
|
|
|
.appWidgetInnerRadius(),
|
2022-07-31 17:31:40 +02:00
|
|
|
) {
|
|
|
|
if (cover != null) {
|
|
|
|
Image(
|
|
|
|
provider = ImageProvider(cover),
|
|
|
|
contentDescription = null,
|
|
|
|
modifier = GlanceModifier
|
|
|
|
.fillMaxSize()
|
|
|
|
.appWidgetInnerRadius(),
|
|
|
|
contentScale = ContentScale.Crop,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
// Enjoy placeholder
|
|
|
|
Image(
|
2022-08-08 15:20:45 +02:00
|
|
|
provider = ImageProvider(R.drawable.appwidget_cover_error),
|
2022-07-31 17:31:40 +02:00
|
|
|
contentDescription = null,
|
2022-08-08 15:20:45 +02:00
|
|
|
modifier = GlanceModifier.fillMaxSize(),
|
2022-07-31 17:31:40 +02:00
|
|
|
contentScale = ContentScale.Crop,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun loadData(list: List<UpdatesView>? = null) {
|
|
|
|
coroutineScope.launchIO {
|
|
|
|
// Don't show anything when lock is active
|
|
|
|
if (preferences.useAuthenticator().get()) {
|
|
|
|
updateAll(app)
|
|
|
|
return@launchIO
|
|
|
|
}
|
|
|
|
|
|
|
|
val manager = GlanceAppWidgetManager(app)
|
|
|
|
val ids = manager.getGlanceIds(this@UpdatesGridGlanceWidget::class.java)
|
|
|
|
if (ids.isEmpty()) return@launchIO
|
|
|
|
|
|
|
|
val processList = list
|
|
|
|
?: Injekt.get<DatabaseHandler>()
|
|
|
|
.awaitList { updatesViewQueries.updates(after = DateLimit.timeInMillis) }
|
|
|
|
val (rowCount, columnCount) = ids
|
|
|
|
.flatMap { manager.getAppWidgetSizes(it) }
|
|
|
|
.maxBy { it.height.value * it.width.value }
|
|
|
|
.calculateRowAndColumnCount()
|
|
|
|
|
|
|
|
data = prepareList(processList, rowCount * columnCount)
|
|
|
|
ids.forEach { update(app, it) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun prepareList(processList: List<UpdatesView>, take: Int): List<Pair<Long, Bitmap?>> {
|
|
|
|
// Resize to cover size
|
|
|
|
val widthPx = CoverWidth.value.toInt().dpToPx
|
|
|
|
val heightPx = CoverHeight.value.toInt().dpToPx
|
|
|
|
val roundPx = app.resources.getDimension(R.dimen.appwidget_inner_radius)
|
|
|
|
return processList
|
|
|
|
.distinctBy { it.mangaId }
|
|
|
|
.take(take)
|
|
|
|
.map { updatesView ->
|
|
|
|
val request = ImageRequest.Builder(app)
|
|
|
|
.data(
|
|
|
|
MangaCover(
|
|
|
|
mangaId = updatesView.mangaId,
|
|
|
|
sourceId = updatesView.source,
|
|
|
|
isMangaFavorite = updatesView.favorite,
|
|
|
|
url = updatesView.thumbnailUrl,
|
|
|
|
lastModified = updatesView.coverLastModified,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
|
|
|
.precision(Precision.EXACT)
|
|
|
|
.size(widthPx, heightPx)
|
|
|
|
.scale(Scale.FILL)
|
|
|
|
.let {
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
|
|
it.transformations(RoundedCornersTransformation(roundPx))
|
2022-09-11 05:57:03 +02:00
|
|
|
} else {
|
|
|
|
it // Handled by system
|
|
|
|
}
|
2022-07-31 17:31:40 +02:00
|
|
|
}
|
|
|
|
.build()
|
|
|
|
Pair(updatesView.mangaId, app.imageLoader.executeBlocking(request).drawable?.toBitmap())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
val DateLimit: Calendar
|
|
|
|
get() = Calendar.getInstance().apply {
|
|
|
|
time = Date()
|
|
|
|
add(Calendar.MONTH, -3)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private val CoverWidth = 58.dp
|
|
|
|
private val CoverHeight = 87.dp
|
|
|
|
|
|
|
|
private val ContainerModifier = GlanceModifier
|
|
|
|
.fillMaxSize()
|
|
|
|
.background(ImageProvider(R.drawable.appwidget_background))
|
|
|
|
.appWidgetBackground()
|
|
|
|
.appWidgetBackgroundRadius()
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculates row-column count.
|
|
|
|
*
|
|
|
|
* Row
|
|
|
|
* Numerator: Container height - container vertical padding
|
|
|
|
* Denominator: Cover height + cover vertical padding
|
|
|
|
*
|
|
|
|
* Column
|
|
|
|
* Numerator: Container width - container horizontal padding
|
|
|
|
* Denominator: Cover width + cover horizontal padding
|
|
|
|
*
|
|
|
|
* @return pair of row and column count
|
|
|
|
*/
|
|
|
|
private fun DpSize.calculateRowAndColumnCount(): Pair<Int, Int> {
|
|
|
|
// Hack: Size provided by Glance manager is not reliable so take at least 1 row and 1 column
|
2022-08-08 15:20:45 +02:00
|
|
|
// Set max to 10 children each direction because of Glance limitation
|
|
|
|
val rowCount = (height.value / 95).toInt().coerceIn(1, 10)
|
|
|
|
val columnCount = (width.value / 64).toInt().coerceIn(1, 10)
|
2022-07-31 17:31:40 +02:00
|
|
|
return Pair(rowCount, columnCount)
|
|
|
|
}
|