tachiyomi/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt
arkon 0d1bced122 Replace remaining Android-specific strings
Also renaming the helper composables so it's a bit easier to find/replace everything
in forks.
2023-11-18 19:41:33 -05:00

296 lines
12 KiB
Kotlin

package eu.kanade.presentation.more.settings.screen.about
import android.content.Context
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Public
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.dp
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.more.LogoHeader
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.util.LocalBackPress
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.release.interactor.GetApplicationRelease
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.LinkIcon
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.icons.CustomIcons
import tachiyomi.presentation.core.icons.Discord
import tachiyomi.presentation.core.icons.Facebook
import tachiyomi.presentation.core.icons.Github
import tachiyomi.presentation.core.icons.Reddit
import tachiyomi.presentation.core.icons.X
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
object AboutScreen : Screen() {
@Composable
override fun Content() {
val scope = rememberCoroutineScope()
val context = LocalContext.current
val uriHandler = LocalUriHandler.current
val handleBack = LocalBackPress.current
val navigator = LocalNavigator.currentOrThrow
var isCheckingUpdates by remember { mutableStateOf(false) }
Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = stringResource(MR.strings.pref_category_about),
navigateUp = if (handleBack != null) handleBack::invoke else null,
scrollBehavior = scrollBehavior,
)
},
) { contentPadding ->
ScrollbarLazyColumn(
contentPadding = contentPadding,
) {
item {
LogoHeader()
}
item {
TextPreferenceWidget(
title = stringResource(MR.strings.version),
subtitle = getVersionName(withBuildDate = true),
onPreferenceClick = {
val deviceInfo = CrashLogUtil(context).getDebugInfo()
context.copyToClipboard("Debug information", deviceInfo)
},
)
}
if (BuildConfig.INCLUDE_UPDATER) {
item {
TextPreferenceWidget(
title = stringResource(MR.strings.check_for_updates),
widget = {
AnimatedVisibility(visible = isCheckingUpdates) {
CircularProgressIndicator(
modifier = Modifier.size(28.dp),
strokeWidth = 3.dp,
)
}
},
onPreferenceClick = {
if (!isCheckingUpdates) {
scope.launch {
isCheckingUpdates = true
checkVersion(
context = context,
onAvailableUpdate = { result ->
val updateScreen = NewUpdateScreen(
versionName = result.release.version,
changelogInfo = result.release.info,
releaseLink = result.release.releaseLink,
downloadLink = result.release.getDownloadLink(),
)
navigator.push(updateScreen)
},
onFinish = {
isCheckingUpdates = false
},
)
}
}
},
)
}
}
if (!BuildConfig.DEBUG) {
item {
TextPreferenceWidget(
title = stringResource(MR.strings.whats_new),
onPreferenceClick = { uriHandler.openUri(RELEASE_URL) },
)
}
}
item {
TextPreferenceWidget(
title = stringResource(MR.strings.help_translate),
onPreferenceClick = { uriHandler.openUri("https://tachiyomi.org/docs/contribute#translation") },
)
}
item {
TextPreferenceWidget(
title = stringResource(MR.strings.licenses),
onPreferenceClick = { navigator.push(OpenSourceLicensesScreen()) },
)
}
item {
TextPreferenceWidget(
title = stringResource(MR.strings.privacy_policy),
onPreferenceClick = { uriHandler.openUri("https://tachiyomi.org/privacy/") },
)
}
item {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.Center,
) {
LinkIcon(
label = stringResource(MR.strings.website),
icon = Icons.Outlined.Public,
url = "https://tachiyomi.org",
)
LinkIcon(
label = "Discord",
icon = CustomIcons.Discord,
url = "https://discord.gg/tachiyomi",
)
LinkIcon(
label = "X",
icon = CustomIcons.X,
url = "https://x.com/tachiyomiorg",
)
LinkIcon(
label = "Facebook",
icon = CustomIcons.Facebook,
url = "https://facebook.com/tachiyomiorg",
)
LinkIcon(
label = "Reddit",
icon = CustomIcons.Reddit,
url = "https://www.reddit.com/r/Tachiyomi",
)
LinkIcon(
label = "GitHub",
icon = CustomIcons.Github,
url = "https://github.com/tachiyomiorg",
)
}
}
}
}
}
/**
* Checks version and shows a user prompt if an update is available.
*/
private suspend fun checkVersion(
context: Context,
onAvailableUpdate: (GetApplicationRelease.Result.NewUpdate) -> Unit,
onFinish: () -> Unit,
) {
val updateChecker = AppUpdateChecker()
withUIContext {
try {
when (val result = withIOContext { updateChecker.checkForUpdate(context, forceCheck = true) }) {
is GetApplicationRelease.Result.NewUpdate -> {
onAvailableUpdate(result)
}
is GetApplicationRelease.Result.NoNewUpdate -> {
context.toast(MR.strings.update_check_no_new_updates)
}
is GetApplicationRelease.Result.OsTooOld -> {
context.toast(MR.strings.update_check_eol)
}
else -> {}
}
} catch (e: Exception) {
context.toast(e.message)
logcat(LogPriority.ERROR, e)
} finally {
onFinish()
}
}
}
fun getVersionName(withBuildDate: Boolean): String {
return when {
BuildConfig.DEBUG -> {
"Debug ${BuildConfig.COMMIT_SHA}".let {
if (withBuildDate) {
"$it (${getFormattedBuildTime()})"
} else {
it
}
}
}
BuildConfig.PREVIEW -> {
"Preview r${BuildConfig.COMMIT_COUNT}".let {
if (withBuildDate) {
"$it (${BuildConfig.COMMIT_SHA}, ${getFormattedBuildTime()})"
} else {
"$it (${BuildConfig.COMMIT_SHA})"
}
}
}
else -> {
"Stable ${BuildConfig.VERSION_NAME}".let {
if (withBuildDate) {
"$it (${getFormattedBuildTime()})"
} else {
it
}
}
}
}
}
internal fun getFormattedBuildTime(): String {
return try {
val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US)
inputDf.timeZone = TimeZone.getTimeZone("UTC")
val buildTime = inputDf.parse(BuildConfig.BUILD_TIME)
val outputDf = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM,
DateFormat.SHORT,
Locale.getDefault(),
)
outputDf.timeZone = TimeZone.getDefault()
buildTime!!.toDateTimestampString(UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat().get()))
} catch (e: Exception) {
BuildConfig.BUILD_TIME
}
}
}