Merge branch 'compose-dev' into compose/downloader-system

This commit is contained in:
Ax333l 2024-07-12 15:29:09 +02:00
commit 9ddd421b2a
No known key found for this signature in database
GPG Key ID: D2B4D85271127D23
17 changed files with 322 additions and 297 deletions

View File

@ -23,8 +23,8 @@ jobs:
java-version: '17' java-version: '17'
distribution: 'temurin' distribution: 'temurin'
- name: Setup Gradle - name: Set up Gradle
uses: gradle/gradle-build-action@v2 uses: gradle/actions/setup-gradle@v3
- name: Build with Gradle - name: Build with Gradle
env: env:
@ -38,7 +38,7 @@ jobs:
run: mv app/build/outputs/apk/release/app-release.apk revanced-manager-${{ env.COMMIT_HASH }}.apk run: mv app/build/outputs/apk/release/app-release.apk revanced-manager-${{ env.COMMIT_HASH }}.apk
- name: Upload build - name: Upload build
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: revanced-manager name: revanced-manager
path: revanced-manager-${{ env.COMMIT_HASH }}.apk path: revanced-manager-${{ env.COMMIT_HASH }}.apk

View File

@ -20,10 +20,8 @@ jobs:
java-version: '17' java-version: '17'
distribution: 'temurin' distribution: 'temurin'
- name: Setup Gradle - name: Set up Gradle
uses: gradle/gradle-build-action@v2 uses: gradle/actions/setup-gradle@v3
with:
cache-disabled: true
- name: Build with Gradle - name: Build with Gradle
env: env:

View File

@ -11,7 +11,7 @@ jobs:
name: Dispatch event to documentation repository name: Dispatch event to documentation repository
if: github.ref == 'refs/heads/main' if: github.ref == 'refs/heads/main'
steps: steps:
- uses: peter-evans/repository-dispatch@v2 - uses: peter-evans/repository-dispatch@v3
with: with:
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }} token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
repository: revanced/revanced-documentation repository: revanced/revanced-documentation

View File

@ -44,7 +44,7 @@ fun AlertDialogExtended(
titleContentColor: Color = AlertDialogDefaults.titleContentColor, titleContentColor: Color = AlertDialogDefaults.titleContentColor,
textContentColor: Color = AlertDialogDefaults.textContentColor, textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation, tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
textHorizontalPadding: PaddingValues = PaddingValues(horizontal = 24.dp) textHorizontalPadding: PaddingValues = TextHorizontalPadding
) { ) {
BasicAlertDialog(onDismissRequest = onDismissRequest) { BasicAlertDialog(onDismissRequest = onDismissRequest) {
Surface( Surface(
@ -147,4 +147,6 @@ private fun ContentStyle(
content() content()
} }
} }
} }
val TextHorizontalPadding = PaddingValues(horizontal = 24.dp)

View File

