diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 41ecc3c1cf..7dcd19c166 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -85,6 +85,12 @@ android {
buildFeatures {
viewBinding = true
+ compose = true
+
+ // Disable some unused things
+ aidl = false
+ renderScript = false
+ shaders = false
}
flavorDimensions.add("default")
@@ -105,6 +111,10 @@ android {
checkReleaseBuilds = false
}
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.4.2"
+ }
+
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
@@ -116,6 +126,20 @@ android {
}
dependencies {
+ // Compose
+ implementation("androidx.activity:activity-compose:1.6.1")
+ implementation("androidx.compose.foundation:foundation:1.3.1")
+ implementation("androidx.compose.animation:animation:1.3.3")
+ implementation("androidx.compose.ui:ui:1.3.3")
+ implementation("androidx.compose.material:material:1.3.1")
+ implementation("androidx.compose.material3:material3:1.0.1")
+ implementation("com.google.android.material:compose-theme-adapter-3:1.1.1")
+ implementation("androidx.compose.material:material-icons-extended:1.3.1")
+ implementation("androidx.compose.ui:ui-tooling-preview:1.3.3")
+ debugImplementation("androidx.compose.ui:ui-tooling:1.3.3")
+ implementation("com.google.accompanist:accompanist-webview:0.28.0")
+ implementation("androidx.glance:glance-appwidget:1.0.0-alpha03")
+
// Modified dependencies
implementation("com.github.jays2kings:subsampling-scale-image-view:756849e") {
exclude(module = "image-decoder")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3e4f4aca03..30bebe8036 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -230,6 +230,20 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/eu/kanade/tachiyomi/appwidget/TachiyomiWidgetManager.kt b/app/src/main/java/eu/kanade/tachiyomi/appwidget/TachiyomiWidgetManager.kt
new file mode 100644
index 0000000000..71272eaf4f
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/appwidget/TachiyomiWidgetManager.kt
@@ -0,0 +1,14 @@
+package eu.kanade.tachiyomi.appwidget
+
+import android.content.Context
+import androidx.glance.appwidget.GlanceAppWidgetManager
+
+class TachiyomiWidgetManager {
+
+ suspend fun Context.init() {
+ val manager = GlanceAppWidgetManager(this)
+ if (manager.getGlanceIds(UpdatesGridGlanceWidget::class.java).isNotEmpty()) {
+ UpdatesGridGlanceWidget().loadData()
+ }
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceReceiver.kt
new file mode 100644
index 0000000000..4423aeebf5
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceReceiver.kt
@@ -0,0 +1,8 @@
+package eu.kanade.tachiyomi.appwidget
+
+import androidx.glance.appwidget.GlanceAppWidget
+import androidx.glance.appwidget.GlanceAppWidgetReceiver
+
+class UpdatesGridGlanceReceiver : GlanceAppWidgetReceiver() {
+ override val glanceAppWidget: GlanceAppWidget = UpdatesGridGlanceWidget().apply { loadData() }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt b/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt
new file mode 100644
index 0000000000..4eac09374c
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt
@@ -0,0 +1,126 @@
+package eu.kanade.tachiyomi.appwidget
+
+import android.app.Application
+import android.graphics.Bitmap
+import android.os.Build
+import androidx.compose.runtime.Composable
+import androidx.core.graphics.drawable.toBitmap
+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.updateAll
+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.R
+import eu.kanade.tachiyomi.appwidget.components.CoverHeight
+import eu.kanade.tachiyomi.appwidget.components.CoverWidth
+import eu.kanade.tachiyomi.appwidget.components.LockedWidget
+import eu.kanade.tachiyomi.appwidget.util.appWidgetBackgroundRadius
+import eu.kanade.tachiyomi.appwidget.util.calculateRowAndColumnCount
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.ui.recents.RecentsPresenter
+import eu.kanade.tachiyomi.util.system.dpToPx
+import eu.kanade.tachiyomi.util.system.launchIO
+import kotlinx.coroutines.MainScope
+import tachiyomi.presentation.widget.components.UpdatesWidget
+import uy.kohesive.injekt.injectLazy
+import java.util.Calendar
+import java.util.Date
+
+class UpdatesGridGlanceWidget : GlanceAppWidget() {
+ private val app: Application by injectLazy()
+ private val preferences: PreferencesHelper by injectLazy()
+
+ private val coroutineScope = MainScope()
+
+ private var data: List>? = null
+
+ override val sizeMode = SizeMode.Exact
+
+ @Composable
+ override fun Content() {
+ // If app lock enabled, don't do anything
+ if (preferences.useBiometrics().get()) {
+ LockedWidget()
+ return
+ }
+ UpdatesWidget(data)
+ }
+
+ fun loadData(list: List>? = null) {
+ coroutineScope.launchIO {
+ // Don't show anything when lock is active
+ if (preferences.useBiometrics().get()) {
+ updateAll(app)
+ return@launchIO
+ }
+
+ val manager = GlanceAppWidgetManager(app)
+ val ids = manager.getGlanceIds(this@UpdatesGridGlanceWidget::class.java)
+ if (ids.isEmpty()) return@launchIO
+
+ val (rowCount, columnCount) = ids
+ .flatMap { manager.getAppWidgetSizes(it) }
+ .maxBy { it.height.value * it.width.value }
+ .calculateRowAndColumnCount()
+ val processList = list ?: RecentsPresenter.getRecentManga(customAmount = rowCount * columnCount)
+
+ data = prepareList(processList, rowCount * columnCount)
+ ids.forEach { update(app, it) }
+ }
+ }
+
+ private fun prepareList(processList: List>, take: Int): List> {
+ // 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.first.id }
+ .sortedByDescending { it.second }
+ .take(take)
+ .map { it.first }
+ .map { updatesView ->
+ val request = ImageRequest.Builder(app)
+ .data(updatesView)
+ .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.id!!, app.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()
diff --git a/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt b/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt
new file mode 100644
index 0000000000..8911445446
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt
@@ -0,0 +1,44 @@
+package eu.kanade.tachiyomi.appwidget.components
+
+import android.content.Intent
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.glance.GlanceModifier
+import androidx.glance.LocalContext
+import androidx.glance.action.clickable
+import androidx.glance.appwidget.action.actionStartActivity
+import androidx.glance.layout.Alignment
+import androidx.glance.layout.Box
+import androidx.glance.layout.padding
+import androidx.glance.text.Text
+import androidx.glance.text.TextAlign
+import androidx.glance.text.TextStyle
+import androidx.glance.unit.ColorProvider
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.appwidget.ContainerModifier
+import eu.kanade.tachiyomi.appwidget.util.stringResource
+import eu.kanade.tachiyomi.ui.main.MainActivity
+
+@Composable
+fun LockedWidget() {
+ val intent = Intent(LocalContext.current, Class.forName(MainActivity.MAIN_ACTIVITY)).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ Box(
+ modifier = GlanceModifier
+ .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),
+ fontSize = 12.sp,
+ textAlign = TextAlign.Center,
+ ),
+ )
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesMangaCover.kt b/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesMangaCover.kt
new file mode 100644
index 0000000000..a9aa232d08
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesMangaCover.kt
@@ -0,0 +1,48 @@
+package eu.kanade.tachiyomi.appwidget.components
+
+import android.graphics.Bitmap
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.dp
+import androidx.glance.GlanceModifier
+import androidx.glance.Image
+import androidx.glance.ImageProvider
+import androidx.glance.layout.Box
+import androidx.glance.layout.ContentScale
+import androidx.glance.layout.fillMaxSize
+import androidx.glance.layout.size
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.appwidget.util.appWidgetInnerRadius
+
+val CoverWidth = 58.dp
+val CoverHeight = 87.dp
+
+@Composable
+fun UpdatesMangaCover(
+ modifier: GlanceModifier = GlanceModifier,
+ cover: Bitmap?,
+) {
+ Box(
+ modifier = modifier
+ .size(width = CoverWidth, height = CoverHeight)
+ .appWidgetInnerRadius(),
+ ) {
+ if (cover != null) {
+ Image(
+ provider = ImageProvider(cover),
+ contentDescription = null,
+ modifier = GlanceModifier
+ .fillMaxSize()
+ .appWidgetInnerRadius(),
+ contentScale = ContentScale.Crop,
+ )
+ } else {
+ // Enjoy placeholder
+ Image(
+ provider = ImageProvider(R.drawable.appwidget_cover_error),
+ contentDescription = null,
+ modifier = GlanceModifier.fillMaxSize(),
+ contentScale = ContentScale.Crop,
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt b/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt
new file mode 100644
index 0000000000..a347285d2b
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt
@@ -0,0 +1,73 @@
+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.glance.GlanceModifier
+import androidx.glance.LocalContext
+import androidx.glance.LocalSize
+import androidx.glance.action.clickable
+import androidx.glance.appwidget.CircularProgressIndicator
+import androidx.glance.appwidget.action.actionStartActivity
+import androidx.glance.layout.Alignment
+import androidx.glance.layout.Box
+import androidx.glance.layout.Column
+import androidx.glance.layout.Row
+import androidx.glance.layout.fillMaxWidth
+import androidx.glance.layout.padding
+import androidx.glance.text.Text
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.appwidget.ContainerModifier
+import eu.kanade.tachiyomi.appwidget.components.UpdatesMangaCover
+import eu.kanade.tachiyomi.appwidget.util.calculateRowAndColumnCount
+import eu.kanade.tachiyomi.appwidget.util.stringResource
+import eu.kanade.tachiyomi.ui.main.SearchActivity
+
+@Composable
+fun UpdatesWidget(data: List>?) {
+ val (rowCount, columnCount) = LocalSize.current.calculateRowAndColumnCount()
+ Column(
+ modifier = ContainerModifier,
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ if (data == null) {
+ CircularProgressIndicator()
+ } else if (data.isEmpty()) {
+ Text(text = stringResource(R.string.no_recent_read_updated_manga))
+ } else {
+ (0 until rowCount).forEach { i ->
+ val coverRow = (0 until 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 = SearchActivity.openMangaIntent(LocalContext.current, mangaId, true)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ // https://issuetracker.google.com/issues/238793260
+ .addCategory(mangaId.toString())
+ UpdatesMangaCover(
+ modifier = GlanceModifier.clickable(actionStartActivity(intent)),
+ cover = cover,
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/appwidget/util/GlanceUtils.kt b/app/src/main/java/eu/kanade/tachiyomi/appwidget/util/GlanceUtils.kt
new file mode 100644
index 0000000000..5f56877211
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/appwidget/util/GlanceUtils.kt
@@ -0,0 +1,43 @@
+package eu.kanade.tachiyomi.appwidget.util
+
+import androidx.annotation.StringRes
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.DpSize
+import androidx.glance.GlanceModifier
+import androidx.glance.LocalContext
+import androidx.glance.appwidget.cornerRadius
+import eu.kanade.tachiyomi.R
+
+fun GlanceModifier.appWidgetBackgroundRadius(): GlanceModifier {
+ return this.cornerRadius(R.dimen.appwidget_background_radius)
+}
+
+fun GlanceModifier.appWidgetInnerRadius(): GlanceModifier {
+ return this.cornerRadius(R.dimen.appwidget_inner_radius)
+}
+
+@Composable
+fun stringResource(@StringRes id: Int): String {
+ return LocalContext.current.getString(id)
+}
+
+/**
+ * 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
+ */
+fun DpSize.calculateRowAndColumnCount(): Pair {
+ // 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 rowCount = (height.value / 95).toInt().coerceIn(1, 10)
+ val columnCount = (width.value / 64).toInt().coerceIn(1, 10)
+ return Pair(rowCount, columnCount)
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
index 7f7232c0fa..7a0fe38e9c 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
@@ -792,7 +792,7 @@ open class MainActivity : BaseActivity(), DownloadServiceLi
saveExtras()
}
- fun saveExtras() {
+ private fun saveExtras() {
mangaShortcutManager.updateShortcuts(this)
MangaCoverMetadata.savePrefs()
}
@@ -1397,6 +1397,8 @@ open class MainActivity : BaseActivity(), DownloadServiceLi
private const val SWIPE_THRESHOLD = 100
private const val SWIPE_VELOCITY_THRESHOLD = 100
+ const val MAIN_ACTIVITY = "eu.kanade.tachiyomi.ui.main.MainActivity"
+
// Shortcut actions
const val SHORTCUT_LIBRARY = "eu.kanade.tachiyomi.SHOW_LIBRARY"
const val SHORTCUT_RECENTLY_UPDATED = "eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt
index c48a87e633..8573788515 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt
@@ -38,6 +38,7 @@ import java.util.Date
import java.util.TreeMap
import java.util.concurrent.TimeUnit
import kotlin.math.abs
+import kotlin.math.roundToInt
class RecentsPresenter(
val preferences: PreferencesHelper = Injekt.get(),
@@ -115,12 +116,23 @@ class RecentsPresenter(
}
}
+ /**
+ * Gets a set of recent entries based on preferred view type, unless changed by [customViewType]
+ *
+ * @param oldQuery used to determine while running this method the query has changed, and to cancel this
+ * @param updatePageCount make true when fetching for more pages in the pagination scroll, otherwise make false to restart the list
+ * @param retryCount used to not burden the db with infinite calls, should not be set as its a recursive param
+ * @param itemCount also used in recursion to know how many items have been collected so far
+ * @param limit used by the companion method to not recursively call this method, since the first batch is good enough
+ * @param customViewType used to decide to use another view type instead of the one saved by preferences
+ * @param includeReadAnyway also used by companion method to include the read manga, by default only unread manga is used
+ */
private suspend fun runRecents(
oldQuery: String = "",
updatePageCount: Boolean = false,
retryCount: Int = 0,
itemCount: Int = 0,
- limit: Boolean = false,
+ limit: Int = -1,
customViewType: Int? = null,
includeReadAnyway: Boolean = false,
) {
@@ -137,13 +149,13 @@ class RecentsPresenter(
}
val viewType = customViewType ?: viewType
- val showRead = ((preferences.showReadInAllRecents().get() || query.isNotEmpty()) && !limit) || includeReadAnyway
+ val showRead = ((preferences.showReadInAllRecents().get() || query.isNotEmpty()) && limit != 0) || includeReadAnyway
val isUngrouped = viewType > VIEW_TYPE_GROUP_ALL || query.isNotEmpty()
val groupChaptersUpdates = preferences.groupChaptersUpdates().get()
val groupChaptersHistory = preferences.groupChaptersHistory().get()
val isCustom = customViewType != null
- val isEndless = isUngrouped && !limit
+ val isEndless = isUngrouped && limit != 0
val cReading = when {
viewType <= VIEW_TYPE_UNGROUP_ALL -> {
db.getAllRecentsTypes(
@@ -320,11 +332,14 @@ class RecentsPresenter(
}
val newCount = itemCount + newItems.size
val hasNewItems = newItems.isNotEmpty()
- if (updatePageCount && newCount < 25 && (viewType != VIEW_TYPE_GROUP_ALL || query.isNotEmpty()) && !limit) {
+ if (updatePageCount && (newCount < if (limit > 0) limit else 25) &&
+ (viewType != VIEW_TYPE_GROUP_ALL || query.isNotEmpty()) &&
+ limit != 0
+ ) {
runRecents(oldQuery, true, retryCount + (if (hasNewItems) 0 else 1), newCount)
return
}
- if (!limit) {
+ if (limit == -1) {
setDownloadedChapters(recentItems)
if (customViewType == null) {
withContext(Dispatchers.Main) {
@@ -549,13 +564,19 @@ class RecentsPresenter(
var SHORT_LIMIT = 25
private set
- suspend fun getRecentManga(includeRead: Boolean = false): List> {
+ suspend fun getRecentManga(includeRead: Boolean = false, customAmount: Int = 0): List> {
val presenter = RecentsPresenter()
presenter.viewType = 1
- SHORT_LIMIT = if (includeRead) 50 else 25
- presenter.runRecents(limit = true, includeReadAnyway = includeRead)
+ SHORT_LIMIT = when {
+ customAmount > 0 -> (customAmount * 1.5).roundToInt()
+ includeRead -> 50
+ else -> 25
+ }
+ presenter.runRecents(limit = customAmount, includeReadAnyway = includeRead)
SHORT_LIMIT = 25
- return presenter.recentItems.filter { it.mch.manga.id != null }.map { it.mch.manga to it.mch.history.last_read }
+ return presenter.recentItems
+ .filter { it.mch.manga.id != null }
+ .map { it.mch.manga to it.mch.history.last_read }
}
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaShortcutManager.kt b/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaShortcutManager.kt
index b9cfb045dc..20f70d8f27 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaShortcutManager.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaShortcutManager.kt
@@ -10,6 +10,7 @@ import android.graphics.drawable.Icon
import coil.Coil
import coil.request.ImageRequest
import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.appwidget.TachiyomiWidgetManager
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
@@ -35,13 +36,14 @@ class MangaShortcutManager(
) {
fun updateShortcuts(context: Context) {
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1) {
- if (!preferences.showSeriesInShortcuts() && !preferences.showSourcesInShortcuts()) {
- val shortcutManager = context.getSystemService(ShortcutManager::class.java)
- shortcutManager.removeAllDynamicShortcuts()
- return
- }
- launchIO {
+ launchIO {
+ with(TachiyomiWidgetManager()) { context.init() }
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1) {
+ if (!preferences.showSeriesInShortcuts() && !preferences.showSourcesInShortcuts()) {
+ val shortcutManager = context.getSystemService(ShortcutManager::class.java)
+ shortcutManager.removeAllDynamicShortcuts()
+ return@launchIO
+ }
val shortcutManager = context.getSystemService(ShortcutManager::class.java)
val recentManga = if (preferences.showSeriesInShortcuts()) {
@@ -78,8 +80,14 @@ class MangaShortcutManager(
context,
"Manga-${item.id?.toString() ?: item.title}",
)
- .setShortLabel(item.title.takeUnless { it.isBlank() } ?: context.getString(R.string.manga))
- .setLongLabel(item.title.takeUnless { it.isBlank() } ?: context.getString(R.string.manga))
+ .setShortLabel(
+ item.title.takeUnless { it.isBlank() }
+ ?: context.getString(R.string.manga),
+ )
+ .setLongLabel(
+ item.title.takeUnless { it.isBlank() }
+ ?: context.getString(R.string.manga),
+ )
.setIcon(
if (bitmap != null) if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
Icon.createWithAdaptiveBitmap(bitmap.toSquare())
diff --git a/app/src/main/res/drawable/appwidget_background.xml b/app/src/main/res/drawable/appwidget_background.xml
new file mode 100644
index 0000000000..3b99826f58
--- /dev/null
+++ b/app/src/main/res/drawable/appwidget_background.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/appwidget_cover_error.xml b/app/src/main/res/drawable/appwidget_cover_error.xml
new file mode 100644
index 0000000000..355d4bd0c2
--- /dev/null
+++ b/app/src/main/res/drawable/appwidget_cover_error.xml
@@ -0,0 +1,24 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
diff --git a/app/src/main/res/drawable/updates_grid_widget_preview.webp b/app/src/main/res/drawable/updates_grid_widget_preview.webp
new file mode 100644
index 0000000000..44b9b05d56
Binary files /dev/null and b/app/src/main/res/drawable/updates_grid_widget_preview.webp differ
diff --git a/app/src/main/res/layout/appwidget_loading.xml b/app/src/main/res/layout/appwidget_loading.xml
new file mode 100644
index 0000000000..88a57693fe
--- /dev/null
+++ b/app/src/main/res/layout/appwidget_loading.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/values-night-v31/colors_appwidget.xml b/app/src/main/res/values-night-v31/colors_appwidget.xml
new file mode 100644
index 0000000000..93df05d201
--- /dev/null
+++ b/app/src/main/res/values-night-v31/colors_appwidget.xml
@@ -0,0 +1,9 @@
+
+
+ @color/m3_sys_color_dynamic_dark_surface
+ @color/m3_sys_color_dynamic_dark_on_surface
+ @color/m3_sys_color_dynamic_dark_surface_variant
+ @color/m3_sys_color_dynamic_dark_on_surface_variant
+ @color/m3_sys_color_dynamic_dark_secondary_container
+ @color/m3_sys_color_dynamic_dark_on_secondary_container
+
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
index 721b10a6c9..c7f2be403b 100644
--- a/app/src/main/res/values-night/colors.xml
+++ b/app/src/main/res/values-night/colors.xml
@@ -1,5 +1,28 @@
+ #AEC6FF
+ #002C71
+ #00419E
+ #D8E2FF
+ #AEC6FF
+ #002C71
+ #00419E
+ #D8E2FF
+ #7ADC77
+ #003907
+ #00530D
+ #95F990
+ #1B1B1E
+ #E4E2E6
+ #1B1B1E
+ #E4E2E6
+ #44464E
+ #C5C6D0
+ #8E9099
+ #1B1B1E
+ #E4E2E6
+ #0057CE
+
#272829
@color/md_white_1000_76
diff --git a/app/src/main/res/values-v31/colors_appwidget.xml b/app/src/main/res/values-v31/colors_appwidget.xml
new file mode 100644
index 0000000000..2c4d91f5b3
--- /dev/null
+++ b/app/src/main/res/values-v31/colors_appwidget.xml
@@ -0,0 +1,9 @@
+
+
+ @color/m3_sys_color_dynamic_light_surface
+ @color/m3_sys_color_dynamic_light_on_surface
+ @color/m3_sys_color_dynamic_light_surface_variant
+ @color/m3_sys_color_dynamic_light_on_surface_variant
+ @color/m3_sys_color_dynamic_light_secondary_container
+ @color/m3_sys_color_dynamic_light_on_secondary_container
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index f2a08bbc7d..7ac1f930aa 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,5 +1,30 @@
+ #0057CE
+ #FFFFFF
+ #D8E2FF
+ #001947
+ #0057CE
+ #FFFFFF
+ #D8E2FF
+ #001947
+ #006E17
+ #FFFFFF
+ #95F990
+ #002202
+ #FDFBFF
+ #1B1B1E
+ #FDFBFF
+ #1B1B1E
+ #E1E2EC
+ #44464E
+ #757780
+ #F2F0F4
+ #303033
+ #AEC6FF
+
+ #1F888888
+
#E5ECF4
#C2424242
diff --git a/app/src/main/res/values/colors_appwidget.xml b/app/src/main/res/values/colors_appwidget.xml
new file mode 100644
index 0000000000..7d07ea1f8a
--- /dev/null
+++ b/app/src/main/res/values/colors_appwidget.xml
@@ -0,0 +1,9 @@
+
+
+ @color/tachiyomi_surface
+ @color/tachiyomi_onSurface
+ @color/tachiyomi_surfaceVariant
+ @color/tachiyomi_onSurfaceVariant
+ @color/tachiyomi_secondaryContainer
+ @color/tachiyomi_onSecondaryContainer
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 9eb8afce9b..4446867e33 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -27,4 +27,7 @@
8dp
12dp
56dp
+
+ 16dp
+ 12dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 21229cc97f..df8d496611 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1042,6 +1042,10 @@
WebView is required for Tachiyomi
+
+ See your recently updated library entries
+ Widget not available when app lock is enabled
+
Add
Add to %1$s
diff --git a/app/src/main/res/xml/updates_grid_glance_widget_info.xml b/app/src/main/res/xml/updates_grid_glance_widget_info.xml
new file mode 100644
index 0000000000..4a3142f81c
--- /dev/null
+++ b/app/src/main/res/xml/updates_grid_glance_widget_info.xml
@@ -0,0 +1,15 @@
+
+
diff --git a/build.gradle.kts b/build.gradle.kts
index 4a594e1ec5..ddb3eab198 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -26,9 +26,9 @@ subprojects {
buildscript {
dependencies {
classpath("com.android.tools.build:gradle:7.4.1")
- classpath("com.google.gms:google-services:4.3.14")
+ classpath("com.google.gms:google-services:4.3.15")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${AndroidVersions.kotlin}")
- classpath("com.google.android.gms:oss-licenses-plugin:0.10.5")
+ classpath("com.google.android.gms:oss-licenses-plugin:0.10.6")
classpath("org.jetbrains.kotlin:kotlin-serialization:${AndroidVersions.kotlin}")
}
repositories {
diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt
index bd97eac6d7..d4d2df6a2b 100644
--- a/buildSrc/src/main/kotlin/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/Dependencies.kt
@@ -7,7 +7,7 @@ object AndroidVersions {
const val versionCode = 99
const val versionName = "1.6.1"
const val ndk = "23.1.7779620"
- const val kotlin = "1.7.20"
+ const val kotlin = "1.8.10"
}
object Plugins {