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
|
@ -21,6 +21,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co
|
||||||
- Refactor EmptyView to use Compose
|
- Refactor EmptyView to use Compose
|
||||||
- Refactor Reader ChapterTransition to use Compose (@arkon)
|
- Refactor Reader ChapterTransition to use Compose (@arkon)
|
||||||
- [Experimental] Add modified version of LargeTopAppBar that mimic J2K's ExpandedAppBarLayout
|
- [Experimental] Add modified version of LargeTopAppBar that mimic J2K's ExpandedAppBarLayout
|
||||||
|
- Refactor About page to use Compose
|
||||||
|
|
||||||
## [1.9.7]
|
## [1.9.7]
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,17 @@ import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import eu.kanade.tachiyomi.core.preference.Preference
|
import eu.kanade.tachiyomi.core.preference.Preference
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> Preference<T>.collectAsState(): State<T> {
|
fun <T> Preference<T>.collectAsState(): State<T> {
|
||||||
val flow = remember(this) { changes() }
|
val flow = remember(this) { changes() }
|
||||||
return flow.collectAsState(initial = get())
|
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.Preference
|
||||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
||||||
import eu.kanade.tachiyomi.core.preference.getEnum
|
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.data.updater.AppDownloadInstallJob
|
||||||
import eu.kanade.tachiyomi.domain.manga.models.Manga
|
import eu.kanade.tachiyomi.domain.manga.models.Manga
|
||||||
import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder
|
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.reader.viewer.ViewerNavigation
|
||||||
import eu.kanade.tachiyomi.ui.recents.RecentsPresenter
|
import eu.kanade.tachiyomi.ui.recents.RecentsPresenter
|
||||||
import eu.kanade.tachiyomi.util.system.Themes
|
import eu.kanade.tachiyomi.util.system.Themes
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.util.Locale
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
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.PreferenceKeys as Keys
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
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 anilistScoreType() = preferenceStore.getString("anilist_score_type", "POINT_10")
|
||||||
|
|
||||||
fun dateFormat(format: String = preferenceStore.getString(Keys.dateFormat, "").get()): DateFormat = when (format) {
|
fun dateFormatRaw() = preferenceStore.getString(Keys.dateFormat, "")
|
||||||
"" -> DateFormat.getDateInstance(DateFormat.SHORT)
|
|
||||||
else -> SimpleDateFormat(format, Locale.getDefault())
|
@Deprecated("Use dateFormatRaw().get().asDateFormat() instead")
|
||||||
}
|
fun dateFormat(format: String = dateFormatRaw().get()): DateFormat = format.asDateFormat()
|
||||||
|
|
||||||
fun appLanguage() = preferenceStore.getString("app_language", "")
|
fun appLanguage() = preferenceStore.getString("app_language", "")
|
||||||
|
|
||||||
|
|
|
@ -1,173 +1,53 @@
|
||||||
package eu.kanade.tachiyomi.ui.more
|
package eu.kanade.tachiyomi.ui.more
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.ClipData
|
|
||||||
import android.content.ClipboardManager
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.getSystemService
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.core.net.toUri
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.preference.PreferenceScreen
|
import cafe.adriel.voyager.core.stack.StackEvent
|
||||||
import co.touchlab.kermit.Logger
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import cafe.adriel.voyager.transitions.ScreenTransition
|
||||||
import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob
|
import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController
|
||||||
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.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsLegacyController
|
import eu.kanade.tachiyomi.util.compose.LocalAlertDialog
|
||||||
import eu.kanade.tachiyomi.ui.setting.add
|
import eu.kanade.tachiyomi.util.compose.LocalBackPress
|
||||||
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.system.materialAlertDialog
|
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.setNegativeButton
|
||||||
import eu.kanade.tachiyomi.util.view.setPositiveButton
|
import eu.kanade.tachiyomi.util.view.setPositiveButton
|
||||||
import eu.kanade.tachiyomi.util.view.setTitle
|
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 io.noties.markwon.Markwon
|
||||||
import kotlinx.coroutines.Dispatchers
|
import soup.compose.material.motion.animation.materialSharedAxisZ
|
||||||
import kotlinx.coroutines.launch
|
import yokai.domain.ComposableAlertDialog
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import yokai.i18n.MR
|
import yokai.i18n.MR
|
||||||
import yokai.util.lang.getString
|
import yokai.presentation.settings.screen.about.AboutScreen
|
||||||
import java.text.DateFormat
|
|
||||||
import java.text.ParseException
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.*
|
|
||||||
import android.R as AR
|
import android.R as AR
|
||||||
|
|
||||||
class AboutController : SettingsLegacyController() {
|
class AboutController : BaseComposeController() {
|
||||||
|
|
||||||
/**
|
@Composable
|
||||||
* Checks for new releases
|
override fun ScreenContent() {
|
||||||
*/
|
Navigator(
|
||||||
private val updateChecker by lazy { AppUpdateChecker() }
|
screen = AboutScreen { body, url, isBeta ->
|
||||||
|
NewUpdateDialogController(body, url, isBeta).showDialog(router)
|
||||||
private val dateFormat: DateFormat by lazy {
|
},
|
||||||
preferences.dateFormat()
|
content = {
|
||||||
}
|
CompositionLocalProvider(
|
||||||
|
LocalAlertDialog provides ComposableAlertDialog(null),
|
||||||
private val isUpdaterEnabled = BuildConfig.INCLUDE_UPDATER
|
LocalBackPress provides router::handleBack,
|
||||||
|
) {
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
ScreenTransition(
|
||||||
titleMRes = MR.strings.about
|
navigator = it,
|
||||||
|
// FIXME: Mimic J2K's Conductor transition
|
||||||
preference {
|
transition = { materialSharedAxisZ(forward = it.lastEvent != StackEvent.Pop) },
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
)
|
||||||
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) {
|
class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundle) {
|
||||||
|
@ -220,19 +100,4 @@ class AboutController : SettingsLegacyController() {
|
||||||
const val IS_BETA = "NewUpdateDialogController.is_beta"
|
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.preference.PreferenceScreen
|
||||||
import androidx.webkit.WebViewCompat
|
import androidx.webkit.WebViewCompat
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
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.SettingsLegacyController
|
||||||
import eu.kanade.tachiyomi.ui.setting.onClick
|
import eu.kanade.tachiyomi.ui.setting.onClick
|
||||||
import eu.kanade.tachiyomi.ui.setting.preference
|
import eu.kanade.tachiyomi.ui.setting.preference
|
||||||
import eu.kanade.tachiyomi.ui.setting.preferenceCategory
|
import eu.kanade.tachiyomi.ui.setting.preferenceCategory
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
||||||
import yokai.i18n.MR
|
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
|
import yokai.i18n.MR
|
||||||
|
import yokai.presentation.settings.screen.about.getFormattedBuildTime
|
||||||
|
|
||||||
class DebugController : SettingsLegacyController() {
|
class DebugController : SettingsLegacyController() {
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class DebugController : SettingsLegacyController() {
|
||||||
preference {
|
preference {
|
||||||
key = "pref_build_time"
|
key = "pref_build_time"
|
||||||
title = "Build Time"
|
title = "Build Time"
|
||||||
summary = AboutController.getFormattedBuildTime(dateFormat)
|
summary = getFormattedBuildTime(dateFormat)
|
||||||
}
|
}
|
||||||
preference {
|
preference {
|
||||||
key = "pref_webview_version"
|
key = "pref_webview_version"
|
||||||
|
|
|
@ -69,7 +69,8 @@ import kotlinx.coroutines.launch
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import yokai.domain.manga.interactor.GetManga
|
import yokai.domain.manga.interactor.GetManga
|
||||||
import yokai.i18n.MR
|
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
|
import yokai.util.lang.getString
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -610,7 +611,7 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||||
if (presenter.source is HttpSource) {
|
if (presenter.source is HttpSource) {
|
||||||
Icons.Filled.ExploreOff
|
Icons.Filled.ExploreOff
|
||||||
} else {
|
} else {
|
||||||
Icons.Filled.LocalSource
|
CustomIcons.LocalSource
|
||||||
},
|
},
|
||||||
message,
|
message,
|
||||||
actions,
|
actions,
|
||||||
|
|
|
@ -23,7 +23,6 @@ import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
import yokai.i18n.MR
|
import yokai.i18n.MR
|
||||||
|
@ -35,14 +34,16 @@ fun YokaiScaffold(
|
||||||
onNavigationIconClicked: () -> Unit,
|
onNavigationIconClicked: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
title: String = "",
|
title: String = "",
|
||||||
scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(state = rememberTopAppBarState()),
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
fab: @Composable () -> Unit = {},
|
fab: @Composable () -> Unit = {},
|
||||||
navigationIcon: ImageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
navigationIcon: ImageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
navigationIconLabel: String = stringResource(MR.strings.back),
|
navigationIconLabel: String = stringResource(MR.strings.back),
|
||||||
actions: @Composable RowScope.() -> Unit = {},
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
appBarType: AppBarType = AppBarType.LARGE,
|
appBarType: AppBarType = AppBarType.LARGE,
|
||||||
|
snackbarHost: @Composable () -> Unit = {},
|
||||||
content: @Composable (PaddingValues) -> Unit,
|
content: @Composable (PaddingValues) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val scrollBehaviorOrDefault = scrollBehavior ?: TopAppBarDefaults.enterAlwaysScrollBehavior(state = rememberTopAppBarState())
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
val useDarkIcons = MaterialTheme.colorScheme.surface.luminance() > .5
|
val useDarkIcons = MaterialTheme.colorScheme.surface.luminance() > .5
|
||||||
val (color, scrolledColor) = getTopAppBarColor(title)
|
val (color, scrolledColor) = getTopAppBarColor(title)
|
||||||
|
@ -56,7 +57,7 @@ fun YokaiScaffold(
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
modifier = modifier.nestedScroll(scrollBehaviorOrDefault.nestedScrollConnection),
|
||||||
floatingActionButton = fab,
|
floatingActionButton = fab,
|
||||||
topBar = {
|
topBar = {
|
||||||
when (appBarType) {
|
when (appBarType) {
|
||||||
|
@ -76,7 +77,7 @@ fun YokaiScaffold(
|
||||||
buttonClicked = onNavigationIconClicked,
|
buttonClicked = onNavigationIconClicked,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehaviorOrDefault,
|
||||||
actions = actions,
|
actions = actions,
|
||||||
)
|
)
|
||||||
AppBarType.LARGE -> ExpandedAppBar(
|
AppBarType.LARGE -> ExpandedAppBar(
|
||||||
|
@ -95,11 +96,12 @@ fun YokaiScaffold(
|
||||||
buttonClicked = onNavigationIconClicked,
|
buttonClicked = onNavigationIconClicked,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehaviorOrDefault,
|
||||||
actions = actions,
|
actions = actions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
snackbarHost = snackbarHost,
|
||||||
content = content,
|
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.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
@ -35,11 +36,12 @@ fun SettingsScaffold(
|
||||||
title: String,
|
title: String,
|
||||||
appBarType: AppBarType? = null,
|
appBarType: AppBarType? = null,
|
||||||
appBarActions: @Composable RowScope.() -> Unit = {},
|
appBarActions: @Composable RowScope.() -> Unit = {},
|
||||||
itemsProvider: @Composable () -> List<Preference>,
|
appBarScrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
|
snackbarHost: @Composable () -> Unit = {},
|
||||||
|
content: @Composable (PaddingValues) -> Unit,
|
||||||
) {
|
) {
|
||||||
val preferences: PreferencesHelper by injectLazy()
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
val useLargeAppBar by preferences.useLargeToolbar().collectAsState()
|
val useLargeAppBar by preferences.useLargeToolbar().collectAsState()
|
||||||
val listState = rememberLazyListState()
|
|
||||||
val onBackPress = LocalBackPress.currentOrThrow
|
val onBackPress = LocalBackPress.currentOrThrow
|
||||||
val alertDialog = LocalAlertDialog.currentOrThrow
|
val alertDialog = LocalAlertDialog.currentOrThrow
|
||||||
|
|
||||||
|
@ -48,14 +50,34 @@ fun SettingsScaffold(
|
||||||
title = title,
|
title = title,
|
||||||
appBarType = appBarType ?: if (useLargeAppBar) AppBarType.LARGE else AppBarType.SMALL,
|
appBarType = appBarType ?: if (useLargeAppBar) AppBarType.LARGE else AppBarType.SMALL,
|
||||||
actions = appBarActions,
|
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(),
|
state = rememberTopAppBarState(),
|
||||||
canScroll = { listState.canScrollForward || listState.canScrollBackward },
|
canScroll = { listState.canScrollForward || listState.canScrollBackward },
|
||||||
isAtTop = { listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0 },
|
isAtTop = { listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0 },
|
||||||
),
|
),
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
alertDialog.content?.let { it() }
|
|
||||||
|
|
||||||
PreferenceScreen(
|
PreferenceScreen(
|
||||||
items = itemsProvider(),
|
items = itemsProvider(),
|
||||||
listState = listState,
|
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.Column
|
||||||
import androidx.compose.foundation.layout.padding
|
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.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
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"
|
|
@ -0,0 +1,31 @@
|
||||||
|
package yokai.presentation.core.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LinkIcon(
|
||||||
|
label: String,
|
||||||
|
icon: ImageVector,
|
||||||
|
url: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
IconButton(
|
||||||
|
modifier = modifier.padding(4.dp),
|
||||||
|
onClick = { uriHandler.openUri(url) },
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
contentDescription = label,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package yokai.presentation.core.icons
|
||||||
|
|
||||||
|
object CustomIcons
|
|
@ -0,0 +1,86 @@
|
||||||
|
package yokai.presentation.core.icons
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
|
||||||
|
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector.Builder
|
||||||
|
import androidx.compose.ui.graphics.vector.path
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Suppress("UnusedReceiverParameter", "BooleanLiteralArgument")
|
||||||
|
val CustomIcons.Discord: ImageVector
|
||||||
|
get() {
|
||||||
|
if (_discord != null) {
|
||||||
|
return _discord!!
|
||||||
|
}
|
||||||
|
_discord = Builder(
|
||||||
|
name = "Discord",
|
||||||
|
defaultWidth = 24.0.dp,
|
||||||
|
defaultHeight = 24.0.dp,
|
||||||
|
viewportWidth = 24.0f,
|
||||||
|
viewportHeight = 24.0f,
|
||||||
|
).apply {
|
||||||
|
path(
|
||||||
|
fill = SolidColor(Color(0xFF000000)),
|
||||||
|
stroke = null,
|
||||||
|
strokeLineWidth = 0.0f,
|
||||||
|
strokeLineCap = Butt,
|
||||||
|
strokeLineJoin = Miter,
|
||||||
|
strokeLineMiter = 4.0f,
|
||||||
|
pathFillType = NonZero,
|
||||||
|
) {
|
||||||
|
moveTo(20.317f, 4.3698f)
|
||||||
|
arcToRelative(19.7913f, 19.7913f, 0.0f, false, false, -4.8851f, -1.5152f)
|
||||||
|
arcToRelative(0.0741f, 0.0741f, 0.0f, false, false, -0.0785f, 0.0371f)
|
||||||
|
curveToRelative(-0.211f, 0.3753f, -0.4447f, 0.8648f, -0.6083f, 1.2495f)
|
||||||
|
curveToRelative(-1.8447f, -0.2762f, -3.68f, -0.2762f, -5.4868f, 0.0f)
|
||||||
|
curveToRelative(-0.1636f, -0.3933f, -0.4058f, -0.8742f, -0.6177f, -1.2495f)
|
||||||
|
arcToRelative(0.077f, 0.077f, 0.0f, false, false, -0.0785f, -0.037f)
|
||||||
|
arcToRelative(19.7363f, 19.7363f, 0.0f, false, false, -4.8852f, 1.515f)
|
||||||
|
arcToRelative(0.0699f, 0.0699f, 0.0f, false, false, -0.0321f, 0.0277f)
|
||||||
|
curveTo(0.5334f, 9.0458f, -0.319f, 13.5799f, 0.0992f, 18.0578f)
|
||||||
|
arcToRelative(0.0824f, 0.0824f, 0.0f, false, false, 0.0312f, 0.0561f)
|
||||||
|
curveToRelative(2.0528f, 1.5076f, 4.0413f, 2.4228f, 5.9929f, 3.0294f)
|
||||||
|
arcToRelative(0.0777f, 0.0777f, 0.0f, false, false, 0.0842f, -0.0276f)
|
||||||
|
curveToRelative(0.4616f, -0.6304f, 0.8731f, -1.2952f, 1.226f, -1.9942f)
|
||||||
|
arcToRelative(0.076f, 0.076f, 0.0f, false, false, -0.0416f, -0.1057f)
|
||||||
|
curveToRelative(-0.6528f, -0.2476f, -1.2743f, -0.5495f, -1.8722f, -0.8923f)
|
||||||
|
arcToRelative(0.077f, 0.077f, 0.0f, false, true, -0.0076f, -0.1277f)
|
||||||
|
curveToRelative(0.1258f, -0.0943f, 0.2517f, -0.1923f, 0.3718f, -0.2914f)
|
||||||
|
arcToRelative(0.0743f, 0.0743f, 0.0f, false, true, 0.0776f, -0.0105f)
|
||||||
|
curveToRelative(3.9278f, 1.7933f, 8.18f, 1.7933f, 12.0614f, 0.0f)
|
||||||
|
arcToRelative(0.0739f, 0.0739f, 0.0f, false, true, 0.0785f, 0.0095f)
|
||||||
|
curveToRelative(0.1202f, 0.099f, 0.246f, 0.1981f, 0.3728f, 0.2924f)
|
||||||
|
arcToRelative(0.077f, 0.077f, 0.0f, false, true, -0.0066f, 0.1276f)
|
||||||
|
arcToRelative(12.2986f, 12.2986f, 0.0f, false, true, -1.873f, 0.8914f)
|
||||||
|
arcToRelative(0.0766f, 0.0766f, 0.0f, false, false, -0.0407f, 0.1067f)
|
||||||
|
curveToRelative(0.3604f, 0.698f, 0.7719f, 1.3628f, 1.225f, 1.9932f)
|
||||||
|
arcToRelative(0.076f, 0.076f, 0.0f, false, false, 0.0842f, 0.0286f)
|
||||||
|
curveToRelative(1.961f, -0.6067f, 3.9495f, -1.5219f, 6.0023f, -3.0294f)
|
||||||
|
arcToRelative(0.077f, 0.077f, 0.0f, false, false, 0.0313f, -0.0552f)
|
||||||
|
curveToRelative(0.5004f, -5.177f, -0.8382f, -9.6739f, -3.5485f, -13.6604f)
|
||||||
|
arcToRelative(0.061f, 0.061f, 0.0f, false, false, -0.0312f, -0.0286f)
|
||||||
|
close()
|
||||||
|
moveTo(8.02f, 15.3312f)
|
||||||
|
curveToRelative(-1.1825f, 0.0f, -2.1569f, -1.0857f, -2.1569f, -2.419f)
|
||||||
|
curveToRelative(0.0f, -1.3332f, 0.9555f, -2.4189f, 2.157f, -2.4189f)
|
||||||
|
curveToRelative(1.2108f, 0.0f, 2.1757f, 1.0952f, 2.1568f, 2.419f)
|
||||||
|
curveToRelative(0.0f, 1.3332f, -0.9555f, 2.4189f, -2.1569f, 2.4189f)
|
||||||
|
close()
|
||||||
|
moveTo(15.9948f, 15.3312f)
|
||||||
|
curveToRelative(-1.1825f, 0.0f, -2.1569f, -1.0857f, -2.1569f, -2.419f)
|
||||||
|
curveToRelative(0.0f, -1.3332f, 0.9554f, -2.4189f, 2.1569f, -2.4189f)
|
||||||
|
curveToRelative(1.2108f, 0.0f, 2.1757f, 1.0952f, 2.1568f, 2.419f)
|
||||||
|
curveToRelative(0.0f, 1.3332f, -0.946f, 2.4189f, -2.1568f, 2.4189f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
return _discord!!
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("ObjectPropertyName")
|
||||||
|
private var _discord: ImageVector? = null
|
|
@ -0,0 +1,68 @@
|
||||||
|
package yokai.presentation.core.icons
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
|
||||||
|
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector.Builder
|
||||||
|
import androidx.compose.ui.graphics.vector.path
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Suppress("UnusedReceiverParameter")
|
||||||
|
val CustomIcons.GitHub: ImageVector
|
||||||
|
get() {
|
||||||
|
if (_github != null) {
|
||||||
|
return _github!!
|
||||||
|
}
|
||||||
|
_github = Builder(
|
||||||
|
name = "GitHub",
|
||||||
|
defaultWidth = 24.0.dp,
|
||||||
|
defaultHeight = 24.0.dp,
|
||||||
|
viewportWidth = 24.0f,
|
||||||
|
viewportHeight = 24.0f,
|
||||||
|
).apply {
|
||||||
|
path(
|
||||||
|
fill = SolidColor(Color(0xFF000000)),
|
||||||
|
stroke = null,
|
||||||
|
strokeLineWidth = 0.0f,
|
||||||
|
strokeLineCap = Butt,
|
||||||
|
strokeLineJoin = Miter,
|
||||||
|
strokeLineMiter = 4.0f,
|
||||||
|
pathFillType = NonZero,
|
||||||
|
) {
|
||||||
|
moveTo(12.0f, 0.297f)
|
||||||
|
curveToRelative(-6.63f, 0.0f, -12.0f, 5.373f, -12.0f, 12.0f)
|
||||||
|
curveToRelative(0.0f, 5.303f, 3.438f, 9.8f, 8.205f, 11.385f)
|
||||||
|
curveToRelative(0.6f, 0.113f, 0.82f, -0.258f, 0.82f, -0.577f)
|
||||||
|
curveToRelative(0.0f, -0.285f, -0.01f, -1.04f, -0.015f, -2.04f)
|
||||||
|
curveToRelative(-3.338f, 0.724f, -4.042f, -1.61f, -4.042f, -1.61f)
|
||||||
|
curveTo(4.422f, 18.07f, 3.633f, 17.7f, 3.633f, 17.7f)
|
||||||
|
curveToRelative(-1.087f, -0.744f, 0.084f, -0.729f, 0.084f, -0.729f)
|
||||||
|
curveToRelative(1.205f, 0.084f, 1.838f, 1.236f, 1.838f, 1.236f)
|
||||||
|
curveToRelative(1.07f, 1.835f, 2.809f, 1.305f, 3.495f, 0.998f)
|
||||||
|
curveToRelative(0.108f, -0.776f, 0.417f, -1.305f, 0.76f, -1.605f)
|
||||||
|
curveToRelative(-2.665f, -0.3f, -5.466f, -1.332f, -5.466f, -5.93f)
|
||||||
|
curveToRelative(0.0f, -1.31f, 0.465f, -2.38f, 1.235f, -3.22f)
|
||||||
|
curveToRelative(-0.135f, -0.303f, -0.54f, -1.523f, 0.105f, -3.176f)
|
||||||
|
curveToRelative(0.0f, 0.0f, 1.005f, -0.322f, 3.3f, 1.23f)
|
||||||
|
curveToRelative(0.96f, -0.267f, 1.98f, -0.399f, 3.0f, -0.405f)
|
||||||
|
curveToRelative(1.02f, 0.006f, 2.04f, 0.138f, 3.0f, 0.405f)
|
||||||
|
curveToRelative(2.28f, -1.552f, 3.285f, -1.23f, 3.285f, -1.23f)
|
||||||
|
curveToRelative(0.645f, 1.653f, 0.24f, 2.873f, 0.12f, 3.176f)
|
||||||
|
curveToRelative(0.765f, 0.84f, 1.23f, 1.91f, 1.23f, 3.22f)
|
||||||
|
curveToRelative(0.0f, 4.61f, -2.805f, 5.625f, -5.475f, 5.92f)
|
||||||
|
curveToRelative(0.42f, 0.36f, 0.81f, 1.096f, 0.81f, 2.22f)
|
||||||
|
curveToRelative(0.0f, 1.606f, -0.015f, 2.896f, -0.015f, 3.286f)
|
||||||
|
curveToRelative(0.0f, 0.315f, 0.21f, 0.69f, 0.825f, 0.57f)
|
||||||
|
curveTo(20.565f, 22.092f, 24.0f, 17.592f, 24.0f, 12.297f)
|
||||||
|
curveToRelative(0.0f, -6.627f, -5.373f, -12.0f, -12.0f, -12.0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
return _github!!
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("ObjectPropertyName")
|
||||||
|
private var _github: ImageVector? = null
|
|
@ -0,0 +1,56 @@
|
||||||
|
package yokai.presentation.core.icons
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
|
||||||
|
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector.Builder
|
||||||
|
import androidx.compose.ui.graphics.vector.path
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Suppress("UnusedReceiverParameter")
|
||||||
|
val CustomIcons.LocalSource: ImageVector
|
||||||
|
get() {
|
||||||
|
if (_localSource != null) {
|
||||||
|
return _localSource!!
|
||||||
|
}
|
||||||
|
_localSource = Builder(
|
||||||
|
name = "localSource",
|
||||||
|
defaultWidth = 24.0.dp,
|
||||||
|
defaultHeight = 24.0.dp,
|
||||||
|
viewportWidth = 24.0f,
|
||||||
|
viewportHeight = 24.0f,
|
||||||
|
).apply {
|
||||||
|
path(
|
||||||
|
fill = SolidColor(Color(0xFF000000)),
|
||||||
|
stroke = null,
|
||||||
|
strokeLineWidth = 0.0f,
|
||||||
|
strokeLineCap = Butt,
|
||||||
|
strokeLineJoin = Miter,
|
||||||
|
strokeLineMiter = 4.0f,
|
||||||
|
pathFillType = NonZero,
|
||||||
|
) {
|
||||||
|
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!!
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("ObjectPropertyName")
|
||||||
|
private var _localSource: ImageVector? = null
|
Loading…
Add table
Add a link
Reference in a new issue