@ -18,7 +18,8 @@ fun Markdown(
colors = markdownColor( colors = markdownColor(
text = MaterialTheme.colorScheme.onSurfaceVariant, text = MaterialTheme.colorScheme.onSurfaceVariant,
codeBackground = MaterialTheme.colorScheme.secondaryContainer, codeBackground = MaterialTheme.colorScheme.secondaryContainer,
codeText = MaterialTheme.colorScheme.onSecondaryContainer codeText = MaterialTheme.colorScheme.onSecondaryContainer,
linkText = MaterialTheme.colorScheme.primary
), ),
typography = markdownTypography( typography = markdownTypography(
h1 = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Bold), h1 = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Bold),

View File

@ -4,52 +4,55 @@ import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Topic import androidx.compose.material.icons.filled.Topic
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.ListItem
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue 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.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp 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.R
import app.revanced.manager.ui.component.AlertDialogExtended
import app.revanced.manager.ui.component.TextHorizontalPadding
import app.revanced.manager.ui.model.BundleType import app.revanced.manager.ui.model.BundleType
import app.revanced.manager.util.APK_MIMETYPE import app.revanced.manager.util.APK_MIMETYPE
import app.revanced.manager.util.JAR_MIMETYPE import app.revanced.manager.util.JAR_MIMETYPE
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ImportBundleDialog( fun ImportPatchBundleDialog(
onDismissRequest: () -> Unit, onDismiss: () -> Unit,
onRemoteSubmit: (String, Boolean) -> Unit, onRemoteSubmit: (String, Boolean) -> Unit,
onLocalSubmit: (Uri, Uri?) -> Unit, onLocalSubmit: (Uri, Uri?) -> Unit
initialBundleType: BundleType
) { ) {
var remoteUrl by rememberSaveable { mutableStateOf("") } var currentStep by rememberSaveable { mutableIntStateOf(0) }
var autoUpdate by rememberSaveable { mutableStateOf(true) } var bundleType by rememberSaveable { mutableStateOf(BundleType.Remote) }
var bundleType by rememberSaveable { mutableStateOf(initialBundleType) }
var patchBundle by rememberSaveable { mutableStateOf<Uri?>(null) } var patchBundle by rememberSaveable { mutableStateOf<Uri?>(null) }
var integrations by rememberSaveable { mutableStateOf<Uri?>(null) } var integrations by rememberSaveable { mutableStateOf<Uri?>(null) }
var remoteUrl by rememberSaveable { mutableStateOf("") }
val inputsAreValid by remember { var autoUpdate by rememberSaveable { mutableStateOf(false) }
derivedStateOf {
if (bundleType == BundleType.Local) patchBundle != null else remoteUrl.isNotEmpty()
}
}
val patchActivityLauncher = val patchActivityLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri -> rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
@ -69,97 +72,208 @@ fun ImportBundleDialog(
integrationsActivityLauncher.launch(APK_MIMETYPE) integrationsActivityLauncher.launch(APK_MIMETYPE)
} }
Dialog( val steps = listOf<@Composable () -> Unit>(
onDismissRequest = onDismissRequest, {
properties = DialogProperties( SelectBundleTypeStep(bundleType) { selectedType ->
usePlatformDefaultWidth = false, bundleType = selectedType
dismissOnBackPress = true
)
) {
Scaffold(
topBar = {
BundleTopBar(
title = stringResource(R.string.import_bundle),
onBackClick = onDismissRequest,
backIcon = {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.close)
)
},
actions = {
TextButton(
enabled = inputsAreValid,
onClick = {
when (bundleType) {
BundleType.Local -> onLocalSubmit(patchBundle!!, integrations)
BundleType.Remote -> onRemoteSubmit(remoteUrl, autoUpdate)
}
},
modifier = Modifier.padding(end = 16.dp)
) {
Text(stringResource(R.string.import_))
}
}
)
},
) { paddingValues ->
BaseBundleDialog(
modifier = Modifier.padding(paddingValues),
isDefault = false,
name = null,
remoteUrl = remoteUrl.takeUnless { bundleType == BundleType.Local },
onRemoteUrlChange = { remoteUrl = it },
patchCount = 0,
version = null,
autoUpdate = autoUpdate,
onAutoUpdateChange = { autoUpdate = it },
onPatchesClick = {},
onBundleTypeClick = {
bundleType = when (bundleType) {
BundleType.Local -> BundleType.Remote
BundleType.Remote -> BundleType.Local
}
},
) {
if (bundleType == BundleType.Remote) return@BaseBundleDialog
BundleListItem(
headlineText = stringResource(R.string.patch_bundle_field),
supportingText = stringResource(if (patchBundle != null) R.string.file_field_set else R.string.file_field_not_set),
trailingContent = {
IconButton(
onClick = ::launchPatchActivity
) {
Icon(
imageVector = Icons.Default.Topic,
contentDescription = null
)
}
},
modifier = Modifier.clickable {
launchPatchActivity()
}
)
BundleListItem(
headlineText = stringResource(R.string.integrations_field),
supportingText = stringResource(if (integrations != null) R.string.file_field_set else R.string.file_field_not_set),
trailingContent = {
IconButton(
onClick = ::launchIntegrationsActivity
) {
Icon(
imageVector = Icons.Default.Topic,
contentDescription = null
)
}
},
modifier = Modifier.clickable {
launchIntegrationsActivity()
}
)
} }
},
{
ImportBundleStep(
bundleType,
patchBundle,
integrations,
remoteUrl,
autoUpdate,
{ launchPatchActivity() },
{ launchIntegrationsActivity() },
{ remoteUrl = it },
{ autoUpdate = it }
)
}
)
val inputsAreValid by remember {
derivedStateOf {
(bundleType == BundleType.Local && patchBundle != null) ||
(bundleType == BundleType.Remote && remoteUrl.isNotEmpty())
}
}
AlertDialogExtended(
onDismissRequest = onDismiss,
title = {
Text(stringResource(if (currentStep == 0) R.string.select else R.string.add_patch_bundle))
},
text = {
steps[currentStep]()
},
confirmButton = {
if (currentStep == steps.lastIndex) {
TextButton(
enabled = inputsAreValid,
onClick = {
when (bundleType) {
BundleType.Local -> patchBundle?.let {
onLocalSubmit(
it,
integrations
)
}
BundleType.Remote -> onRemoteSubmit(remoteUrl, autoUpdate)
}
}
) {
Text(stringResource(R.string.add))
}
} else {
TextButton(onClick = { currentStep++ }) {
Text(stringResource(R.string.next))
}
}
},
dismissButton = {
if (currentStep > 0) {
TextButton(onClick = { currentStep-- }) {
Text(stringResource(R.string.back))
}
} else {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.cancel))
}
}
},
textHorizontalPadding = PaddingValues(0.dp)
)
}
@Composable
fun SelectBundleTypeStep(
bundleType: BundleType,
onBundleTypeSelected: (BundleType) -> Unit
) {
Column(
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
Text(
modifier = Modifier.padding(horizontal = 24.dp),
text = stringResource(R.string.select_bundle_type_dialog_description)
)
Column {
ListItem(
modifier = Modifier.clickable(
role = Role.RadioButton,
onClick = { onBundleTypeSelected(BundleType.Remote) }
),
headlineContent = { Text(stringResource(R.string.enter_url)) },
overlineContent = { Text(stringResource(R.string.recommended)) },
supportingContent = { Text(stringResource(R.string.remote_bundle_description)) },
leadingContent = {
RadioButton(
selected = bundleType == BundleType.Remote,
onClick = null
)
}
)
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
ListItem(
modifier = Modifier.clickable(
role = Role.RadioButton,
onClick = { onBundleTypeSelected(BundleType.Local) }
),
headlineContent = { Text(stringResource(R.string.select_from_storage)) },
supportingContent = { Text(stringResource(R.string.local_bundle_description)) },
overlineContent = { },
leadingContent = {
RadioButton(
selected = bundleType == BundleType.Local,
onClick = null
)
}
)
} }
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ImportBundleStep(
bundleType: BundleType,
patchBundle: Uri?,
integrations: Uri?,
remoteUrl: String,
autoUpdate: Boolean,
launchPatchActivity: () -> Unit,
launchIntegrationsActivity: () -> Unit,
onRemoteUrlChange: (String) -> Unit,
onAutoUpdateChange: (Boolean) -> Unit
) {
Column {
when (bundleType) {
BundleType.Local -> {
Column(
modifier = Modifier.padding(horizontal = 8.dp)
) {
ListItem(
headlineContent = {
Text(stringResource(R.string.patch_bundle_field))
},
supportingContent = { Text(stringResource(if (patchBundle != null) R.string.file_field_set else R.string.file_field_not_set)) },
trailingContent = {
IconButton(onClick = launchPatchActivity) {
Icon(imageVector = Icons.Default.Topic, contentDescription = null)
}
},
modifier = Modifier.clickable { launchPatchActivity() }
)
ListItem(
headlineContent = {
Text(stringResource(R.string.integrations_field))
},
supportingContent = { Text(stringResource(if (integrations != null) R.string.file_field_set else R.string.file_field_not_set)) },
trailingContent = {
IconButton(onClick = launchIntegrationsActivity) {
Icon(imageVector = Icons.Default.Topic, contentDescription = null)
}
},
modifier = Modifier.clickable { launchIntegrationsActivity() }
)
}
}
BundleType.Remote -> {
Column(
modifier = Modifier.padding(TextHorizontalPadding)
) {
OutlinedTextField(
value = remoteUrl,
onValueChange = onRemoteUrlChange,
label = { Text(stringResource(R.string.bundle_url)) }
)
}
Column(
modifier = Modifier.padding(horizontal = 8.dp)
) {
ListItem(
modifier = Modifier.clickable(
role = Role.Checkbox,
onClick = { onAutoUpdateChange(!autoUpdate) }
),
headlineContent = { Text(stringResource(R.string.auto_update)) },
leadingContent = {
CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) {
Checkbox(
checked = autoUpdate,
onCheckedChange = {
onAutoUpdateChange(!autoUpdate)
}
)
}
},
)
}
}
}
}
}

View File

@ -1,95 +0,0 @@
package app.revanced.manager.ui.component.bundle
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.ListItem
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
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.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import app.revanced.manager.R
import app.revanced.manager.ui.component.AlertDialogExtended
import app.revanced.manager.ui.model.BundleType
@Composable
fun ImportBundleTypeSelectorDialog(
onDismiss: () -> Unit,
onConfirm: (BundleType) -> Unit,
) {
var bundleType: BundleType by rememberSaveable { mutableStateOf(BundleType.Remote) }
AlertDialogExtended(
onDismissRequest = onDismiss,
confirmButton = {
TextButton(
onClick = { onConfirm(bundleType) }
) {
Text(stringResource(R.string.select))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.cancel))
}
},
title = {
Text(stringResource(R.string.select_bundle_type_dialog_title))
},
text = {
Column(
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
Text(
modifier = Modifier.padding(horizontal = 24.dp),
text = stringResource(R.string.select_bundle_type_dialog_description)
)
Column {
ListItem(
modifier = Modifier.clickable(
role = Role.RadioButton,
onClick = { bundleType = BundleType.Remote }
),
headlineContent = { Text(stringResource(R.string.remote)) },
overlineContent = { Text(stringResource(R.string.recommended)) },
supportingContent = { Text(stringResource(R.string.remote_bundle_description)) },
leadingContent = {
RadioButton(
selected = bundleType == BundleType.Remote,
onClick = null
)
}
)
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
ListItem(
modifier = Modifier.clickable(
role = Role.RadioButton,
onClick = { bundleType = BundleType.Local }
),
headlineContent = { Text(stringResource(R.string.local)) },
supportingContent = { Text(stringResource(R.string.local_bundle_description)) },
overlineContent = { }, // we're using this parameter to force the 3-line ListItem state
leadingContent = {
RadioButton(
selected = bundleType == BundleType.Local,
onClick = null
)
}
)
}
}
},
textHorizontalPadding = PaddingValues(0.dp)
)
}

