diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml
index da79440dc..5a7bfdd10 100644
--- a/.github/workflows/build_pull_request.yml
+++ b/.github/workflows/build_pull_request.yml
@@ -28,7 +28,7 @@ jobs:
uses: actions/dependency-review-action@v3
- name: Set up JDK
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
java-version: 17
distribution: adopt
diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml
index e43c229a0..6127d3f30 100644
--- a/.github/workflows/build_push.yml
+++ b/.github/workflows/build_push.yml
@@ -23,7 +23,7 @@ jobs:
uses: gradle/wrapper-validation-action@v1
- name: Set up JDK
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
java-version: 17
distribution: adopt
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a48438b57..5b9056960 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -196,7 +196,6 @@ dependencies {
// RxJava
implementation(libs.rxjava)
- implementation(libs.flowreactivenetwork)
// Networking
implementation(libs.bundles.okhttp)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fe6f4a6c7..ef9cfd256 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -21,6 +21,8 @@
+
+
-
-
@@ -168,6 +166,11 @@
android:value="true" />
+
+
SourceSwitchPreference(
+ modifier = Modifier.animateItemPlacement(),
source = source,
onClickSourcePreferences = onClickSourcePreferences,
onClickSource = onClickSource,
diff --git a/app/src/main/java/eu/kanade/presentation/browse/ExtensionFilterScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/ExtensionFilterScreen.kt
index 51acdfccf..c65f0d0b1 100644
--- a/app/src/main/java/eu/kanade/presentation/browse/ExtensionFilterScreen.kt
+++ b/app/src/main/java/eu/kanade/presentation/browse/ExtensionFilterScreen.kt
@@ -58,6 +58,7 @@ private fun ExtensionFilterContent(
) {
items(state.languages) { language ->
SwitchPreferenceWidget(
+ modifier = Modifier.animateItemPlacement(),
title = LocaleHelper.getSourceDisplayName(language, context),
checked = language in state.enabledLanguages,
onCheckedChanged = { onClickLang(language) },
diff --git a/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt
index 3469af6ca..2e4c6fc0b 100644
--- a/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt
+++ b/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt
@@ -148,12 +148,14 @@ private fun ExtensionContent(
}
ExtensionHeader(
textRes = header.textRes,
+ modifier = Modifier.animateItemPlacement(),
action = action,
)
}
is ExtensionUiModel.Header.Text -> {
ExtensionHeader(
text = header.text,
+ modifier = Modifier.animateItemPlacement(),
)
}
}
@@ -165,6 +167,7 @@ private fun ExtensionContent(
key = { "extension-${it.hashCode()}" },
) { item ->
ExtensionItem(
+ modifier = Modifier.animateItemPlacement(),
item = item,
onClickItem = {
when (it) {
diff --git a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt
index b7dc70ebb..e45ca5189 100644
--- a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt
+++ b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt
@@ -132,6 +132,7 @@ private fun MigrateSourceList(
key = { (source, _) -> "migrate-${source.id}" },
) { (source, count) ->
MigrateSourceItem(
+ modifier = Modifier.animateItemPlacement(),
source = source,
count = count,
onClickItem = { onClickItem(source) },
diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourcesFilterScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourcesFilterScreen.kt
index 6306800b8..e334a451e 100644
--- a/app/src/main/java/eu/kanade/presentation/browse/SourcesFilterScreen.kt
+++ b/app/src/main/java/eu/kanade/presentation/browse/SourcesFilterScreen.kt
@@ -68,6 +68,7 @@ private fun SourcesFilterContent(
contentType = "source-filter-header",
) {
SourcesFilterHeader(
+ modifier = Modifier.animateItemPlacement(),
language = language,
enabled = enabled,
onClickItem = onClickLanguage,
@@ -80,6 +81,7 @@ private fun SourcesFilterContent(
contentType = { "source-filter-item" },
) { source ->
SourcesFilterItem(
+ modifier = Modifier.animateItemPlacement(),
source = source,
enabled = "${source.id}" !in state.disabledSources,
onClickItem = onClickSource,
diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt
index 94cca357d..d0411b7a8 100644
--- a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt
+++ b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt
@@ -74,10 +74,12 @@ fun SourcesScreen(
when (model) {
is SourceUiModel.Header -> {
SourceHeader(
+ modifier = Modifier.animateItemPlacement(),
language = model.language,
)
}
is SourceUiModel.Item -> SourceItem(
+ modifier = Modifier.animateItemPlacement(),
source = model.source,
onClickItem = onClickItem,
onLongClickItem = onLongClickItem,
diff --git a/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt b/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt
index 471ec7fc9..34a6a1217 100644
--- a/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt
+++ b/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt
@@ -107,6 +107,7 @@ private fun CategoryContent(
key = { _, category -> "category-${category.id}" },
) { index, category ->
CategoryListItem(
+ modifier = Modifier.animateItemPlacement(),
category = category,
canMoveUp = index != 0,
canMoveDown = index != categories.lastIndex,
diff --git a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt
index ec6416480..ce6871e13 100644
--- a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt
+++ b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt
@@ -123,6 +123,7 @@ private fun HistoryScreenContent(
when (item) {
is HistoryUiModel.Header -> {
RelativeDateHeader(
+ modifier = Modifier.animateItemPlacement(),
date = item.date,
relativeTime = relativeTime,
dateFormat = dateFormat,
@@ -131,6 +132,7 @@ private fun HistoryScreenContent(
is HistoryUiModel.Item -> {
val value = item.item
HistoryItem(
+ modifier = Modifier.animateItemPlacement(),
history = value,
onClickCover = { onClickCover(value) },
onClickResume = { onClickResume(value) },
diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt
index b4be0ebd9..3eecee438 100644
--- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt
+++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt
@@ -13,7 +13,6 @@ import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.PreferenceScaffold
import eu.kanade.presentation.more.settings.screen.about.AboutScreen
import eu.kanade.presentation.util.Screen
-import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.WebViewUtil
import kotlinx.coroutines.guava.await
diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/WorkerInfoScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/WorkerInfoScreen.kt
index 2c891e271..ea8db81e8 100644
--- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/WorkerInfoScreen.kt
+++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/WorkerInfoScreen.kt
@@ -18,17 +18,18 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
-import androidx.lifecycle.asFlow
import androidx.work.WorkInfo
import androidx.work.WorkQuery
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
+import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.util.Screen
import eu.kanade.presentation.util.ioCoroutineScope
+import eu.kanade.tachiyomi.util.lang.toDateTimestampString
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.workManager
import kotlinx.collections.immutable.persistentListOf
@@ -39,6 +40,9 @@ import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.plus
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import java.util.Date
class WorkerInfoScreen : Screen() {
@@ -116,22 +120,19 @@ class WorkerInfoScreen : Screen() {
private val workManager = context.workManager
val finished = workManager
- .getWorkInfosLiveData(
+ .getWorkInfosFlow(
WorkQuery.fromStates(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED),
)
- .asFlow()
.map(::constructString)
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
val running = workManager
- .getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.RUNNING))
- .asFlow()
+ .getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.RUNNING))
.map(::constructString)
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
val enqueued = workManager
- .getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.ENQUEUED))
- .asFlow()
+ .getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.ENQUEUED))
.map(::constructString)
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
@@ -146,6 +147,16 @@ class WorkerInfoScreen : Screen() {
appendLine(" - $it")
}
appendLine("State: ${workInfo.state}")
+ if (workInfo.state == WorkInfo.State.ENQUEUED) {
+ appendLine(
+ "Next scheduled run: ${Date(workInfo.nextScheduleTimeMillis).toDateTimestampString(
+ UiPreferences.dateFormat(
+ Injekt.get().dateFormat().get(),
+ ),
+ )}",
+ )
+ appendLine("Attempt #${workInfo.runAttemptCount + 1}")
+ }
appendLine()
}
}
diff --git a/app/src/main/java/eu/kanade/presentation/reader/OrientationSelectDialog.kt b/app/src/main/java/eu/kanade/presentation/reader/OrientationSelectDialog.kt
index ea6f3d58f..22a02444c 100644
--- a/app/src/main/java/eu/kanade/presentation/reader/OrientationSelectDialog.kt
+++ b/app/src/main/java/eu/kanade/presentation/reader/OrientationSelectDialog.kt
@@ -11,8 +11,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.PreviewLightDark
import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.manga.model.readerOrientation
@@ -72,7 +70,7 @@ private fun DialogContent(
selected = mode
},
modifier = Modifier.fillMaxWidth(),
- imageVector = ImageVector.vectorResource(mode.iconRes),
+ imageVector = mode.icon,
title = stringResource(mode.stringRes),
)
}
diff --git a/app/src/main/java/eu/kanade/presentation/reader/appbars/BottomReaderBar.kt b/app/src/main/java/eu/kanade/presentation/reader/appbars/BottomReaderBar.kt
index b1f7189ed..8d0cae57b 100644
--- a/app/src/main/java/eu/kanade/presentation/reader/appbars/BottomReaderBar.kt
+++ b/app/src/main/java/eu/kanade/presentation/reader/appbars/BottomReaderBar.kt
@@ -49,7 +49,7 @@ fun BottomReaderBar(
IconButton(onClick = onClickOrientation) {
Icon(
- painter = painterResource(orientation.iconRes),
+ imageVector = orientation.icon,
contentDescription = stringResource(MR.strings.rotation_type),
)
}
diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt
index ad2e0dbc3..eeaee2008 100644
--- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt
+++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt
@@ -53,6 +53,7 @@ internal fun LazyListScope.updatesLastUpdatedItem(
item(key = "updates-lastUpdated") {
Box(
modifier = Modifier
+ .animateItemPlacement()
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
) {
Text(
@@ -89,12 +90,14 @@ internal fun LazyListScope.updatesUiItems(
when (item) {
is UpdatesUiModel.Header -> {
ListGroupHeader(
+ modifier = Modifier.animateItemPlacement(),
text = item.date,
)
}
is UpdatesUiModel.Item -> {
val updatesItem = item.item
UpdatesUiItem(
+ modifier = Modifier.animateItemPlacement(),
update = updatesItem.update,
selected = updatesItem.selected,
readProgress = updatesItem.update.lastPageRead
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadJob.kt
new file mode 100644
index 000000000..d0d58a843
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadJob.kt
@@ -0,0 +1,121 @@
+package eu.kanade.tachiyomi.data.download
+
+import android.content.Context
+import android.content.pm.ServiceInfo
+import android.os.Build
+import androidx.lifecycle.asFlow
+import androidx.work.CoroutineWorker
+import androidx.work.ExistingWorkPolicy
+import androidx.work.ForegroundInfo
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+import androidx.work.WorkerParameters
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.notification.Notifications
+import eu.kanade.tachiyomi.util.system.isConnectedToWifi
+import eu.kanade.tachiyomi.util.system.isOnline
+import eu.kanade.tachiyomi.util.system.notificationBuilder
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import logcat.LogPriority
+import tachiyomi.core.util.system.logcat
+import tachiyomi.domain.download.service.DownloadPreferences
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+
+/**
+ * This worker is used to manage the downloader. The system can decide to stop the worker, in
+ * which case the downloader is also stopped. It's also stopped while there's no network available.
+ */
+class DownloadJob(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
+
+ private val downloadManager: DownloadManager = Injekt.get()
+ private val downloadPreferences: DownloadPreferences = Injekt.get()
+
+ override suspend fun getForegroundInfo(): ForegroundInfo {
+ val notification = applicationContext.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
+ setContentTitle(applicationContext.getString(R.string.download_notifier_downloader_title))
+ setSmallIcon(android.R.drawable.stat_sys_download)
+ }.build()
+ return ForegroundInfo(
+ Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS,
+ notification,
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
+ } else {
+ 0
+ },
+ )
+ }
+
+ override suspend fun doWork(): Result {
+ try {
+ setForeground(getForegroundInfo())
+ } catch (e: IllegalStateException) {
+ logcat(LogPriority.ERROR, e) { "Not allowed to set foreground job" }
+ }
+
+ var networkCheck = checkConnectivity()
+ var active = networkCheck
+ downloadManager.downloaderStart()
+
+ // Keep the worker running when needed
+ while (active) {
+ delay(100)
+ networkCheck = checkConnectivity()
+ active = !isStopped && networkCheck && downloadManager.isRunning
+ }
+
+ return Result.success()
+ }
+
+ private fun checkConnectivity(): Boolean {
+ return with(applicationContext) {
+ if (isOnline()) {
+ val noWifi = downloadPreferences.downloadOnlyOverWifi().get() && !isConnectedToWifi()
+ if (noWifi) {
+ downloadManager.downloaderStop(
+ applicationContext.getString(R.string.download_notifier_text_only_wifi),
+ )
+ }
+ !noWifi
+ } else {
+ downloadManager.downloaderStop(applicationContext.getString(R.string.download_notifier_no_network))
+ false
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "Downloader"
+
+ fun start(context: Context) {
+ val request = OneTimeWorkRequestBuilder()
+ .addTag(TAG)
+ .build()
+ WorkManager.getInstance(context)
+ .enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request)
+ }
+
+ fun stop(context: Context) {
+ WorkManager.getInstance(context)
+ .cancelUniqueWork(TAG)
+ }
+
+ fun isRunning(context: Context): Boolean {
+ return WorkManager.getInstance(context)
+ .getWorkInfosForUniqueWork(TAG)
+ .get()
+ .let { list -> list.count { it.state == WorkInfo.State.RUNNING } == 1 }
+ }
+
+ fun isRunningFlow(context: Context): Flow {
+ return WorkManager.getInstance(context)
+ .getWorkInfosForUniqueWorkLiveData(TAG)
+ .asFlow()
+ .map { list -> list.count { it.state == WorkInfo.State.RUNNING } == 1 }
+ }
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
index ba5c4d81a..dca4fac23 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
@@ -46,6 +46,9 @@ class DownloadManager(
*/
private val downloader = Downloader(context, provider, cache)
+ val isRunning: Boolean
+ get() = downloader.isRunning
+
/**
* Queue to delay the deletion of a list of chapters until triggered.
*/
@@ -59,13 +62,13 @@ class DownloadManager(
fun downloaderStop(reason: String? = null) = downloader.stop(reason)
val isDownloaderRunning
- get() = DownloadService.isRunning
+ get() = DownloadJob.isRunningFlow(context)
/**
* Tells the downloader to begin downloads.
*/
fun startDownloads() {
- DownloadService.start(context)
+ DownloadJob.start(context)
}
/**
@@ -104,10 +107,10 @@ class DownloadManager(
queue.add(0, toAdd)
reorderQueue(queue)
if (!downloader.isRunning) {
- if (DownloadService.isRunning(context)) {
+ if (DownloadJob.isRunning(context)) {
downloader.start()
} else {
- DownloadService.start(context)
+ DownloadJob.start(context)
}
}
}
@@ -143,7 +146,7 @@ class DownloadManager(
addAll(0, downloads)
reorderQueue(this)
}
- if (!DownloadService.isRunning(context)) DownloadService.start(context)
+ if (!DownloadJob.isRunning(context)) DownloadJob.start(context)
}
/**
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt
deleted file mode 100644
index 9ce5c5b2a..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-package eu.kanade.tachiyomi.data.download
-
-import android.app.Notification
-import android.app.Service
-import android.content.Context
-import android.content.Intent
-import android.os.IBinder
-import android.os.PowerManager
-import androidx.core.content.ContextCompat
-import dev.icerock.moko.resources.StringResource
-import eu.kanade.tachiyomi.data.notification.Notifications
-import eu.kanade.tachiyomi.util.system.acquireWakeLock
-import eu.kanade.tachiyomi.util.system.isConnectedToWifi
-import eu.kanade.tachiyomi.util.system.isOnline
-import eu.kanade.tachiyomi.util.system.isServiceRunning
-import eu.kanade.tachiyomi.util.system.notificationBuilder
-import eu.kanade.tachiyomi.util.system.toast
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import logcat.LogPriority
-import ru.beryukhov.reactivenetwork.ReactiveNetwork
-import tachiyomi.core.i18n.stringResource
-import tachiyomi.core.util.lang.withUIContext
-import tachiyomi.core.util.system.logcat
-import tachiyomi.domain.download.service.DownloadPreferences
-import tachiyomi.i18n.MR
-import uy.kohesive.injekt.injectLazy
-
-/**
- * This service is used to manage the downloader. The system can decide to stop the service, in
- * which case the downloader is also stopped. It's also stopped while there's no network available.
- * While the downloader is running, a wake lock will be held.
- */
-class DownloadService : Service() {
-
- companion object {
-
- private val _isRunning = MutableStateFlow(false)
- val isRunning = _isRunning.asStateFlow()
-
- /**
- * Starts this service.
- *
- * @param context the application context.
- */
- fun start(context: Context) {
- val intent = Intent(context, DownloadService::class.java)
- ContextCompat.startForegroundService(context, intent)
- }
-
- /**
- * Stops this service.
- *
- * @param context the application context.
- */
- fun stop(context: Context) {
- context.stopService(Intent(context, DownloadService::class.java))
- }
-
- /**
- * Returns the status of the service.
- *
- * @param context the application context.
- * @return true if the service is running, false otherwise.
- */
- fun isRunning(context: Context): Boolean {
- return context.isServiceRunning(DownloadService::class.java)
- }
- }
-
- private val downloadManager: DownloadManager by injectLazy()
- private val downloadPreferences: DownloadPreferences by injectLazy()
-
- /**
- * Wake lock to prevent the device to enter sleep mode.
- */
- private lateinit var wakeLock: PowerManager.WakeLock
-
- private lateinit var scope: CoroutineScope
-
- override fun onCreate() {
- scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
- startForeground(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS, getPlaceholderNotification())
- wakeLock = acquireWakeLock(javaClass.name)
- _isRunning.value = true
- listenNetworkChanges()
- }
-
- override fun onDestroy() {
- scope.cancel()
- _isRunning.value = false
- downloadManager.downloaderStop()
- if (wakeLock.isHeld) {
- wakeLock.release()
- }
- }
-
- // Not used
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- return START_NOT_STICKY
- }
-
- // Not used
- override fun onBind(intent: Intent): IBinder? {
- return null
- }
-
- private fun downloaderStop(string: StringResource) {
- downloadManager.downloaderStop(stringResource(string))
- }
-
- private fun listenNetworkChanges() {
- ReactiveNetwork()
- .observeNetworkConnectivity(applicationContext)
- .onEach {
- withUIContext {
- if (isOnline()) {
- if (downloadPreferences.downloadOnlyOverWifi().get() && !isConnectedToWifi()) {
- downloaderStop(MR.strings.download_notifier_text_only_wifi)
- } else {
- val started = downloadManager.downloaderStart()
- if (!started) stopSelf()
- }
- } else {
- downloaderStop(MR.strings.download_notifier_no_network)
- }
- }
- }
- .catch { error ->
- withUIContext {
- logcat(LogPriority.ERROR, error)
- toast(MR.strings.download_queue_error)
- stopSelf()
- }
- }
- .launchIn(scope)
- }
-
- private fun getPlaceholderNotification(): Notification {
- return notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
- setContentTitle(stringResource(MR.strings.download_notifier_downloader_title))
- }.build()
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
index 5e58d4f6a..1dd117f41 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
@@ -161,10 +161,7 @@ class Downloader(
isPaused = false
- // Prevent recursion when DownloadService.onDestroy() calls downloader.stop()
- if (DownloadService.isRunning.value) {
- DownloadService.stop(context)
- }
+ DownloadJob.stop(context)
}
/**
@@ -310,7 +307,7 @@ class Downloader(
)
}
}
- DownloadService.start(context)
+ DownloadJob.start(context)
}
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/MigrationFlags.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/MigrationFlags.kt
index c79c111fc..3064ad60f 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/MigrationFlags.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/MigrationFlags.kt
@@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.download.DownloadCache
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
-import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
data class MigrationFlag(
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreenModel.kt
index 3cfb3ff1a..3f8896632 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreenModel.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreenModel.kt
@@ -11,12 +11,14 @@ import eu.kanade.tachiyomi.source.model.Page
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt
@@ -137,8 +139,8 @@ class DownloadQueueScreenModel(
adapter = null
}
- val isDownloaderRunning
- get() = downloadManager.isDownloaderRunning
+ val isDownloaderRunning = downloadManager.isDownloaderRunning
+ .stateIn(screenModelScope, SharingStarted.WhileSubscribed(5000), false)
fun getDownloadStatusFlow() = downloadManager.statusFlow()
fun getDownloadProgressFlow() = downloadManager.progressFlow()
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderOrientation.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderOrientation.kt
index 01073cd60..ad0e58a15 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderOrientation.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderOrientation.kt
@@ -1,57 +1,62 @@
package eu.kanade.tachiyomi.ui.reader.setting
import android.content.pm.ActivityInfo
-import androidx.annotation.DrawableRes
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ScreenLockLandscape
+import androidx.compose.material.icons.filled.ScreenLockPortrait
+import androidx.compose.material.icons.filled.ScreenRotation
+import androidx.compose.material.icons.filled.StayCurrentLandscape
+import androidx.compose.material.icons.filled.StayCurrentPortrait
+import androidx.compose.ui.graphics.vector.ImageVector
import dev.icerock.moko.resources.StringResource
-import eu.kanade.tachiyomi.R
import tachiyomi.i18n.MR
enum class ReaderOrientation(
val flag: Int,
val stringRes: StringResource,
- @DrawableRes val iconRes: Int,
+ val icon: ImageVector,
val flagValue: Int,
) {
DEFAULT(
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED,
MR.strings.label_default,
- R.drawable.ic_screen_rotation_24dp,
+ Icons.Default.ScreenRotation,
0x00000000,
),
FREE(
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED,
MR.strings.rotation_free,
- R.drawable.ic_screen_rotation_24dp,
+ Icons.Default.ScreenRotation,
0x00000008,
),
PORTRAIT(
ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT,
MR.strings.rotation_portrait,
- R.drawable.ic_stay_current_portrait_24dp,
+ Icons.Default.StayCurrentPortrait,
0x00000010,
),
LANDSCAPE(
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
MR.strings.rotation_landscape,
- R.drawable.ic_stay_current_landscape_24dp,
+ Icons.Default.StayCurrentLandscape,
0x00000018,
),
LOCKED_PORTRAIT(
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
MR.strings.rotation_force_portrait,
- R.drawable.ic_screen_lock_portrait_24dp,
+ Icons.Default.ScreenLockPortrait,
0x00000020,
),
LOCKED_LANDSCAPE(
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
MR.strings.rotation_force_landscape,
- R.drawable.ic_screen_lock_landscape_24dp,
+ Icons.Default.ScreenLockLandscape,
0x00000028,
),
REVERSE_PORTRAIT(
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
MR.strings.rotation_reverse_portrait,
- R.drawable.ic_stay_current_portrait_24dp,
+ Icons.Default.StayCurrentPortrait,
0x00000030,
),
;
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt
index 5f91bfbac..44635e98d 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt
@@ -1,13 +1,11 @@
package eu.kanade.tachiyomi.util.system
-import android.app.ActivityManager
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
-import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.os.PowerManager
@@ -68,26 +66,6 @@ fun Context.hasPermission(
val Context.powerManager: PowerManager
get() = getSystemService()!!
-/**
- * Convenience method to acquire a partial wake lock.
- */
-fun Context.acquireWakeLock(tag: String): PowerManager.WakeLock {
- val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$tag:WakeLock")
- wakeLock.acquire()
- return wakeLock
-}
-
-/**
- * Returns true if the given service class is running.
- */
-fun Context.isServiceRunning(serviceClass: Class<*>): Boolean {
- val className = serviceClass.name
- val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
- @Suppress("DEPRECATION")
- return manager.getRunningServices(Integer.MAX_VALUE)
- .any { className == it.service.className }
-}
-
fun Context.openInBrowser(url: String, forceDefaultBrowser: Boolean = false) {
this.openInBrowser(url.toUri(), forceDefaultBrowser)
}
@@ -200,11 +178,3 @@ fun Context.isInstalledFromFDroid(): Boolean {
// F-Droid builds typically disable the updater
(!BuildConfig.INCLUDE_UPDATER && !isDevFlavor)
}
-
-fun Context.getApplicationIcon(pkgName: String): Drawable? {
- return try {
- packageManager.getApplicationIcon(pkgName)
- } catch (e: PackageManager.NameNotFoundException) {
- null
- }
-}
diff --git a/app/src/main/res/color/ripple_toolbar_fainter.xml b/app/src/main/res/color/ripple_toolbar_fainter.xml
deleted file mode 100644
index e7fb4ba66..000000000
--- a/app/src/main/res/color/ripple_toolbar_fainter.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/color/slider_active_track.xml b/app/src/main/res/color/slider_active_track.xml
deleted file mode 100644
index 764d21bf3..000000000
--- a/app/src/main/res/color/slider_active_track.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/color/slider_inactive_track.xml b/app/src/main/res/color/slider_inactive_track.xml
deleted file mode 100644
index 0f624c117..000000000
--- a/app/src/main/res/color/slider_inactive_track.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_blank_24dp.xml b/app/src/main/res/drawable/ic_blank_24dp.xml
deleted file mode 100644
index 940db069f..000000000
--- a/app/src/main/res/drawable/ic_blank_24dp.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_offline_pin_24dp.xml b/app/src/main/res/drawable/ic_offline_pin_24dp.xml
deleted file mode 100644
index f3dcb3625..000000000
--- a/app/src/main/res/drawable/ic_offline_pin_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_screen_lock_landscape_24dp.xml b/app/src/main/res/drawable/ic_screen_lock_landscape_24dp.xml
deleted file mode 100644
index 6c46c7b8a..000000000
--- a/app/src/main/res/drawable/ic_screen_lock_landscape_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_screen_lock_portrait_24dp.xml b/app/src/main/res/drawable/ic_screen_lock_portrait_24dp.xml
deleted file mode 100644
index c7ee82f6b..000000000
--- a/app/src/main/res/drawable/ic_screen_lock_portrait_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_screen_rotation_24dp.xml b/app/src/main/res/drawable/ic_screen_rotation_24dp.xml
deleted file mode 100644
index 04460bd85..000000000
--- a/app/src/main/res/drawable/ic_screen_rotation_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_stay_current_landscape_24dp.xml b/app/src/main/res/drawable/ic_stay_current_landscape_24dp.xml
deleted file mode 100644
index a36b366c4..000000000
--- a/app/src/main/res/drawable/ic_stay_current_landscape_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_stay_current_portrait_24dp.xml b/app/src/main/res/drawable/ic_stay_current_portrait_24dp.xml
deleted file mode 100644
index 25ce49739..000000000
--- a/app/src/main/res/drawable/ic_stay_current_portrait_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_webview_24dp.xml b/app/src/main/res/drawable/ic_webview_24dp.xml
deleted file mode 100644
index 9400f45ed..000000000
--- a/app/src/main/res/drawable/ic_webview_24dp.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/transparent_tabs_background.xml b/app/src/main/res/drawable/transparent_tabs_background.xml
deleted file mode 100644
index a503ef698..000000000
--- a/app/src/main/res/drawable/transparent_tabs_background.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- -
-
-
-
-
- -
-
-
-
-
-
-
diff --git a/app/src/main/res/values-sw720dp/dimens.xml b/app/src/main/res/values-sw720dp/dimens.xml
deleted file mode 100644
index 5cb983dc4..000000000
--- a/app/src/main/res/values-sw720dp/dimens.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- 24dp
-
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index e82288767..af52b6c3d 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,6 +1,4 @@
- 16dp
-
16dp
12dp
diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml
index 7ef758874..cc110cdf6 100644
--- a/gradle/androidx.versions.toml
+++ b/gradle/androidx.versions.toml
@@ -20,14 +20,14 @@ lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref
lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle_version" }
lifecycle-runtimektx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle_version" }
-workmanager = "androidx.work:work-runtime-ktx:2.8.1"
+workmanager = "androidx.work:work-runtime:2.9.0"
paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" }
paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" }
-benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.1"
-test-ext = "androidx.test.ext:junit-ktx:1.2.0-alpha01"
-test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha01"
+benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.2"
+test-ext = "androidx.test.ext:junit-ktx:1.2.0-alpha02"
+test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha02"
test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0-alpha05"
[bundles]
diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml
index ce7b074bf..6bfad292f 100644
--- a/gradle/compose.versions.toml
+++ b/gradle/compose.versions.toml
@@ -1,6 +1,6 @@
[versions]
-compiler = "1.5.4"
-compose-bom = "2023.12.00-alpha02"
+compiler = "1.5.5"
+compose-bom = "2023.12.00-alpha03"
accompanist = "0.33.2-alpha"
[libraries]
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c556a1674..1e2c7017c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -15,7 +15,6 @@ android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1
google-services-gradle = "com.google.gms:google-services:4.4.0"
rxjava = "io.reactivex:rxjava:1.3.8"
-flowreactivenetwork = "ru.beryukhov:flowreactivenetwork:1.0.4"
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp_version" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp_version" }
@@ -27,7 +26,7 @@ conscrypt-android = "org.conscrypt:conscrypt-android:2.5.2"
quickjs-android = "app.cash.quickjs:quickjs-android:0.9.2"
-jsoup = "org.jsoup:jsoup:1.16.2"
+jsoup = "org.jsoup:jsoup:1.17.1"
disklrucache = "com.jakewharton:disklrucache:2.0.2"
unifile = "com.github.tachiyomiorg:unifile:7c257e1c64"
@@ -94,11 +93,11 @@ voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.
voyager-tab-navigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" }
voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" }
-ktlint = "org.jlleitschuh.gradle:ktlint-gradle:11.6.1"
+ktlint = "org.jlleitschuh.gradle:ktlint-gradle:12.0.2"
+
google-api-services-drive = "com.google.apis:google-api-services-drive:v3-rev197-1.25.0"
google-api-client-oauth = "com.google.oauth-client:google-oauth-client:1.34.1"
-
[bundles]
okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsoverhttps"]
js-engine = ["quickjs-android"]
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 3fa8f862f..1af9e0930 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME