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 {