mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-20 17:09:19 +01:00
Merge branch 'master' into sync-part-final
This commit is contained in:
commit
c75bf56b9a
2
.github/workflows/build_pull_request.yml
vendored
2
.github/workflows/build_pull_request.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
uses: actions/dependency-review-action@v3
|
uses: actions/dependency-review-action@v3
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: adopt
|
distribution: adopt
|
||||||
|
2
.github/workflows/build_push.yml
vendored
2
.github/workflows/build_push.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: adopt
|
distribution: adopt
|
||||||
|
@ -196,7 +196,6 @@ dependencies {
|
|||||||
|
|
||||||
// RxJava
|
// RxJava
|
||||||
implementation(libs.rxjava)
|
implementation(libs.rxjava)
|
||||||
implementation(libs.flowreactivenetwork)
|
|
||||||
|
|
||||||
// Networking
|
// Networking
|
||||||
implementation(libs.bundles.okhttp)
|
implementation(libs.bundles.okhttp)
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" />
|
<uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
|
||||||
<!-- Remove permission from Firebase dependency -->
|
<!-- Remove permission from Firebase dependency -->
|
||||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID"
|
<uses-permission android:name="com.google.android.gms.permission.AD_ID"
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
@ -151,10 +153,6 @@
|
|||||||
android:name=".data.notification.NotificationReceiver"
|
android:name=".data.notification.NotificationReceiver"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".data.download.DownloadService"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".extension.util.ExtensionInstallService"
|
android:name=".extension.util.ExtensionInstallService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
@ -168,6 +166,11 @@
|
|||||||
android:value="true" />
|
android:value="true" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
||||||
|
android:foregroundServiceType="dataSync"
|
||||||
|
tools:node="merge" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
|
@ -190,6 +190,7 @@ private fun ExtensionDetails(
|
|||||||
key = { it.source.id },
|
key = { it.source.id },
|
||||||
) { source ->
|
) { source ->
|
||||||
SourceSwitchPreference(
|
SourceSwitchPreference(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
source = source,
|
source = source,
|
||||||
onClickSourcePreferences = onClickSourcePreferences,
|
onClickSourcePreferences = onClickSourcePreferences,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
|
@ -58,6 +58,7 @@ private fun ExtensionFilterContent(
|
|||||||
) {
|
) {
|
||||||
items(state.languages) { language ->
|
items(state.languages) { language ->
|
||||||
SwitchPreferenceWidget(
|
SwitchPreferenceWidget(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
title = LocaleHelper.getSourceDisplayName(language, context),
|
title = LocaleHelper.getSourceDisplayName(language, context),
|
||||||
checked = language in state.enabledLanguages,
|
checked = language in state.enabledLanguages,
|
||||||
onCheckedChanged = { onClickLang(language) },
|
onCheckedChanged = { onClickLang(language) },
|
||||||
|
@ -148,12 +148,14 @@ private fun ExtensionContent(
|
|||||||
}
|
}
|
||||||
ExtensionHeader(
|
ExtensionHeader(
|
||||||
textRes = header.textRes,
|
textRes = header.textRes,
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
action = action,
|
action = action,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is ExtensionUiModel.Header.Text -> {
|
is ExtensionUiModel.Header.Text -> {
|
||||||
ExtensionHeader(
|
ExtensionHeader(
|
||||||
text = header.text,
|
text = header.text,
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,6 +167,7 @@ private fun ExtensionContent(
|
|||||||
key = { "extension-${it.hashCode()}" },
|
key = { "extension-${it.hashCode()}" },
|
||||||
) { item ->
|
) { item ->
|
||||||
ExtensionItem(
|
ExtensionItem(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
item = item,
|
item = item,
|
||||||
onClickItem = {
|
onClickItem = {
|
||||||
when (it) {
|
when (it) {
|
||||||
|
@ -132,6 +132,7 @@ private fun MigrateSourceList(
|
|||||||
key = { (source, _) -> "migrate-${source.id}" },
|
key = { (source, _) -> "migrate-${source.id}" },
|
||||||
) { (source, count) ->
|
) { (source, count) ->
|
||||||
MigrateSourceItem(
|
MigrateSourceItem(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
source = source,
|
source = source,
|
||||||
count = count,
|
count = count,
|
||||||
onClickItem = { onClickItem(source) },
|
onClickItem = { onClickItem(source) },
|
||||||
|
@ -68,6 +68,7 @@ private fun SourcesFilterContent(
|
|||||||
contentType = "source-filter-header",
|
contentType = "source-filter-header",
|
||||||
) {
|
) {
|
||||||
SourcesFilterHeader(
|
SourcesFilterHeader(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
language = language,
|
language = language,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
onClickItem = onClickLanguage,
|
onClickItem = onClickLanguage,
|
||||||
@ -80,6 +81,7 @@ private fun SourcesFilterContent(
|
|||||||
contentType = { "source-filter-item" },
|
contentType = { "source-filter-item" },
|
||||||
) { source ->
|
) { source ->
|
||||||
SourcesFilterItem(
|
SourcesFilterItem(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
source = source,
|
source = source,
|
||||||
enabled = "${source.id}" !in state.disabledSources,
|
enabled = "${source.id}" !in state.disabledSources,
|
||||||
onClickItem = onClickSource,
|
onClickItem = onClickSource,
|
||||||
|
@ -74,10 +74,12 @@ fun SourcesScreen(
|
|||||||
when (model) {
|
when (model) {
|
||||||
is SourceUiModel.Header -> {
|
is SourceUiModel.Header -> {
|
||||||
SourceHeader(
|
SourceHeader(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
language = model.language,
|
language = model.language,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is SourceUiModel.Item -> SourceItem(
|
is SourceUiModel.Item -> SourceItem(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
source = model.source,
|
source = model.source,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = onLongClickItem,
|
||||||
|
@ -107,6 +107,7 @@ private fun CategoryContent(
|
|||||||
key = { _, category -> "category-${category.id}" },
|
key = { _, category -> "category-${category.id}" },
|
||||||
) { index, category ->
|
) { index, category ->
|
||||||
CategoryListItem(
|
CategoryListItem(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
category = category,
|
category = category,
|
||||||
canMoveUp = index != 0,
|
canMoveUp = index != 0,
|
||||||
canMoveDown = index != categories.lastIndex,
|
canMoveDown = index != categories.lastIndex,
|
||||||
|
@ -123,6 +123,7 @@ private fun HistoryScreenContent(
|
|||||||
when (item) {
|
when (item) {
|
||||||
is HistoryUiModel.Header -> {
|
is HistoryUiModel.Header -> {
|
||||||
RelativeDateHeader(
|
RelativeDateHeader(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
date = item.date,
|
date = item.date,
|
||||||
relativeTime = relativeTime,
|
relativeTime = relativeTime,
|
||||||
dateFormat = dateFormat,
|
dateFormat = dateFormat,
|
||||||
@ -131,6 +132,7 @@ private fun HistoryScreenContent(
|
|||||||
is HistoryUiModel.Item -> {
|
is HistoryUiModel.Item -> {
|
||||||
val value = item.item
|
val value = item.item
|
||||||
HistoryItem(
|
HistoryItem(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
history = value,
|
history = value,
|
||||||
onClickCover = { onClickCover(value) },
|
onClickCover = { onClickCover(value) },
|
||||||
onClickResume = { onClickResume(value) },
|
onClickResume = { onClickResume(value) },
|
||||||
|
@ -13,7 +13,6 @@ import eu.kanade.presentation.more.settings.Preference
|
|||||||
import eu.kanade.presentation.more.settings.PreferenceScaffold
|
import eu.kanade.presentation.more.settings.PreferenceScaffold
|
||||||
import eu.kanade.presentation.more.settings.screen.about.AboutScreen
|
import eu.kanade.presentation.more.settings.screen.about.AboutScreen
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
import kotlinx.coroutines.guava.await
|
import kotlinx.coroutines.guava.await
|
||||||
|
@ -18,17 +18,18 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import androidx.lifecycle.asFlow
|
|
||||||
import androidx.work.WorkInfo
|
import androidx.work.WorkInfo
|
||||||
import androidx.work.WorkQuery
|
import androidx.work.WorkQuery
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.presentation.util.ioCoroutineScope
|
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.copyToClipboard
|
||||||
import eu.kanade.tachiyomi.util.system.workManager
|
import eu.kanade.tachiyomi.util.system.workManager
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
@ -39,6 +40,9 @@ import tachiyomi.i18n.MR
|
|||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.plus
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
class WorkerInfoScreen : Screen() {
|
class WorkerInfoScreen : Screen() {
|
||||||
|
|
||||||
@ -116,22 +120,19 @@ class WorkerInfoScreen : Screen() {
|
|||||||
private val workManager = context.workManager
|
private val workManager = context.workManager
|
||||||
|
|
||||||
val finished = workManager
|
val finished = workManager
|
||||||
.getWorkInfosLiveData(
|
.getWorkInfosFlow(
|
||||||
WorkQuery.fromStates(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED),
|
WorkQuery.fromStates(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED),
|
||||||
)
|
)
|
||||||
.asFlow()
|
|
||||||
.map(::constructString)
|
.map(::constructString)
|
||||||
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
|
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
|
||||||
|
|
||||||
val running = workManager
|
val running = workManager
|
||||||
.getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.RUNNING))
|
.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.RUNNING))
|
||||||
.asFlow()
|
|
||||||
.map(::constructString)
|
.map(::constructString)
|
||||||
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
|
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
|
||||||
|
|
||||||
val enqueued = workManager
|
val enqueued = workManager
|
||||||
.getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.ENQUEUED))
|
.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.ENQUEUED))
|
||||||
.asFlow()
|
|
||||||
.map(::constructString)
|
.map(::constructString)
|
||||||
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
|
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
|
||||||
|
|
||||||
@ -146,6 +147,16 @@ class WorkerInfoScreen : Screen() {
|
|||||||
appendLine(" - $it")
|
appendLine(" - $it")
|
||||||
}
|
}
|
||||||
appendLine("State: ${workInfo.state}")
|
appendLine("State: ${workInfo.state}")
|
||||||
|
if (workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||||
|
appendLine(
|
||||||
|
"Next scheduled run: ${Date(workInfo.nextScheduleTimeMillis).toDateTimestampString(
|
||||||
|
UiPreferences.dateFormat(
|
||||||
|
Injekt.get<UiPreferences>().dateFormat().get(),
|
||||||
|
),
|
||||||
|
)}",
|
||||||
|
)
|
||||||
|
appendLine("Attempt #${workInfo.runAttemptCount + 1}")
|
||||||
|
}
|
||||||
appendLine()
|
appendLine()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,6 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
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 androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.domain.manga.model.readerOrientation
|
import eu.kanade.domain.manga.model.readerOrientation
|
||||||
@ -72,7 +70,7 @@ private fun DialogContent(
|
|||||||
selected = mode
|
selected = mode
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
imageVector = ImageVector.vectorResource(mode.iconRes),
|
imageVector = mode.icon,
|
||||||
title = stringResource(mode.stringRes),
|
title = stringResource(mode.stringRes),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ fun BottomReaderBar(
|
|||||||
|
|
||||||
IconButton(onClick = onClickOrientation) {
|
IconButton(onClick = onClickOrientation) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(orientation.iconRes),
|
imageVector = orientation.icon,
|
||||||
contentDescription = stringResource(MR.strings.rotation_type),
|
contentDescription = stringResource(MR.strings.rotation_type),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ internal fun LazyListScope.updatesLastUpdatedItem(
|
|||||||
item(key = "updates-lastUpdated") {
|
item(key = "updates-lastUpdated") {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.animateItemPlacement()
|
||||||
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@ -89,12 +90,14 @@ internal fun LazyListScope.updatesUiItems(
|
|||||||
when (item) {
|
when (item) {
|
||||||
is UpdatesUiModel.Header -> {
|
is UpdatesUiModel.Header -> {
|
||||||
ListGroupHeader(
|
ListGroupHeader(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
text = item.date,
|
text = item.date,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is UpdatesUiModel.Item -> {
|
is UpdatesUiModel.Item -> {
|
||||||
val updatesItem = item.item
|
val updatesItem = item.item
|
||||||
UpdatesUiItem(
|
UpdatesUiItem(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
update = updatesItem.update,
|
update = updatesItem.update,
|
||||||
selected = updatesItem.selected,
|
selected = updatesItem.selected,
|
||||||
readProgress = updatesItem.update.lastPageRead
|
readProgress = updatesItem.update.lastPageRead
|
||||||
|
@ -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<DownloadJob>()
|
||||||
|
.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<Boolean> {
|
||||||
|
return WorkManager.getInstance(context)
|
||||||
|
.getWorkInfosForUniqueWorkLiveData(TAG)
|
||||||
|
.asFlow()
|
||||||
|
.map { list -> list.count { it.state == WorkInfo.State.RUNNING } == 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,6 +46,9 @@ class DownloadManager(
|
|||||||
*/
|
*/
|
||||||
private val downloader = Downloader(context, provider, cache)
|
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.
|
* 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)
|
fun downloaderStop(reason: String? = null) = downloader.stop(reason)
|
||||||
|
|
||||||
val isDownloaderRunning
|
val isDownloaderRunning
|
||||||
get() = DownloadService.isRunning
|
get() = DownloadJob.isRunningFlow(context)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells the downloader to begin downloads.
|
* Tells the downloader to begin downloads.
|
||||||
*/
|
*/
|
||||||
fun startDownloads() {
|
fun startDownloads() {
|
||||||
DownloadService.start(context)
|
DownloadJob.start(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,10 +107,10 @@ class DownloadManager(
|
|||||||
queue.add(0, toAdd)
|
queue.add(0, toAdd)
|
||||||
reorderQueue(queue)
|
reorderQueue(queue)
|
||||||
if (!downloader.isRunning) {
|
if (!downloader.isRunning) {
|
||||||
if (DownloadService.isRunning(context)) {
|
if (DownloadJob.isRunning(context)) {
|
||||||
downloader.start()
|
downloader.start()
|
||||||
} else {
|
} else {
|
||||||
DownloadService.start(context)
|
DownloadJob.start(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,7 +146,7 @@ class DownloadManager(
|
|||||||
addAll(0, downloads)
|
addAll(0, downloads)
|
||||||
reorderQueue(this)
|
reorderQueue(this)
|
||||||
}
|
}
|
||||||
if (!DownloadService.isRunning(context)) DownloadService.start(context)
|
if (!DownloadJob.isRunning(context)) DownloadJob.start(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -161,10 +161,7 @@ class Downloader(
|
|||||||
|
|
||||||
isPaused = false
|
isPaused = false
|
||||||
|
|
||||||
// Prevent recursion when DownloadService.onDestroy() calls downloader.stop()
|
DownloadJob.stop(context)
|
||||||
if (DownloadService.isRunning.value) {
|
|
||||||
DownloadService.stop(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -310,7 +307,7 @@ class Downloader(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DownloadService.start(context)
|
DownloadJob.start(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.data.cache.CoverCache
|
|||||||
import eu.kanade.tachiyomi.data.download.DownloadCache
|
import eu.kanade.tachiyomi.data.download.DownloadCache
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
data class MigrationFlag(
|
data class MigrationFlag(
|
||||||
|
@ -11,12 +11,14 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -137,8 +139,8 @@ class DownloadQueueScreenModel(
|
|||||||
adapter = null
|
adapter = null
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDownloaderRunning
|
val isDownloaderRunning = downloadManager.isDownloaderRunning
|
||||||
get() = downloadManager.isDownloaderRunning
|
.stateIn(screenModelScope, SharingStarted.WhileSubscribed(5000), false)
|
||||||
|
|
||||||
fun getDownloadStatusFlow() = downloadManager.statusFlow()
|
fun getDownloadStatusFlow() = downloadManager.statusFlow()
|
||||||
fun getDownloadProgressFlow() = downloadManager.progressFlow()
|
fun getDownloadProgressFlow() = downloadManager.progressFlow()
|
||||||
|
@ -1,57 +1,62 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.setting
|
package eu.kanade.tachiyomi.ui.reader.setting
|
||||||
|
|
||||||
import android.content.pm.ActivityInfo
|
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 dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
|
||||||
enum class ReaderOrientation(
|
enum class ReaderOrientation(
|
||||||
val flag: Int,
|
val flag: Int,
|
||||||
val stringRes: StringResource,
|
val stringRes: StringResource,
|
||||||
@DrawableRes val iconRes: Int,
|
val icon: ImageVector,
|
||||||
val flagValue: Int,
|
val flagValue: Int,
|
||||||
) {
|
) {
|
||||||
DEFAULT(
|
DEFAULT(
|
||||||
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED,
|
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED,
|
||||||
MR.strings.label_default,
|
MR.strings.label_default,
|
||||||
R.drawable.ic_screen_rotation_24dp,
|
Icons.Default.ScreenRotation,
|
||||||
0x00000000,
|
0x00000000,
|
||||||
),
|
),
|
||||||
FREE(
|
FREE(
|
||||||
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED,
|
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED,
|
||||||
MR.strings.rotation_free,
|
MR.strings.rotation_free,
|
||||||
R.drawable.ic_screen_rotation_24dp,
|
Icons.Default.ScreenRotation,
|
||||||
0x00000008,
|
0x00000008,
|
||||||
),
|
),
|
||||||
PORTRAIT(
|
PORTRAIT(
|
||||||
ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT,
|
ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT,
|
||||||
MR.strings.rotation_portrait,
|
MR.strings.rotation_portrait,
|
||||||
R.drawable.ic_stay_current_portrait_24dp,
|
Icons.Default.StayCurrentPortrait,
|
||||||
0x00000010,
|
0x00000010,
|
||||||
),
|
),
|
||||||
LANDSCAPE(
|
LANDSCAPE(
|
||||||
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
|
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
|
||||||
MR.strings.rotation_landscape,
|
MR.strings.rotation_landscape,
|
||||||
R.drawable.ic_stay_current_landscape_24dp,
|
Icons.Default.StayCurrentLandscape,
|
||||||
0x00000018,
|
0x00000018,
|
||||||
),
|
),
|
||||||
LOCKED_PORTRAIT(
|
LOCKED_PORTRAIT(
|
||||||
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
|
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
|
||||||
MR.strings.rotation_force_portrait,
|
MR.strings.rotation_force_portrait,
|
||||||
R.drawable.ic_screen_lock_portrait_24dp,
|
Icons.Default.ScreenLockPortrait,
|
||||||
0x00000020,
|
0x00000020,
|
||||||
),
|
),
|
||||||
LOCKED_LANDSCAPE(
|
LOCKED_LANDSCAPE(
|
||||||
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
|
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
|
||||||
MR.strings.rotation_force_landscape,
|
MR.strings.rotation_force_landscape,
|
||||||
R.drawable.ic_screen_lock_landscape_24dp,
|
Icons.Default.ScreenLockLandscape,
|
||||||
0x00000028,
|
0x00000028,
|
||||||
),
|
),
|
||||||
REVERSE_PORTRAIT(
|
REVERSE_PORTRAIT(
|
||||||
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
|
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
|
||||||
MR.strings.rotation_reverse_portrait,
|
MR.strings.rotation_reverse_portrait,
|
||||||
R.drawable.ic_stay_current_portrait_24dp,
|
Icons.Default.StayCurrentPortrait,
|
||||||
0x00000030,
|
0x00000030,
|
||||||
),
|
),
|
||||||
;
|
;
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package eu.kanade.tachiyomi.util.system
|
package eu.kanade.tachiyomi.util.system
|
||||||
|
|
||||||
import android.app.ActivityManager
|
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
@ -68,26 +66,6 @@ fun Context.hasPermission(
|
|||||||
val Context.powerManager: PowerManager
|
val Context.powerManager: PowerManager
|
||||||
get() = getSystemService()!!
|
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) {
|
fun Context.openInBrowser(url: String, forceDefaultBrowser: Boolean = false) {
|
||||||
this.openInBrowser(url.toUri(), forceDefaultBrowser)
|
this.openInBrowser(url.toUri(), forceDefaultBrowser)
|
||||||
}
|
}
|
||||||
@ -200,11 +178,3 @@ fun Context.isInstalledFromFDroid(): Boolean {
|
|||||||
// F-Droid builds typically disable the updater
|
// F-Droid builds typically disable the updater
|
||||||
(!BuildConfig.INCLUDE_UPDATER && !isDevFlavor)
|
(!BuildConfig.INCLUDE_UPDATER && !isDevFlavor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.getApplicationIcon(pkgName: String): Drawable? {
|
|
||||||
return try {
|
|
||||||
packageManager.getApplicationIcon(pkgName)
|
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<!-- 34% of 12% = ~4% -->
|
|
||||||
<item android:alpha="0.34" android:color="?attr/colorControlHighlight" />
|
|
||||||
</selector>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:color="?attr/colorPrimary" android:state_enabled="true"/>
|
|
||||||
<item android:alpha="@dimen/material_emphasis_disabled" android:color="?attr/colorOnSurface"/>
|
|
||||||
</selector>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:alpha="0.24" android:color="?attr/colorPrimary" android:state_enabled="true"/>
|
|
||||||
<item android:alpha="@dimen/material_emphasis_disabled" android:color="?attr/colorOnSurface"/>
|
|
||||||
</selector>
|
|
@ -1,7 +0,0 @@
|
|||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
<size
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp" />
|
|
||||||
<solid android:color="@android:color/transparent" />
|
|
||||||
</shape>
|
|
@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10s10,-4.5 10,-10S17.5,2 12,2zM17,18H7v-2h10V18zM10.3,14L7,10.7l1.4,-1.4l1.9,1.9l5.3,-5.3L17,7.3L10.3,14z" />
|
|
||||||
</vector>
|
|
@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:pathData="M21,5L3,5c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,7c0,-1.1 -0.9,-2 -2,-2zM19,17L5,17L5,7h14v10zM10,16h4c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1v-1c0,-1.11 -0.9,-2 -2,-2 -1.11,0 -2,0.9 -2,2v1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1zM10.8,10c0,-0.66 0.54,-1.2 1.2,-1.2 0.66,0 1.2,0.54 1.2,1.2v1h-2.4v-1z" />
|
|
||||||
</vector>
|
|
@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:pathData="M10,16h4c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1v-1c0,-1.11 -0.9,-2 -2,-2 -1.11,0 -2,0.9 -2,2v1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1zM10.8,10c0,-0.66 0.54,-1.2 1.2,-1.2 0.66,0 1.2,0.54 1.2,1.2v1h-2.4v-1zM17,1L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-2 -2,-2zM17,19L7,19L7,5h10v14z" />
|
|
||||||
</vector>
|
|
@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:pathData="M16.48,2.52c3.27,1.55 5.61,4.72 5.97,8.48h1.5C23.44,4.84 18.29,0 12,0l-0.66,0.03 3.81,3.81 1.33,-1.32zM10.23,1.75c-0.59,-0.59 -1.54,-0.59 -2.12,0L1.75,8.11c-0.59,0.59 -0.59,1.54 0,2.12l12.02,12.02c0.59,0.59 1.54,0.59 2.12,0l6.36,-6.36c0.59,-0.59 0.59,-1.54 0,-2.12L10.23,1.75zM14.83,21.19L2.81,9.17l6.36,-6.36 12.02,12.02 -6.36,6.36zM7.52,21.48C4.25,19.94 1.91,16.76 1.55,13L0.05,13C0.56,19.16 5.71,24 12,24l0.66,-0.03 -3.81,-3.81 -1.33,1.32z" />
|
|
||||||
</vector>
|
|
@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:pathData="M1.01,7L1,17c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V7c0,-1.1 -0.9,-2 -2,-2H3c-1.1,0 -1.99,0.9 -1.99,2zM19,7v10H5V7h14z" />
|
|
||||||
</vector>
|
|
@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:pathData="M17,1.01L7,1c-1.1,0 -1.99,0.9 -1.99,2v18c0,1.1 0.89,2 1.99,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z" />
|
|
||||||
</vector>
|
|
@ -1,7 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#FFF" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,19.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zM17.9,17.39c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L8,12v-2h2c0.55,0 1,-0.45 1,-1L11,7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z"/>
|
|
||||||
</vector>
|
|
@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item>
|
|
||||||
<shape
|
|
||||||
android:shape="rectangle">
|
|
||||||
<solid android:color="@android:color/transparent"/>
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
<item
|
|
||||||
android:gravity="bottom">
|
|
||||||
<shape>
|
|
||||||
<size android:height="1dp" />
|
|
||||||
<solid android:color="?attr/colorSurfaceVariant" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
</layer-list>
|
|
@ -1,3 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<dimen name="screen_edge_margin">24dp</dimen>
|
|
||||||
</resources>
|
|
@ -1,6 +1,4 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<dimen name="screen_edge_margin">16dp</dimen>
|
|
||||||
|
|
||||||
<dimen name="appwidget_background_radius">16dp</dimen>
|
<dimen name="appwidget_background_radius">16dp</dimen>
|
||||||
<dimen name="appwidget_inner_radius">12dp</dimen>
|
<dimen name="appwidget_inner_radius">12dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -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-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle_version" }
|
||||||
lifecycle-runtimektx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", 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-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" }
|
||||||
paging-compose = { module = "androidx.paging:paging-compose", 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"
|
benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.2"
|
||||||
test-ext = "androidx.test.ext:junit-ktx:1.2.0-alpha01"
|
test-ext = "androidx.test.ext:junit-ktx:1.2.0-alpha02"
|
||||||
test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha01"
|
test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha02"
|
||||||
test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0-alpha05"
|
test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0-alpha05"
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[versions]
|
[versions]
|
||||||
compiler = "1.5.4"
|
compiler = "1.5.5"
|
||||||
compose-bom = "2023.12.00-alpha02"
|
compose-bom = "2023.12.00-alpha03"
|
||||||
accompanist = "0.33.2-alpha"
|
accompanist = "0.33.2-alpha"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
@ -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"
|
google-services-gradle = "com.google.gms:google-services:4.4.0"
|
||||||
|
|
||||||
rxjava = "io.reactivex:rxjava:1.3.8"
|
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-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp_version" }
|
||||||
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", 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"
|
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"
|
disklrucache = "com.jakewharton:disklrucache:2.0.2"
|
||||||
unifile = "com.github.tachiyomiorg:unifile:7c257e1c64"
|
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-tab-navigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" }
|
||||||
voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", 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-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"
|
google-api-client-oauth = "com.google.oauth-client:google-oauth-client:1.34.1"
|
||||||
|
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsoverhttps"]
|
okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsoverhttps"]
|
||||||
js-engine = ["quickjs-android"]
|
js-engine = ["quickjs-android"]
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
Loading…
Reference in New Issue
Block a user