mirror of
https://github.com/revanced/revanced-manager
synced 2024-05-14 13:56:57 +02:00
feat: add patch bundle info screen (#55)
This commit is contained in:
parent
1331479072
commit
21d99a1f24
@ -10,7 +10,6 @@ import java.io.File
|
|||||||
@Stable
|
@Stable
|
||||||
class RemoteSource(name: String, id: Int, directory: File) : Source(name, id, directory) {
|
class RemoteSource(name: String, id: Int, directory: File) : Source(name, id, directory) {
|
||||||
private val api: ManagerAPI = get()
|
private val api: ManagerAPI = get()
|
||||||
|
|
||||||
suspend fun downloadLatest() = withContext(Dispatchers.IO) {
|
suspend fun downloadLatest() = withContext(Dispatchers.IO) {
|
||||||
api.downloadBundle(patchesJar, integrations).also { (patchesVer, integrationsVer) ->
|
api.downloadBundle(patchesJar, integrations).also { (patchesVer, integrationsVer) ->
|
||||||
saveVersion(patchesVer, integrationsVer)
|
saveVersion(patchesVer, integrationsVer)
|
||||||
|
@ -59,4 +59,5 @@ fun AppTopBar(
|
|||||||
containerColor = containerColor
|
containerColor = containerColor
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
package app.revanced.manager.ui.component
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NotificationCard(
|
||||||
|
color: Color,
|
||||||
|
icon: ImageVector,
|
||||||
|
text: String,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(28.dp))
|
||||||
|
.background(color)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(
|
||||||
|
16.dp,
|
||||||
|
Alignment.Start
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.width(220.dp),
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
package app.revanced.manager.ui.component
|
||||||
|
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import app.revanced.manager.R
|
||||||
|
import app.revanced.manager.domain.sources.RemoteSource
|
||||||
|
import app.revanced.manager.domain.sources.Source
|
||||||
|
import app.revanced.manager.ui.component.bundle.BundleInformationDialog
|
||||||
|
import app.revanced.manager.ui.viewmodel.SourcesViewModel
|
||||||
|
import app.revanced.manager.util.uiSafe
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SourceItem(
|
||||||
|
source: Source, onDelete: () -> Unit,
|
||||||
|
coroutineScope: CoroutineScope,
|
||||||
|
) {
|
||||||
|
var viewBundleDialogPage by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val bundle by source.bundle.collectAsStateWithLifecycle()
|
||||||
|
val patchCount = bundle.patches.size
|
||||||
|
val padding = PaddingValues(16.dp, 0.dp)
|
||||||
|
|
||||||
|
val androidContext = LocalContext.current
|
||||||
|
|
||||||
|
if (viewBundleDialogPage) {
|
||||||
|
BundleInformationDialog(
|
||||||
|
onDismissRequest = { viewBundleDialogPage = false },
|
||||||
|
onDeleteRequest = {
|
||||||
|
viewBundleDialogPage = false
|
||||||
|
onDelete()
|
||||||
|
},
|
||||||
|
source = source,
|
||||||
|
patchCount = patchCount,
|
||||||
|
onRefreshButton = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
uiSafe(
|
||||||
|
androidContext,
|
||||||
|
R.string.source_download_fail,
|
||||||
|
SourcesViewModel.failLogMsg
|
||||||
|
) {
|
||||||
|
if (source is RemoteSource) source.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.height(64.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
viewBundleDialogPage = true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = source.name,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
modifier = Modifier.padding(padding)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = pluralStringResource(R.plurals.patches_count, patchCount, patchCount),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(padding)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package app.revanced.manager.ui.component.bundle
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.ArrowRight
|
||||||
|
import androidx.compose.material3.FilledTonalButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import app.revanced.manager.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BundleInfoContent(
|
||||||
|
switchChecked: Boolean,
|
||||||
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
|
patchInfoText: String,
|
||||||
|
patchCount: Int,
|
||||||
|
onArrowClick: () -> Unit,
|
||||||
|
isLocal: Boolean,
|
||||||
|
tonalButtonOnClick: () -> Unit = {},
|
||||||
|
tonalButtonContent: @Composable RowScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
if(!isLocal) {
|
||||||
|
BundleInfoListItem(
|
||||||
|
headlineText = stringResource(R.string.automatically_update),
|
||||||
|
supportingText = stringResource(R.string.automatically_update_description),
|
||||||
|
trailingContent = {
|
||||||
|
Switch(
|
||||||
|
checked = switchChecked,
|
||||||
|
onCheckedChange = onCheckedChange
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
BundleInfoListItem(
|
||||||
|
headlineText = stringResource(R.string.bundle_type),
|
||||||
|
supportingText = stringResource(R.string.bundle_type_description)
|
||||||
|
) {
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = tonalButtonOnClick,
|
||||||
|
content = tonalButtonContent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.information),
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
horizontal = 16.dp,
|
||||||
|
vertical = 12.dp
|
||||||
|
),
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
|
||||||
|
BundleInfoListItem(
|
||||||
|
headlineText = stringResource(R.string.patches),
|
||||||
|
supportingText = patchInfoText,
|
||||||
|
trailingContent = {
|
||||||
|
if (patchCount > 0) {
|
||||||
|
IconButton(onClick = onArrowClick) {
|
||||||
|
Icon(
|
||||||
|
Icons.Outlined.ArrowRight,
|
||||||
|
stringResource(R.string.patches)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
BundleInfoListItem(
|
||||||
|
headlineText = stringResource(R.string.patches_version),
|
||||||
|
supportingText = "1.0.0",
|
||||||
|
)
|
||||||
|
|
||||||
|
BundleInfoListItem(
|
||||||
|
headlineText = stringResource(R.string.integrations_version),
|
||||||
|
supportingText = "1.0.0",
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package app.revanced.manager.ui.component.bundle
|
||||||
|
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BundleInfoListItem(
|
||||||
|
headlineText: String,
|
||||||
|
supportingText: String = "",
|
||||||
|
trailingContent: @Composable (() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Text(
|
||||||
|
text = headlineText,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Text(
|
||||||
|
text = supportingText,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.outline
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailingContent = trailingContent,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,143 @@
|
|||||||
|
package app.revanced.manager.ui.component.bundle
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.outlined.DeleteOutline
|
||||||
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import app.revanced.manager.R
|
||||||
|
import app.revanced.manager.domain.sources.LocalSource
|
||||||
|
import app.revanced.manager.domain.sources.RemoteSource
|
||||||
|
import app.revanced.manager.domain.sources.Source
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun BundleInformationDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onDeleteRequest: () -> Unit,
|
||||||
|
source: Source,
|
||||||
|
remoteName: String = "",
|
||||||
|
patchCount: Int = 0,
|
||||||
|
onRefreshButton: () -> Unit,
|
||||||
|
) {
|
||||||
|
var checked by remember { mutableStateOf(true) }
|
||||||
|
var viewCurrentBundlePatches by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val isLocal = source is LocalSource
|
||||||
|
|
||||||
|
val patchInfoText = if (patchCount == 0) stringResource(R.string.no_patches)
|
||||||
|
else stringResource(R.string.patches_available, patchCount)
|
||||||
|
|
||||||
|
if (viewCurrentBundlePatches) {
|
||||||
|
BundlePatchesDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
viewCurrentBundlePatches = false
|
||||||
|
},
|
||||||
|
source = source,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
properties = DialogProperties(
|
||||||
|
usePlatformDefaultWidth = false,
|
||||||
|
dismissOnBackPress = true
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
BundleTopBar(
|
||||||
|
title = stringResource(R.string.bundle_information),
|
||||||
|
onBackClick = onDismissRequest,
|
||||||
|
onBackIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ArrowBack,
|
||||||
|
contentDescription = stringResource(R.string.back)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = onDeleteRequest) {
|
||||||
|
Icon(
|
||||||
|
Icons.Outlined.DeleteOutline,
|
||||||
|
stringResource(R.string.delete)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if(!isLocal) {
|
||||||
|
IconButton(onClick = onRefreshButton) {
|
||||||
|
Icon(
|
||||||
|
Icons.Outlined.Refresh,
|
||||||
|
stringResource(R.string.refresh)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
start = 24.dp,
|
||||||
|
top = 16.dp,
|
||||||
|
end = 24.dp,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
BundleTextContent(
|
||||||
|
name = source.name,
|
||||||
|
isLocal = isLocal,
|
||||||
|
remoteUrl = remoteName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
Modifier.padding(
|
||||||
|
start = 8.dp,
|
||||||
|
top = 8.dp,
|
||||||
|
end = 4.dp,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
BundleInfoContent(
|
||||||
|
switchChecked = checked,
|
||||||
|
onCheckedChange = { checked = it },
|
||||||
|
patchInfoText = patchInfoText,
|
||||||
|
patchCount = patchCount,
|
||||||
|
isLocal = isLocal,
|
||||||
|
onArrowClick = {
|
||||||
|
viewCurrentBundlePatches = true
|
||||||
|
},
|
||||||
|
tonalButtonContent = {
|
||||||
|
when(source) {
|
||||||
|
is RemoteSource -> Text(stringResource(R.string.remote))
|
||||||
|
is LocalSource -> Text(stringResource(R.string.local))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
package app.revanced.manager.ui.component.bundle
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.outlined.Close
|
||||||
|
import androidx.compose.material.icons.outlined.Lightbulb
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import app.revanced.manager.R
|
||||||
|
import app.revanced.manager.domain.sources.Source
|
||||||
|
import app.revanced.manager.ui.component.NotificationCard
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun BundlePatchesDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
source: Source,
|
||||||
|
) {
|
||||||
|
var informationCardVisible by remember { mutableStateOf(true) }
|
||||||
|
val bundle by source.bundle.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
properties = DialogProperties(
|
||||||
|
usePlatformDefaultWidth = false,
|
||||||
|
dismissOnBackPress = true
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
BundleTopBar(
|
||||||
|
title = stringResource(R.string.bundle_patches),
|
||||||
|
onBackClick = onDismissRequest,
|
||||||
|
onBackIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ArrowBack,
|
||||||
|
contentDescription = stringResource(R.string.back)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
AnimatedVisibility(visible = informationCardVisible) {
|
||||||
|
NotificationCard(
|
||||||
|
color = MaterialTheme.colorScheme.secondaryContainer,
|
||||||
|
icon = Icons.Outlined.Lightbulb,
|
||||||
|
text = stringResource(R.string.tap_on_patches)
|
||||||
|
) {
|
||||||
|
IconButton(onClick = { informationCardVisible = false }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Close,
|
||||||
|
contentDescription = stringResource(R.string.close),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(bundle.patches.size) { bundleIndex ->
|
||||||
|
val patch = bundle.patches[bundleIndex]
|
||||||
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Text(
|
||||||
|
text = patch.name,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
patch.description?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package app.revanced.manager.ui.component.bundle
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import app.revanced.manager.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BundleTextContent(
|
||||||
|
name: String,
|
||||||
|
onNameChange: (String) -> Unit = {},
|
||||||
|
isLocal: Boolean,
|
||||||
|
remoteUrl: String,
|
||||||
|
onRemoteUrlChange: (String) -> Unit = {},
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 16.dp),
|
||||||
|
value = name,
|
||||||
|
onValueChange = onNameChange,
|
||||||
|
label = {
|
||||||
|
Text(stringResource(R.string.bundle_input_name))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (!isLocal) {
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 16.dp),
|
||||||
|
value = remoteUrl,
|
||||||
|
onValueChange = onRemoteUrlChange,
|
||||||
|
label = {
|
||||||
|
Text(stringResource(R.string.bundle_input_source_url))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package app.revanced.manager.ui.component.bundle
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun BundleTopBar(
|
||||||
|
title: String,
|
||||||
|
onBackClick: (() -> Unit)? = null,
|
||||||
|
actions: @Composable (RowScope.() -> Unit) = {},
|
||||||
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
|
onBackIcon: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
val containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp)
|
||||||
|
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
navigationIcon = {
|
||||||
|
if (onBackClick != null) {
|
||||||
|
IconButton(onClick = onBackClick) {
|
||||||
|
onBackIcon()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = actions,
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = containerColor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,214 @@
|
|||||||
|
package app.revanced.manager.ui.component.bundle
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.Topic
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import app.revanced.manager.R
|
||||||
|
import app.revanced.manager.util.APK_MIMETYPE
|
||||||
|
import app.revanced.manager.util.JAR_MIMETYPE
|
||||||
|
import app.revanced.manager.util.parseUrlOrNull
|
||||||
|
import io.ktor.http.Url
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ImportBundleDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onRemoteSubmit: (String, Url) -> Unit,
|
||||||
|
onLocalSubmit: (String, Uri, Uri?) -> Unit,
|
||||||
|
patchCount: Int = 0,
|
||||||
|
) {
|
||||||
|
var name by rememberSaveable { mutableStateOf("") }
|
||||||
|
var remoteUrl by rememberSaveable { mutableStateOf("") }
|
||||||
|
var checked by remember { mutableStateOf(true) }
|
||||||
|
var isLocal by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var patchBundle by rememberSaveable { mutableStateOf<Uri?>(null) }
|
||||||
|
var integrations by rememberSaveable { mutableStateOf<Uri?>(null) }
|
||||||
|
|
||||||
|
val patchBundleText = patchBundle?.toString().orEmpty()
|
||||||
|
val integrationText = integrations?.toString().orEmpty()
|
||||||
|
|
||||||
|
val inputsAreValid by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
val nameSize = name.length
|
||||||
|
nameSize in 4..19 && if (isLocal) patchBundle != null else {
|
||||||
|
remoteUrl.isNotEmpty() && remoteUrl.parseUrlOrNull() != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val patchActivityLauncher =
|
||||||
|
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||||
|
uri?.let { patchBundle = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
val integrationsActivityLauncher =
|
||||||
|
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||||
|
uri?.let { integrations = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
val onPatchLauncherClick = {
|
||||||
|
patchActivityLauncher.launch(JAR_MIMETYPE)
|
||||||
|
}
|
||||||
|
|
||||||
|
val onIntegrationLauncherClick = {
|
||||||
|
integrationsActivityLauncher.launch(APK_MIMETYPE)
|
||||||
|
}
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
properties = DialogProperties(
|
||||||
|
usePlatformDefaultWidth = false,
|
||||||
|
dismissOnBackPress = true
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
BundleTopBar(
|
||||||
|
title = stringResource(R.string.import_bundle),
|
||||||
|
onBackClick = onDismissRequest,
|
||||||
|
onBackIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Close,
|
||||||
|
contentDescription = stringResource(R.string.close)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.import_),
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 16.dp)
|
||||||
|
.clickable {
|
||||||
|
if (inputsAreValid) {
|
||||||
|
if (isLocal) {
|
||||||
|
onLocalSubmit(name, patchBundle!!, integrations)
|
||||||
|
} else {
|
||||||
|
onRemoteSubmit(name, remoteUrl.parseUrlOrNull()!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
start = 24.dp,
|
||||||
|
top = 16.dp,
|
||||||
|
end = 24.dp,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
BundleTextContent(
|
||||||
|
name = name,
|
||||||
|
onNameChange = { name = it },
|
||||||
|
isLocal = isLocal,
|
||||||
|
remoteUrl = remoteUrl,
|
||||||
|
onRemoteUrlChange = { remoteUrl = it },
|
||||||
|
)
|
||||||
|
|
||||||
|
if(isLocal) {
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 16.dp),
|
||||||
|
value = patchBundleText,
|
||||||
|
onValueChange = {},
|
||||||
|
label = {
|
||||||
|
Text("Patches Source File")
|
||||||
|
},
|
||||||
|
trailingIcon = {
|
||||||
|
IconButton(
|
||||||
|
onClick = onPatchLauncherClick
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Topic,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 16.dp),
|
||||||
|
value = integrationText,
|
||||||
|
onValueChange = {},
|
||||||
|
label = {
|
||||||
|
Text("Integrations Source File")
|
||||||
|
},
|
||||||
|
trailingIcon = {
|
||||||
|
IconButton(onClick = onIntegrationLauncherClick) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Topic,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
Modifier.padding(
|
||||||
|
start = 8.dp,
|
||||||
|
top = 8.dp,
|
||||||
|
end = 4.dp,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
BundleInfoContent(
|
||||||
|
switchChecked = checked,
|
||||||
|
onCheckedChange = { checked = it },
|
||||||
|
patchInfoText = stringResource(R.string.no_patches),
|
||||||
|
patchCount = patchCount,
|
||||||
|
onArrowClick = {},
|
||||||
|
tonalButtonContent = {
|
||||||
|
if (isLocal) {
|
||||||
|
Text(stringResource(R.string.local))
|
||||||
|
} else {
|
||||||
|
Text(stringResource(R.string.remote))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tonalButtonOnClick = { isLocal = !isLocal },
|
||||||
|
isLocal = isLocal,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.manager.ui.component.sources
|
package app.revanced.manager.ui.component.bundle
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
@ -1,32 +0,0 @@
|
|||||||
package app.revanced.manager.ui.component.sources
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import app.revanced.manager.ui.component.ContentSelector
|
|
||||||
import app.revanced.manager.util.APK_MIMETYPE
|
|
||||||
import app.revanced.manager.util.JAR_MIMETYPE
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LocalBundleSelectors(onPatchesSelection: (Uri) -> Unit, onIntegrationsSelection: (Uri) -> Unit) {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
|
||||||
) {
|
|
||||||
ContentSelector(
|
|
||||||
mime = JAR_MIMETYPE,
|
|
||||||
onSelect = onPatchesSelection
|
|
||||||
) {
|
|
||||||
Text("Patches")
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentSelector(
|
|
||||||
mime = APK_MIMETYPE,
|
|
||||||
onSelect = onIntegrationsSelection
|
|
||||||
) {
|
|
||||||
Text("Integrations")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
package app.revanced.manager.ui.component.sources
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Cancel
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
|
||||||
import app.revanced.manager.R
|
|
||||||
import app.revanced.manager.util.parseUrlOrNull
|
|
||||||
import io.ktor.http.*
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun NewSourceDialog(
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
onRemoteSubmit: (String, Url) -> Unit,
|
|
||||||
onLocalSubmit: (String, Uri, Uri?) -> Unit
|
|
||||||
) {
|
|
||||||
Dialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
properties = DialogProperties(
|
|
||||||
usePlatformDefaultWidth = false,
|
|
||||||
dismissOnBackPress = true
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Surface(modifier = Modifier.fillMaxSize()) {
|
|
||||||
Column {
|
|
||||||
IconButton(onClick = onDismissRequest) {
|
|
||||||
Icon(Icons.Filled.Cancel, stringResource(R.string.cancel))
|
|
||||||
}
|
|
||||||
var isLocal by rememberSaveable { mutableStateOf(false) }
|
|
||||||
var patchBundle by rememberSaveable { mutableStateOf<Uri?>(null) }
|
|
||||||
var integrations by rememberSaveable { mutableStateOf<Uri?>(null) }
|
|
||||||
var remoteUrl by rememberSaveable { mutableStateOf("") }
|
|
||||||
|
|
||||||
var name by rememberSaveable { mutableStateOf("") }
|
|
||||||
|
|
||||||
val inputsAreValid by remember {
|
|
||||||
derivedStateOf {
|
|
||||||
val nameSize = name.length
|
|
||||||
|
|
||||||
nameSize in 4..19 && if (isLocal) patchBundle != null else {
|
|
||||||
remoteUrl.isNotEmpty() && remoteUrl.parseUrlOrNull() != null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(isLocal) {
|
|
||||||
integrations = null
|
|
||||||
patchBundle = null
|
|
||||||
remoteUrl = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(text = if (isLocal) "Local" else "Remote")
|
|
||||||
Switch(checked = isLocal, onCheckedChange = { isLocal = it })
|
|
||||||
|
|
||||||
TextField(
|
|
||||||
value = name,
|
|
||||||
onValueChange = { name = it },
|
|
||||||
label = {
|
|
||||||
Text("Name")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isLocal) {
|
|
||||||
LocalBundleSelectors(
|
|
||||||
onPatchesSelection = { patchBundle = it },
|
|
||||||
onIntegrationsSelection = { integrations = it },
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
TextField(
|
|
||||||
value = remoteUrl,
|
|
||||||
onValueChange = { remoteUrl = it },
|
|
||||||
label = {
|
|
||||||
Text("API Url")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
if (isLocal) {
|
|
||||||
onLocalSubmit(name, patchBundle!!, integrations)
|
|
||||||
} else {
|
|
||||||
onRemoteSubmit(name, remoteUrl.parseUrlOrNull()!!)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled = inputsAreValid
|
|
||||||
) {
|
|
||||||
Text("Save")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,155 +0,0 @@
|
|||||||
package app.revanced.manager.ui.component.sources
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
||||||
import app.revanced.manager.R
|
|
||||||
import app.revanced.manager.domain.sources.LocalSource
|
|
||||||
import app.revanced.manager.domain.sources.RemoteSource
|
|
||||||
import app.revanced.manager.domain.sources.Source
|
|
||||||
import app.revanced.manager.ui.viewmodel.SourcesViewModel
|
|
||||||
import app.revanced.manager.util.uiSafe
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun SourceItem(source: Source, onDelete: () -> Unit, coroutineScope: CoroutineScope) {
|
|
||||||
val composableScope = rememberCoroutineScope()
|
|
||||||
var sheetActive by rememberSaveable { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val bundle by source.bundle.collectAsStateWithLifecycle()
|
|
||||||
val patchCount = bundle.patches.size
|
|
||||||
val padding = PaddingValues(16.dp, 0.dp)
|
|
||||||
|
|
||||||
if (sheetActive) {
|
|
||||||
val modalSheetState = rememberModalBottomSheetState(
|
|
||||||
confirmValueChange = { it != SheetValue.PartiallyExpanded },
|
|
||||||
skipPartiallyExpanded = true
|
|
||||||
)
|
|
||||||
|
|
||||||
ModalBottomSheet(
|
|
||||||
sheetState = modalSheetState,
|
|
||||||
onDismissRequest = { sheetActive = false }
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = source.name,
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
|
||||||
|
|
||||||
when (source) {
|
|
||||||
is RemoteSource -> RemoteSourceItem(source, coroutineScope)
|
|
||||||
is LocalSource -> LocalSourceItem(source, coroutineScope)
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
composableScope.launch {
|
|
||||||
modalSheetState.hide()
|
|
||||||
sheetActive = false
|
|
||||||
onDelete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text("Delete this source")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.height(64.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable {
|
|
||||||
sheetActive = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = source.name,
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
modifier = Modifier.padding(padding)
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = pluralStringResource(R.plurals.patches_count, patchCount, patchCount),
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
modifier = Modifier.padding(padding)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun RemoteSourceItem(source: RemoteSource, coroutineScope: CoroutineScope) {
|
|
||||||
val androidContext = LocalContext.current
|
|
||||||
Text(text = "(api url here)")
|
|
||||||
|
|
||||||
Button(onClick = {
|
|
||||||
coroutineScope.launch {
|
|
||||||
uiSafe(androidContext, R.string.source_download_fail, SourcesViewModel.failLogMsg) {
|
|
||||||
source.update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Text(text = "Check for updates")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun LocalSourceItem(source: LocalSource, coroutineScope: CoroutineScope) {
|
|
||||||
val androidContext = LocalContext.current
|
|
||||||
val resolver = remember { androidContext.contentResolver!! }
|
|
||||||
|
|
||||||
fun loadAndReplace(
|
|
||||||
uri: Uri,
|
|
||||||
@StringRes toastMsg: Int,
|
|
||||||
errorLogMsg: String,
|
|
||||||
callback: suspend (InputStream) -> Unit
|
|
||||||
) = coroutineScope.launch {
|
|
||||||
uiSafe(androidContext, toastMsg, errorLogMsg) {
|
|
||||||
resolver.openInputStream(uri)!!.use {
|
|
||||||
callback(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalBundleSelectors(
|
|
||||||
onPatchesSelection = { uri ->
|
|
||||||
loadAndReplace(uri, R.string.source_replace_fail, "Failed to replace patch bundle") {
|
|
||||||
source.replace(it, null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onIntegrationsSelection = { uri ->
|
|
||||||
loadAndReplace(
|
|
||||||
uri,
|
|
||||||
R.string.source_replace_integrations_fail,
|
|
||||||
"Failed to replace integrations"
|
|
||||||
) {
|
|
||||||
source.replace(null, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
@ -12,7 +12,16 @@ import androidx.compose.material.icons.outlined.Apps
|
|||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material.icons.outlined.Source
|
import androidx.compose.material.icons.outlined.Source
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
|
import androidx.compose.material3.TabRow
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -35,7 +44,7 @@ enum class DashboardPage(
|
|||||||
@Composable
|
@Composable
|
||||||
fun DashboardScreen(
|
fun DashboardScreen(
|
||||||
onAppSelectorClick: () -> Unit,
|
onAppSelectorClick: () -> Unit,
|
||||||
onSettingsClick: () -> Unit
|
onSettingsClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val pages: Array<DashboardPage> = DashboardPage.values()
|
val pages: Array<DashboardPage> = DashboardPage.values()
|
||||||
|
|
||||||
|
@ -1,35 +1,44 @@
|
|||||||
package app.revanced.manager.ui.screen
|
package app.revanced.manager.ui.screen
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.ui.component.sources.NewSourceDialog
|
import app.revanced.manager.ui.component.bundle.ImportBundleDialog
|
||||||
import app.revanced.manager.ui.component.sources.SourceItem
|
import app.revanced.manager.ui.component.SourceItem
|
||||||
import app.revanced.manager.ui.viewmodel.SourcesViewModel
|
import app.revanced.manager.ui.viewmodel.SourcesViewModel
|
||||||
import org.koin.androidx.compose.getViewModel
|
import org.koin.androidx.compose.getViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourcesScreen(vm: SourcesViewModel = getViewModel()) {
|
fun SourcesScreen(
|
||||||
|
vm: SourcesViewModel = getViewModel(),
|
||||||
|
) {
|
||||||
var showNewSourceDialog by rememberSaveable { mutableStateOf(false) }
|
var showNewSourceDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
val sources by vm.sources.collectAsStateWithLifecycle(initialValue = emptyList())
|
val sources by vm.sources.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||||
|
|
||||||
if (showNewSourceDialog) NewSourceDialog(
|
if (showNewSourceDialog) {
|
||||||
onDismissRequest = { showNewSourceDialog = false },
|
ImportBundleDialog(
|
||||||
onLocalSubmit = { name, patches, integrations ->
|
onDismissRequest = { showNewSourceDialog = false },
|
||||||
showNewSourceDialog = false
|
onLocalSubmit = { name, patches, integrations ->
|
||||||
vm.addLocal(name, patches, integrations)
|
showNewSourceDialog = false
|
||||||
},
|
vm.addLocal(name, patches, integrations)
|
||||||
onRemoteSubmit = { name, url ->
|
},
|
||||||
showNewSourceDialog = false
|
onRemoteSubmit = { name, url ->
|
||||||
vm.addRemote(name, url)
|
showNewSourceDialog = false
|
||||||
}
|
vm.addRemote(name, url)
|
||||||
)
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -31,7 +31,7 @@ import app.revanced.manager.ui.viewmodel.ImportExportViewModel
|
|||||||
import app.revanced.manager.ui.component.AppTopBar
|
import app.revanced.manager.ui.component.AppTopBar
|
||||||
import app.revanced.manager.ui.component.GroupHeader
|
import app.revanced.manager.ui.component.GroupHeader
|
||||||
import app.revanced.manager.ui.component.PasswordField
|
import app.revanced.manager.ui.component.PasswordField
|
||||||
import app.revanced.manager.ui.component.sources.SourceSelector
|
import app.revanced.manager.ui.component.bundle.SourceSelector
|
||||||
import app.revanced.manager.util.toast
|
import app.revanced.manager.util.toast
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.androidx.compose.getViewModel
|
import org.koin.androidx.compose.getViewModel
|
||||||
|
@ -10,6 +10,12 @@
|
|||||||
<string name="settings">Settings</string>
|
<string name="settings">Settings</string>
|
||||||
<string name="select_app">Select an app</string>
|
<string name="select_app">Select an app</string>
|
||||||
<string name="select_patches">Select patches</string>
|
<string name="select_patches">Select patches</string>
|
||||||
|
|
||||||
|
<string name="import_">Import</string>
|
||||||
|
<string name="import_bundle">Import Bundle</string>
|
||||||
|
<string name="bundle_information">Bundle information</string>
|
||||||
|
<string name="bundle_patches">Bundle patches</string>
|
||||||
|
|
||||||
<string name="select_version">Select version</string>
|
<string name="select_version">Select version</string>
|
||||||
|
|
||||||
<string name="general">General</string>
|
<string name="general">General</string>
|
||||||
@ -77,9 +83,10 @@
|
|||||||
<string name="help">Help</string>
|
<string name="help">Help</string>
|
||||||
<string name="back">Back</string>
|
<string name="back">Back</string>
|
||||||
<string name="add">Add</string>
|
<string name="add">Add</string>
|
||||||
<string name="delete">Delete</string>
|
<string name="close">Close</string>
|
||||||
<string name="system">System</string>
|
<string name="system">System</string>
|
||||||
<string name="light">Light</string>
|
<string name="light">Light</string>
|
||||||
|
<string name="information">Information</string>
|
||||||
<string name="dark">Dark</string>
|
<string name="dark">Dark</string>
|
||||||
<string name="appearance">Appearance</string>
|
<string name="appearance">Appearance</string>
|
||||||
<string name="downloaded_apps">Downloaded apps</string>
|
<string name="downloaded_apps">Downloaded apps</string>
|
||||||
@ -94,6 +101,10 @@
|
|||||||
<string name="storage">Storage</string>
|
<string name="storage">Storage</string>
|
||||||
<string name="tab_apps">Apps</string>
|
<string name="tab_apps">Apps</string>
|
||||||
<string name="tab_sources">Sources</string>
|
<string name="tab_sources">Sources</string>
|
||||||
|
<string name="delete">Delete</string>
|
||||||
|
<string name="refresh">Refresh</string>
|
||||||
|
<string name="remote">Remote</string>
|
||||||
|
<string name="local">Local</string>
|
||||||
<string name="reload_sources">Reload all sources</string>
|
<string name="reload_sources">Reload all sources</string>
|
||||||
<string name="continue_anyways">Continue anyways</string>
|
<string name="continue_anyways">Continue anyways</string>
|
||||||
<string name="download_another_version">Download another version</string>
|
<string name="download_another_version">Download another version</string>
|
||||||
@ -102,6 +113,9 @@
|
|||||||
<string name="source_replace_fail">Failed to load updated patch bundle: %s</string>
|
<string name="source_replace_fail">Failed to load updated patch bundle: %s</string>
|
||||||
<string name="source_replace_integrations_fail">Failed to update integrations: %s</string>
|
<string name="source_replace_integrations_fail">Failed to update integrations: %s</string>
|
||||||
<string name="no_patched_apps_found">No patched apps found</string>
|
<string name="no_patched_apps_found">No patched apps found</string>
|
||||||
|
<string name="no_patches">No patches available to view</string>
|
||||||
|
<string name="patches_available">%d Patches available, tap to view</string>
|
||||||
|
<string name="tap_on_patches">Tap on the patches to get more information about them</string>
|
||||||
<string name="unsupported_app">Unsupported app</string>
|
<string name="unsupported_app">Unsupported app</string>
|
||||||
<string name="unsupported_patches">Unsupported patches</string>
|
<string name="unsupported_patches">Unsupported patches</string>
|
||||||
<string name="universal_patches">Universal patches</string>
|
<string name="universal_patches">Universal patches</string>
|
||||||
@ -160,6 +174,14 @@
|
|||||||
<string name="submit_feedback_description">Help us improve this application</string>
|
<string name="submit_feedback_description">Help us improve this application</string>
|
||||||
<string name="developer_options">Developer options</string>
|
<string name="developer_options">Developer options</string>
|
||||||
<string name="developer_options_description">Options for debugging issues</string>
|
<string name="developer_options_description">Options for debugging issues</string>
|
||||||
|
<string name="bundle_input_name">Name</string>
|
||||||
|
<string name="bundle_input_source_url">Source URL</string>
|
||||||
|
<string name="automatically_update">Automatically update</string>
|
||||||
|
<string name="automatically_update_description">Automatically update this bundle when ReVanced starts</string>
|
||||||
|
<string name="bundle_type">Bundle type</string>
|
||||||
|
<string name="bundle_type_description">Choose the type of bundle you want</string>
|
||||||
|
<string name="patches_version">Patches version</string>
|
||||||
|
<string name="integrations_version">Integrations version</string>
|
||||||
<string name="about_revanced_manager">About ReVanced Manager</string>
|
<string name="about_revanced_manager">About ReVanced Manager</string>
|
||||||
<string name="revanced_manager_description">ReVanced Manager is an application designed to work with ReVanced Patcher, which allows for long-lasting patches to be created for Android apps. The patching system is designed to automatically work with new versions of apps with minimal maintenance.</string>
|
<string name="revanced_manager_description">ReVanced Manager is an application designed to work with ReVanced Patcher, which allows for long-lasting patches to be created for Android apps. The patching system is designed to automatically work with new versions of apps with minimal maintenance.</string>
|
||||||
<string name="update_notification">A minor update for ReVanced Manager is available. Click here to update and get the latest features and fixes!</string>
|
<string name="update_notification">A minor update for ReVanced Manager is available. Click here to update and get the latest features and fixes!</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user