Compare commits

...

2 Commits

9 changed files with 43 additions and 29 deletions

View File

@@ -1,6 +1,5 @@
package org.xtimms.tokusho package org.xtimms.tokusho
import android.annotation.SuppressLint
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
@@ -10,15 +9,14 @@ import android.os.StrictMode
import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColors
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.xtimms.tokusho.core.database.MangaDatabase import org.xtimms.tokusho.core.database.MangaDatabase
import org.xtimms.tokusho.core.updates.Updater import org.xtimms.tokusho.core.updates.Updater
import org.xtimms.tokusho.crash.CrashActivity import org.xtimms.tokusho.crash.CrashActivity
import org.xtimms.tokusho.crash.GlobalExceptionHandler import org.xtimms.tokusho.crash.GlobalExceptionHandler
import org.xtimms.tokusho.utils.lang.processLifecycleScope
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider import javax.inject.Provider
@@ -31,18 +29,16 @@ class App : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
MMKV.initialize(this) MMKV.initialize(this)
context = applicationContext
packageInfo = packageManager.run { packageInfo = packageManager.run {
if (Build.VERSION.SDK_INT >= 33) getPackageInfo( if (Build.VERSION.SDK_INT >= 33) getPackageInfo(
packageName, PackageManager.PackageInfoFlags.of(0) packageName, PackageManager.PackageInfoFlags.of(0)
) else getPackageInfo(packageName, 0) ) else getPackageInfo(packageName, 0)
} }
applicationScope = CoroutineScope(SupervisorJob())
DynamicColors.applyToActivitiesIfAvailable(this) DynamicColors.applyToActivitiesIfAvailable(this)
applicationScope.launch((Dispatchers.IO)) { processLifecycleScope.launch((Dispatchers.IO)) {
try { try {
Updater.deleteOutdatedApk() Updater.deleteOutdatedApk(this@App)
} catch (_: Throwable) { } catch (_: Throwable) {
} }
@@ -76,9 +72,9 @@ class App : Application() {
companion object { companion object {
lateinit var applicationScope: CoroutineScope
lateinit var packageInfo: PackageInfo lateinit var packageInfo: PackageInfo
@Suppress("DEPRECATION")
fun getVersionReport(): String { fun getVersionReport(): String {
val versionName = packageInfo.versionName val versionName = packageInfo.versionName
val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
@@ -95,9 +91,6 @@ class App : Application() {
.append("Device information: Android $release (API ${Build.VERSION.SDK_INT})\n") .append("Device information: Android $release (API ${Build.VERSION.SDK_INT})\n")
.append("Supported ABIs: ${Build.SUPPORTED_ABIS.contentToString()}\n").toString() .append("Supported ABIs: ${Build.SUPPORTED_ABIS.contentToString()}\n").toString()
} }
@SuppressLint("StaticFieldLeak")
lateinit var context: Context
} }
} }

View File

@@ -44,6 +44,7 @@ import org.xtimms.tokusho.core.Navigation
import org.xtimms.tokusho.core.components.BottomNavBar import org.xtimms.tokusho.core.components.BottomNavBar
import org.xtimms.tokusho.core.components.TopAppBar import org.xtimms.tokusho.core.components.TopAppBar
import org.xtimms.tokusho.ui.theme.TokushoTheme import org.xtimms.tokusho.ui.theme.TokushoTheme
import org.xtimms.tokusho.utils.lang.processLifecycleScope
import javax.inject.Inject import javax.inject.Inject
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
@@ -83,7 +84,7 @@ class MainActivity : ComponentActivity() {
val localeListCompat = val localeListCompat =
if (locale.isEmpty()) LocaleListCompat.getEmptyLocaleList() if (locale.isEmpty()) LocaleListCompat.getEmptyLocaleList()
else LocaleListCompat.forLanguageTags(locale) else LocaleListCompat.forLanguageTags(locale)
App.applicationScope.launch(Dispatchers.Main) { processLifecycleScope.launch(Dispatchers.Main) {
AppCompatDelegate.setApplicationLocales(localeListCompat) AppCompatDelegate.setApplicationLocales(localeListCompat)
} }
} }

View File

@@ -13,9 +13,9 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.xtimms.shiki.ui.theme.SEED import org.xtimms.shiki.ui.theme.SEED
import org.xtimms.tokusho.App.Companion.applicationScope
import org.xtimms.tokusho.R import org.xtimms.tokusho.R
import org.xtimms.tokusho.ui.monet.PaletteStyle import org.xtimms.tokusho.ui.monet.PaletteStyle
import org.xtimms.tokusho.utils.lang.processLifecycleScope
import org.xtimms.tokusho.utils.system.languageMap import org.xtimms.tokusho.utils.system.languageMap
private const val DYNAMIC_COLOR = "dynamic_color" private const val DYNAMIC_COLOR = "dynamic_color"
@@ -85,7 +85,7 @@ object AppSettings {
fun encodeString(key: String, string: String) = key.updateString(string) fun encodeString(key: String, string: String) = key.updateString(string)
fun containsKey(key: String) = kv.containsKey(key) fun containsKey(key: String) = kv.containsKey(key)
fun isAutoUpdateEnabled() = AUTO_UPDATE.getBoolean(true) fun isAutoUpdateEnabled() = AUTO_UPDATE.getBoolean(false)
fun getLanguageConfiguration(languageNumber: Int = kv.decodeInt(LANGUAGE)) = fun getLanguageConfiguration(languageNumber: Int = kv.decodeInt(LANGUAGE)) =
languageMap.getOrElse(languageNumber) { "" } languageMap.getOrElse(languageNumber) { "" }
@@ -129,7 +129,7 @@ object AppSettings {
darkThemeValue: Int = AppSettingsStateFlow.value.darkTheme.darkThemeValue, darkThemeValue: Int = AppSettingsStateFlow.value.darkTheme.darkThemeValue,
isHighContrastModeEnabled: Boolean = AppSettingsStateFlow.value.darkTheme.isHighContrastModeEnabled isHighContrastModeEnabled: Boolean = AppSettingsStateFlow.value.darkTheme.isHighContrastModeEnabled
) { ) {
applicationScope.launch(Dispatchers.IO) { processLifecycleScope.launch(Dispatchers.IO) {
mutableAppSettingsStateFlow.update { mutableAppSettingsStateFlow.update {
it.copy( it.copy(
darkTheme = AppSettingsStateFlow.value.darkTheme.copy( darkTheme = AppSettingsStateFlow.value.darkTheme.copy(
@@ -144,7 +144,7 @@ object AppSettings {
} }
fun modifyThemeSeedColor(colorArgb: Int, paletteStyleIndex: Int) { fun modifyThemeSeedColor(colorArgb: Int, paletteStyleIndex: Int) {
applicationScope.launch(Dispatchers.IO) { processLifecycleScope.launch(Dispatchers.IO) {
mutableAppSettingsStateFlow.update { mutableAppSettingsStateFlow.update {
it.copy(seedColor = colorArgb, paletteStyleIndex = paletteStyleIndex) it.copy(seedColor = colorArgb, paletteStyleIndex = paletteStyleIndex)
} }
@@ -154,7 +154,7 @@ object AppSettings {
} }
fun switchDynamicColor(enabled: Boolean = !mutableAppSettingsStateFlow.value.isDynamicColorEnabled) { fun switchDynamicColor(enabled: Boolean = !mutableAppSettingsStateFlow.value.isDynamicColorEnabled) {
applicationScope.launch(Dispatchers.IO) { processLifecycleScope.launch(Dispatchers.IO) {
mutableAppSettingsStateFlow.update { mutableAppSettingsStateFlow.update {
it.copy(isDynamicColorEnabled = enabled) it.copy(isDynamicColorEnabled = enabled)
} }

View File

@@ -39,11 +39,14 @@ fun UpdateDialog(
onConfirmUpdate = { onConfirmUpdate = {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
runCatching { runCatching {
Updater.downloadApk(latestRelease = latestRelease) Updater.downloadApk(
context = context,
latestRelease = latestRelease
)
.collect { downloadStatus -> .collect { downloadStatus ->
currentDownloadStatus = downloadStatus currentDownloadStatus = downloadStatus
if (downloadStatus is Updater.DownloadStatus.Finished) { if (downloadStatus is Updater.DownloadStatus.Finished) {
Updater.installLatestApk() Updater.installLatestApk(context)
} }
} }
}.onFailure { }.onFailure {

View File

@@ -58,7 +58,7 @@ object Updater {
latestRelease latestRelease
} }
suspend fun checkForUpdate(context: Context = App.context): LatestRelease? { suspend fun checkForUpdate(context: Context): LatestRelease? {
val currentVersion = context.getCurrentVersion() val currentVersion = context.getCurrentVersion()
val latestRelease = getLatestRelease() val latestRelease = getLatestRelease()
val latestVersion = latestRelease.name.toVersion() val latestVersion = latestRelease.name.toVersion()
@@ -80,7 +80,7 @@ object Updater {
private fun Context.getLatestApk() = private fun Context.getLatestApk() =
File(getExternalFilesDir("apk"), "latest.apk") File(getExternalFilesDir("apk"), "latest.apk")
fun installLatestApk(context: Context = App.context) = context.run { fun installLatestApk(context: Context) = context.run {
kotlin.runCatching { kotlin.runCatching {
val contentUri = FileProvider.getUriForFile(this, getFileProvider(), getLatestApk()) val contentUri = FileProvider.getUriForFile(this, getFileProvider(), getLatestApk())
val intent = Intent(Intent.ACTION_VIEW).apply { val intent = Intent(Intent.ACTION_VIEW).apply {
@@ -96,7 +96,7 @@ object Updater {
} }
suspend fun deleteOutdatedApk( suspend fun deleteOutdatedApk(
context: Context = App.context, context: Context,
) = context.runCatching { ) = context.runCatching {
val apkFile = getLatestApk() val apkFile = getLatestApk()
if (apkFile.exists()) { if (apkFile.exists()) {
@@ -110,7 +110,7 @@ object Updater {
} }
suspend fun downloadApk( suspend fun downloadApk(
context: Context = App.context, context: Context,
latestRelease: LatestRelease latestRelease: LatestRelease
): Flow<DownloadStatus> = withContext(Dispatchers.IO) { ): Flow<DownloadStatus> = withContext(Dispatchers.IO) {
val apkVersion = context.packageManager.getPackageArchiveInfo( val apkVersion = context.packageManager.getPackageArchiveInfo(

View File

@@ -3,8 +3,10 @@ package org.xtimms.tokusho.sections.settings.about
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Description
import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Update import androidx.compose.material.icons.outlined.Update
import androidx.compose.material.icons.outlined.UpdateDisabled
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -13,6 +15,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import org.xtimms.tokusho.App import org.xtimms.tokusho.App
@@ -27,6 +30,7 @@ import org.xtimms.tokusho.utils.system.toast
const val ABOUT_DESTINATION = "about" const val ABOUT_DESTINATION = "about"
private const val repoUrl = "https://git.kotatsu.app/Xtimms/Tokusho"
const val weblate = "https://hosted.weblate.org/engage/tokusho/" const val weblate = "https://hosted.weblate.org/engage/tokusho/"
@Composable @Composable
@@ -42,6 +46,11 @@ fun AboutView(
val info = App.getVersionReport() val info = App.getVersionReport()
val versionName = packageInfo.versionName val versionName = packageInfo.versionName
val uriHandler = LocalUriHandler.current
fun openUrl(url: String) {
uriHandler.openUri(url)
}
ScaffoldWithTopAppBar( ScaffoldWithTopAppBar(
title = stringResource(R.string.about), title = stringResource(R.string.about),
navigateBack = navigateBack navigateBack = navigateBack
@@ -50,13 +59,19 @@ fun AboutView(
modifier = Modifier modifier = Modifier
.padding(padding) .padding(padding)
) { ) {
item {
PreferenceItem(
title = stringResource(R.string.readme),
description = stringResource(R.string.readme_desc),
icon = Icons.Outlined.Description,
) { openUrl(repoUrl) }
}
item { item {
PreferenceSwitchWithDivider( PreferenceSwitchWithDivider(
title = stringResource(R.string.auto_update), title = stringResource(R.string.auto_update),
description = stringResource(R.string.check_for_updates_desc), description = stringResource(R.string.check_for_updates_desc),
icon = Icons.Outlined.Update, icon = if (isAutoUpdateEnabled) Icons.Outlined.Update else Icons.Outlined.UpdateDisabled,
isChecked = isAutoUpdateEnabled, isChecked = isAutoUpdateEnabled,
isSwitchEnabled = true,
onClick = navigateToUpdatePage, onClick = navigateToUpdatePage,
onChecked = { onChecked = {
isAutoUpdateEnabled = !isAutoUpdateEnabled isAutoUpdateEnabled = !isAutoUpdateEnabled

View File

@@ -131,7 +131,7 @@ fun UpdateView(
runCatching { runCatching {
isLoading = true isLoading = true
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
Updater.checkForUpdate()?.let { Updater.checkForUpdate(context)?.let {
latestRelease = it latestRelease = it
showUpdateDialog = true showUpdateDialog = true
} }

View File

@@ -5,7 +5,7 @@ import android.widget.Toast
import androidx.annotation.StringRes import androidx.annotation.StringRes
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.xtimms.tokusho.App.Companion.applicationScope import org.xtimms.tokusho.utils.lang.processLifecycleScope
fun Context.toast(message: String) { fun Context.toast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show() Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
@@ -16,7 +16,7 @@ fun Context.toast(@StringRes stringRes: Int) {
} }
fun Context.suspendToast(@StringRes stringRes: Int) { fun Context.suspendToast(@StringRes stringRes: Int) {
applicationScope.launch(Dispatchers.Main) { processLifecycleScope.launch(Dispatchers.Main) {
toast(getString(stringRes)) toast(getString(stringRes))
} }
} }

View File

@@ -25,7 +25,7 @@
<string name="off">Off</string> <string name="off">Off</string>
<string name="open_settings">Open settings</string> <string name="open_settings">Open settings</string>
<string name="auto_update">Automatic updates</string> <string name="auto_update">Automatic updates</string>
<string name="check_for_updates_desc">Automatically check for the latest version on GitHub</string> <string name="check_for_updates_desc">Automatically check for the latest version on Gitea</string>
<string name="version">Version</string> <string name="version">Version</string>
<string name="info_copied">Info copied to clipboard</string> <string name="info_copied">Info copied to clipboard</string>
<string name="enable_auto_update">Enable auto update</string> <string name="enable_auto_update">Enable auto update</string>
@@ -57,4 +57,6 @@
<string name="no_results_found">No results found</string> <string name="no_results_found">No results found</string>
<string name="error_no_match">No matches</string> <string name="error_no_match">No matches</string>
<string name="information_no_manga_category">No manga in this category</string> <string name="information_no_manga_category">No manga in this category</string>
<string name="readme">README</string>
<string name="readme_desc">Check the Gitea repository and the README</string>
</resources> </resources>