View File

@ -55,10 +55,6 @@ fun Changelog(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) { ) {
Tag(
Icons.Outlined.Sell,
version
)
Tag( Tag(
Icons.Outlined.FileDownload, Icons.Outlined.FileDownload,
downloadCount downloadCount

View File

@ -58,9 +58,7 @@ import app.revanced.manager.ui.component.AutoUpdatesDialog
import app.revanced.manager.ui.component.NotificationCard import app.revanced.manager.ui.component.NotificationCard
import app.revanced.manager.ui.component.bundle.BundleItem import app.revanced.manager.ui.component.bundle.BundleItem
import app.revanced.manager.ui.component.bundle.BundleTopBar import app.revanced.manager.ui.component.bundle.BundleTopBar
import app.revanced.manager.ui.component.bundle.ImportBundleDialog import app.revanced.manager.ui.component.bundle.ImportPatchBundleDialog
import app.revanced.manager.ui.component.bundle.ImportBundleTypeSelectorDialog
import app.revanced.manager.ui.model.BundleType
import app.revanced.manager.ui.viewmodel.DashboardViewModel import app.revanced.manager.ui.viewmodel.DashboardViewModel
import app.revanced.manager.util.toast import app.revanced.manager.util.toast
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -100,33 +98,17 @@ fun DashboardScreen(
val firstLaunch by vm.prefs.firstLaunch.getAsState() val firstLaunch by vm.prefs.firstLaunch.getAsState()
if (firstLaunch) AutoUpdatesDialog(vm::applyAutoUpdatePrefs) if (firstLaunch) AutoUpdatesDialog(vm::applyAutoUpdatePrefs)
var selectedBundleType: BundleType? by rememberSaveable { mutableStateOf(null) } var showAddBundleDialog by rememberSaveable { mutableStateOf(false) }
selectedBundleType?.let { if (showAddBundleDialog) {
fun dismiss() { ImportPatchBundleDialog(
selectedBundleType = null onDismiss = { showAddBundleDialog = false },
}
ImportBundleDialog(
onDismissRequest = ::dismiss,
onLocalSubmit = { patches, integrations -> onLocalSubmit = { patches, integrations ->
dismiss() showAddBundleDialog = false
vm.createLocalSource(patches, integrations) vm.createLocalSource(patches, integrations)
}, },
onRemoteSubmit = { url, autoUpdate -> onRemoteSubmit = { url, autoUpdate ->
dismiss() showAddBundleDialog = false
vm.createRemoteSource(url, autoUpdate) vm.createRemoteSource(url, autoUpdate)
},
initialBundleType = it
)
}
var showBundleTypeSelectorDialog by rememberSaveable { mutableStateOf(false) }
if (showBundleTypeSelectorDialog) {
ImportBundleTypeSelectorDialog(
onDismiss = { showBundleTypeSelectorDialog = false },
onConfirm = {
selectedBundleType = it
showBundleTypeSelectorDialog = false
} }
) )
} }
@ -200,7 +182,7 @@ fun DashboardScreen(
} }
DashboardPage.BUNDLES.ordinal -> { DashboardPage.BUNDLES.ordinal -> {
showBundleTypeSelectorDialog = true showAddBundleDialog = true
} }
} }
} }
@ -238,7 +220,6 @@ fun DashboardScreen(
if (vm.showBatteryOptimizationsWarning) { if (vm.showBatteryOptimizationsWarning) {
{ {
NotificationCard( NotificationCard(
modifier = Modifier.padding(16.dp),
isWarning = true, isWarning = true,
icon = Icons.Default.BatteryAlert, icon = Icons.Default.BatteryAlert,
text = stringResource(R.string.battery_optimization_notification), text = stringResource(R.string.battery_optimization_notification),
@ -260,7 +241,7 @@ fun DashboardScreen(
Text(stringResource(R.string.dismiss)) Text(stringResource(R.string.dismiss))
} }
TextButton(onClick = onUpdateClick) { TextButton(onClick = onUpdateClick) {
Text(stringResource(R.string.update)) Text(stringResource(R.string.show))
} }
} }
) )

View File

@ -3,12 +3,13 @@ package app.revanced.manager.ui.screen
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowRight import androidx.compose.material.icons.automirrored.outlined.ArrowRight
import androidx.compose.material.icons.filled.AutoFixHigh
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -26,6 +27,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.AppInfo import app.revanced.manager.ui.component.AppInfo
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.destination.SelectedAppInfoDestination import app.revanced.manager.ui.destination.SelectedAppInfoDestination
import app.revanced.manager.ui.model.BundleInfo.Extensions.bundleInfoFlow import app.revanced.manager.ui.model.BundleInfo.Extensions.bundleInfoFlow
import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.model.SelectedApp
@ -164,9 +166,16 @@ private fun SelectedAppInfoScreen(
title = stringResource(R.string.app_info), title = stringResource(R.string.app_info),
onBackClick = onBackClick onBackClick = onBackClick
) )
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text(stringResource(R.string.patch)) },
icon = { Icon(Icons.Default.AutoFixHigh, null) },
onClick = onPatchClick
)
} }
) { paddingValues -> ) { paddingValues ->
Column( ColumnWithScrollbar(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(paddingValues) .padding(paddingValues)
@ -179,15 +188,6 @@ private fun SelectedAppInfoScreen(
) )
} }
PageItem(R.string.patch, stringResource(R.string.patch_item_description), onPatchClick)
Text(
stringResource(R.string.advanced),
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.labelLarge,
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp)
)
PageItem( PageItem(
R.string.patch_selector_item, R.string.patch_selector_item,
stringResource(R.string.patch_selector_item_description, selectedPatchCount), stringResource(R.string.patch_selector_item_description, selectedPatchCount),

View File

@ -10,7 +10,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Http import androidx.compose.material.icons.outlined.Api
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -32,6 +32,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.revanced.manager.BuildConfig
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.ColumnWithScrollbar
@ -145,22 +146,17 @@ fun AdvancedSettingsScreen(
description = R.string.patch_selection_safeguard_description description = R.string.patch_selection_safeguard_description
) )
GroupHeader(stringResource(R.string.device)) GroupHeader(stringResource(R.string.debugging))
SettingsListItem( SettingsListItem(
headlineContent = stringResource(R.string.device_model), headlineContent = stringResource(R.string.about_device),
supportingContent = Build.MODEL supportingContent = """
) **Version**: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})
SettingsListItem( **Build type**: ${BuildConfig.BUILD_TYPE}
headlineContent = stringResource(R.string.device_android_version), **Model**: ${Build.MODEL}
supportingContent = Build.VERSION.RELEASE **Android version**: ${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT})
) **Supported Archs**: ${Build.SUPPORTED_ABIS.joinToString(", ")}
SettingsListItem( **Memory limit**: $memoryLimit
headlineContent = stringResource(R.string.device_architectures), """.trimIndent()
supportingContent = Build.SUPPORTED_ABIS.joinToString(", ")
)
SettingsListItem(
headlineContent = stringResource(R.string.device_memory_limit),
supportingContent = memoryLimit
) )
} }
} }
@ -187,7 +183,7 @@ private fun APIUrlDialog(currentUrl: String, onSubmit: (String?) -> Unit) {
} }
}, },
icon = { icon = {
Icon(Icons.Outlined.Http, null) Icon(Icons.Outlined.Api, null)
}, },
title = { title = {
Text( Text(

View File

@ -112,7 +112,7 @@ private fun ThemePicker(
} }
}, },
confirmButton = { confirmButton = {
Button( TextButton(
onClick = { onClick = {
onConfirm(selectedTheme) onConfirm(selectedTheme)
onDismiss() onDismiss()

View File

@ -56,7 +56,7 @@ fun UpdateScreen(
Scaffold( Scaffold(
topBar = { topBar = {
AppTopBar( AppTopBar(
title = stringResource(R.string.updates), title = stringResource(R.string.update),
onBackClick = onBackClick onBackClick = onBackClick
) )
} }

View File

@ -11,6 +11,7 @@ import android.os.Build
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
@ -175,6 +176,9 @@ fun String.relativeTime(context: Context): String {
} }
} }
const val isScrollingUpSensitivity = 10
@Composable @Composable
fun LazyListState.isScrollingUp(): State<Boolean> { fun LazyListState.isScrollingUp(): State<Boolean> {
return remember(this) { return remember(this) {
@ -182,10 +186,16 @@ fun LazyListState.isScrollingUp(): State<Boolean> {
var previousScrollOffset by mutableIntStateOf(firstVisibleItemScrollOffset) var previousScrollOffset by mutableIntStateOf(firstVisibleItemScrollOffset)
derivedStateOf { derivedStateOf {
if (previousIndex != firstVisibleItemIndex) { val indexChanged = previousIndex != firstVisibleItemIndex
val offsetChanged =
kotlin.math.abs(previousScrollOffset - firstVisibleItemScrollOffset) > isScrollingUpSensitivity
if (indexChanged) {
previousIndex > firstVisibleItemIndex previousIndex > firstVisibleItemIndex
} else if (offsetChanged) {
previousScrollOffset > firstVisibleItemScrollOffset
} else { } else {
previousScrollOffset >= firstVisibleItemScrollOffset true
}.also { }.also {
previousIndex = firstVisibleItemIndex previousIndex = firstVisibleItemIndex
previousScrollOffset = firstVisibleItemScrollOffset previousScrollOffset = firstVisibleItemScrollOffset
@ -194,4 +204,18 @@ fun LazyListState.isScrollingUp(): State<Boolean> {
} }
} }
val LazyListState.isScrollingUp: Boolean @Composable get() = this.isScrollingUp().value // TODO: support sensitivity
@Composable
fun ScrollState.isScrollingUp(): State<Boolean> {
return remember(this) {
var previousScrollOffset by mutableIntStateOf(value)
derivedStateOf {
(previousScrollOffset >= value).also {
previousScrollOffset = value
}
}
}
}
val LazyListState.isScrollingUp: Boolean @Composable get() = this.isScrollingUp().value
val ScrollState.isScrollingUp: Boolean @Composable get() = this.isScrollingUp().value

View File

@ -18,8 +18,8 @@
<string name="bundle_patches">Bundle patches</string> <string name="bundle_patches">Bundle patches</string>
<string name="patch_bundle_field">Patch bundle</string> <string name="patch_bundle_field">Patch bundle</string>
<string name="integrations_field">Integrations</string> <string name="integrations_field">Integrations</string>
<string name="file_field_set">Provided</string> <string name="file_field_set">Selected</string>
<string name="file_field_not_set">Not provided</string> <string name="file_field_not_set">Not selected</string>
<string name="field_not_set">Not set</string> <string name="field_not_set">Not set</string>
@ -42,8 +42,8 @@
<string name="legacy_import_failed">Could not import legacy settings</string> <string name="legacy_import_failed">Could not import legacy settings</string>
<string name="auto_updates_dialog_title">Select updates to receive</string> <string name="auto_updates_dialog_title">Configure updates</string>
<string name="auto_updates_dialog_description">Periodically connect to update providers to check for updates.</string> <string name="auto_updates_dialog_description">Do you want ReVanced Manager to periodically check for updates for the following components?</string>
<string name="auto_updates_dialog_manager">ReVanced Manager</string> <string name="auto_updates_dialog_manager">ReVanced Manager</string>
<string name="auto_updates_dialog_patches">ReVanced Patches</string> <string name="auto_updates_dialog_patches">ReVanced Patches</string>
<string name="auto_updates_dialog_note">These settings can be changed later.</string> <string name="auto_updates_dialog_note">These settings can be changed later.</string>
@ -169,7 +169,7 @@
<string name="device_model">Model</string> <string name="device_model">Model</string>
<string name="device_architectures">CPU Architectures</string> <string name="device_architectures">CPU Architectures</string>
<string name="device_memory_limit">Memory limits</string> <string name="device_memory_limit">Memory limits</string>
<string name="device_memory_limit_format">Normal: %1$d MB, Large: %2$d MB</string> <string name="device_memory_limit_format">%1$dMB (Normal) - %2$dMB (Large)</string>
<string name="patch_bundles_section">Patch bundles</string> <string name="patch_bundles_section">Patch bundles</string>
<string name="patch_bundles_redownload">Redownload all patch bundles</string> <string name="patch_bundles_redownload">Redownload all patch bundles</string>
<string name="patch_bundles_reset">Reset patch bundles</string> <string name="patch_bundles_reset">Reset patch bundles</string>
@ -363,9 +363,17 @@
<string name="no_contributors_found">No contributors found</string> <string name="no_contributors_found">No contributors found</string>
<string name="select">Select</string> <string name="select">Select</string>
<string name="select_deselect_all">Select or deselect all</string> <string name="select_deselect_all">Select or deselect all</string>
<string name="select_bundle_type_dialog_title">Select bundle type</string> <string name="select_bundle_type_dialog_title">Add new bundle</string>
<string name="select_bundle_type_dialog_description">Select the type that is right for you.</string> <string name="select_bundle_type_dialog_description">Add a new bundle from a URL or storage</string>
<string name="local_bundle_description">Import local files from your storage, does not automatically update</string> <string name="local_bundle_description">Import local files from your storage, does not automatically update</string>
<string name="remote_bundle_description">Import remote files from a URL, can automatically update</string> <string name="remote_bundle_description">Import remote files from a URL, can automatically update</string>
<string name="recommended">Recommended</string> <string name="recommended">Recommended</string>
<string name="show">Show</string>
<string name="debugging">Debugging</string>
<string name="about_device">About device</string>
<string name="enter_url">Enter URL</string>
<string name="next">Next</string>
<string name="add_patch_bundle">Add patch bundle</string>
<string name="bundle_url">Bundle URL</string>
<string name="auto_update">Auto update</string>
</resources> </resources>

View File

@ -1,5 +1,4 @@
[versions] [versions]
kotlin = "1.9.22"
ktx = "1.13.1" ktx = "1.13.1"
material3 = "1.3.0-beta04" material3 = "1.3.0-beta04"
ui-tooling = "1.6.8" ui-tooling = "1.6.8"
@ -24,9 +23,10 @@ reimagined-navigation = "1.5.0"
ktor = "2.3.9" ktor = "2.3.9"
markdown-renderer = "0.22.0" markdown-renderer = "0.22.0"
fading-edges = "1.0.4" fading-edges = "1.0.4"
androidGradlePlugin = "8.3.2" android-gradle-plugin = "8.3.2"
devToolsGradlePlugin = "1.9.22-1.0.17" kotlin-gradle-plugin = "1.9.22"
aboutLibrariesGradlePlugin = "11.1.1" dev-tools-gradle-plugin = "1.9.22-1.0.17"
about-libraries-gradle-plugin = "11.1.1"
binary-compatibility-validator = "0.15.1" binary-compatibility-validator = "0.15.1"
coil = "2.6.0" coil = "2.6.0"
app-icon-loader-coil = "1.5.0" app-icon-loader-coil = "1.5.0"
@ -89,8 +89,8 @@ koin-workmanager = { group = "io.insert-koin", name = "koin-androidx-workmanager
# Compose Navigation # Compose Navigation
reimagined-navigation = { group = "dev.olshevski.navigation", name = "reimagined", version.ref = "reimagined-navigation" } reimagined-navigation = { group = "dev.olshevski.navigation", name = "reimagined", version.ref = "reimagined-navigation" }
# about-libraries # About Libraries
about-libraries = { group = "com.mikepenz", name = "aboutlibraries-compose", version.ref = "aboutLibrariesGradlePlugin" } about-libraries = { group = "com.mikepenz", name = "aboutlibraries-compose", version.ref = "about-libraries-gradle-plugin" }
# Ktor # Ktor
ktor-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } ktor-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
@ -131,9 +131,9 @@ reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reo
compose-icons-fontawesome = { group = "com.github.BenjaminHalko.compose-icons", name = "font-awesome", version.ref = "compose-icons" } compose-icons-fontawesome = { group = "com.github.BenjaminHalko.compose-icons", name = "font-awesome", version.ref = "compose-icons" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" }
devtools = { id = "com.google.devtools.ksp", version.ref = "devToolsGradlePlugin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-gradle-plugin" }
about-libraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutLibrariesGradlePlugin" } devtools = { id = "com.google.devtools.ksp", version.ref = "dev-tools-gradle-plugin" }
android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } about-libraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "about-libraries-gradle-plugin" }
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" } binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }