2022-05-07 23:34:55 -04:00
package eu.kanade.presentation.browse
2022-07-16 20:44:37 +02:00
import android.content.Intent
import android.net.Uri
import android.provider.Settings
2022-05-20 03:31:07 +06:00
import android.util.DisplayMetrics
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
2022-05-07 23:34:55 -04:00
import androidx.compose.foundation.layout.Column
2022-08-29 16:10:55 -04:00
import androidx.compose.foundation.layout.PaddingValues
2022-05-07 23:34:55 -04:00
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
2022-05-20 03:31:07 +06:00
import androidx.compose.foundation.layout.size
2022-05-07 23:34:55 -04:00
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
2024-01-07 22:22:32 -05:00
import androidx.compose.material.icons.automirrored.outlined.Launch
2022-05-07 23:34:55 -04:00
import androidx.compose.material.icons.outlined.Settings
2022-05-20 03:31:07 +06:00
import androidx.compose.material3.AlertDialog
2022-05-07 23:34:55 -04:00
import androidx.compose.material3.Button
2023-07-26 22:33:10 -04:00
import androidx.compose.material3.HorizontalDivider
2022-05-07 23:34:55 -04:00
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
2022-05-20 03:31:07 +06:00
import androidx.compose.material3.TextButton
2023-07-26 22:33:10 -04:00
import androidx.compose.material3.VerticalDivider
2022-05-07 23:34:55 -04:00
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
2022-05-20 03:31:07 +06:00
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
2022-05-19 17:43:27 -04:00
import androidx.compose.runtime.setValue
2022-05-07 23:34:55 -04:00
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
2024-01-05 17:28:08 -05:00
import androidx.compose.ui.platform.LocalUriHandler
2022-05-20 03:31:07 +06:00
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
2022-06-08 03:47:46 +06:00
import androidx.compose.ui.text.style.TextAlign
2022-05-07 23:34:55 -04:00
import androidx.compose.ui.unit.dp
2022-11-20 20:36:03 +01:00
import eu.kanade.domain.extension.interactor.ExtensionSourceItem
2022-05-07 23:34:55 -04:00
import eu.kanade.presentation.browse.components.ExtensionIcon
2022-08-29 16:10:55 -04:00
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
2022-10-30 15:50:09 -04:00
import eu.kanade.presentation.components.WarningBanner
2022-10-30 11:37:02 -04:00
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
2022-05-07 23:34:55 -04:00
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.source.ConfigurableSource
2023-07-23 20:03:37 -04:00
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
2022-05-07 23:34:55 -04:00
import eu.kanade.tachiyomi.util.system.LocaleHelper
2024-01-07 16:35:25 -05:00
import kotlinx.collections.immutable.ImmutableList
2023-11-17 09:46:13 -05:00
import kotlinx.collections.immutable.persistentListOf
2023-11-18 13:54:56 -05:00
import tachiyomi.i18n.MR
2023-02-18 16:33:03 -05:00
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
2023-02-18 15:52:52 -05:00
import tachiyomi.presentation.core.components.material.Scaffold
2023-02-18 16:03:01 -05:00
import tachiyomi.presentation.core.components.material.padding
2023-11-18 19:41:33 -05:00
import tachiyomi.presentation.core.i18n.stringResource
2023-02-20 10:12:41 -05:00
import tachiyomi.presentation.core.screens.EmptyScreen
2022-05-07 23:34:55 -04:00
@Composable
fun ExtensionDetailsScreen (
2022-08-29 16:10:55 -04:00
navigateUp : ( ) -> Unit ,
2023-07-23 20:03:37 -04:00
state : ExtensionDetailsScreenModel . State ,
2022-08-29 16:10:55 -04:00
onClickSourcePreferences : ( sourceId : Long ) -> Unit ,
2022-11-20 20:36:03 +01:00
onClickEnableAll : ( ) -> Unit ,
onClickDisableAll : ( ) -> Unit ,
onClickClearCookies : ( ) -> Unit ,
onClickUninstall : ( ) -> Unit ,
onClickSource : ( sourceId : Long ) -> Unit ,
2022-08-29 16:10:55 -04:00
) {
2024-01-07 22:22:32 -05:00
val uriHandler = LocalUriHandler . current
val url = remember ( state . extension ) {
val regex = """ https://raw.githubusercontent.com/(.+?)/(.+?)/.+ """ . toRegex ( )
regex . find ( state . extension ?. repoUrl . orEmpty ( ) )
?. let {
val ( user , repo ) = it . destructured
" https://github.com/ $user / $repo "
}
?: state . extension ?. repoUrl
}
2022-08-29 16:10:55 -04:00
Scaffold (
2022-08-31 16:31:08 -04:00
topBar = { scrollBehavior ->
2022-08-29 16:10:55 -04:00
AppBar (
2023-11-18 19:41:33 -05:00
title = stringResource ( MR . strings . label _extension _info ) ,
2022-08-29 16:10:55 -04:00
navigateUp = navigateUp ,
actions = {
AppBarActions (
2023-11-17 09:46:13 -05:00
actions = persistentListOf < AppBar . AppBarAction > ( ) . builder ( )
. apply {
2024-01-07 22:22:32 -05:00
if ( url != null ) {
2023-11-17 09:46:13 -05:00
add (
AppBar . Action (
2024-01-07 22:22:32 -05:00
title = stringResource ( MR . strings . action _open _repo ) ,
icon = Icons . AutoMirrored . Outlined . Launch ,
onClick = {
uriHandler . openUri ( url )
} ,
2023-11-17 09:46:13 -05:00
) ,
)
}
addAll (
listOf (
AppBar . OverflowAction (
2023-11-18 19:41:33 -05:00
title = stringResource ( MR . strings . action _enable _all ) ,
2023-11-17 09:46:13 -05:00
onClick = onClickEnableAll ,
) ,
AppBar . OverflowAction (
2023-11-18 19:41:33 -05:00
title = stringResource ( MR . strings . action _disable _all ) ,
2023-11-17 09:46:13 -05:00
onClick = onClickDisableAll ,
) ,
AppBar . OverflowAction (
2023-11-18 19:41:33 -05:00
title = stringResource ( MR . strings . pref _clear _cookies ) ,
2023-11-17 09:46:13 -05:00
onClick = onClickClearCookies ,
) ,
2022-08-29 16:10:55 -04:00
) ,
)
}
2023-11-17 09:46:13 -05:00
. build ( ) ,
2022-08-29 16:10:55 -04:00
)
} ,
2022-08-31 16:31:08 -04:00
scrollBehavior = scrollBehavior ,
2022-08-29 16:10:55 -04:00
)
} ,
) { paddingValues ->
2022-11-20 20:36:03 +01:00
if ( state . extension == null ) {
EmptyScreen (
2024-01-05 17:28:08 -05:00
MR . strings . empty _screen ,
2022-11-20 20:36:03 +01:00
modifier = Modifier . padding ( paddingValues ) ,
)
return @Scaffold
}
ExtensionDetails (
contentPadding = paddingValues ,
extension = state . extension ,
sources = state . sources ,
onClickSourcePreferences = onClickSourcePreferences ,
onClickUninstall = onClickUninstall ,
onClickSource = onClickSource ,
)
2022-08-29 16:10:55 -04:00
}
}
@Composable
private fun ExtensionDetails (
2022-09-18 18:38:22 -04:00
contentPadding : PaddingValues ,
2022-11-20 20:36:03 +01:00
extension : Extension . Installed ,
2024-01-07 16:35:25 -05:00
sources : ImmutableList < ExtensionSourceItem > ,
2022-05-07 23:34:55 -04:00
onClickSourcePreferences : ( sourceId : Long ) -> Unit ,
2022-11-20 20:36:03 +01:00
onClickUninstall : ( ) -> Unit ,
onClickSource : ( sourceId : Long ) -> Unit ,
2022-05-07 23:34:55 -04:00
) {
2022-11-20 20:36:03 +01:00
val context = LocalContext . current
var showNsfwWarning by remember { mutableStateOf ( false ) }
2022-05-07 23:34:55 -04:00
2022-11-20 20:36:03 +01:00
ScrollbarLazyColumn (
contentPadding = contentPadding ,
) {
2024-01-07 22:22:32 -05:00
if ( extension . isObsolete ) {
item {
WarningBanner ( MR . strings . obsolete _extension _message )
}
2022-11-20 20:36:03 +01:00
}
2022-05-07 23:34:55 -04:00
2022-11-20 20:36:03 +01:00
item {
DetailsHeader (
extension = extension ,
onClickUninstall = onClickUninstall ,
onClickAppInfo = {
Intent ( Settings . ACTION _APPLICATION _DETAILS _SETTINGS ) . apply {
data = Uri . fromParts ( " package " , extension . pkgName , null )
context . startActivity ( this )
}
2023-08-05 23:15:52 +07:00
Unit
} . takeIf { extension . isShared } ,
2022-11-20 20:36:03 +01:00
onClickAgeRating = {
showNsfwWarning = true
} ,
)
}
items (
items = sources ,
key = { it . source . id } ,
) { source ->
SourceSwitchPreference (
2023-12-01 22:27:15 -05:00
modifier = Modifier . animateItemPlacement ( ) ,
2022-11-20 20:36:03 +01:00
source = source ,
onClickSourcePreferences = onClickSourcePreferences ,
onClickSource = onClickSource ,
)
2022-05-07 23:34:55 -04:00
}
}
2022-11-20 20:36:03 +01:00
if ( showNsfwWarning ) {
NsfwWarningDialog (
onClickConfirm = {
showNsfwWarning = false
} ,
)
}
2022-05-07 23:34:55 -04:00
}
@Composable
private fun DetailsHeader (
extension : Extension ,
2022-05-20 03:31:07 +06:00
onClickAgeRating : ( ) -> Unit ,
2022-05-07 23:34:55 -04:00
onClickUninstall : ( ) -> Unit ,
2023-08-05 23:15:52 +07:00
onClickAppInfo : ( ( ) -> Unit ) ? ,
2022-05-07 23:34:55 -04:00
) {
val context = LocalContext . current
Column {
2022-05-20 03:31:07 +06:00
Column (
modifier = Modifier
. fillMaxWidth ( )
. padding (
2022-11-13 18:11:51 +01:00
start = MaterialTheme . padding . medium ,
end = MaterialTheme . padding . medium ,
top = MaterialTheme . padding . medium ,
bottom = MaterialTheme . padding . small ,
2022-05-20 03:31:07 +06:00
) ,
horizontalAlignment = Alignment . CenterHorizontally ,
2022-05-07 23:34:55 -04:00
) {
ExtensionIcon (
modifier = Modifier
2022-05-20 03:31:07 +06:00
. size ( 112. dp ) ,
2022-05-07 23:34:55 -04:00
extension = extension ,
2022-05-20 03:31:07 +06:00
density = DisplayMetrics . DENSITY _XXXHIGH ,
2022-05-07 23:34:55 -04:00
)
2022-05-20 03:31:07 +06:00
Text (
text = extension . name ,
style = MaterialTheme . typography . headlineSmall ,
2022-11-01 22:03:31 +06:00
textAlign = TextAlign . Center ,
2022-05-20 03:31:07 +06:00
)
val strippedPkgName = extension . pkgName . substringAfter ( " eu.kanade.tachiyomi.extension. " )
Text (
text = strippedPkgName ,
style = MaterialTheme . typography . bodySmall ,
)
}
Row (
modifier = Modifier
. fillMaxWidth ( )
. padding (
2022-11-13 18:11:51 +01:00
horizontal = MaterialTheme . padding . extraLarge ,
vertical = MaterialTheme . padding . small ,
2022-05-20 03:31:07 +06:00
) ,
horizontalArrangement = Arrangement . SpaceEvenly ,
verticalAlignment = Alignment . CenterVertically ,
) {
InfoText (
2022-06-08 03:47:46 +06:00
modifier = Modifier . weight ( 1f ) ,
2022-05-20 03:31:07 +06:00
primaryText = extension . versionName ,
2023-11-18 19:41:33 -05:00
secondaryText = stringResource ( MR . strings . ext _info _version ) ,
2022-05-20 03:31:07 +06:00
)
InfoDivider ( )
InfoText (
2022-06-08 03:47:46 +06:00
modifier = Modifier . weight ( if ( extension . isNsfw ) 1.5f else 1f ) ,
2022-05-20 03:31:07 +06:00
primaryText = LocaleHelper . getSourceDisplayName ( extension . lang , context ) ,
2023-11-18 19:41:33 -05:00
secondaryText = stringResource ( MR . strings . ext _info _language ) ,
2022-05-20 03:31:07 +06:00
)
if ( extension . isNsfw ) {
InfoDivider ( )
InfoText (
2022-06-08 03:47:46 +06:00
modifier = Modifier . weight ( 1f ) ,
2023-11-18 19:41:33 -05:00
primaryText = stringResource ( MR . strings . ext _nsfw _short ) ,
2022-05-20 03:31:07 +06:00
primaryTextStyle = MaterialTheme . typography . bodyLarge . copy (
2022-05-07 23:34:55 -04:00
color = MaterialTheme . colorScheme . error ,
2022-05-20 03:31:07 +06:00
fontWeight = FontWeight . Medium ,
) ,
2023-11-18 19:41:33 -05:00
secondaryText = stringResource ( MR . strings . ext _info _age _rating ) ,
2022-05-19 17:43:27 -04:00
onClick = onClickAgeRating ,
2022-05-07 23:34:55 -04:00
)
}
}
Row (
modifier = Modifier . padding (
2022-11-13 18:11:51 +01:00
start = MaterialTheme . padding . medium ,
end = MaterialTheme . padding . medium ,
top = MaterialTheme . padding . small ,
bottom = MaterialTheme . padding . medium ,
2022-05-07 23:34:55 -04:00
) ,
2023-12-30 10:30:32 -05:00
horizontalArrangement = Arrangement . spacedBy ( MaterialTheme . padding . medium ) ,
2022-05-07 23:34:55 -04:00
) {
OutlinedButton (
modifier = Modifier . weight ( 1f ) ,
onClick = onClickUninstall ,
) {
2023-11-18 19:41:33 -05:00
Text ( stringResource ( MR . strings . ext _uninstall ) )
2022-05-07 23:34:55 -04:00
}
2023-08-05 23:15:52 +07:00
if ( onClickAppInfo != null ) {
Button (
modifier = Modifier . weight ( 1f ) ,
onClick = onClickAppInfo ,
) {
Text (
2023-11-18 19:41:33 -05:00
text = stringResource ( MR . strings . ext _app _info ) ,
2023-08-05 23:15:52 +07:00
color = MaterialTheme . colorScheme . onPrimary ,
)
}
2022-05-07 23:34:55 -04:00
}
}
2023-07-26 22:33:10 -04:00
HorizontalDivider ( )
2022-05-07 23:34:55 -04:00
}
}
2022-05-20 03:31:07 +06:00
@Composable
private fun InfoText (
primaryText : String ,
secondaryText : String ,
2023-11-17 09:46:13 -05:00
modifier : Modifier = Modifier ,
primaryTextStyle : TextStyle = MaterialTheme . typography . bodyLarge ,
2022-05-19 17:43:27 -04:00
onClick : ( ( ) -> Unit ) ? = null ,
2022-05-20 03:31:07 +06:00
) {
val interactionSource = remember { MutableInteractionSource ( ) }
2022-06-08 03:47:46 +06:00
val clickableModifier = if ( onClick != null ) {
2022-05-19 17:43:27 -04:00
Modifier . clickable ( interactionSource , indication = null ) { onClick ( ) }
2022-09-10 23:57:03 -04:00
} else {
Modifier
}
2022-05-20 03:31:07 +06:00
Column (
2022-06-08 03:47:46 +06:00
modifier = modifier . then ( clickableModifier ) ,
2022-05-20 03:31:07 +06:00
horizontalAlignment = Alignment . CenterHorizontally ,
verticalArrangement = Arrangement . Center ,
) {
Text (
text = primaryText ,
2022-06-08 03:47:46 +06:00
textAlign = TextAlign . Center ,
2022-05-20 03:31:07 +06:00
style = primaryTextStyle ,
)
Text (
2022-05-19 17:43:27 -04:00
text = secondaryText + if ( onClick != null ) " ⓘ " else " " ,
2022-06-08 03:47:46 +06:00
textAlign = TextAlign . Center ,
2022-05-20 03:31:07 +06:00
style = MaterialTheme . typography . bodyMedium ,
2022-05-19 17:43:27 -04:00
color = MaterialTheme . colorScheme . onSurface . copy ( alpha = 0.5f ) ,
2022-05-20 03:31:07 +06:00
)
}
}
@Composable
private fun InfoDivider ( ) {
2023-07-26 22:33:10 -04:00
VerticalDivider (
modifier = Modifier . height ( 20. dp ) ,
2022-05-20 03:31:07 +06:00
)
}
2022-05-07 23:34:55 -04:00
@Composable
private fun SourceSwitchPreference (
source : ExtensionSourceItem ,
onClickSourcePreferences : ( sourceId : Long ) -> Unit ,
onClickSource : ( sourceId : Long ) -> Unit ,
2023-11-17 09:46:13 -05:00
modifier : Modifier = Modifier ,
2022-05-07 23:34:55 -04:00
) {
val context = LocalContext . current
2022-10-30 11:37:02 -04:00
TextPreferenceWidget (
2022-05-07 23:34:55 -04:00
modifier = modifier ,
title = if ( source . labelAsName ) {
source . source . toString ( )
} else {
LocaleHelper . getSourceDisplayName ( source . source . lang , context )
} ,
2022-10-30 11:37:02 -04:00
widget = {
2022-05-07 23:34:55 -04:00
Row (
verticalAlignment = Alignment . CenterVertically ,
) {
if ( source . source is ConfigurableSource ) {
IconButton ( onClick = { onClickSourcePreferences ( source . source . id ) } ) {
Icon (
imageVector = Icons . Outlined . Settings ,
2023-11-18 19:41:33 -05:00
contentDescription = stringResource ( MR . strings . label _settings ) ,
2022-05-07 23:34:55 -04:00
tint = MaterialTheme . colorScheme . onSurface ,
)
}
}
2022-10-30 11:37:02 -04:00
Switch (
checked = source . enabled ,
onCheckedChange = null ,
modifier = Modifier . padding ( start = TrailingWidgetBuffer ) ,
)
2022-05-07 23:34:55 -04:00
}
} ,
2022-10-30 11:37:02 -04:00
onPreferenceClick = { onClickSource ( source . source . id ) } ,
2022-05-07 23:34:55 -04:00
)
}
2022-05-20 03:31:07 +06:00
@Composable
2022-09-18 22:38:44 -04:00
private fun NsfwWarningDialog (
2022-05-20 03:31:07 +06:00
onClickConfirm : ( ) -> Unit ,
) {
AlertDialog (
text = {
2023-11-18 19:41:33 -05:00
Text ( text = stringResource ( MR . strings . ext _nsfw _warning ) )
2022-05-20 03:31:07 +06:00
} ,
confirmButton = {
TextButton ( onClick = onClickConfirm ) {
2023-11-18 19:41:33 -05:00
Text ( text = stringResource ( MR . strings . action _ok ) )
2022-05-20 03:31:07 +06:00
}
} ,
onDismissRequest = onClickConfirm ,
)
}