mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-21 20:27:06 +03:00
parent
3a3115304e
commit
18e04b75df
50 changed files with 673 additions and 645 deletions
2
.github/workflows/build_pull_request.yml
vendored
2
.github/workflows/build_pull_request.yml
vendored
|
@ -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
|
||||
|
|
2
.github/workflows/build_push.yml
vendored
2
.github/workflows/build_push.yml
vendored
|
@ -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
|
||||
|
|
|
@ -199,34 +199,6 @@
|
|||
android:name=".data.notification.NotificationReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name="tachiyomi.presentation.widget.entries.manga.MangaUpdatesGridGlanceReceiver"
|
||||
android:enabled="@bool/glance_appwidget_available"
|
||||
android:exported="false"
|
||||
android:label="@string/label_recent_updates">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/updates_grid_glance_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="tachiyomi.presentation.widget.entries.anime.AnimeUpdatesGridGlanceReceiver"
|
||||
android:enabled="@bool/glance_appwidget_available"
|
||||
android:exported="false"
|
||||
android:label="@string/label_recent_anime_updates">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/updates_grid_glance_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".data.download.manga.MangaDownloadService"
|
||||
android:exported="false" />
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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<ViewGroup.MarginLayoutParams> {
|
||||
leftMargin = left
|
||||
topMargin = top
|
||||
rightMargin = right
|
||||
bottomMargin = bottom
|
||||
}
|
||||
|
||||
screenModel.adapter?.updateDataSet(downloadList)
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ViewGroup.MarginLayoutParams> {
|
||||
leftMargin = left
|
||||
topMargin = top
|
||||
rightMargin = right
|
||||
bottomMargin = bottom
|
||||
}
|
||||
|
||||
screenModel.adapter?.updateDataSet(downloadList)
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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.).
|
||||
*
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="8dp" />
|
||||
<solid android:color="?attr/colorAccent" />
|
||||
<size android:width="6dp" android:height="54dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="8dp" />
|
||||
<solid android:color="@color/fast_scroller_handle_idle" />
|
||||
<size android:width="6dp" android:height="54dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -1,24 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/frame_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/download_item" />
|
||||
|
||||
<eu.kanade.tachiyomi.widget.MaterialFastScroll
|
||||
android:id="@+id/fast_scroller"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
app:fastScrollerBubbleEnabled="false"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/download_item" />
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<View
|
||||
android:id="@+id/fast_scroller_bar"
|
||||
android:layout_width="7dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:background="@null" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end">
|
||||
|
||||
<!-- No margin, use padding at the handle -->
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/fast_scroller_bubble"
|
||||
style="@style/FloatingTextView"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_toStartOf="@+id/fast_scroller_handle"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
tools:text="A"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!-- Padding is here to have better grab -->
|
||||
<ImageView
|
||||
android:id="@+id/fast_scroller_handle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:contentDescription="@null"
|
||||
android:paddingStart="6dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:src="@drawable/material_thumb_drawable" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</merge>
|
|
@ -56,21 +56,6 @@
|
|||
</style>
|
||||
|
||||
|
||||
<!--============-->
|
||||
<!--FastScroller-->
|
||||
<!--============-->
|
||||
<style name="FloatingTextView" parent="TextAppearance.AppCompat">
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:elevation">5dp</item>
|
||||
<item name="android:paddingStart">12dp</item>
|
||||
<item name="android:paddingEnd">12dp</item>
|
||||
<item name="android:paddingTop">8dp</item>
|
||||
<item name="android:paddingBottom">8dp</item>
|
||||
<item name="android:textColor">?attr/colorOnPrimary</item>
|
||||
<item name="android:textSize">15sp</item>
|
||||
</style>
|
||||
|
||||
<!--===========-->
|
||||
<!--Preferences-->
|
||||
<!--===========-->
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<string name="pref_bottom_nav_no_history">Move History to the More tab</string>
|
||||
<string name="pref_bottom_nav_no_updates">Move Updates to the More tab</string>
|
||||
<string name="pref_bottom_nav_no_manga">Move Manga to the More tab</string>
|
||||
<string name="unlock_app">Unlock Aniyomi</string>
|
||||
<string name="unlock_app_title">Unlock Aniyomi</string>
|
||||
<string name="action_filter_unseen">Unseen</string>
|
||||
<string name="action_global_manga_search">Global Manga Search</string>
|
||||
<string name="action_global_anime_search">Global Anime Search</string>
|
||||
|
@ -77,12 +77,12 @@
|
|||
<string name="default_anime_category">Default anime category</string>
|
||||
<string name="pref_manga_library_update_categories_details">Manga in excluded categories will not be updated even if they are also in included categories.</string>
|
||||
<string name="pref_anime_library_update_categories_details">Anime in excluded categories will not be updated even if they are also in included categories.</string>
|
||||
<string name="untrusted_extension_message">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.</string>
|
||||
<string name="unofficial_extension_message_aniyomi">This extension is not from the official Aniyomi extensions list.</string>
|
||||
<string name="untrusted_extension_message">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.</string>
|
||||
<string name="unofficial_extension_message_aniyomi">This extension is not from the official list.</string>
|
||||
<string name="rotation_reverse_landscape">Reverse landscape</string>
|
||||
<string name="rotation_sensor_portrait">Sensor portrait</string>
|
||||
<string name="rotation_sensor_landscape">Sensor landscape</string>
|
||||
<string name="unofficial_anime_extension_message">This extension is not from the official Aniyomi extensions list.</string>
|
||||
<string name="unofficial_anime_extension_message">This extension is not from the official list.</string>
|
||||
<!-- Player section -->
|
||||
<string name="pref_category_player">Player</string>
|
||||
<string name="pref_category_progress">Progress</string>
|
||||
|
@ -241,7 +241,7 @@
|
|||
</plurals>
|
||||
<string name="information_no_recent_anime">Nothing watched recently</string>
|
||||
<!-- Do not translate "WebView" -->
|
||||
<string name="information_webview_required">WebView is required for Aniyomi</string>
|
||||
<string name="information_webview_required">WebView is required for the app to function</string>
|
||||
<string name="episode_settings_updated">Updated default episode settings</string>
|
||||
<string name="download_notifier_download_paused_chapters">Chapter download paused</string>
|
||||
<string name="download_notifier_download_paused_episodes">Episode download paused</string>
|
||||
|
|
|
@ -1,2 +1,73 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application>
|
||||
|
||||
<receiver
|
||||
android:name="tachiyomi.presentation.widget.entries.manga.MangaUpdatesGridGlanceReceiver"
|
||||
android:enabled="@bool/glance_appwidget_available"
|
||||
android:exported="false"
|
||||
android:label="@string/label_recent_updates">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/updates_grid_homescreen_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="tachiyomi.presentation.widget.entries.anime.AnimeUpdatesGridGlanceReceiver"
|
||||
android:enabled="@bool/glance_appwidget_available"
|
||||
android:exported="false"
|
||||
android:label="@string/label_recent_anime_updates">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/updates_grid_homescreen_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="tachiyomi.presentation.widget.entries.manga.MangaUpdatesGridCoverScreenGlanceReceiver"
|
||||
android:enabled="@bool/glance_appwidget_available"
|
||||
android:exported="false"
|
||||
android:label="@string/label_recent_updates">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/updates_grid_lockscreen_widget_info" />
|
||||
<meta-data
|
||||
android:name="com.samsung.android.appwidget.provider"
|
||||
android:resource="@xml/updates_grid_samsung_cover_widget_info" />
|
||||
<meta-data
|
||||
android:name="com.samsung.android.sdk.subscreen.widget.support_visibility_callback"
|
||||
android:value="true" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="tachiyomi.presentation.widget.entries.anime.AnimeUpdatesGridCoverScreenGlanceReceiver"
|
||||
android:enabled="@bool/glance_appwidget_available"
|
||||
android:exported="false"
|
||||
android:label="@string/label_recent_anime_updates">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/updates_grid_lockscreen_widget_info" />
|
||||
<meta-data
|
||||
android:name="com.samsung.android.appwidget.provider"
|
||||
android:resource="@xml/updates_grid_samsung_cover_widget_info" />
|
||||
<meta-data
|
||||
android:name="com.samsung.android.sdk.subscreen.widget.support_visibility_callback"
|
||||
android:value="true" />
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
</manifest>
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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<Pair<Long, Bitmap?>>?) {
|
||||
val (rowCount, columnCount) = LocalSize.current.calculateRowAndColumnCount()
|
||||
Column(
|
||||
modifier = ContainerModifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
fun UpdatesAnimeWidget(
|
||||
data: List<Pair<Long, Bitmap?>>?,
|
||||
modifier: GlanceModifier = GlanceModifier,
|
||||
contentColor: ColorProvider,
|
||||
topPadding: Dp,
|
||||
bottomPadding: Dp,
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier,
|
||||
) {
|
||||
if (data == null) {
|
||||
CircularProgressIndicator()
|
||||
CircularProgressIndicator(color = contentColor)
|
||||
} else if (data.isEmpty()) {
|
||||
Text(text = stringResource(R.string.information_no_recent))
|
||||
Text(
|
||||
text = stringResource(R.string.information_no_recent),
|
||||
style = TextStyle(color = contentColor),
|
||||
)
|
||||
} else {
|
||||
(0 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..<rowCount).forEach { i ->
|
||||
val coverRow = (0..<columnCount).mapNotNull { j ->
|
||||
data.getOrNull(j + (i * columnCount))
|
||||
}
|
||||
if (coverRow.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = GlanceModifier
|
||||
.padding(vertical = 4.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
coverRow.forEach { (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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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<Pair<Long, Bitmap?>>?) {
|
||||
val (rowCount, columnCount) = LocalSize.current.calculateRowAndColumnCount()
|
||||
Column(
|
||||
modifier = ContainerModifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
fun UpdatesMangaWidget(
|
||||
data: List<Pair<Long, Bitmap?>>?,
|
||||
modifier: GlanceModifier = GlanceModifier,
|
||||
contentColor: ColorProvider,
|
||||
topPadding: Dp,
|
||||
bottomPadding: Dp,
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier,
|
||||
) {
|
||||
if (data == null) {
|
||||
CircularProgressIndicator()
|
||||
CircularProgressIndicator(color = contentColor)
|
||||
} else if (data.isEmpty()) {
|
||||
Text(text = stringResource(R.string.information_no_recent))
|
||||
Text(
|
||||
text = stringResource(R.string.information_no_recent),
|
||||
style = TextStyle(color = contentColor),
|
||||
)
|
||||
} else {
|
||||
(0..<rowCount).forEach { i ->
|
||||
val coverRow = (0..<columnCount).mapNotNull { j ->
|
||||
data.getOrNull(j + (i * columnCount))
|
||||
}
|
||||
if (coverRow.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = GlanceModifier
|
||||
.padding(vertical = 4.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
coverRow.forEach { (mangaId, cover) ->
|
||||
Box(
|
||||
modifier = GlanceModifier
|
||||
.padding(horizontal = 3.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val intent = Intent(
|
||||
LocalContext.current,
|
||||
Class.forName(Constants.MAIN_ACTIVITY),
|
||||
).apply {
|
||||
action = Constants.SHORTCUT_MANGA
|
||||
putExtra(Constants.MANGA_EXTRA, mangaId)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
val (rowCount, columnCount) = LocalSize.current.calculateRowAndColumnCount(topPadding, bottomPadding)
|
||||
Column(
|
||||
modifier = GlanceModifier.fillMaxHeight(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
(0..<rowCount).forEach { i ->
|
||||
val coverRow = (0..<columnCount).mapNotNull { j ->
|
||||
data.getOrNull(j + (i * columnCount))
|
||||
}
|
||||
if (coverRow.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = GlanceModifier
|
||||
.padding(vertical = 4.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
coverRow.forEach { (mangaId, cover) ->
|
||||
Box(
|
||||
modifier = GlanceModifier
|
||||
.padding(horizontal = 3.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val intent = Intent(LocalContext.current, Class.forName(Constants.MAIN_ACTIVITY)).apply {
|
||||
action = Constants.SHORTCUT_MANGA
|
||||
putExtra(Constants.MANGA_EXTRA, mangaId)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
|
||||
// https://issuetracker.google.com/issues/238793260
|
||||
addCategory(mangaId.toString())
|
||||
// https://issuetracker.google.com/issues/238793260
|
||||
addCategory(mangaId.toString())
|
||||
}
|
||||
UpdatesMangaCover(
|
||||
modifier = GlanceModifier.clickable(actionStartActivity(intent)),
|
||||
cover = cover,
|
||||
)
|
||||
}
|
||||
UpdatesMangaCover(
|
||||
modifier = GlanceModifier.clickable(actionStartActivity(intent)),
|
||||
cover = cover,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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<Application>(),
|
||||
private val getUpdates: GetAnimeUpdates = Injekt.get(),
|
||||
private val preferences: SecurityPreferences = Injekt.get(),
|
||||
) : GlanceAppWidget() {
|
||||
|
||||
private var data: List<Pair<Long, Bitmap?>>? = null
|
||||
|
||||
override val sizeMode = SizeMode.Exact
|
||||
|
||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||
val locked = preferences.useAuthenticator().get()
|
||||
if (!locked) loadData()
|
||||
|
||||
provideContent {
|
||||
// If app lock enabled, don't do anything
|
||||
if (locked) {
|
||||
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<AnimeUpdatesWithRelations>, take: Int): List<Pair<Long, Bitmap?>> {
|
||||
// Resize to cover size
|
||||
val widthPx = CoverWidth.value.toInt().dpToPx
|
||||
val heightPx = CoverHeight.value.toInt().dpToPx
|
||||
val roundPx = context.resources.getDimension(R.dimen.appwidget_inner_radius)
|
||||
return processList
|
||||
.distinctBy { it.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()
|
||||
|
|
|
@ -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<Application>(),
|
||||
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<AnimeUpdatesWithRelations>.prepareData(
|
||||
rowCount: Int,
|
||||
columnCount: Int,
|
||||
): List<Pair<Long, Bitmap?>> {
|
||||
// Resize to cover size
|
||||
val widthPx = CoverWidth.value.toInt().dpToPx
|
||||
val heightPx = CoverHeight.value.toInt().dpToPx
|
||||
val roundPx = context.resources.getDimension(R.dimen.appwidget_inner_radius)
|
||||
return withIOContext {
|
||||
this@prepareData
|
||||
.distinctBy { it.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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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" }
|
||||
}
|
||||
|
|
|
@ -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<Application>(),
|
||||
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<MangaUpdatesWithRelations>.prepareData(
|
||||
rowCount: Int,
|
||||
columnCount: Int,
|
||||
): List<Pair<Long, Bitmap?>> {
|
||||
// Resize to cover size
|
||||
val widthPx = CoverWidth.value.toInt().dpToPx
|
||||
val heightPx = CoverHeight.value.toInt().dpToPx
|
||||
val roundPx = context.resources.getDimension(R.dimen.appwidget_inner_radius)
|
||||
return withIOContext {
|
||||
this@prepareData
|
||||
.distinctBy { it.mangaId }
|
||||
.take(rowCount * columnCount)
|
||||
.map { updatesView ->
|
||||
val request = ImageRequest.Builder(context)
|
||||
.data(
|
||||
MangaCover(
|
||||
mangaId = updatesView.mangaId,
|
||||
sourceId = updatesView.sourceId,
|
||||
isMangaFavorite = true,
|
||||
url = updatesView.coverData.url,
|
||||
lastModified = updatesView.coverData.lastModified,
|
||||
),
|
||||
)
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
.precision(Precision.EXACT)
|
||||
.size(widthPx, heightPx)
|
||||
.scale(Scale.FILL)
|
||||
.let {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||
it.transformations(RoundedCornersTransformation(roundPx))
|
||||
} else {
|
||||
it // Handled by system
|
||||
}
|
||||
}
|
||||
.build()
|
||||
Pair(updatesView.mangaId, context.imageLoader.executeBlocking(request).drawable?.toBitmap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DateLimit: Calendar
|
||||
get() = Calendar.getInstance().apply {
|
||||
time = Date()
|
||||
add(Calendar.MONTH, -3)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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<Application>(),
|
||||
private val getUpdates: GetMangaUpdates = Injekt.get(),
|
||||
private val preferences: SecurityPreferences = Injekt.get(),
|
||||
) : GlanceAppWidget() {
|
||||
|
||||
private var data: List<Pair<Long, Bitmap?>>? = null
|
||||
|
||||
override val sizeMode = SizeMode.Exact
|
||||
|
||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||
val locked = preferences.useAuthenticator().get()
|
||||
if (!locked) loadData()
|
||||
|
||||
provideContent {
|
||||
// If app lock enabled, don't do anything
|
||||
if (locked) {
|
||||
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<MangaUpdatesWithRelations>, take: Int): List<Pair<Long, Bitmap?>> {
|
||||
// Resize to cover size
|
||||
val widthPx = CoverWidth.value.toInt().dpToPx
|
||||
val heightPx = CoverHeight.value.toInt().dpToPx
|
||||
val roundPx = context.resources.getDimension(R.dimen.appwidget_inner_radius)
|
||||
return processList
|
||||
.distinctBy { it.mangaId }
|
||||
.take(take)
|
||||
.map { updatesView ->
|
||||
val request = ImageRequest.Builder(context)
|
||||
.data(
|
||||
MangaCover(
|
||||
mangaId = updatesView.mangaId,
|
||||
sourceId = updatesView.sourceId,
|
||||
isMangaFavorite = true,
|
||||
url = updatesView.coverData.url,
|
||||
lastModified = updatesView.coverData.lastModified,
|
||||
),
|
||||
)
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
.precision(Precision.EXACT)
|
||||
.size(widthPx, heightPx)
|
||||
.scale(Scale.FILL)
|
||||
.let {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||
it.transformations(RoundedCornersTransformation(roundPx))
|
||||
} else {
|
||||
it // Handled by system
|
||||
}
|
||||
}
|
||||
.build()
|
||||
Pair(
|
||||
updatesView.mangaId,
|
||||
context.imageLoader.executeBlocking(request).drawable?.toBitmap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DateLimit: Calendar
|
||||
get() = Calendar.getInstance().apply {
|
||||
time = Date()
|
||||
add(Calendar.MONTH, -3)
|
||||
}
|
||||
}
|
||||
class 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()
|
||||
|
|
|
@ -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" }
|
||||
}
|
||||
|
|
|
@ -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<Int, Int> {
|
||||
fun DpSize.calculateRowAndColumnCount(
|
||||
topPadding: Dp,
|
||||
bottomPadding: Dp,
|
||||
): Pair<Int, Int> {
|
||||
// Hack: Size provided by Glance manager is not reliable so take at least 1 row and 1 column
|
||||
// Set max to 10 children each direction because of Glance limitation
|
||||
val height = this.height - topPadding - bottomPadding
|
||||
val rowCount = (height.value / 95).toInt().coerceIn(1, 10)
|
||||
val columnCount = (width.value / 64).toInt().coerceIn(1, 10)
|
||||
return Pair(rowCount, columnCount)
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 410 KiB After Width: | Height: | Size: 410 KiB |
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/appwidget_coverscreen_background" />
|
||||
</shape>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/appwidget_coverscreen_background">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/loading"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
</FrameLayout>
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="appwidget_background">@color/tachiyomi_surface</color>
|
||||
<color name="appwidget_coverscreen_background">#00000000</color>
|
||||
<color name="appwidget_on_background">@color/tachiyomi_onSurface</color>
|
||||
<color name="appwidget_surface_variant">@color/tachiyomi_surfaceVariant</color>
|
||||
<color name="appwidget_on_surface_variant">@color/tachiyomi_onSurfaceVariant</color>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:description="@string/appwidget_updates_description"
|
||||
android:previewImage="@drawable/updates_grid_coverscreen_widget_preview"
|
||||
android:initialLayout="@layout/appwidget_coverscreen_loading"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:widgetCategory="keyguard" />
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<samsung-appwidget-provider
|
||||
display="sub_screen"
|
||||
privacyWidget="true" />
|
|
@ -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<SEpisode> {
|
||||
|
@ -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<Video> {
|
||||
|
|
|
@ -27,7 +27,9 @@ interface MangaSource {
|
|||
/**
|
||||
* Get the updated details for a manga.
|
||||
*
|
||||
* @since extensions-lib 1.4
|
||||
* @param manga the manga to update.
|
||||
* @return the updated manga.
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
suspend fun getMangaDetails(manga: SManga): SManga {
|
||||
|
@ -37,7 +39,9 @@ interface MangaSource {
|
|||
/**
|
||||
* Get all the available chapters for a manga.
|
||||
*
|
||||
* @since extensions-lib 1.4
|
||||
* @param manga the manga to update.
|
||||
* @return the chapters for the manga.
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
suspend fun getChapterList(manga: SManga): List<SChapter> {
|
||||
|
@ -48,7 +52,9 @@ interface MangaSource {
|
|||
* Get the list of pages a chapter has. Pages should be returned
|
||||
* in the expected order; the index is ignored.
|
||||
*
|
||||
* @since extensions-lib 1.4
|
||||
* @param chapter the chapter.
|
||||
* @return the pages for the chapter.
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
suspend fun getPageList(chapter: SChapter): List<Page> {
|
||||
|
|
Loading…
Reference in a new issue