mirror of
https://git.mihon.tech/mihonapp/mihon
synced 2024-11-21 20:55:41 +03:00
"Updates" widget for Galaxy Z Flip5 cover screen (#9892)
This commit is contained in:
parent
dbc7fe4d54
commit
816d7815e9
19 changed files with 322 additions and 189 deletions
|
@ -141,20 +141,6 @@
|
||||||
android:name=".data.notification.NotificationReceiver"
|
android:name=".data.notification.NotificationReceiver"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<receiver
|
|
||||||
android:name="tachiyomi.presentation.widget.UpdatesGridGlanceReceiver"
|
|
||||||
android:enabled="@bool/glance_appwidget_available"
|
|
||||||
android:exported="false"
|
|
||||||
android:label="@string/label_recent_updates">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.appwidget.provider"
|
|
||||||
android:resource="@xml/updates_grid_glance_widget_info" />
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".data.download.DownloadService"
|
android:name=".data.download.DownloadService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
|
@ -1,2 +1,39 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest />
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name="tachiyomi.presentation.widget.UpdatesGridGlanceReceiver"
|
||||||
|
android:enabled="@bool/glance_appwidget_available"
|
||||||
|
android:exported="false"
|
||||||
|
android:label="@string/label_recent_updates">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/updates_grid_homescreen_widget_info" />
|
||||||
|
</receiver>
|
||||||
|
<receiver
|
||||||
|
android:name="tachiyomi.presentation.widget.UpdatesGridCoverScreenGlanceReceiver"
|
||||||
|
android:enabled="@bool/glance_appwidget_available"
|
||||||
|
android:exported="false"
|
||||||
|
android:label="@string/label_recent_updates">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/updates_grid_lockscreen_widget_info" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.samsung.android.appwidget.provider"
|
||||||
|
android:resource="@xml/updates_grid_samsung_cover_widget_info" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.samsung.android.sdk.subscreen.widget.support_visibility_callback"
|
||||||
|
android:value="true" />
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
</manifest>
|
|
@ -0,0 +1,153 @@
|
||||||
|
package tachiyomi.presentation.widget
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
|
import androidx.glance.GlanceId
|
||||||
|
import androidx.glance.GlanceModifier
|
||||||
|
import androidx.glance.ImageProvider
|
||||||
|
import androidx.glance.appwidget.GlanceAppWidget
|
||||||
|
import androidx.glance.appwidget.GlanceAppWidgetManager
|
||||||
|
import androidx.glance.appwidget.SizeMode
|
||||||
|
import androidx.glance.appwidget.appWidgetBackground
|
||||||
|
import androidx.glance.appwidget.provideContent
|
||||||
|
import androidx.glance.background
|
||||||
|
import androidx.glance.layout.fillMaxSize
|
||||||
|
import androidx.glance.layout.padding
|
||||||
|
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
|
||||||
|
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||||
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
|
import tachiyomi.domain.updates.interactor.GetUpdates
|
||||||
|
import tachiyomi.domain.updates.model.UpdatesWithRelations
|
||||||
|
import tachiyomi.presentation.widget.components.CoverHeight
|
||||||
|
import tachiyomi.presentation.widget.components.CoverWidth
|
||||||
|
import tachiyomi.presentation.widget.components.LockedWidget
|
||||||
|
import tachiyomi.presentation.widget.components.UpdatesWidget
|
||||||
|
import tachiyomi.presentation.widget.util.appWidgetBackgroundRadius
|
||||||
|
import tachiyomi.presentation.widget.util.calculateRowAndColumnCount
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
abstract class BaseUpdatesGridGlanceWidget(
|
||||||
|
private val context: Context = Injekt.get<Application>(),
|
||||||
|
private val getUpdates: GetUpdates = Injekt.get(),
|
||||||
|
private val preferences: SecurityPreferences = Injekt.get(),
|
||||||
|
) : GlanceAppWidget() {
|
||||||
|
|
||||||
|
override val sizeMode = SizeMode.Exact
|
||||||
|
|
||||||
|
abstract val foreground: ColorProvider
|
||||||
|
abstract val background: ImageProvider
|
||||||
|
abstract val topPadding: Dp
|
||||||
|
abstract val bottomPadding: Dp
|
||||||
|
|
||||||
|
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||||
|
val locked = preferences.useAuthenticator().get()
|
||||||
|
val containerModifier = GlanceModifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(background)
|
||||||
|
.appWidgetBackground()
|
||||||
|
.padding(top = topPadding, bottom = bottomPadding)
|
||||||
|
.appWidgetBackgroundRadius()
|
||||||
|
|
||||||
|
val manager = GlanceAppWidgetManager(context)
|
||||||
|
val ids = manager.getGlanceIds(javaClass)
|
||||||
|
val (rowCount, columnCount) = ids
|
||||||
|
.flatMap { manager.getAppWidgetSizes(it) }
|
||||||
|
.maxBy { it.height.value * it.width.value }
|
||||||
|
.calculateRowAndColumnCount(topPadding, bottomPadding)
|
||||||
|
|
||||||
|
provideContent {
|
||||||
|
// If app lock enabled, don't do anything
|
||||||
|
if (locked) {
|
||||||
|
LockedWidget(
|
||||||
|
foreground = foreground,
|
||||||
|
modifier = containerModifier,
|
||||||
|
)
|
||||||
|
return@provideContent
|
||||||
|
}
|
||||||
|
|
||||||
|
val flow = remember {
|
||||||
|
getUpdates
|
||||||
|
.subscribe(false, DateLimit.timeInMillis)
|
||||||
|
.map { rawData ->
|
||||||
|
rawData.prepareData(rowCount, columnCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val data by flow.collectAsState(initial = null)
|
||||||
|
UpdatesWidget(
|
||||||
|
data = data,
|
||||||
|
modifier = containerModifier,
|
||||||
|
contentColor = foreground,
|
||||||
|
topPadding = topPadding,
|
||||||
|
bottomPadding = bottomPadding,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun List<UpdatesWithRelations>.prepareData(
|
||||||
|
rowCount: Int,
|
||||||
|
columnCount: Int,
|
||||||
|
): List<Pair<Long, Bitmap?>> {
|
||||||
|
// Resize to cover size
|
||||||
|
val widthPx = CoverWidth.value.toInt().dpToPx
|
||||||
|
val heightPx = CoverHeight.value.toInt().dpToPx
|
||||||
|
val roundPx = context.resources.getDimension(R.dimen.appwidget_inner_radius)
|
||||||
|
return withIOContext {
|
||||||
|
this@prepareData
|
||||||
|
.distinctBy { it.mangaId }
|
||||||
|
.take(rowCount * columnCount)
|
||||||
|
.map { updatesView ->
|
||||||
|
val request = ImageRequest.Builder(context)
|
||||||
|
.data(
|
||||||
|
MangaCover(
|
||||||
|
mangaId = updatesView.mangaId,
|
||||||
|
sourceId = updatesView.sourceId,
|
||||||
|
isMangaFavorite = true,
|
||||||
|
url = updatesView.coverData.url,
|
||||||
|
lastModified = updatesView.coverData.lastModified,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.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))
|
||||||
|
} else {
|
||||||
|
it // Handled by system
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
Pair(updatesView.mangaId, context.imageLoader.executeBlocking(request).drawable?.toBitmap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DateLimit: Calendar
|
||||||
|
get() = Calendar.getInstance().apply {
|
||||||
|
time = Date()
|
||||||
|
add(Calendar.MONTH, -3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ class TachiyomiWidgetManager(
|
||||||
|
|
||||||
fun Context.init(scope: LifecycleCoroutineScope) {
|
fun Context.init(scope: LifecycleCoroutineScope) {
|
||||||
combine(
|
combine(
|
||||||
getUpdates.subscribe(read = false, after = UpdatesGridGlanceWidget.DateLimit.timeInMillis),
|
getUpdates.subscribe(read = false, after = BaseUpdatesGridGlanceWidget.DateLimit.timeInMillis),
|
||||||
securityPreferences.useAuthenticator().changes(),
|
securityPreferences.useAuthenticator().changes(),
|
||||||
transform = { a, _ -> a },
|
transform = { a, _ -> a },
|
||||||
)
|
)
|
||||||
|
@ -27,6 +27,7 @@ class TachiyomiWidgetManager(
|
||||||
.onEach {
|
.onEach {
|
||||||
try {
|
try {
|
||||||
UpdatesGridGlanceWidget().updateAll(this)
|
UpdatesGridGlanceWidget().updateAll(this)
|
||||||
|
UpdatesGridCoverScreenGlanceWidget().updateAll(this)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e) { "Failed to update widget" }
|
logcat(LogPriority.ERROR, e) { "Failed to update widget" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package tachiyomi.presentation.widget
|
||||||
|
|
||||||
|
import androidx.glance.appwidget.GlanceAppWidget
|
||||||
|
import androidx.glance.appwidget.GlanceAppWidgetReceiver
|
||||||
|
|
||||||
|
class UpdatesGridCoverScreenGlanceReceiver : GlanceAppWidgetReceiver() {
|
||||||
|
override val glanceAppWidget: GlanceAppWidget
|
||||||
|
get() = UpdatesGridCoverScreenGlanceWidget()
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package tachiyomi.presentation.widget
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.glance.ImageProvider
|
||||||
|
import androidx.glance.unit.ColorProvider
|
||||||
|
|
||||||
|
class UpdatesGridCoverScreenGlanceWidget : BaseUpdatesGridGlanceWidget() {
|
||||||
|
override val foreground = ColorProvider(Color.White)
|
||||||
|
override val background = ImageProvider(R.drawable.appwidget_coverscreen_background)
|
||||||
|
override val topPadding = 0.dp
|
||||||
|
override val bottomPadding = 24.dp
|
||||||
|
}
|
|
@ -4,5 +4,6 @@ import androidx.glance.appwidget.GlanceAppWidget
|
||||||
import androidx.glance.appwidget.GlanceAppWidgetReceiver
|
import androidx.glance.appwidget.GlanceAppWidgetReceiver
|
||||||
|
|
||||||
class UpdatesGridGlanceReceiver : GlanceAppWidgetReceiver() {
|
class UpdatesGridGlanceReceiver : GlanceAppWidgetReceiver() {
|
||||||
override val glanceAppWidget: GlanceAppWidget = UpdatesGridGlanceWidget()
|
override val glanceAppWidget: GlanceAppWidget
|
||||||
|
get() = UpdatesGridGlanceWidget()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,133 +1,12 @@
|
||||||
package tachiyomi.presentation.widget
|
package tachiyomi.presentation.widget
|
||||||
|
|
||||||
import android.app.Application
|
import androidx.compose.ui.unit.dp
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
|
||||||
import androidx.glance.GlanceId
|
|
||||||
import androidx.glance.GlanceModifier
|
|
||||||
import androidx.glance.ImageProvider
|
import androidx.glance.ImageProvider
|
||||||
import androidx.glance.appwidget.GlanceAppWidget
|
import androidx.glance.unit.ColorProvider
|
||||||
import androidx.glance.appwidget.GlanceAppWidgetManager
|
|
||||||
import androidx.glance.appwidget.SizeMode
|
|
||||||
import androidx.glance.appwidget.appWidgetBackground
|
|
||||||
import androidx.glance.appwidget.provideContent
|
|
||||||
import androidx.glance.background
|
|
||||||
import androidx.glance.layout.fillMaxSize
|
|
||||||
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
|
|
||||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
|
||||||
import tachiyomi.domain.updates.interactor.GetUpdates
|
|
||||||
import tachiyomi.domain.updates.model.UpdatesWithRelations
|
|
||||||
import tachiyomi.presentation.widget.components.CoverHeight
|
|
||||||
import tachiyomi.presentation.widget.components.CoverWidth
|
|
||||||
import tachiyomi.presentation.widget.components.LockedWidget
|
|
||||||
import tachiyomi.presentation.widget.components.UpdatesWidget
|
|
||||||
import tachiyomi.presentation.widget.util.appWidgetBackgroundRadius
|
|
||||||
import tachiyomi.presentation.widget.util.calculateRowAndColumnCount
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
class UpdatesGridGlanceWidget(
|
class UpdatesGridGlanceWidget : BaseUpdatesGridGlanceWidget() {
|
||||||
private val context: Context = Injekt.get<Application>(),
|
override val foreground = ColorProvider(R.color.appwidget_on_secondary_container)
|
||||||
private val getUpdates: GetUpdates = Injekt.get(),
|
override val background = ImageProvider(R.drawable.appwidget_background)
|
||||||
private val preferences: SecurityPreferences = Injekt.get(),
|
override val topPadding = 0.dp
|
||||||
) : GlanceAppWidget() {
|
override val bottomPadding = 0.dp
|
||||||
|
|
||||||
private var data: List<Pair<Long, Bitmap?>>? = null
|
|
||||||
|
|
||||||
override val sizeMode = SizeMode.Exact
|
|
||||||
|
|
||||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
|
||||||
val locked = preferences.useAuthenticator().get()
|
|
||||||
if (!locked) loadData()
|
|
||||||
|
|
||||||
provideContent {
|
|
||||||
// If app lock enabled, don't do anything
|
|
||||||
if (locked) {
|
|
||||||
LockedWidget()
|
|
||||||
return@provideContent
|
|
||||||
}
|
|
||||||
UpdatesWidget(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun loadData() {
|
|
||||||
val manager = GlanceAppWidgetManager(context)
|
|
||||||
val ids = manager.getGlanceIds(this@UpdatesGridGlanceWidget::class.java)
|
|
||||||
if (ids.isEmpty()) return
|
|
||||||
|
|
||||||
withIOContext {
|
|
||||||
val updates = getUpdates.await(
|
|
||||||
read = false,
|
|
||||||
after = DateLimit.timeInMillis,
|
|
||||||
)
|
|
||||||
val (rowCount, columnCount) = ids
|
|
||||||
.flatMap { manager.getAppWidgetSizes(it) }
|
|
||||||
.maxBy { it.height.value * it.width.value }
|
|
||||||
.calculateRowAndColumnCount()
|
|
||||||
|
|
||||||
data = prepareList(updates, rowCount * columnCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun prepareList(processList: List<UpdatesWithRelations>, take: Int): List<Pair<Long, Bitmap?>> {
|
|
||||||
// Resize to cover size
|
|
||||||
val widthPx = CoverWidth.value.toInt().dpToPx
|
|
||||||
val heightPx = CoverHeight.value.toInt().dpToPx
|
|
||||||
val roundPx = context.resources.getDimension(R.dimen.appwidget_inner_radius)
|
|
||||||
return processList
|
|
||||||
.distinctBy { it.mangaId }
|
|
||||||
.take(take)
|
|
||||||
.map { updatesView ->
|
|
||||||
val request = ImageRequest.Builder(context)
|
|
||||||
.data(
|
|
||||||
MangaCover(
|
|
||||||
mangaId = updatesView.mangaId,
|
|
||||||
sourceId = updatesView.sourceId,
|
|
||||||
isMangaFavorite = true,
|
|
||||||
url = updatesView.coverData.url,
|
|
||||||
lastModified = updatesView.coverData.lastModified,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.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))
|
|
||||||
} else {
|
|
||||||
it // Handled by system
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
Pair(updatesView.mangaId, context.imageLoader.executeBlocking(request).drawable?.toBitmap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val DateLimit: Calendar
|
|
||||||
get() = Calendar.getInstance().apply {
|
|
||||||
time = Date()
|
|
||||||
add(Calendar.MONTH, -3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val ContainerModifier = GlanceModifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(ImageProvider(R.drawable.appwidget_background))
|
|
||||||
.appWidgetBackground()
|
|
||||||
.appWidgetBackgroundRadius()
|
|
||||||
|
|
|
@ -16,26 +16,27 @@ import androidx.glance.text.TextAlign
|
||||||
import androidx.glance.text.TextStyle
|
import androidx.glance.text.TextStyle
|
||||||
import androidx.glance.unit.ColorProvider
|
import androidx.glance.unit.ColorProvider
|
||||||
import tachiyomi.core.Constants
|
import tachiyomi.core.Constants
|
||||||
import tachiyomi.presentation.widget.ContainerModifier
|
|
||||||
import tachiyomi.presentation.widget.R
|
import tachiyomi.presentation.widget.R
|
||||||
import tachiyomi.presentation.widget.util.stringResource
|
import tachiyomi.presentation.widget.util.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LockedWidget() {
|
fun LockedWidget(
|
||||||
|
foreground: ColorProvider,
|
||||||
|
modifier: GlanceModifier = GlanceModifier,
|
||||||
|
) {
|
||||||
val intent = Intent(LocalContext.current, Class.forName(Constants.MAIN_ACTIVITY)).apply {
|
val intent = Intent(LocalContext.current, Class.forName(Constants.MAIN_ACTIVITY)).apply {
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
modifier = GlanceModifier
|
modifier = modifier
|
||||||
.clickable(actionStartActivity(intent))
|
.clickable(actionStartActivity(intent))
|
||||||
.then(ContainerModifier)
|
|
||||||
.padding(8.dp),
|
.padding(8.dp),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.appwidget_unavailable_locked),
|
text = stringResource(R.string.appwidget_unavailable_locked),
|
||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
color = ColorProvider(R.color.appwidget_on_secondary_container),
|
color = foreground,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,6 +3,7 @@ package tachiyomi.presentation.widget.components
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.glance.GlanceModifier
|
import androidx.glance.GlanceModifier
|
||||||
import androidx.glance.LocalContext
|
import androidx.glance.LocalContext
|
||||||
|
@ -14,59 +15,75 @@ import androidx.glance.layout.Alignment
|
||||||
import androidx.glance.layout.Box
|
import androidx.glance.layout.Box
|
||||||
import androidx.glance.layout.Column
|
import androidx.glance.layout.Column
|
||||||
import androidx.glance.layout.Row
|
import androidx.glance.layout.Row
|
||||||
|
import androidx.glance.layout.fillMaxHeight
|
||||||
import androidx.glance.layout.fillMaxWidth
|
import androidx.glance.layout.fillMaxWidth
|
||||||
import androidx.glance.layout.padding
|
import androidx.glance.layout.padding
|
||||||
import androidx.glance.text.Text
|
import androidx.glance.text.Text
|
||||||
|
import androidx.glance.text.TextStyle
|
||||||
|
import androidx.glance.unit.ColorProvider
|
||||||
import tachiyomi.core.Constants
|
import tachiyomi.core.Constants
|
||||||
import tachiyomi.presentation.widget.ContainerModifier
|
|
||||||
import tachiyomi.presentation.widget.R
|
import tachiyomi.presentation.widget.R
|
||||||
import tachiyomi.presentation.widget.util.calculateRowAndColumnCount
|
import tachiyomi.presentation.widget.util.calculateRowAndColumnCount
|
||||||
import tachiyomi.presentation.widget.util.stringResource
|
import tachiyomi.presentation.widget.util.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UpdatesWidget(data: List<Pair<Long, Bitmap?>>?) {
|
fun UpdatesWidget(
|
||||||
val (rowCount, columnCount) = LocalSize.current.calculateRowAndColumnCount()
|
data: List<Pair<Long, Bitmap?>>?,
|
||||||
Column(
|
modifier: GlanceModifier = GlanceModifier,
|
||||||
modifier = ContainerModifier,
|
contentColor: ColorProvider,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
topPadding: Dp,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
bottomPadding: Dp,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
CircularProgressIndicator()
|
CircularProgressIndicator(color = contentColor)
|
||||||
} else if (data.isEmpty()) {
|
} else if (data.isEmpty()) {
|
||||||
Text(text = stringResource(R.string.information_no_recent))
|
Text(
|
||||||
|
text = stringResource(R.string.information_no_recent),
|
||||||
|
style = TextStyle(color = contentColor),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
(0..<rowCount).forEach { i ->
|
val (rowCount, columnCount) = LocalSize.current.calculateRowAndColumnCount(topPadding, bottomPadding)
|
||||||
val coverRow = (0..<columnCount).mapNotNull { j ->
|
Column(
|
||||||
data.getOrNull(j + (i * columnCount))
|
modifier = GlanceModifier.fillMaxHeight(),
|
||||||
}
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
if (coverRow.isNotEmpty()) {
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
Row(
|
) {
|
||||||
modifier = GlanceModifier
|
(0..<rowCount).forEach { i ->
|
||||||
.padding(vertical = 4.dp)
|
val coverRow = (0..<columnCount).mapNotNull { j ->
|
||||||
.fillMaxWidth(),
|
data.getOrNull(j + (i * columnCount))
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
}
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
if (coverRow.isNotEmpty()) {
|
||||||
) {
|
Row(
|
||||||
coverRow.forEach { (mangaId, cover) ->
|
modifier = GlanceModifier
|
||||||
Box(
|
.padding(vertical = 4.dp)
|
||||||
modifier = GlanceModifier
|
.fillMaxWidth(),
|
||||||
.padding(horizontal = 3.dp),
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
contentAlignment = Alignment.Center,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
val intent = Intent(LocalContext.current, Class.forName(Constants.MAIN_ACTIVITY)).apply {
|
coverRow.forEach { (mangaId, cover) ->
|
||||||
action = Constants.SHORTCUT_MANGA
|
Box(
|
||||||
putExtra(Constants.MANGA_EXTRA, mangaId)
|
modifier = GlanceModifier
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
.padding(horizontal = 3.dp),
|
||||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
val intent = Intent(LocalContext.current, Class.forName(Constants.MAIN_ACTIVITY)).apply {
|
||||||
|
action = Constants.SHORTCUT_MANGA
|
||||||
|
putExtra(Constants.MANGA_EXTRA, mangaId)
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
|
||||||
// https://issuetracker.google.com/issues/238793260
|
// https://issuetracker.google.com/issues/238793260
|
||||||
addCategory(mangaId.toString())
|
addCategory(mangaId.toString())
|
||||||
|
}
|
||||||
|
UpdatesMangaCover(
|
||||||
|
modifier = GlanceModifier.clickable(actionStartActivity(intent)),
|
||||||
|
cover = cover,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
UpdatesMangaCover(
|
|
||||||
modifier = GlanceModifier.clickable(actionStartActivity(intent)),
|
|
||||||
cover = cover,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package tachiyomi.presentation.widget.util
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.glance.GlanceModifier
|
import androidx.glance.GlanceModifier
|
||||||
import androidx.glance.LocalContext
|
import androidx.glance.LocalContext
|
||||||
|
@ -34,9 +35,13 @@ fun stringResource(@StringRes id: Int): String {
|
||||||
*
|
*
|
||||||
* @return pair of row and column count
|
* @return pair of row and column count
|
||||||
*/
|
*/
|
||||||
fun DpSize.calculateRowAndColumnCount(): Pair<Int, Int> {
|
fun DpSize.calculateRowAndColumnCount(
|
||||||
|
topPadding: Dp,
|
||||||
|
bottomPadding: Dp,
|
||||||
|
): Pair<Int, Int> {
|
||||||
// Hack: Size provided by Glance manager is not reliable so take at least 1 row and 1 column
|
// Hack: Size provided by Glance manager is not reliable so take at least 1 row and 1 column
|
||||||
// Set max to 10 children each direction because of Glance limitation
|
// Set max to 10 children each direction because of Glance limitation
|
||||||
|
val height = this.height - topPadding - bottomPadding
|
||||||
val rowCount = (height.value / 95).toInt().coerceIn(1, 10)
|
val rowCount = (height.value / 95).toInt().coerceIn(1, 10)
|
||||||
val columnCount = (width.value / 64).toInt().coerceIn(1, 10)
|
val columnCount = (width.value / 64).toInt().coerceIn(1, 10)
|
||||||
return Pair(rowCount, columnCount)
|
return Pair(rowCount, columnCount)
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 410 KiB After Width: | Height: | Size: 410 KiB |
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@color/appwidget_coverscreen_background" />
|
||||||
|
</shape>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/appwidget_coverscreen_background">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:text="@string/loading"
|
||||||
|
android:textColor="@android:color/white" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="appwidget_background">@color/tachiyomi_surface</color>
|
<color name="appwidget_background">@color/tachiyomi_surface</color>
|
||||||
|
<color name="appwidget_coverscreen_background">#00000000</color>
|
||||||
<color name="appwidget_on_background">@color/tachiyomi_onSurface</color>
|
<color name="appwidget_on_background">@color/tachiyomi_onSurface</color>
|
||||||
<color name="appwidget_surface_variant">@color/tachiyomi_surfaceVariant</color>
|
<color name="appwidget_surface_variant">@color/tachiyomi_surfaceVariant</color>
|
||||||
<color name="appwidget_on_surface_variant">@color/tachiyomi_onSurfaceVariant</color>
|
<color name="appwidget_on_surface_variant">@color/tachiyomi_onSurfaceVariant</color>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:description="@string/appwidget_updates_description"
|
||||||
|
android:previewImage="@drawable/updates_grid_coverscreen_widget_preview"
|
||||||
|
android:initialLayout="@layout/appwidget_coverscreen_loading"
|
||||||
|
android:resizeMode="horizontal|vertical"
|
||||||
|
android:widgetCategory="keyguard" />
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<samsung-appwidget-provider
|
||||||
|
display="sub_screen"
|
||||||
|
privacyWidget="true" />
|
Loading…
Reference in a new issue