浏览代码

"Updates" widget for Galaxy Z Flip5 cover screen (#9892)

Ivan Iskandar 1 年之前
父节点
当前提交
816d7815e9
共有 19 个文件被更改,包括 322 次插入189 次删除
  1. 0 14
      app/src/main/AndroidManifest.xml
  2. 38 1
      presentation-widget/src/main/AndroidManifest.xml
  3. 153 0
      presentation-widget/src/main/java/tachiyomi/presentation/widget/BaseUpdatesGridGlanceWidget.kt
  4. 2 1
      presentation-widget/src/main/java/tachiyomi/presentation/widget/TachiyomiWidgetManager.kt
  5. 9 0
      presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridCoverScreenGlanceReceiver.kt
  6. 13 0
      presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridCoverScreenGlanceWidget.kt
  7. 2 1
      presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridGlanceReceiver.kt
  8. 7 128
      presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridGlanceWidget.kt
  9. 6 5
      presentation-widget/src/main/java/tachiyomi/presentation/widget/components/LockedWidget.kt
  10. 55 38
      presentation-widget/src/main/java/tachiyomi/presentation/widget/components/UpdatesWidget.kt
  11. 6 1
      presentation-widget/src/main/java/tachiyomi/presentation/widget/util/GlanceUtils.kt
  12. 二进制
      presentation-widget/src/main/res/drawable-nodpi/updates_grid_coverscreen_widget_preview.webp
  13. 0 0
      presentation-widget/src/main/res/drawable-nodpi/updates_grid_widget_preview.webp
  14. 5 0
      presentation-widget/src/main/res/drawable/appwidget_coverscreen_background.xml
  15. 14 0
      presentation-widget/src/main/res/layout/appwidget_coverscreen_loading.xml
  16. 1 0
      presentation-widget/src/main/res/values/colors_appwidget.xml
  17. 0 0
      presentation-widget/src/main/res/xml/updates_grid_homescreen_widget_info.xml
  18. 7 0
      presentation-widget/src/main/res/xml/updates_grid_lockscreen_widget_info.xml
  19. 4 0
      presentation-widget/src/main/res/xml/updates_grid_samsung_cover_widget_info.xml

+ 0 - 14
app/src/main/AndroidManifest.xml

@@ -141,20 +141,6 @@
             android:name=".data.notification.NotificationReceiver"
             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
             android:name=".data.download.DownloadService"
             android:exported="false" />

+ 38 - 1
presentation-widget/src/main/AndroidManifest.xml

@@ -1,2 +1,39 @@
 <?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>

+ 153 - 0
presentation-widget/src/main/java/tachiyomi/presentation/widget/BaseUpdatesGridGlanceWidget.kt

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

+ 2 - 1
presentation-widget/src/main/java/tachiyomi/presentation/widget/TachiyomiWidgetManager.kt

@@ -19,7 +19,7 @@ class TachiyomiWidgetManager(
 
     fun Context.init(scope: LifecycleCoroutineScope) {
         combine(
-            getUpdates.subscribe(read = false, after = UpdatesGridGlanceWidget.DateLimit.timeInMillis),
+            getUpdates.subscribe(read = false, after = BaseUpdatesGridGlanceWidget.DateLimit.timeInMillis),
             securityPreferences.useAuthenticator().changes(),
             transform = { a, _ -> a },
         )
@@ -27,6 +27,7 @@ class TachiyomiWidgetManager(
             .onEach {
                 try {
                     UpdatesGridGlanceWidget().updateAll(this)
+                    UpdatesGridCoverScreenGlanceWidget().updateAll(this)
                 } catch (e: Exception) {
                     logcat(LogPriority.ERROR, e) { "Failed to update widget" }
                 }

+ 9 - 0
presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridCoverScreenGlanceReceiver.kt

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

+ 13 - 0
presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridCoverScreenGlanceWidget.kt

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

+ 2 - 1
presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridGlanceReceiver.kt

@@ -4,5 +4,6 @@ import androidx.glance.appwidget.GlanceAppWidget
 import androidx.glance.appwidget.GlanceAppWidgetReceiver
 
 class UpdatesGridGlanceReceiver : GlanceAppWidgetReceiver() {
-    override val glanceAppWidget: GlanceAppWidget = UpdatesGridGlanceWidget()
+    override val glanceAppWidget: GlanceAppWidget
+        get() = UpdatesGridGlanceWidget()
 }

+ 7 - 128
presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridGlanceWidget.kt

@@ -1,133 +1,12 @@
 package tachiyomi.presentation.widget
 
-import android.app.Application
-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.compose.ui.unit.dp
 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 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
+import androidx.glance.unit.ColorProvider
 
-class UpdatesGridGlanceWidget(
-    private val context: Context = Injekt.get<Application>(),
-    private val getUpdates: GetUpdates = Injekt.get(),
-    private val preferences: SecurityPreferences = Injekt.get(),
-) : GlanceAppWidget() {
-
-    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)
-            }
-    }
+class UpdatesGridGlanceWidget : BaseUpdatesGridGlanceWidget() {
+    override val foreground = ColorProvider(R.color.appwidget_on_secondary_container)
+    override val background = ImageProvider(R.drawable.appwidget_background)
+    override val topPadding = 0.dp
+    override val bottomPadding = 0.dp
 }
-
-val ContainerModifier = GlanceModifier
-    .fillMaxSize()
-    .background(ImageProvider(R.drawable.appwidget_background))
-    .appWidgetBackground()
-    .appWidgetBackgroundRadius()

+ 6 - 5
presentation-widget/src/main/java/tachiyomi/presentation/widget/components/LockedWidget.kt

@@ -16,26 +16,27 @@ import androidx.glance.text.TextAlign
 import androidx.glance.text.TextStyle
 import androidx.glance.unit.ColorProvider
 import tachiyomi.core.Constants
-import tachiyomi.presentation.widget.ContainerModifier
 import tachiyomi.presentation.widget.R
 import tachiyomi.presentation.widget.util.stringResource
 
 @Composable
-fun LockedWidget() {
+fun LockedWidget(
+    foreground: ColorProvider,
+    modifier: GlanceModifier = GlanceModifier,
+) {
     val intent = Intent(LocalContext.current, Class.forName(Constants.MAIN_ACTIVITY)).apply {
         addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
     }
     Box(
-        modifier = GlanceModifier
+        modifier = modifier
             .clickable(actionStartActivity(intent))
-            .then(ContainerModifier)
             .padding(8.dp),
         contentAlignment = Alignment.Center,
     ) {
         Text(
             text = stringResource(R.string.appwidget_unavailable_locked),
             style = TextStyle(
-                color = ColorProvider(R.color.appwidget_on_secondary_container),
+                color = foreground,
                 fontSize = 12.sp,
                 textAlign = TextAlign.Center,
             ),

+ 55 - 38
presentation-widget/src/main/java/tachiyomi/presentation/widget/components/UpdatesWidget.kt

@@ -3,6 +3,7 @@ package tachiyomi.presentation.widget.components
 import android.content.Intent
 import android.graphics.Bitmap
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.glance.GlanceModifier
 import androidx.glance.LocalContext
@@ -14,59 +15,75 @@ import androidx.glance.layout.Alignment
 import androidx.glance.layout.Box
 import androidx.glance.layout.Column
 import androidx.glance.layout.Row
+import androidx.glance.layout.fillMaxHeight
 import androidx.glance.layout.fillMaxWidth
 import androidx.glance.layout.padding
 import androidx.glance.text.Text
+import androidx.glance.text.TextStyle
+import androidx.glance.unit.ColorProvider
 import tachiyomi.core.Constants
-import tachiyomi.presentation.widget.ContainerModifier
 import tachiyomi.presentation.widget.R
 import tachiyomi.presentation.widget.util.calculateRowAndColumnCount
 import tachiyomi.presentation.widget.util.stringResource
 
 @Composable
-fun UpdatesWidget(data: List<Pair<Long, Bitmap?>>?) {
-    val (rowCount, columnCount) = LocalSize.current.calculateRowAndColumnCount()
-    Column(
-        modifier = ContainerModifier,
-        verticalAlignment = Alignment.CenterVertically,
-        horizontalAlignment = Alignment.CenterHorizontally,
+fun UpdatesWidget(
+    data: List<Pair<Long, Bitmap?>>?,
+    modifier: GlanceModifier = GlanceModifier,
+    contentColor: ColorProvider,
+    topPadding: Dp,
+    bottomPadding: Dp,
+) {
+    Box(
+        contentAlignment = Alignment.Center,
+        modifier = modifier,
     ) {
         if (data == null) {
-            CircularProgressIndicator()
+            CircularProgressIndicator(color = contentColor)
         } 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 {
-            (0..<rowCount).forEach { i ->
-                val coverRow = (0..<columnCount).mapNotNull { j ->
-                    data.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,
-                            ) {
-                                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)
+            val (rowCount, columnCount) = LocalSize.current.calculateRowAndColumnCount(topPadding, bottomPadding)
+            Column(
+                modifier = GlanceModifier.fillMaxHeight(),
+                verticalAlignment = Alignment.CenterVertically,
+                horizontalAlignment = Alignment.CenterHorizontally,
+            ) {
+                (0..<rowCount).forEach { i ->
+                    val coverRow = (0..<columnCount).mapNotNull { j ->
+                        data.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,
+                                ) {
+                                    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
-                                    addCategory(mangaId.toString())
+                                        // https://issuetracker.google.com/issues/238793260
+                                        addCategory(mangaId.toString())
+                                    }
+                                    UpdatesMangaCover(
+                                        modifier = GlanceModifier.clickable(actionStartActivity(intent)),
+                                        cover = cover,
+                                    )
                                 }
-                                UpdatesMangaCover(
-                                    modifier = GlanceModifier.clickable(actionStartActivity(intent)),
-                                    cover = cover,
-                                )
                             }
                         }
                     }

+ 6 - 1
presentation-widget/src/main/java/tachiyomi/presentation/widget/util/GlanceUtils.kt

@@ -2,6 +2,7 @@ package tachiyomi.presentation.widget.util
 
 import androidx.annotation.StringRes
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.glance.GlanceModifier
 import androidx.glance.LocalContext
@@ -34,9 +35,13 @@ fun stringResource(@StringRes id: Int): String {
  *
  * @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
     // 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 columnCount = (width.value / 64).toInt().coerceIn(1, 10)
     return Pair(rowCount, columnCount)

二进制
presentation-widget/src/main/res/drawable-nodpi/updates_grid_coverscreen_widget_preview.webp


+ 0 - 0
app/src/main/res/drawable-nodpi/updates_grid_widget_preview.webp → presentation-widget/src/main/res/drawable-nodpi/updates_grid_widget_preview.webp


+ 5 - 0
presentation-widget/src/main/res/drawable/appwidget_coverscreen_background.xml

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

+ 14 - 0
presentation-widget/src/main/res/layout/appwidget_coverscreen_loading.xml

@@ -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 - 0
presentation-widget/src/main/res/values/colors_appwidget.xml

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <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_surface_variant">@color/tachiyomi_surfaceVariant</color>
     <color name="appwidget_on_surface_variant">@color/tachiyomi_onSurfaceVariant</color>

+ 0 - 0
app/src/main/res/xml/updates_grid_glance_widget_info.xml → presentation-widget/src/main/res/xml/updates_grid_homescreen_widget_info.xml


+ 7 - 0
presentation-widget/src/main/res/xml/updates_grid_lockscreen_widget_info.xml

@@ -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" />

+ 4 - 0
presentation-widget/src/main/res/xml/updates_grid_samsung_cover_widget_info.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<samsung-appwidget-provider
+    display="sub_screen"
+    privacyWidget="true" />