mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
refactor: Use Compose for About page
This commit is contained in:
parent
37f1f0e330
commit
cab40214d2
19 changed files with 571 additions and 301 deletions
|
@ -5,9 +5,17 @@ import androidx.compose.runtime.State
|
|||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import eu.kanade.tachiyomi.core.preference.Preference
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun <T> Preference<T>.collectAsState(): State<T> {
|
||||
val flow = remember(this) { changes() }
|
||||
return flow.collectAsState(initial = get())
|
||||
}
|
||||
|
||||
fun String.asDateFormat(): DateFormat = when (this) {
|
||||
"" -> DateFormat.getDateInstance(DateFormat.SHORT)
|
||||
else -> SimpleDateFormat(this, Locale.getDefault())
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.BuildConfig
|
|||
import eu.kanade.tachiyomi.core.preference.Preference
|
||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
||||
import eu.kanade.tachiyomi.core.preference.getEnum
|
||||
import eu.kanade.tachiyomi.core.storage.preference.asDateFormat
|
||||
import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob
|
||||
import eu.kanade.tachiyomi.domain.manga.models.Manga
|
||||
import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder
|
||||
|
@ -19,13 +20,12 @@ import eu.kanade.tachiyomi.ui.reader.settings.ReadingModeType
|
|||
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation
|
||||
import eu.kanade.tachiyomi.ui.recents.RecentsPresenter
|
||||
import eu.kanade.tachiyomi.util.system.Themes
|
||||
import java.text.DateFormat
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
||||
|
||||
|
@ -187,10 +187,10 @@ class PreferencesHelper(val context: Context, val preferenceStore: PreferenceSto
|
|||
|
||||
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", "POINT_10")
|
||||
|
||||
fun dateFormat(format: String = preferenceStore.getString(Keys.dateFormat, "").get()): DateFormat = when (format) {
|
||||
"" -> DateFormat.getDateInstance(DateFormat.SHORT)
|
||||
else -> SimpleDateFormat(format, Locale.getDefault())
|
||||
}
|
||||
fun dateFormatRaw() = preferenceStore.getString(Keys.dateFormat, "")
|
||||
|
||||
@Deprecated("Use dateFormatRaw().get().asDateFormat() instead")
|
||||
fun dateFormat(format: String = dateFormatRaw().get()): DateFormat = format.asDateFormat()
|
||||
|
||||
fun appLanguage() = preferenceStore.getString("app_language", "")
|
||||
|
||||
|
|
|
@ -1,173 +1,53 @@
|
|||
package eu.kanade.tachiyomi.ui.more
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.net.toUri
|
||||
import androidx.preference.PreferenceScreen
|
||||
import co.touchlab.kermit.Logger
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import cafe.adriel.voyager.core.stack.StackEvent
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.transitions.ScreenTransition
|
||||
import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateNotifier
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
||||
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import eu.kanade.tachiyomi.ui.setting.SettingsLegacyController
|
||||
import eu.kanade.tachiyomi.ui.setting.add
|
||||
import eu.kanade.tachiyomi.ui.setting.onClick
|
||||
import eu.kanade.tachiyomi.ui.setting.preference
|
||||
import eu.kanade.tachiyomi.ui.setting.preferenceCategory
|
||||
import eu.kanade.tachiyomi.ui.setting.titleMRes
|
||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
||||
import eu.kanade.tachiyomi.util.lang.toTimestampString
|
||||
import eu.kanade.tachiyomi.util.system.isOnline
|
||||
import eu.kanade.tachiyomi.util.system.localeContext
|
||||
import eu.kanade.tachiyomi.util.compose.LocalAlertDialog
|
||||
import eu.kanade.tachiyomi.util.compose.LocalBackPress
|
||||
import eu.kanade.tachiyomi.util.system.materialAlertDialog
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.setNegativeButton
|
||||
import eu.kanade.tachiyomi.util.view.setPositiveButton
|
||||
import eu.kanade.tachiyomi.util.view.setTitle
|
||||
import eu.kanade.tachiyomi.util.view.snack
|
||||
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
||||
import io.noties.markwon.Markwon
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import soup.compose.material.motion.animation.materialSharedAxisZ
|
||||
import yokai.domain.ComposableAlertDialog
|
||||
import yokai.i18n.MR
|
||||
import yokai.util.lang.getString
|
||||
import java.text.DateFormat
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import yokai.presentation.settings.screen.about.AboutScreen
|
||||
import android.R as AR
|
||||
|
||||
class AboutController : SettingsLegacyController() {
|
||||
class AboutController : BaseComposeController() {
|
||||
|
||||
/**
|
||||
* Checks for new releases
|
||||
*/
|
||||
private val updateChecker by lazy { AppUpdateChecker() }
|
||||
|
||||
private val dateFormat: DateFormat by lazy {
|
||||
preferences.dateFormat()
|
||||
}
|
||||
|
||||
private val isUpdaterEnabled = BuildConfig.INCLUDE_UPDATER
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
||||
titleMRes = MR.strings.about
|
||||
|
||||
preference {
|
||||
key = "pref_whats_new"
|
||||
titleMRes = MR.strings.whats_new_this_release
|
||||
onClick {
|
||||
val intent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
if (BuildConfig.DEBUG) {
|
||||
"https://github.com/null2264/yokai/commits/master"
|
||||
} else {
|
||||
RELEASE_URL
|
||||
}.toUri(),
|
||||
)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
if (isUpdaterEnabled) {
|
||||
preference {
|
||||
key = "pref_check_for_updates"
|
||||
titleMRes = MR.strings.check_for_updates
|
||||
onClick {
|
||||
if (activity!!.isOnline()) {
|
||||
checkVersion()
|
||||
} else {
|
||||
activity!!.toast(MR.strings.no_network_connection)
|
||||
}
|
||||
@Composable
|
||||
override fun ScreenContent() {
|
||||
Navigator(
|
||||
screen = AboutScreen { body, url, isBeta ->
|
||||
NewUpdateDialogController(body, url, isBeta).showDialog(router)
|
||||
},
|
||||
content = {
|
||||
CompositionLocalProvider(
|
||||
LocalAlertDialog provides ComposableAlertDialog(null),
|
||||
LocalBackPress provides router::handleBack,
|
||||
) {
|
||||
ScreenTransition(
|
||||
navigator = it,
|
||||
// FIXME: Mimic J2K's Conductor transition
|
||||
transition = { materialSharedAxisZ(forward = it.lastEvent != StackEvent.Pop) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
preference {
|
||||
key = "pref_version"
|
||||
titleMRes = MR.strings.version
|
||||
summary = if (BuildConfig.DEBUG || BuildConfig.NIGHTLY) {
|
||||
"r" + BuildConfig.COMMIT_COUNT
|
||||
} else {
|
||||
BuildConfig.VERSION_NAME
|
||||
}
|
||||
|
||||
onClick {
|
||||
activity?.let {
|
||||
val deviceInfo = CrashLogUtil(it.localeContext).getDebugInfo()
|
||||
val clipboard = it.getSystemService<ClipboardManager>()!!
|
||||
val appInfo = it.getString(MR.strings.app_info)
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText(appInfo, deviceInfo))
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
view?.snack(context.getString(MR.strings._copied_to_clipboard, appInfo))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
preference {
|
||||
key = "pref_build_time"
|
||||
titleMRes = MR.strings.build_time
|
||||
summary = getFormattedBuildTime(dateFormat)
|
||||
}
|
||||
|
||||
preferenceCategory {
|
||||
preference {
|
||||
key = "pref_oss"
|
||||
titleMRes = MR.strings.open_source_licenses
|
||||
|
||||
onClick {
|
||||
router.pushController(AboutLicenseController().withFadeTransaction())
|
||||
}
|
||||
}
|
||||
}
|
||||
add(AboutLinksPreference(context))
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks version and shows a user prompt if an update is available.
|
||||
*/
|
||||
private fun checkVersion() {
|
||||
val activity = activity ?: return
|
||||
|
||||
activity.toast(MR.strings.searching_for_updates)
|
||||
viewScope.launch {
|
||||
val result = try {
|
||||
updateChecker.checkForUpdate(activity, true)
|
||||
} catch (error: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
activity.toast(error.message)
|
||||
Logger.e(error) { "Couldn't check new update" }
|
||||
}
|
||||
}
|
||||
when (result) {
|
||||
is AppUpdateResult.NewUpdate -> {
|
||||
val body = result.release.info
|
||||
val url = result.release.downloadLink
|
||||
val isBeta = result.release.preRelease == true
|
||||
|
||||
// Create confirmation window
|
||||
withContext(Dispatchers.Main) {
|
||||
AppUpdateNotifier.releasePageUrl = result.release.releaseLink
|
||||
NewUpdateDialogController(body, url, isBeta).showDialog(router)
|
||||
}
|
||||
}
|
||||
is AppUpdateResult.NoNewUpdate -> {
|
||||
withContext(Dispatchers.Main) {
|
||||
activity.toast(MR.strings.no_new_updates_available)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundle) {
|
||||
|
@ -220,19 +100,4 @@ class AboutController : SettingsLegacyController() {
|
|||
const val IS_BETA = "NewUpdateDialogController.is_beta"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getFormattedBuildTime(dateFormat: DateFormat): String {
|
||||
try {
|
||||
val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.getDefault())
|
||||
inputDf.timeZone = TimeZone.getTimeZone("UTC")
|
||||
val buildTime =
|
||||
inputDf.parse(BuildConfig.BUILD_TIME) ?: return BuildConfig.BUILD_TIME
|
||||
|
||||
return buildTime.toTimestampString(dateFormat)
|
||||
} catch (e: ParseException) {
|
||||
return BuildConfig.BUILD_TIME
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.more
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import cafe.adriel.voyager.core.stack.StackEvent
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.transitions.ScreenTransition
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController
|
||||
import eu.kanade.tachiyomi.util.compose.LocalBackPress
|
||||
import soup.compose.material.motion.animation.materialSharedAxisZ
|
||||
|
||||
class AboutLicenseController : BaseComposeController() {
|
||||
@Composable
|
||||
override fun ScreenContent() {
|
||||
Navigator(
|
||||
screen = AboutLicenseScreen(),
|
||||
content = {
|
||||
CompositionLocalProvider(LocalBackPress provides router::handleBack) {
|
||||
ScreenTransition(
|
||||
navigator = it,
|
||||
transition = { materialSharedAxisZ(forward = it.lastEvent != StackEvent.Pop) },
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.more
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceViewHolder
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
import eu.kanade.tachiyomi.util.view.compatToolTipText
|
||||
|
||||
class AboutLinksPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
Preference(context, attrs) {
|
||||
|
||||
init {
|
||||
layoutResource = R.layout.pref_about_links
|
||||
isSelectable = false
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
||||
super.onBindViewHolder(holder)
|
||||
|
||||
/*
|
||||
(holder.itemView as LinearLayout).apply {
|
||||
checkHeightThen {
|
||||
val childCount = (this.getChildAt(0) as ViewGroup).childCount
|
||||
val childCount2 = (this.getChildAt(1) as ViewGroup).childCount
|
||||
val fullCount = childCount + childCount2
|
||||
orientation =
|
||||
if (width >= (56 * fullCount).dpToPx) LinearLayout.HORIZONTAL else LinearLayout.VERTICAL
|
||||
}
|
||||
}
|
||||
*/
|
||||
holder.findViewById(R.id.btn_website).apply {
|
||||
compatToolTipText = (contentDescription.toString())
|
||||
setOnClickListener { context.openInBrowser("https://mihon.app") }
|
||||
}
|
||||
holder.findViewById(R.id.btn_discord).apply {
|
||||
compatToolTipText = (contentDescription.toString())
|
||||
setOnClickListener { context.openInBrowser("https://discord.gg/mihon") }
|
||||
}
|
||||
holder.findViewById(R.id.btn_github).apply {
|
||||
compatToolTipText = (contentDescription.toString())
|
||||
setOnClickListener { context.openInBrowser("https://github.com/null2264/yokai") }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,15 +4,15 @@ import android.os.Build
|
|||
import androidx.preference.PreferenceScreen
|
||||
import androidx.webkit.WebViewCompat
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.ui.more.AboutController
|
||||
import eu.kanade.tachiyomi.ui.setting.SettingsLegacyController
|
||||
import eu.kanade.tachiyomi.ui.setting.onClick
|
||||
import eu.kanade.tachiyomi.ui.setting.preference
|
||||
import eu.kanade.tachiyomi.ui.setting.preferenceCategory
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
||||
import yokai.i18n.MR
|
||||
import java.text.DateFormat
|
||||
import yokai.i18n.MR
|
||||
import yokai.presentation.settings.screen.about.getFormattedBuildTime
|
||||
|
||||
class DebugController : SettingsLegacyController() {
|
||||
|
||||
|
@ -49,7 +49,7 @@ class DebugController : SettingsLegacyController() {
|
|||
preference {
|
||||
key = "pref_build_time"
|
||||
title = "Build Time"
|
||||
summary = AboutController.getFormattedBuildTime(dateFormat)
|
||||
summary = getFormattedBuildTime(dateFormat)
|
||||
}
|
||||
preference {
|
||||
key = "pref_webview_version"
|
||||
|
|
|
@ -69,7 +69,8 @@ import kotlinx.coroutines.launch
|
|||
import uy.kohesive.injekt.injectLazy
|
||||
import yokai.domain.manga.interactor.GetManga
|
||||
import yokai.i18n.MR
|
||||
import yokai.presentation.component.icons.LocalSource
|
||||
import yokai.presentation.core.icons.CustomIcons
|
||||
import yokai.presentation.core.icons.LocalSource
|
||||
import yokai.util.lang.getString
|
||||
|
||||
/**
|
||||
|
@ -610,7 +611,7 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||
if (presenter.source is HttpSource) {
|
||||
Icons.Filled.ExploreOff
|
||||
} else {
|
||||
Icons.Filled.LocalSource
|
||||
CustomIcons.LocalSource
|
||||
},
|
||||
message,
|
||||
actions,
|
||||
|
|
|
@ -23,7 +23,6 @@ import androidx.compose.ui.graphics.toArgb
|
|||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import yokai.i18n.MR
|
||||
|
@ -35,14 +34,16 @@ fun YokaiScaffold(
|
|||
onNavigationIconClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
title: String = "",
|
||||
scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(state = rememberTopAppBarState()),
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||
fab: @Composable () -> Unit = {},
|
||||
navigationIcon: ImageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
navigationIconLabel: String = stringResource(MR.strings.back),
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
appBarType: AppBarType = AppBarType.LARGE,
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
content: @Composable (PaddingValues) -> Unit,
|
||||
) {
|
||||
val scrollBehaviorOrDefault = scrollBehavior ?: TopAppBarDefaults.enterAlwaysScrollBehavior(state = rememberTopAppBarState())
|
||||
val view = LocalView.current
|
||||
val useDarkIcons = MaterialTheme.colorScheme.surface.luminance() > .5
|
||||
val (color, scrolledColor) = getTopAppBarColor(title)
|
||||
|
@ -56,7 +57,7 @@ fun YokaiScaffold(
|
|||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
modifier = modifier.nestedScroll(scrollBehaviorOrDefault.nestedScrollConnection),
|
||||
floatingActionButton = fab,
|
||||
topBar = {
|
||||
when (appBarType) {
|
||||
|
@ -76,7 +77,7 @@ fun YokaiScaffold(
|
|||
buttonClicked = onNavigationIconClicked,
|
||||
)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
scrollBehavior = scrollBehaviorOrDefault,
|
||||
actions = actions,
|
||||
)
|
||||
AppBarType.LARGE -> ExpandedAppBar(
|
||||
|
@ -95,11 +96,12 @@ fun YokaiScaffold(
|
|||
buttonClicked = onNavigationIconClicked,
|
||||
)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
scrollBehavior = scrollBehaviorOrDefault,
|
||||
actions = actions,
|
||||
)
|
||||
}
|
||||
},
|
||||
snackbarHost = snackbarHost,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
package yokai.presentation.component.icons
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
private var _localSource: ImageVector? = null
|
||||
|
||||
val Icons.Filled.LocalSource: ImageVector get() {
|
||||
if (_localSource != null) return _localSource!!
|
||||
_localSource = ImageVector.Builder(
|
||||
name = "localSource",
|
||||
defaultWidth = 24.0.dp,
|
||||
defaultHeight = 24.0.dp,
|
||||
viewportWidth = 24.0f,
|
||||
viewportHeight = 24.0f,
|
||||
).apply {
|
||||
path(fill = SolidColor(Color.Black)) {
|
||||
moveTo(12f, 11.55f)
|
||||
curveTo(9.64f, 9.35f, 6.48f, 8f, 3f, 8f)
|
||||
verticalLineToRelative(11f)
|
||||
curveToRelative(3.48f, 0f, 6.64f, 1.35f, 9f, 3.55f)
|
||||
curveToRelative(2.36f, -2.19f, 5.52f, -3.55f, 9f, -3.55f)
|
||||
verticalLineTo(8f)
|
||||
curveToRelative(-3.48f, 0f, -6.64f, 1.35f, -9f, 3.55f)
|
||||
close()
|
||||
moveTo(12f, 8f)
|
||||
curveToRelative(1.66f, 0f, 3f, -1.34f, 3f, -3f)
|
||||
reflectiveCurveToRelative(-1.34f, -3f, -3f, -3f)
|
||||
reflectiveCurveToRelative(-3f, 1.34f, -3f, 3f)
|
||||
reflectiveCurveToRelative(1.34f, 3f, 3f, 3f)
|
||||
close()
|
||||
}
|
||||
}.build()
|
||||
return _localSource!!
|
||||
}
|
|
@ -7,6 +7,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
|
@ -35,11 +36,12 @@ fun SettingsScaffold(
|
|||
title: String,
|
||||
appBarType: AppBarType? = null,
|
||||
appBarActions: @Composable RowScope.() -> Unit = {},
|
||||
itemsProvider: @Composable () -> List<Preference>,
|
||||
appBarScrollBehavior: TopAppBarScrollBehavior? = null,
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
content: @Composable (PaddingValues) -> Unit,
|
||||
) {
|
||||
val preferences: PreferencesHelper by injectLazy()
|
||||
val useLargeAppBar by preferences.useLargeToolbar().collectAsState()
|
||||
val listState = rememberLazyListState()
|
||||
val onBackPress = LocalBackPress.currentOrThrow
|
||||
val alertDialog = LocalAlertDialog.currentOrThrow
|
||||
|
||||
|
@ -48,14 +50,34 @@ fun SettingsScaffold(
|
|||
title = title,
|
||||
appBarType = appBarType ?: if (useLargeAppBar) AppBarType.LARGE else AppBarType.SMALL,
|
||||
actions = appBarActions,
|
||||
scrollBehavior = enterAlwaysCollapsedScrollBehavior(
|
||||
scrollBehavior = appBarScrollBehavior,
|
||||
snackbarHost = snackbarHost,
|
||||
) { innerPadding ->
|
||||
alertDialog.content?.let { it() }
|
||||
|
||||
content(innerPadding)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsScaffold(
|
||||
title: String,
|
||||
appBarType: AppBarType? = null,
|
||||
appBarActions: @Composable RowScope.() -> Unit = {},
|
||||
itemsProvider: @Composable () -> List<Preference>,
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
SettingsScaffold(
|
||||
title = title,
|
||||
appBarType = appBarType,
|
||||
appBarActions = appBarActions,
|
||||
appBarScrollBehavior = enterAlwaysCollapsedScrollBehavior(
|
||||
state = rememberTopAppBarState(),
|
||||
canScroll = { listState.canScrollForward || listState.canScrollBackward },
|
||||
isAtTop = { listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0 },
|
||||
),
|
||||
) { innerPadding ->
|
||||
alertDialog.content?.let { it() }
|
||||
|
||||
PreferenceScreen(
|
||||
items = itemsProvider(),
|
||||
listState = listState,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package eu.kanade.tachiyomi.ui.more
|
||||
package yokai.presentation.settings.screen.about
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
|
@ -1,4 +1,4 @@
|
|||
package eu.kanade.tachiyomi.ui.more
|
||||
package yokai.presentation.settings.screen.about
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
|
@ -0,0 +1,239 @@
|
|||
package yokai.presentation.settings.screen.about
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Public
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.getSystemService
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import co.touchlab.kermit.Logger
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.core.storage.preference.asDateFormat
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateNotifier
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
||||
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
|
||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
||||
import eu.kanade.tachiyomi.util.compose.currentOrThrow
|
||||
import eu.kanade.tachiyomi.util.lang.toTimestampString
|
||||
import eu.kanade.tachiyomi.util.system.isOnline
|
||||
import eu.kanade.tachiyomi.util.system.localeContext
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.system.withUIContext
|
||||
import java.text.DateFormat
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import kotlinx.coroutines.launch
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import yokai.i18n.MR
|
||||
import yokai.presentation.component.preference.widget.TextPreferenceWidget
|
||||
import yokai.presentation.core.components.LinkIcon
|
||||
import yokai.presentation.core.enterAlwaysCollapsedScrollBehavior
|
||||
import yokai.presentation.core.icons.CustomIcons
|
||||
import yokai.presentation.core.icons.Discord
|
||||
import yokai.presentation.core.icons.GitHub
|
||||
import yokai.presentation.settings.SettingsScaffold
|
||||
import yokai.util.Screen
|
||||
import yokai.util.lang.getString
|
||||
|
||||
class AboutScreen(private val showNewUpdateDialog: (String, String, Boolean?) -> Unit) : Screen() {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val scope = rememberCoroutineScope()
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
val preferences: PreferencesHelper by injectLazy()
|
||||
val dateFormat by lazy { preferences.dateFormatRaw().get().asDateFormat() }
|
||||
|
||||
SettingsScaffold(
|
||||
title = stringResource(MR.strings.about),
|
||||
snackbarHost = {
|
||||
SnackbarHost(hostState = snackbarHostState)
|
||||
},
|
||||
appBarScrollBehavior = enterAlwaysCollapsedScrollBehavior(
|
||||
state = rememberTopAppBarState(),
|
||||
canScroll = { listState.canScrollForward || listState.canScrollBackward },
|
||||
isAtTop = { listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0 },
|
||||
),
|
||||
content = { contentPadding ->
|
||||
LazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
state = listState,
|
||||
) {
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.whats_new_this_release),
|
||||
onPreferenceClick = {
|
||||
uriHandler.openUri(if (BuildConfig.DEBUG) SOURCE_URL else RELEASE_URL)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (BuildConfig.INCLUDE_UPDATER) {
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.check_for_updates),
|
||||
onPreferenceClick = {
|
||||
if (context.isOnline()) {
|
||||
scope.launch {
|
||||
context.checkVersion()
|
||||
}
|
||||
} else {
|
||||
context.toast(MR.strings.no_network_connection)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.version),
|
||||
subtitle = getVersionName(),
|
||||
onPreferenceClick = {
|
||||
val deviceInfo = CrashLogUtil(context.localeContext).getDebugInfo()
|
||||
val clipboard = context.getSystemService<ClipboardManager>()!!
|
||||
val appInfo = context.getString(MR.strings.app_info)
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText(appInfo, deviceInfo))
|
||||
scope.launch {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
snackbarHostState.showSnackbar(
|
||||
message = context.getString(MR.strings._copied_to_clipboard, appInfo),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.version),
|
||||
subtitle = getFormattedBuildTime(dateFormat),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
HorizontalDivider()
|
||||
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.open_source_licenses),
|
||||
onPreferenceClick = { navigator.push(AboutLicenseScreen()) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
FlowRow(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
LinkIcon(
|
||||
label = "Website",
|
||||
icon = Icons.Outlined.Public,
|
||||
url = "https://mihon.app",
|
||||
)
|
||||
LinkIcon(
|
||||
label = "Discord",
|
||||
icon = CustomIcons.Discord,
|
||||
url = "https://discord.gg/mihon",
|
||||
)
|
||||
LinkIcon(
|
||||
label = "GitHub",
|
||||
icon = CustomIcons.GitHub,
|
||||
url = "https://github.com/null2264/yokai",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun getVersionName(): String = when {
|
||||
BuildConfig.DEBUG -> "Debug ${BuildConfig.COMMIT_SHA}"
|
||||
BuildConfig.NIGHTLY -> "Nightly ${BuildConfig.COMMIT_COUNT} (${BuildConfig.COMMIT_SHA})"
|
||||
else -> "Release ${BuildConfig.VERSION_NAME}"
|
||||
}
|
||||
|
||||
private suspend fun Context.checkVersion() {
|
||||
val updateChecker = AppUpdateChecker()
|
||||
|
||||
withUIContext { toast(MR.strings.searching_for_updates) }
|
||||
|
||||
val result = try {
|
||||
updateChecker.checkForUpdate(this, true)
|
||||
} catch (error: Exception) {
|
||||
withUIContext {
|
||||
toast(error.message)
|
||||
Logger.e(error) { "Couldn't check new update" }
|
||||
}
|
||||
}
|
||||
when (result) {
|
||||
is AppUpdateResult.NewUpdate -> {
|
||||
val body = result.release.info
|
||||
val url = result.release.downloadLink
|
||||
val isBeta = result.release.preRelease == true
|
||||
|
||||
// Create confirmation window
|
||||
withUIContext {
|
||||
AppUpdateNotifier.releasePageUrl = result.release.releaseLink
|
||||
showNewUpdateDialog(body, url, isBeta)
|
||||
}
|
||||
}
|
||||
is AppUpdateResult.NoNewUpdate -> {
|
||||
withUIContext {
|
||||
toast(MR.strings.no_new_updates_available)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getFormattedBuildTime(dateFormat: DateFormat): String {
|
||||
try {
|
||||
val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.getDefault())
|
||||
inputDf.timeZone = TimeZone.getTimeZone("UTC")
|
||||
val buildTime =
|
||||
inputDf.parse(BuildConfig.BUILD_TIME) ?: return BuildConfig.BUILD_TIME
|
||||
|
||||
return buildTime.toTimestampString(dateFormat)
|
||||
} catch (e: ParseException) {
|
||||
return BuildConfig.BUILD_TIME
|
||||
}
|
||||
}
|
||||
|
||||
private const val SOURCE_URL = "https://github.com/null2264/yokai/commits/master"
|
Loading…
Add table
Add a link
Reference in a new issue