From 18e04b75dfe7317f2cb8719c83c1bd89051ab199 Mon Sep 17 00:00:00 2001 From: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun, 19 Nov 2023 19:38:42 +0100 Subject: [PATCH] merge30 Last commit merged: https://github.com/tachiyomiorg/tachiyomi/commit/1668be85875522721ee971afac4b46aa7da343c6 --- .github/workflows/build_pull_request.yml | 2 +- .github/workflows/build_push.yml | 2 +- app/src/main/AndroidManifest.xml | 28 ---- .../settings/screen/SettingsBackupScreen.kt | 2 +- .../tachiyomi/data/backup/BackupManager.kt | 5 +- .../tachiyomi/data/backup/models/Backup.kt | 7 +- .../tachiyomi/data/cache/ChapterCache.kt | 8 +- .../tachiyomi/data/cache/EpisodeCache.kt | 8 +- .../anime/AnimeDownloadQueueScreen.kt | 20 +-- .../anime/AnimeDownloadQueueScreenModel.kt | 2 +- .../manga/MangaDownloadQueueScreen.kt | 20 +-- .../manga/MangaDownloadQueueScreenModel.kt | 2 +- .../tachiyomi/ui/security/UnlockActivity.kt | 2 +- .../util/system/DisplayExtensions.kt | 14 -- .../tachiyomi/widget/MaterialFastScroll.kt | 92 ----------- .../res/drawable/material_thumb_drawable.xml | 17 -- app/src/main/res/layout/download_list.xml | 25 +-- .../main/res/layout/material_fastscroll.xml | 43 ----- app/src/main/res/values/styles.xml | 15 -- gradle/androidx.versions.toml | 10 +- gradle/libs.versions.toml | 6 +- i18n/src/main/res/values/strings-aniyomi.xml | 10 +- .../src/main/AndroidManifest.xml | 73 ++++++++- .../components/anime/LockedAnimeWidget.kt | 11 +- .../components/anime/UpdatesAnimeWidget.kt | 96 ++++++----- .../components/manga/LockedMangaWidget.kt | 11 +- .../components/manga/UpdatesMangaWidget.kt | 96 ++++++----- ...imeUpdatesGridCoverScreenGlanceReceiver.kt | 9 + ...AnimeUpdatesGridCoverScreenGlanceWidget.kt | 14 ++ .../anime/AnimeUpdatesGridGlanceReceiver.kt | 3 +- .../anime/AnimeUpdatesGridGlanceWidget.kt | 137 +--------------- .../anime/BaseAnimeUpdatesGridGlanceWidget.kt | 154 ++++++++++++++++++ .../anime/TachiyomiAnimeWidgetManager.kt | 3 +- .../manga/BaseMangaUpdatesGridGlanceWidget.kt | 154 ++++++++++++++++++ ...ngaUpdatesGridCoverScreenGlanceReceiver.kt | 9 + ...MangaUpdatesGridCoverScreenGlanceWidget.kt | 14 ++ .../manga/MangaUpdatesGridGlanceReceiver.kt | 3 +- .../manga/MangaUpdatesGridGlanceWidget.kt | 138 +--------------- .../manga/TachiyomiMangaWidgetManager.kt | 3 +- .../presentation/widget/util/GlanceUtils.kt | 7 +- ...dates_grid_coverscreen_widget_preview.webp | Bin 0 -> 5200 bytes .../updates_grid_widget_preview.webp | Bin .../appwidget_coverscreen_background.xml | 5 + .../layout/appwidget_coverscreen_loading.xml | 14 ++ .../src/main/res/values/colors_appwidget.xml | 1 + .../updates_grid_homescreen_widget_info.xml | 0 .../updates_grid_lockscreen_widget_info.xml | 7 + ...updates_grid_samsung_cover_widget_info.xml | 4 + .../tachiyomi/animesource/AnimeSource.kt | 6 + .../eu/kanade/tachiyomi/source/MangaSource.kt | 6 + 50 files changed, 673 insertions(+), 645 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/MaterialFastScroll.kt delete mode 100644 app/src/main/res/drawable/material_thumb_drawable.xml delete mode 100644 app/src/main/res/layout/material_fastscroll.xml create mode 100644 presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridCoverScreenGlanceReceiver.kt create mode 100644 presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridCoverScreenGlanceWidget.kt create mode 100644 presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/BaseAnimeUpdatesGridGlanceWidget.kt create mode 100644 presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/BaseMangaUpdatesGridGlanceWidget.kt create mode 100644 presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridCoverScreenGlanceReceiver.kt create mode 100644 presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridCoverScreenGlanceWidget.kt create mode 100644 presentation-widget/src/main/res/drawable-nodpi/updates_grid_coverscreen_widget_preview.webp rename {app => presentation-widget}/src/main/res/drawable-nodpi/updates_grid_widget_preview.webp (100%) create mode 100644 presentation-widget/src/main/res/drawable/appwidget_coverscreen_background.xml create mode 100644 presentation-widget/src/main/res/layout/appwidget_coverscreen_loading.xml rename app/src/main/res/xml/updates_grid_glance_widget_info.xml => presentation-widget/src/main/res/xml/updates_grid_homescreen_widget_info.xml (100%) create mode 100644 presentation-widget/src/main/res/xml/updates_grid_lockscreen_widget_info.xml create mode 100644 presentation-widget/src/main/res/xml/updates_grid_samsung_cover_widget_info.xml diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index 794fe5cc5..e11eef436 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Clone repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index fadbea716..128eb0095 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Clone repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index de6000dd7..e12108cda 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -199,34 +199,6 @@ android:name=".data.notification.NotificationReceiver" android:exported="false" /> - - - - - - - - - - - - - - - - diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt index f1eded1b7..86b921e1c 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt @@ -115,7 +115,7 @@ object SettingsBackupScreen : SearchableSettings { showCreateDialog = false flag = it try { - chooseBackupDir.launch(Backup.getBackupFilename()) + chooseBackupDir.launch(Backup.getFilename()) } catch (e: ActivityNotFoundException) { flag = 0 context.toast(R.string.file_picker_error) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt index 84b3f53e6..272ba4025 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt @@ -149,15 +149,14 @@ class BackupManager( // Delete older backups val numberOfBackups = backupPreferences.numberOfBackups().get() - val backupRegex = Regex("""aniyomi_\d+-\d+-\d+_\d+-\d+.proto.gz""") - dir.listFiles { _, filename -> backupRegex.matches(filename) } + dir.listFiles { _, filename -> Backup.filenameRegex.matches(filename) } .orEmpty() .sortedByDescending { it.name } .drop(numberOfBackups - 1) .forEach { it.delete() } // Create new file to place backup - dir.createFile(Backup.getBackupFilename()) + dir.createFile(Backup.getFilename()) } else { UniFile.fromUri(context, uri) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt index 48bcc0a39..809ea4af5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.data.backup.models +import eu.kanade.tachiyomi.BuildConfig import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import java.text.SimpleDateFormat @@ -23,9 +24,11 @@ data class Backup( ) { companion object { - fun getBackupFilename(): String { + val filenameRegex = """${BuildConfig.APPLICATION_ID}_\d+-\d+-\d+_\d+-\d+.tachibk""".toRegex() + + fun getFilename(): String { val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date()) - return "aniyomi_$date.proto.gz" + return "${BuildConfig.APPLICATION_ID}_$date.tachibk" } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt index 594eaa81a..c3a52eff3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt @@ -9,9 +9,11 @@ import eu.kanade.tachiyomi.util.storage.saveTo import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import logcat.LogPriority import okhttp3.Response import okio.buffer import okio.sink +import tachiyomi.core.util.system.logcat import tachiyomi.domain.items.chapter.model.Chapter import uy.kohesive.injekt.injectLazy import java.io.File @@ -99,6 +101,7 @@ class ChapterCache(private val context: Context) { editor.commit() editor.abortUnlessCommitted() } catch (e: Exception) { + logcat(LogPriority.WARN, e) { "Failed to put page list to cache" } // Ignore. } finally { editor?.abortUnlessCommitted() @@ -176,7 +179,7 @@ class ChapterCache(private val context: Context) { * @return status of deletion for the file. */ private fun removeFileFromCache(file: String): Boolean { - // Make sure we don't delete the journal file (keeps track of cache). + // Make sure we don't delete the journal file (keeps track of cache) if (file == "journal" || file.startsWith("journal.")) { return false } @@ -184,9 +187,10 @@ class ChapterCache(private val context: Context) { return try { // Remove the extension from the file to get the key of the cache val key = file.substringBeforeLast(".") - // Remove file from cache. + // Remove file from cache diskCache.remove(key) } catch (e: Exception) { + logcat(LogPriority.WARN, e) { "Failed to remove file from cache" } false } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/EpisodeCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/EpisodeCache.kt index fc59c425a..41b3c69b9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/EpisodeCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/EpisodeCache.kt @@ -8,9 +8,11 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.saveTo import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import logcat.LogPriority import okhttp3.Response import okio.buffer import okio.sink +import tachiyomi.core.util.system.logcat import tachiyomi.domain.items.episode.model.Episode import uy.kohesive.injekt.injectLazy import java.io.File @@ -71,7 +73,7 @@ class EpisodeCache(private val context: Context) { * @return status of deletion for the file. */ fun removeFileFromCache(file: String): Boolean { - // Make sure we don't delete the journal file (keeps track of cache). + // Make sure we don't delete the journal file (keeps track of cache) if (file == "journal" || file.startsWith("journal.")) { return false } @@ -79,9 +81,10 @@ class EpisodeCache(private val context: Context) { return try { // Remove the extension from the file to get the key of the cache val key = file.substringBeforeLast(".") - // Remove file from cache. + // Remove file from cache diskCache.remove(key) } catch (e: Exception) { + logcat(LogPriority.WARN, e) { "Failed to remove file from cache" } false } } @@ -114,6 +117,7 @@ class EpisodeCache(private val context: Context) { editor.commit() editor.abortUnlessCommitted() } catch (e: Exception) { + logcat(LogPriority.WARN, e) { "Failed to put video list to cache" } // Ignore. } finally { editor?.abortUnlessCommitted() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreen.kt index 2b3b25774..dd36548fa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreen.kt @@ -1,12 +1,12 @@ package eu.kanade.tachiyomi.ui.download.anime import android.view.LayoutInflater -import android.view.ViewGroup import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.PlayArrow @@ -32,7 +32,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Velocity import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.ViewCompat -import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.recyclerview.widget.LinearLayoutManager import eu.kanade.tachiyomi.R @@ -127,6 +126,7 @@ fun AnimeDownloadQueueScreen( ) return@Scaffold } + val density = LocalDensity.current val layoutDirection = LocalLayoutDirection.current val left = with(density) { contentPadding.calculateLeftPadding(layoutDirection).toPx().roundToInt() } @@ -136,15 +136,15 @@ fun AnimeDownloadQueueScreen( Box(modifier = Modifier.nestedScroll(nestedScrollConnection)) { AndroidView( + modifier = Modifier.fillMaxWidth(), factory = { context -> screenModel.controllerBinding = DownloadListBinding.inflate( LayoutInflater.from(context), ) screenModel.adapter = AnimeDownloadAdapter(screenModel.listener) - screenModel.controllerBinding.recycler.adapter = screenModel.adapter + screenModel.controllerBinding.root.adapter = screenModel.adapter screenModel.adapter?.isHandleDragEnabled = true - screenModel.adapter?.fastScroller = screenModel.controllerBinding.fastScroller - screenModel.controllerBinding.recycler.layoutManager = LinearLayoutManager( + screenModel.controllerBinding.root.layoutManager = LinearLayoutManager( context, ) @@ -162,7 +162,7 @@ fun AnimeDownloadQueueScreen( screenModel.controllerBinding.root }, update = { - screenModel.controllerBinding.recycler + screenModel.controllerBinding.root .updatePadding( left = left, top = top, @@ -170,14 +170,6 @@ fun AnimeDownloadQueueScreen( bottom = bottom, ) - screenModel.controllerBinding.fastScroller - .updateLayoutParams { - leftMargin = left - topMargin = top - rightMargin = right - bottomMargin = bottom - } - screenModel.adapter?.updateDataSet(downloadList) }, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreenModel.kt index 3989f14dd..8eaffb5c2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreenModel.kt @@ -231,6 +231,6 @@ class AnimeDownloadQueueScreenModel( * @return the holder of the download or null if it's not bound. */ private fun getHolder(download: AnimeDownload): AnimeDownloadHolder? { - return controllerBinding.recycler.findViewHolderForItemId(download.episode.id) as? AnimeDownloadHolder + return controllerBinding.root.findViewHolderForItemId(download.episode.id) as? AnimeDownloadHolder } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreen.kt index 5674992c9..a6ccf453c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreen.kt @@ -1,12 +1,12 @@ package eu.kanade.tachiyomi.ui.download.manga import android.view.LayoutInflater -import android.view.ViewGroup import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.PlayArrow @@ -32,7 +32,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Velocity import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.ViewCompat -import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.recyclerview.widget.LinearLayoutManager import eu.kanade.tachiyomi.R @@ -127,6 +126,7 @@ fun DownloadQueueScreen( ) return@Scaffold } + val density = LocalDensity.current val layoutDirection = LocalLayoutDirection.current val left = with(density) { contentPadding.calculateLeftPadding(layoutDirection).toPx().roundToInt() } @@ -136,15 +136,15 @@ fun DownloadQueueScreen( Box(modifier = Modifier.nestedScroll(nestedScrollConnection)) { AndroidView( + modifier = Modifier.fillMaxWidth(), factory = { context -> screenModel.controllerBinding = DownloadListBinding.inflate( LayoutInflater.from(context), ) screenModel.adapter = MangaDownloadAdapter(screenModel.listener) - screenModel.controllerBinding.recycler.adapter = screenModel.adapter + screenModel.controllerBinding.root.adapter = screenModel.adapter screenModel.adapter?.isHandleDragEnabled = true - screenModel.adapter?.fastScroller = screenModel.controllerBinding.fastScroller - screenModel.controllerBinding.recycler.layoutManager = LinearLayoutManager( + screenModel.controllerBinding.root.layoutManager = LinearLayoutManager( context, ) @@ -162,7 +162,7 @@ fun DownloadQueueScreen( screenModel.controllerBinding.root }, update = { - screenModel.controllerBinding.recycler + screenModel.controllerBinding.root .updatePadding( left = left, top = top, @@ -170,14 +170,6 @@ fun DownloadQueueScreen( bottom = bottom, ) - screenModel.controllerBinding.fastScroller - .updateLayoutParams { - leftMargin = left - topMargin = top - rightMargin = right - bottomMargin = bottom - } - screenModel.adapter?.updateDataSet(downloadList) }, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreenModel.kt index 3a229860c..6a41d5f7f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreenModel.kt @@ -261,6 +261,6 @@ class MangaDownloadQueueScreenModel( * @return the holder of the download or null if it's not bound. */ private fun getHolder(download: MangaDownload): MangaDownloadHolder? { - return controllerBinding.recycler.findViewHolderForItemId(download.chapter.id) as? MangaDownloadHolder + return controllerBinding.root.findViewHolderForItemId(download.chapter.id) as? MangaDownloadHolder } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/security/UnlockActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/security/UnlockActivity.kt index b697a98d1..09ec00169 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/security/UnlockActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/security/UnlockActivity.kt @@ -19,7 +19,7 @@ class UnlockActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) startAuthentication( - getString(R.string.unlock_app), + getString(R.string.unlock_app_title, getString(R.string.app_name)), confirmationRequired = false, callback = object : AuthenticatorUtil.AuthenticationCallback() { override fun onAuthenticationError( diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt index 50f45ae85..3e2834fd2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt @@ -3,9 +3,7 @@ package eu.kanade.tachiyomi.util.system import android.app.Activity import android.content.Context import android.content.res.Configuration -import android.content.res.Resources import android.os.Build -import android.view.View import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.model.TabletUiMode import uy.kohesive.injekt.Injekt @@ -64,18 +62,6 @@ fun Context.isNightMode(): Boolean { return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES } -val Resources.isLTR - get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR - -/** - * Converts to px and takes into account LTR/RTL layout. - */ -val Float.dpToPxEnd: Float - get() = ( - this * Resources.getSystem().displayMetrics.density * - if (Resources.getSystem().isLTR) 1 else -1 - ) - /** * Checks whether if the device has a display cutout (i.e. notch, camera cutout, etc.). * diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/MaterialFastScroll.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/MaterialFastScroll.kt deleted file mode 100644 index 29e9e43e3..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/MaterialFastScroll.kt +++ /dev/null @@ -1,92 +0,0 @@ -package eu.kanade.tachiyomi.widget - -import android.annotation.SuppressLint -import android.content.Context -import android.util.AttributeSet -import android.view.MotionEvent -import androidx.core.view.ViewCompat -import dev.chrisbanes.insetter.applyInsetter -import eu.davidea.fastscroller.FastScroller -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.util.system.dpToPxEnd -import eu.kanade.tachiyomi.util.system.isLTR - -class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - FastScroller(context, attrs) { - - init { - setViewsToUse( - R.layout.material_fastscroll, - R.id.fast_scroller_bubble, - R.id.fast_scroller_handle, - ) - autoHideEnabled = true - ignoreTouchesOutsideHandle = true - - applyInsetter { - type(navigationBars = true) { - margin() - } - } - } - - // Overridden to handle RTL - @SuppressLint("ClickableViewAccessibility") - override fun onTouchEvent(event: MotionEvent): Boolean { - if (recyclerView.computeVerticalScrollRange() <= recyclerView.computeVerticalScrollExtent()) { - return super.onTouchEvent(event) - } - - when (event.action) { - MotionEvent.ACTION_DOWN -> { - // start: handle RTL differently - if ( - if (context.resources.isLTR) { - event.x < handle.x - ViewCompat.getPaddingStart(handle) - } else { - event.x > handle.width + ViewCompat.getPaddingStart(handle) - } - ) { - return false - } - // end - - if (ignoreTouchesOutsideHandle && - (event.y < handle.y || event.y > handle.y + handle.height) - ) { - return false - } - handle.isSelected = true - notifyScrollStateChange(true) - showBubble() - showScrollbar() - val y = event.y - setBubbleAndHandlePosition(y) - setRecyclerViewPosition(y) - return true - } - MotionEvent.ACTION_MOVE -> { - val y = event.y - setBubbleAndHandlePosition(y) - setRecyclerViewPosition(y) - return true - } - MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { - handle.isSelected = false - notifyScrollStateChange(false) - hideBubble() - if (autoHideEnabled) hideScrollbar() - return true - } - } - return super.onTouchEvent(event) - } - - override fun setBubbleAndHandlePosition(y: Float) { - super.setBubbleAndHandlePosition(y) - if (bubbleEnabled) { - bubble.y = handle.y - bubble.height / 2f + handle.height / 2f - bubble.translationX = (-45f).dpToPxEnd - } - } -} diff --git a/app/src/main/res/drawable/material_thumb_drawable.xml b/app/src/main/res/drawable/material_thumb_drawable.xml deleted file mode 100644 index 2e550c0df..000000000 --- a/app/src/main/res/drawable/material_thumb_drawable.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/download_list.xml b/app/src/main/res/layout/download_list.xml index e2c96ff12..ec3e3bab9 100644 --- a/app/src/main/res/layout/download_list.xml +++ b/app/src/main/res/layout/download_list.xml @@ -1,24 +1,7 @@ - - - - - - - + android:layout_height="match_parent" + android:clipToPadding="false" + tools:listitem="@layout/download_item" /> diff --git a/app/src/main/res/layout/material_fastscroll.xml b/app/src/main/res/layout/material_fastscroll.xml deleted file mode 100644 index 56b16a1b8..000000000 --- a/app/src/main/res/layout/material_fastscroll.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 14dc1a13a..22313ff42 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -56,21 +56,6 @@ - - - - - diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index 38faa67d5..e54ee57d2 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -1,20 +1,20 @@ [versions] agp_version = "8.1.1" -lifecycle_version = "2.6.1" -paging_version = "3.2.0" +lifecycle_version = "2.6.2" +paging_version = "3.2.1" [libraries] gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" } -annotation = "androidx.annotation:annotation:1.7.0-rc01" +annotation = "androidx.annotation:annotation:1.7.0" appcompat = "androidx.appcompat:appcompat:1.6.1" biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05" constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4" -corektx = "androidx.core:core-ktx:1.12.0-rc01" +corektx = "androidx.core:core-ktx:1.12.0" splashscreen = "androidx.core:core-splashscreen:1.0.1" recyclerview = "androidx.recyclerview:recyclerview:1.3.1" viewpager = "androidx.viewpager:viewpager:1.1.0-alpha01" -glance = "androidx.glance:glance-appwidget:1.0.0-rc01" +glance = "androidx.glance:glance-appwidget:1.0.0" profileinstaller = "androidx.profileinstaller:profileinstaller:1.3.1" mediasession = "androidx.media:media:1.6.0" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3e0f9cbc3..7d12722c3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ shizuku_version = "12.2.0" sqlite = "2.3.1" sqldelight = "2.0.0" leakcanary = "2.12" -voyager = "1.0.0-rc06" +voyager = "1.0.0-rc07" richtext = "0.17.0" [libraries] @@ -63,7 +63,7 @@ swipe = "me.saket.swipe:swipe:1.2.0" logcat = "com.squareup.logcat:logcat:0.1" -acra-http = "ch.acra:acra-http:5.11.1" +acra-http = "ch.acra:acra-http:5.11.2" aboutLibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutlib_version" } aboutLibraries-gradle = { module = "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin", version.ref = "aboutlib_version" } @@ -82,7 +82,7 @@ sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", sqldelight-gradle = { module = "app.cash.sqldelight:gradle-plugin", version.ref = "sqldelight" } junit = "org.junit.jupiter:junit-jupiter:5.10.0" -kotest-assertions = "io.kotest:kotest-assertions-core:5.6.2" +kotest-assertions = "io.kotest:kotest-assertions-core:5.7.2" mockk = "io.mockk:mockk:1.13.7" voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } diff --git a/i18n/src/main/res/values/strings-aniyomi.xml b/i18n/src/main/res/values/strings-aniyomi.xml index 7089a7bd5..dfd6a4627 100644 --- a/i18n/src/main/res/values/strings-aniyomi.xml +++ b/i18n/src/main/res/values/strings-aniyomi.xml @@ -26,7 +26,7 @@ Move History to the More tab Move Updates to the More tab Move Manga to the More tab - Unlock Aniyomi + Unlock Aniyomi Unseen Global Manga Search Global Anime Search @@ -77,12 +77,12 @@ Default anime category Manga in excluded categories will not be updated even if they are also in included categories. Anime in excluded categories will not be updated even if they are also in included categories. - This extension was signed with an untrusted certificate and wasn\'t activated.\n\nA malicious extension could read any login credentials stored in Aniyomi or execute arbitrary code.\n\nBy trusting this certificate you accept these risks. - This extension is not from the official Aniyomi extensions list. + This extension was signed with an untrusted certificate and wasn\'t activated.\n\nA malicious extension could read any stored login credentials or execute arbitrary code.\n\nBy trusting this certificate you accept these risks. + This extension is not from the official list. Reverse landscape Sensor portrait Sensor landscape - This extension is not from the official Aniyomi extensions list. + This extension is not from the official list. Player Progress @@ -241,7 +241,7 @@ Nothing watched recently - WebView is required for Aniyomi + WebView is required for the app to function Updated default episode settings Chapter download paused Episode download paused diff --git a/presentation-widget/src/main/AndroidManifest.xml b/presentation-widget/src/main/AndroidManifest.xml index 568741e54..f87debe76 100644 --- a/presentation-widget/src/main/AndroidManifest.xml +++ b/presentation-widget/src/main/AndroidManifest.xml @@ -1,2 +1,73 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/anime/LockedAnimeWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/anime/LockedAnimeWidget.kt index 3563741d9..eaa570247 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/anime/LockedAnimeWidget.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/anime/LockedAnimeWidget.kt @@ -17,25 +17,26 @@ import androidx.glance.text.TextStyle import androidx.glance.unit.ColorProvider import eu.kanade.tachiyomi.core.Constants import tachiyomi.presentation.widget.R -import tachiyomi.presentation.widget.entries.anime.ContainerModifier import tachiyomi.presentation.widget.util.stringResource @Composable -fun LockedAnimeWidget() { +fun LockedAnimeWidget( + 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, ), diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/anime/UpdatesAnimeWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/anime/UpdatesAnimeWidget.kt index 24d070a56..d58ca0c27 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/anime/UpdatesAnimeWidget.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/anime/UpdatesAnimeWidget.kt @@ -3,6 +3,7 @@ package tachiyomi.presentation.widget.components.anime 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,62 +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 eu.kanade.tachiyomi.core.Constants import tachiyomi.presentation.widget.R -import tachiyomi.presentation.widget.entries.anime.ContainerModifier import tachiyomi.presentation.widget.util.calculateRowAndColumnCount import tachiyomi.presentation.widget.util.stringResource @Composable -fun UpdatesAnimeWidget(data: List>?) { - val (rowCount, columnCount) = LocalSize.current.calculateRowAndColumnCount() - Column( - modifier = ContainerModifier, - verticalAlignment = Alignment.CenterVertically, - horizontalAlignment = Alignment.CenterHorizontally, +fun UpdatesAnimeWidget( + data: List>?, + 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 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 { (animeId, 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_ANIME - putExtra(Constants.ANIME_EXTRA, animeId) - 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.. + val coverRow = (0.. + data.getOrNull(j + (i * columnCount)) + } + if (coverRow.isNotEmpty()) { + Row( + modifier = GlanceModifier + .padding(vertical = 4.dp) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalAlignment = Alignment.CenterVertically, + ) { + coverRow.forEach { (animeId, 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_ANIME + putExtra(Constants.ANIME_EXTRA, animeId) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - // https://issuetracker.google.com/issues/238793260 - addCategory(animeId.toString()) + // https://issuetracker.google.com/issues/238793260 + addCategory(animeId.toString()) + } + UpdatesAnimeCover( + modifier = GlanceModifier.clickable(actionStartActivity(intent)), + cover = cover, + ) } - UpdatesAnimeCover( - modifier = GlanceModifier.clickable(actionStartActivity(intent)), - cover = cover, - ) } } } diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/manga/LockedMangaWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/manga/LockedMangaWidget.kt index 4221057b8..8c01365b9 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/manga/LockedMangaWidget.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/manga/LockedMangaWidget.kt @@ -17,25 +17,26 @@ import androidx.glance.text.TextStyle import androidx.glance.unit.ColorProvider import eu.kanade.tachiyomi.core.Constants import tachiyomi.presentation.widget.R -import tachiyomi.presentation.widget.entries.manga.ContainerModifier import tachiyomi.presentation.widget.util.stringResource @Composable -fun LockedMangaWidget() { +fun LockedMangaWidget( + 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, ), diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/manga/UpdatesMangaWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/manga/UpdatesMangaWidget.kt index b50190a25..e758dccba 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/manga/UpdatesMangaWidget.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/manga/UpdatesMangaWidget.kt @@ -3,6 +3,7 @@ package tachiyomi.presentation.widget.components.manga 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,62 +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 eu.kanade.tachiyomi.core.Constants import tachiyomi.presentation.widget.R -import tachiyomi.presentation.widget.entries.manga.ContainerModifier import tachiyomi.presentation.widget.util.calculateRowAndColumnCount import tachiyomi.presentation.widget.util.stringResource @Composable -fun UpdatesMangaWidget(data: List>?) { - val (rowCount, columnCount) = LocalSize.current.calculateRowAndColumnCount() - Column( - modifier = ContainerModifier, - verticalAlignment = Alignment.CenterVertically, - horizontalAlignment = Alignment.CenterHorizontally, +fun UpdatesMangaWidget( + data: List>?, + 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.. - val coverRow = (0.. - 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.. + val coverRow = (0.. + 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, - ) } } } diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridCoverScreenGlanceReceiver.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridCoverScreenGlanceReceiver.kt new file mode 100644 index 000000000..8585a53a5 --- /dev/null +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridCoverScreenGlanceReceiver.kt @@ -0,0 +1,9 @@ +package tachiyomi.presentation.widget.entries.anime + +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetReceiver + +class AnimeUpdatesGridCoverScreenGlanceReceiver : GlanceAppWidgetReceiver() { + override val glanceAppWidget: GlanceAppWidget + get() = AnimeUpdatesGridCoverScreenGlanceWidget() +} diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridCoverScreenGlanceWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridCoverScreenGlanceWidget.kt new file mode 100644 index 000000000..112325261 --- /dev/null +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridCoverScreenGlanceWidget.kt @@ -0,0 +1,14 @@ +package tachiyomi.presentation.widget.entries.anime + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.glance.ImageProvider +import androidx.glance.unit.ColorProvider +import tachiyomi.presentation.widget.R + +class AnimeUpdatesGridCoverScreenGlanceWidget : BaseAnimeUpdatesGridGlanceWidget() { + 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 +} diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridGlanceReceiver.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridGlanceReceiver.kt index 5e5500b46..10a598ed9 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridGlanceReceiver.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridGlanceReceiver.kt @@ -4,5 +4,6 @@ import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.GlanceAppWidgetReceiver class AnimeUpdatesGridGlanceReceiver : GlanceAppWidgetReceiver() { - override val glanceAppWidget: GlanceAppWidget = AnimeUpdatesGridGlanceWidget() + override val glanceAppWidget: GlanceAppWidget + get() = AnimeUpdatesGridGlanceWidget() } diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridGlanceWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridGlanceWidget.kt index 726046c15..5b4ad3cef 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridGlanceWidget.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/AnimeUpdatesGridGlanceWidget.kt @@ -1,137 +1,14 @@ package tachiyomi.presentation.widget.entries.anime -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.entries.anime.model.AnimeCover -import tachiyomi.domain.updates.anime.interactor.GetAnimeUpdates -import tachiyomi.domain.updates.anime.model.AnimeUpdatesWithRelations +import androidx.glance.unit.ColorProvider import tachiyomi.presentation.widget.R -import tachiyomi.presentation.widget.components.anime.CoverHeight -import tachiyomi.presentation.widget.components.anime.CoverWidth -import tachiyomi.presentation.widget.components.anime.LockedAnimeWidget -import tachiyomi.presentation.widget.components.anime.UpdatesAnimeWidget -import tachiyomi.presentation.widget.util.appWidgetBackgroundRadius -import tachiyomi.presentation.widget.util.calculateRowAndColumnCount -import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.util.Calendar -import java.util.Date -class AnimeUpdatesGridGlanceWidget( - private val context: Context = Injekt.get(), - private val getUpdates: GetAnimeUpdates = Injekt.get(), - private val preferences: SecurityPreferences = Injekt.get(), -) : GlanceAppWidget() { - - private var data: List>? = 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) { - LockedAnimeWidget() - return@provideContent - } - UpdatesAnimeWidget(data) - } - } - - private suspend fun loadData() { - val manager = GlanceAppWidgetManager(context) - val ids = manager.getGlanceIds(this@AnimeUpdatesGridGlanceWidget::class.java) - if (ids.isEmpty()) return - - withIOContext { - val updates = getUpdates.await( - seen = 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, take: Int): List> { - // 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.animeId } - .take(take) - .map { animeupdatesView -> - val request = ImageRequest.Builder(context) - .data( - AnimeCover( - animeId = animeupdatesView.animeId, - sourceId = animeupdatesView.sourceId, - isAnimeFavorite = true, - url = animeupdatesView.coverData.url, - lastModified = animeupdatesView.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( - animeupdatesView.animeId, - context.imageLoader.executeBlocking(request).drawable?.toBitmap(), - ) - } - } - - companion object { - val DateLimit: Calendar - get() = Calendar.getInstance().apply { - time = Date() - add(Calendar.MONTH, -3) - } - } +class AnimeUpdatesGridGlanceWidget : BaseAnimeUpdatesGridGlanceWidget() { + 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() diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/BaseAnimeUpdatesGridGlanceWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/BaseAnimeUpdatesGridGlanceWidget.kt new file mode 100644 index 000000000..6d2f4d11e --- /dev/null +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/BaseAnimeUpdatesGridGlanceWidget.kt @@ -0,0 +1,154 @@ +package tachiyomi.presentation.widget.entries.anime + +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.entries.anime.model.AnimeCover +import tachiyomi.domain.updates.anime.interactor.GetAnimeUpdates +import tachiyomi.domain.updates.anime.model.AnimeUpdatesWithRelations +import tachiyomi.presentation.widget.R +import tachiyomi.presentation.widget.components.anime.CoverHeight +import tachiyomi.presentation.widget.components.anime.CoverWidth +import tachiyomi.presentation.widget.components.anime.LockedAnimeWidget +import tachiyomi.presentation.widget.components.anime.UpdatesAnimeWidget +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 BaseAnimeUpdatesGridGlanceWidget( + private val context: Context = Injekt.get(), + private val getUpdates: GetAnimeUpdates = 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) { + LockedAnimeWidget( + 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) + UpdatesAnimeWidget( + data = data, + modifier = containerModifier, + contentColor = foreground, + topPadding = topPadding, + bottomPadding = bottomPadding, + ) + } + } + + private suspend fun List.prepareData( + rowCount: Int, + columnCount: Int, + ): List> { + // 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.animeId } + .take(rowCount * columnCount) + .map { updatesView -> + val request = ImageRequest.Builder(context) + .data( + AnimeCover( + animeId = updatesView.animeId, + sourceId = updatesView.sourceId, + isAnimeFavorite = 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.animeId, context.imageLoader.executeBlocking(request).drawable?.toBitmap()) + } + } + } + + companion object { + val DateLimit: Calendar + get() = Calendar.getInstance().apply { + time = Date() + add(Calendar.MONTH, -3) + } + } +} diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/TachiyomiAnimeWidgetManager.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/TachiyomiAnimeWidgetManager.kt index 1e4a9be21..20dd36534 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/TachiyomiAnimeWidgetManager.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/TachiyomiAnimeWidgetManager.kt @@ -22,7 +22,7 @@ class TachiyomiAnimeWidgetManager( combine( getUpdates.subscribe( seen = false, - after = AnimeUpdatesGridGlanceWidget.DateLimit.timeInMillis, + after = BaseAnimeUpdatesGridGlanceWidget.DateLimit.timeInMillis, ), securityPreferences.useAuthenticator().changes(), transform = { a, _ -> a }, @@ -32,6 +32,7 @@ class TachiyomiAnimeWidgetManager( .onEach { try { AnimeUpdatesGridGlanceWidget().updateAll(this) + AnimeUpdatesGridCoverScreenGlanceWidget().updateAll(this) } catch (e: Exception) { logcat(LogPriority.ERROR, e) { "Failed to update widget" } } diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/BaseMangaUpdatesGridGlanceWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/BaseMangaUpdatesGridGlanceWidget.kt new file mode 100644 index 000000000..243cdba8d --- /dev/null +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/BaseMangaUpdatesGridGlanceWidget.kt @@ -0,0 +1,154 @@ +package tachiyomi.presentation.widget.entries.manga + +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.entries.manga.model.MangaCover +import tachiyomi.domain.updates.manga.interactor.GetMangaUpdates +import tachiyomi.domain.updates.manga.model.MangaUpdatesWithRelations +import tachiyomi.presentation.widget.R +import tachiyomi.presentation.widget.components.manga.CoverHeight +import tachiyomi.presentation.widget.components.manga.CoverWidth +import tachiyomi.presentation.widget.components.manga.LockedMangaWidget +import tachiyomi.presentation.widget.components.manga.UpdatesMangaWidget +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 BaseMangaUpdatesGridGlanceWidget( + private val context: Context = Injekt.get(), + private val getUpdates: GetMangaUpdates = 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) { + LockedMangaWidget( + 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) + UpdatesMangaWidget( + data = data, + modifier = containerModifier, + contentColor = foreground, + topPadding = topPadding, + bottomPadding = bottomPadding, + ) + } + } + + private suspend fun List.prepareData( + rowCount: Int, + columnCount: Int, + ): List> { + // 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) + } + } +} diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridCoverScreenGlanceReceiver.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridCoverScreenGlanceReceiver.kt new file mode 100644 index 000000000..5e3c21db2 --- /dev/null +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridCoverScreenGlanceReceiver.kt @@ -0,0 +1,9 @@ +package tachiyomi.presentation.widget.entries.manga + +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetReceiver + +class MangaUpdatesGridCoverScreenGlanceReceiver : GlanceAppWidgetReceiver() { + override val glanceAppWidget: GlanceAppWidget + get() = MangaUpdatesGridCoverScreenGlanceWidget() +} diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridCoverScreenGlanceWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridCoverScreenGlanceWidget.kt new file mode 100644 index 000000000..913f9371b --- /dev/null +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridCoverScreenGlanceWidget.kt @@ -0,0 +1,14 @@ +package tachiyomi.presentation.widget.entries.manga + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.glance.ImageProvider +import androidx.glance.unit.ColorProvider +import tachiyomi.presentation.widget.R + +class MangaUpdatesGridCoverScreenGlanceWidget : BaseMangaUpdatesGridGlanceWidget() { + 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 +} diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridGlanceReceiver.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridGlanceReceiver.kt index 463c809da..dc08b01d2 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridGlanceReceiver.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridGlanceReceiver.kt @@ -4,5 +4,6 @@ import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.GlanceAppWidgetReceiver class MangaUpdatesGridGlanceReceiver : GlanceAppWidgetReceiver() { - override val glanceAppWidget: GlanceAppWidget = MangaUpdatesGridGlanceWidget() + override val glanceAppWidget: GlanceAppWidget + get() = MangaUpdatesGridGlanceWidget() } diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridGlanceWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridGlanceWidget.kt index 7c12b4cf7..5ad3b32b0 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridGlanceWidget.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/MangaUpdatesGridGlanceWidget.kt @@ -1,137 +1,13 @@ package tachiyomi.presentation.widget.entries.manga -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.entries.manga.model.MangaCover -import tachiyomi.domain.updates.manga.interactor.GetMangaUpdates -import tachiyomi.domain.updates.manga.model.MangaUpdatesWithRelations +import androidx.glance.unit.ColorProvider import tachiyomi.presentation.widget.R -import tachiyomi.presentation.widget.components.manga.CoverHeight -import tachiyomi.presentation.widget.components.manga.CoverWidth -import tachiyomi.presentation.widget.components.manga.LockedMangaWidget -import tachiyomi.presentation.widget.components.manga.UpdatesMangaWidget -import tachiyomi.presentation.widget.util.appWidgetBackgroundRadius -import tachiyomi.presentation.widget.util.calculateRowAndColumnCount -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.util.Calendar -import java.util.Date -class MangaUpdatesGridGlanceWidget( - private val context: Context = Injekt.get(), - private val getUpdates: GetMangaUpdates = Injekt.get(), - private val preferences: SecurityPreferences = Injekt.get(), -) : GlanceAppWidget() { - - private var data: List>? = 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) { - LockedMangaWidget() - return@provideContent - } - UpdatesMangaWidget(data) - } - } - - private suspend fun loadData() { - val manager = GlanceAppWidgetManager(context) - val ids = manager.getGlanceIds(this@MangaUpdatesGridGlanceWidget::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, take: Int): List> { - // 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 MangaUpdatesGridGlanceWidget : BaseMangaUpdatesGridGlanceWidget() { + 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() diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/TachiyomiMangaWidgetManager.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/TachiyomiMangaWidgetManager.kt index 54f36f82d..2fb1f5824 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/TachiyomiMangaWidgetManager.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/TachiyomiMangaWidgetManager.kt @@ -22,7 +22,7 @@ class TachiyomiMangaWidgetManager( combine( getUpdates.subscribe( read = false, - after = MangaUpdatesGridGlanceWidget.DateLimit.timeInMillis, + after = BaseMangaUpdatesGridGlanceWidget.DateLimit.timeInMillis, ), securityPreferences.useAuthenticator().changes(), transform = { a, _ -> a }, @@ -32,6 +32,7 @@ class TachiyomiMangaWidgetManager( .onEach { try { MangaUpdatesGridGlanceWidget().updateAll(this) + MangaUpdatesGridCoverScreenGlanceWidget().updateAll(this) } catch (e: Exception) { logcat(LogPriority.ERROR, e) { "Failed to update widget" } } diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/util/GlanceUtils.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/util/GlanceUtils.kt index 3b48bb7d5..3783c4d65 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/util/GlanceUtils.kt +++ b/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 @@ -33,9 +34,13 @@ fun stringResource(@StringRes id: Int): String { * * @return pair of row and column count */ -internal fun DpSize.calculateRowAndColumnCount(): Pair { +fun DpSize.calculateRowAndColumnCount( + topPadding: Dp, + bottomPadding: Dp, +): 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 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) diff --git a/presentation-widget/src/main/res/drawable-nodpi/updates_grid_coverscreen_widget_preview.webp b/presentation-widget/src/main/res/drawable-nodpi/updates_grid_coverscreen_widget_preview.webp new file mode 100644 index 0000000000000000000000000000000000000000..b28fb91b9a34e2b9eadbec41cb593ca7d7a93204 GIT binary patch literal 5200 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 + + + \ No newline at end of file diff --git a/presentation-widget/src/main/res/layout/appwidget_coverscreen_loading.xml b/presentation-widget/src/main/res/layout/appwidget_coverscreen_loading.xml new file mode 100644 index 000000000..016a1999e --- /dev/null +++ b/presentation-widget/src/main/res/layout/appwidget_coverscreen_loading.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/presentation-widget/src/main/res/values/colors_appwidget.xml b/presentation-widget/src/main/res/values/colors_appwidget.xml index 7d07ea1f8..a5526c94c 100644 --- a/presentation-widget/src/main/res/values/colors_appwidget.xml +++ b/presentation-widget/src/main/res/values/colors_appwidget.xml @@ -1,6 +1,7 @@ @color/tachiyomi_surface + #00000000 @color/tachiyomi_onSurface @color/tachiyomi_surfaceVariant @color/tachiyomi_onSurfaceVariant diff --git a/app/src/main/res/xml/updates_grid_glance_widget_info.xml b/presentation-widget/src/main/res/xml/updates_grid_homescreen_widget_info.xml similarity index 100% rename from app/src/main/res/xml/updates_grid_glance_widget_info.xml rename to presentation-widget/src/main/res/xml/updates_grid_homescreen_widget_info.xml diff --git a/presentation-widget/src/main/res/xml/updates_grid_lockscreen_widget_info.xml b/presentation-widget/src/main/res/xml/updates_grid_lockscreen_widget_info.xml new file mode 100644 index 000000000..64ec20b34 --- /dev/null +++ b/presentation-widget/src/main/res/xml/updates_grid_lockscreen_widget_info.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/presentation-widget/src/main/res/xml/updates_grid_samsung_cover_widget_info.xml b/presentation-widget/src/main/res/xml/updates_grid_samsung_cover_widget_info.xml new file mode 100644 index 000000000..564b59845 --- /dev/null +++ b/presentation-widget/src/main/res/xml/updates_grid_samsung_cover_widget_info.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/AnimeSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/AnimeSource.kt index b882e4801..0534604dc 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/AnimeSource.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/AnimeSource.kt @@ -27,7 +27,9 @@ interface AnimeSource { /** * Get the updated details for a anime. * + * @since extensions-lib 1.4 * @param anime the anime to update. + * @return the updated anime. */ @Suppress("DEPRECATION") suspend fun getAnimeDetails(anime: SAnime): SAnime { @@ -37,7 +39,9 @@ interface AnimeSource { /** * Get all the available episodes for a anime. * + * @since extensions-lib 1.4 * @param anime the anime to update. + * @return the episodes for the anime. */ @Suppress("DEPRECATION") suspend fun getEpisodeList(anime: SAnime): List { @@ -48,7 +52,9 @@ interface AnimeSource { * Get the list of videos a episode has. Pages should be returned * in the expected order; the index is ignored. * + * @since extensions-lib 1.4 * @param episode the episode. + * @return the videos for the episode. */ @Suppress("DEPRECATION") suspend fun getVideoList(episode: SEpisode): List