Glance widget for Recents

Recents because its using the same logic as app shortcuts already use

Also added Compose + Kotlin 1.8.10 but no one cares about that

Co-Authored-By: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>
This commit is contained in:
Jays2Kings 2023-02-14 18:23:57 -05:00
parent 71e2a54d13
commit a09fd8ac48
26 changed files with 588 additions and 22 deletions

View file

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

View file

@ -230,6 +230,20 @@
</intent-filter>
</receiver>
<receiver
android:name=".appwidget.UpdatesGridGlanceReceiver"
android:enabled="@bool/glance_appwidget_available"
android:exported="false"
android:label="@string/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.library.LibraryUpdateService"
android:exported="false" />

View file

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

View file

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

View file

@ -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<Pair<Long, Bitmap?>>? = 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<Pair<Manga, Long>>? = 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<Pair<Manga, Long>>, take: Int): List<Pair<Long, Bitmap?>> {
// Resize to cover size
val widthPx = CoverWidth.value.toInt().dpToPx
val heightPx = CoverHeight.value.toInt().dpToPx
val roundPx = app.resources.getDimension(R.dimen.appwidget_inner_radius)
return processList
// .distinctBy { it.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()

View file

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

View file

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

View file

@ -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<Pair<Long, Bitmap?>>?) {
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,
)
}
}
}
}
}
}
}
}

View file

@ -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<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 rowCount = (height.value / 95).toInt().coerceIn(1, 10)
val columnCount = (width.value / 64).toInt().coerceIn(1, 10)
return Pair(rowCount, columnCount)
}

View file

@ -792,7 +792,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
saveExtras()
}
fun saveExtras() {
private fun saveExtras() {
mangaShortcutManager.updateShortcuts(this)
MangaCoverMetadata.savePrefs()
}
@ -1397,6 +1397,8 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), 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"

View file

@ -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<Pair<Manga, Long>> {
suspend fun getRecentManga(includeRead: Boolean = false, customAmount: Int = 0): List<Pair<Manga, Long>> {
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 }
}
}
}

View file

@ -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) {
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
return@launchIO
}
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())

View file

@ -0,0 +1,6 @@
<?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_secondary_container" />
<corners android:radius="@dimen/appwidget_background_radius" />
</shape>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/cover_placeholder" />
<corners android:radius="@dimen/appwidget_inner_radius" />
</shape>
</item>
<item
android:top="24dp"
android:bottom="24dp"
android:left="24dp"
android:right="24dp">
<vector
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/cover_placeholder"
android:pathData="M21,5v6.59l-2.29,-2.3c-0.39,-0.39 -1.03,-0.39 -1.42,0L14,12.59 10.71,9.3c-0.39,-0.39 -1.02,-0.39 -1.41,0L6,12.59 3,9.58L3,5c0,-1.1 0.9,-2 2,-2h14c1.1,0 2,0.9 2,2zM18,11.42l3,3.01L21,19c0,1.1 -0.9,2 -2,2L5,21c-1.1,0 -2,-0.9 -2,-2v-6.58l2.29,2.29c0.39,0.39 1.02,0.39 1.41,0l3.3,-3.3 3.29,3.29c0.39,0.39 1.02,0.39 1.41,0l3.3,-3.28z"/>
</vector>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

View file

@ -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_background">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/loading"
android:textColor="?android:attr/textColorPrimary" />
</FrameLayout>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="appwidget_background">@color/m3_sys_color_dynamic_dark_surface</color>
<color name="appwidget_on_background">@color/m3_sys_color_dynamic_dark_on_surface</color>
<color name="appwidget_surface_variant">@color/m3_sys_color_dynamic_dark_surface_variant</color>
<color name="appwidget_on_surface_variant">@color/m3_sys_color_dynamic_dark_on_surface_variant</color>
<color name="appwidget_secondary_container">@color/m3_sys_color_dynamic_dark_secondary_container</color>
<color name="appwidget_on_secondary_container">@color/m3_sys_color_dynamic_dark_on_secondary_container</color>
</resources>

View file

