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
|
||||
class RemoteSource(name: String, id: Int, directory: File) : Source(name, id, directory) {
|
||||
private val api: ManagerAPI = get()
|
||||
|
||||
suspend fun downloadLatest() = withContext(Dispatchers.IO) {
|
||||
api.downloadBundle(patchesJar, integrations).also { (patchesVer, integrationsVer) ->
|
||||
saveVersion(patchesVer, integrationsVer)
|
||||
|
@ -60,3 +60,4 @@ fun AppTopBar(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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.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.Settings
|
||||
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.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -35,7 +44,7 @@ enum class DashboardPage(
|
||||
@Composable
|
||||
fun DashboardScreen(
|
||||
onAppSelectorClick: () -> Unit,
|
||||
onSettingsClick: () -> Unit
|
||||
onSettingsClick: () -> Unit,
|
||||
) {
|
||||
val pages: Array<DashboardPage> = DashboardPage.values()
|
||||
|
||||
|
@ -1,35 +1,44 @@
|
||||
package app.revanced.manager.ui.screen
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
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.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.ui.component.sources.NewSourceDialog
|
||||
import app.revanced.manager.ui.component.sources.SourceItem
|
||||
import app.revanced.manager.ui.component.bundle.ImportBundleDialog
|
||||
import app.revanced.manager.ui.component.SourceItem
|
||||
import app.revanced.manager.ui.viewmodel.SourcesViewModel
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@Composable
|
||||
fun SourcesScreen(vm: SourcesViewModel = getViewModel()) {
|
||||
fun SourcesScreen(
|
||||
vm: SourcesViewModel = getViewModel(),
|
||||
) {
|
||||
var showNewSourceDialog by rememberSaveable { mutableStateOf(false) }
|
||||
val sources by vm.sources.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||
|
||||
if (showNewSourceDialog) NewSourceDialog(
|
||||
onDismissRequest = { showNewSourceDialog = false },
|
||||
onLocalSubmit = { name, patches, integrations ->
|
||||
showNewSourceDialog = false
|
||||
vm.addLocal(name, patches, integrations)
|
||||
},
|
||||
onRemoteSubmit = { name, url ->
|
||||
showNewSourceDialog = false
|
||||
vm.addRemote(name, url)
|
||||
}
|
||||
)
|
||||
if (showNewSourceDialog) {
|
||||
ImportBundleDialog(
|
||||
onDismissRequest = { showNewSourceDialog = false },
|
||||
onLocalSubmit = { name, patches, integrations ->
|
||||
showNewSourceDialog = false
|
||||
vm.addLocal(name, patches, integrations)
|
||||
},
|
||||
onRemoteSubmit = { name, url ->
|
||||
showNewSourceDialog = false
|
||||
vm.addRemote(name, url)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
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.GroupHeader
|
||||
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 kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
@ -10,6 +10,12 @@
|
||||
<string name="settings">Settings</string>
|
||||
<string name="select_app">Select an app</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="general">General</string>
|
||||
@ -77,9 +83,10 @@
|
||||
<string name="help">Help</string>
|
||||
<string name="back">Back</string>
|
||||
<string name="add">Add</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="close">Close</string>
|
||||
<string name="system">System</string>
|
||||
<string name="light">Light</string>
|
||||
<string name="information">Information</string>
|
||||
<string name="dark">Dark</string>
|
||||
<string name="appearance">Appearance</string>
|
||||
<string name="downloaded_apps">Downloaded apps</string>
|
||||
@ -94,6 +101,10 @@
|
||||
<string name="storage">Storage</string>
|
||||
<string name="tab_apps">Apps</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="continue_anyways">Continue anyways</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_integrations_fail">Failed to update integrations: %s</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_patches">Unsupported 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="developer_options">Developer options</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="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>
|
||||
|
Loading…
Reference in New Issue
Block a user