@ -1,5 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="tachiyomi_primary">#AEC6FF</color>
<color name="tachiyomi_onPrimary">#002C71</color>
<color name="tachiyomi_primaryContainer">#00419E</color>
<color name="tachiyomi_onPrimaryContainer">#D8E2FF</color>
<color name="tachiyomi_secondary">#AEC6FF</color>
<color name="tachiyomi_onSecondary">#002C71</color>
<color name="tachiyomi_secondaryContainer">#00419E</color>
<color name="tachiyomi_onSecondaryContainer">#D8E2FF</color>
<color name="tachiyomi_tertiary">#7ADC77</color>
<color name="tachiyomi_onTertiary">#003907</color>
<color name="tachiyomi_tertiaryContainer">#00530D</color>
<color name="tachiyomi_onTertiaryContainer">#95F990</color>
<color name="tachiyomi_background">#1B1B1E</color>
<color name="tachiyomi_onBackground">#E4E2E6</color>
<color name="tachiyomi_surface">#1B1B1E</color>
<color name="tachiyomi_onSurface">#E4E2E6</color>
<color name="tachiyomi_surfaceVariant">#44464E</color>
<color name="tachiyomi_onSurfaceVariant">#C5C6D0</color>
<color name="tachiyomi_outline">#8E9099</color>
<color name="tachiyomi_inverseOnSurface">#1B1B1E</color>
<color name="tachiyomi_inverseSurface">#E4E2E6</color>
<color name="tachiyomi_primaryInverse">#0057CE</color>
<!-- Application Colors -->
<color name="primaryVariantTachiyomi">#272829</color>
<color name="colorPrimaryInactive">@color/md_white_1000_76</color>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="appwidget_background">@color/m3_sys_color_dynamic_light_surface</color>
<color name="appwidget_on_background">@color/m3_sys_color_dynamic_light_on_surface</color>
<color name="appwidget_surface_variant">@color/m3_sys_color_dynamic_light_surface_variant</color>
<color name="appwidget_on_surface_variant">@color/m3_sys_color_dynamic_light_on_surface_variant</color>
<color name="appwidget_secondary_container">@color/m3_sys_color_dynamic_light_secondary_container</color>
<color name="appwidget_on_secondary_container">@color/m3_sys_color_dynamic_light_on_secondary_container</color>
</resources>

View file

@ -1,5 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="tachiyomi_primary">#0057CE</color>
<color name="tachiyomi_onPrimary">#FFFFFF</color>
<color name="tachiyomi_primaryContainer">#D8E2FF</color>
<color name="tachiyomi_onPrimaryContainer">#001947</color>
<color name="tachiyomi_secondary">#0057CE</color>
<color name="tachiyomi_onSecondary">#FFFFFF</color>
<color name="tachiyomi_secondaryContainer">#D8E2FF</color>
<color name="tachiyomi_onSecondaryContainer">#001947</color>
<color name="tachiyomi_tertiary">#006E17</color>
<color name="tachiyomi_onTertiary">#FFFFFF</color>
<color name="tachiyomi_tertiaryContainer">#95F990</color>
<color name="tachiyomi_onTertiaryContainer">#002202</color>
<color name="tachiyomi_background">#FDFBFF</color>
<color name="tachiyomi_onBackground">#1B1B1E</color>
<color name="tachiyomi_surface">#FDFBFF</color>
<color name="tachiyomi_onSurface">#1B1B1E</color>
<color name="tachiyomi_surfaceVariant">#E1E2EC</color>
<color name="tachiyomi_onSurfaceVariant">#44464E</color>
<color name="tachiyomi_outline">#757780</color>
<color name="tachiyomi_inverseOnSurface">#F2F0F4</color>
<color name="tachiyomi_inverseSurface">#303033</color>
<color name="tachiyomi_primaryInverse">#AEC6FF</color>
<color name="cover_placeholder">#1F888888</color>
<!-- Application Colors -->
<color name="primaryVariantTachiyomi">#E5ECF4</color>
<color name="colorPrimaryInactive">#C2424242</color>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="appwidget_background">@color/tachiyomi_surface</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>
<color name="appwidget_secondary_container">@color/tachiyomi_secondaryContainer</color>
<color name="appwidget_on_secondary_container">@color/tachiyomi_onSecondaryContainer</color>
</resources>

View file

@ -27,4 +27,7 @@
<dimen name="marginSmall">8dp</dimen>
<dimen name="marginNormal">12dp</dimen>
<dimen name="marginFar">56dp</dimen>
<dimen name="appwidget_background_radius">16dp</dimen>
<dimen name="appwidget_inner_radius">12dp</dimen>
</resources>

View file

@ -1042,6 +1042,10 @@
<!-- Do not translate "WebView" -->
<string name="webview_is_required">WebView is required for Tachiyomi</string>
<!-- App widget -->
<string name="appwidget_updates_description">See your recently updated library entries</string>
<string name="appwidget_unavailable_locked">Widget not available when app lock is enabled</string>
<!-- Miscellaneous -->
<string name="add">Add</string>
<string name="add_to_">Add to %1$s</string>

View file

@ -0,0 +1,15 @@
<?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_widget_preview"
android:initialLayout="@layout/appwidget_loading"
android:minWidth="240dp"
android:minHeight="60dp"
android:minResizeWidth="80dp"
android:minResizeHeight="40dp"
android:maxResizeWidth="600dp"
android:maxResizeHeight="600dp"
android:targetCellWidth="4"
android:targetCellHeight="2"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen" />

View file

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

View file

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