mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2025-07-17 16:28:59 +08:00
Compare commits
11 Commits
compose/fe
...
fix/switch
Author | SHA1 | Date | |
---|---|---|---|
dada509be9 | |||
81a4ebd327 | |||
5fc54eb53b | |||
88b0b8c078 | |||
7959c36e71 | |||
e9542c6cf0 | |||
0992e63c28 | |||
aebad0b0e2 | |||
bdb0317a9e | |||
c0d7cf7c2c | |||
2c1ff4d2cd |
10
.github/workflows/build_pull_request.yml
vendored
10
.github/workflows/build_pull_request.yml
vendored
@ -22,4 +22,12 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: ./gradlew build --no-daemon
|
run: ./gradlew assembleRelease --no-daemon
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: revanced-manager
|
||||||
|
path: |
|
||||||
|
app/build/outputs/apk/release/revanced-manager*.apk
|
||||||
|
app/build/outputs/apk/release/revanced-manager*.apk.asc
|
||||||
|
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@ -44,6 +44,13 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Import GPG key
|
||||||
|
uses: crazy-max/ghaction-import-gpg@v6
|
||||||
|
with:
|
||||||
|
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
|
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
fingerprint: ${{ vars.GPG_FINGERPRINT }}
|
||||||
|
|
||||||
- name: Setup keystore
|
- name: Setup keystore
|
||||||
run: |
|
run: |
|
||||||
echo "${{ secrets.KEYSTORE }}" | base64 --decode > "app/keystore.jks"
|
echo "${{ secrets.KEYSTORE }}" | base64 --decode > "app/keystore.jks"
|
||||||
@ -62,4 +69,4 @@ jobs:
|
|||||||
uses: actions/attest-build-provenance@v2
|
uses: actions/attest-build-provenance@v2
|
||||||
with:
|
with:
|
||||||
subject-name: 'ReVanced Manager ${{ steps.release.outputs.new_release_git_tag }}'
|
subject-name: 'ReVanced Manager ${{ steps.release.outputs.new_release_git_tag }}'
|
||||||
subject-path: build/app/outputs/apk/release/revanced-manager-*.apk
|
subject-path: app/build/outputs/apk/release/revanced-manager*.apk
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||||
tools:ignore="ScopedStorage" />
|
tools:ignore="ScopedStorage" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.ENFORCE_UPDATE_OWNERSHIP" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".ManagerApplication"
|
android:name=".ManagerApplication"
|
||||||
|
@ -12,7 +12,7 @@ interface PatchBundleDao {
|
|||||||
fun getPropsById(uid: Int): Flow<BundleProperties?>
|
fun getPropsById(uid: Int): Flow<BundleProperties?>
|
||||||
|
|
||||||
@Query("UPDATE patch_bundles SET version = :patches WHERE uid = :uid")
|
@Query("UPDATE patch_bundles SET version = :patches WHERE uid = :uid")
|
||||||
suspend fun updateVersion(uid: Int, patches: String?)
|
suspend fun updateVersionHash(uid: Int, patches: String?)
|
||||||
|
|
||||||
@Query("UPDATE patch_bundles SET auto_update = :value WHERE uid = :uid")
|
@Query("UPDATE patch_bundles SET auto_update = :value WHERE uid = :uid")
|
||||||
suspend fun setAutoUpdate(uid: Int, value: Boolean)
|
suspend fun setAutoUpdate(uid: Int, value: Boolean)
|
||||||
@ -26,7 +26,7 @@ interface PatchBundleDao {
|
|||||||
@Transaction
|
@Transaction
|
||||||
suspend fun reset() {
|
suspend fun reset() {
|
||||||
purgeCustomBundles()
|
purgeCustomBundles()
|
||||||
updateVersion(0, null) // Reset the main source
|
updateVersionHash(0, null) // Reset the main source
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query("DELETE FROM patch_bundles WHERE uid = :uid")
|
@Query("DELETE FROM patch_bundles WHERE uid = :uid")
|
||||||
|
@ -33,12 +33,12 @@ sealed class Source {
|
|||||||
data class PatchBundleEntity(
|
data class PatchBundleEntity(
|
||||||
@PrimaryKey val uid: Int,
|
@PrimaryKey val uid: Int,
|
||||||
@ColumnInfo(name = "name") val name: String,
|
@ColumnInfo(name = "name") val name: String,
|
||||||
@ColumnInfo(name = "version") val version: String? = null,
|
@ColumnInfo(name = "version") val versionHash: String? = null,
|
||||||
@ColumnInfo(name = "source") val source: Source,
|
@ColumnInfo(name = "source") val source: Source,
|
||||||
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
|
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
data class BundleProperties(
|
data class BundleProperties(
|
||||||
@ColumnInfo(name = "version") val version: String? = null,
|
@ColumnInfo(name = "version") val versionHash: String? = null,
|
||||||
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
|
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
|
||||||
)
|
)
|
@ -27,10 +27,10 @@ abstract class OptionDao {
|
|||||||
abstract suspend fun createOptionGroup(group: OptionGroup)
|
abstract suspend fun createOptionGroup(group: OptionGroup)
|
||||||
|
|
||||||
@Query("DELETE FROM option_groups WHERE patch_bundle = :uid")
|
@Query("DELETE FROM option_groups WHERE patch_bundle = :uid")
|
||||||
abstract suspend fun clearForPatchBundle(uid: Int)
|
abstract suspend fun resetOptionsForPatchBundle(uid: Int)
|
||||||
|
|
||||||
@Query("DELETE FROM option_groups WHERE package_name = :packageName")
|
@Query("DELETE FROM option_groups WHERE package_name = :packageName")
|
||||||
abstract suspend fun clearForPackage(packageName: String)
|
abstract suspend fun resetOptionsForPackage(packageName: String)
|
||||||
|
|
||||||
@Query("DELETE FROM option_groups")
|
@Query("DELETE FROM option_groups")
|
||||||
abstract suspend fun reset()
|
abstract suspend fun reset()
|
||||||
|
@ -5,6 +5,7 @@ import androidx.room.Insert
|
|||||||
import androidx.room.MapColumn
|
import androidx.room.MapColumn
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
abstract class SelectionDao {
|
abstract class SelectionDao {
|
||||||
@ -34,11 +35,14 @@ abstract class SelectionDao {
|
|||||||
@Insert
|
@Insert
|
||||||
abstract suspend fun createSelection(selection: PatchSelection)
|
abstract suspend fun createSelection(selection: PatchSelection)
|
||||||
|
|
||||||
|
@Query("SELECT package_name FROM patch_selections")
|
||||||
|
abstract fun getPackagesWithSelection(): Flow<List<String>>
|
||||||
|
|
||||||
@Query("DELETE FROM patch_selections WHERE patch_bundle = :uid")
|
@Query("DELETE FROM patch_selections WHERE patch_bundle = :uid")
|
||||||
abstract suspend fun clearForPatchBundle(uid: Int)
|
abstract suspend fun resetForPatchBundle(uid: Int)
|
||||||
|
|
||||||
@Query("DELETE FROM patch_selections WHERE package_name = :packageName")
|
@Query("DELETE FROM patch_selections WHERE package_name = :packageName")
|
||||||
abstract suspend fun clearForPackage(packageName: String)
|
abstract suspend fun resetForPackage(packageName: String)
|
||||||
|
|
||||||
@Query("DELETE FROM patch_selections")
|
@Query("DELETE FROM patch_selections")
|
||||||
abstract suspend fun reset()
|
abstract suspend fun reset()
|
||||||
|
@ -15,7 +15,7 @@ class LocalPatchBundle(name: String, id: Int, directory: File) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
reload()?.also {
|
reload()?.also {
|
||||||
saveVersion(it.readManifestAttribute("Version"))
|
saveVersionHash(it.readManifestAttribute("Version"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,9 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil
|
|||||||
|
|
||||||
suspend fun getName() = nameFlow.first()
|
suspend fun getName() = nameFlow.first()
|
||||||
|
|
||||||
|
val versionFlow = state.map { it.patchBundleOrNull()?.readManifestAttribute("Version") }
|
||||||
|
val patchCountFlow = state.map { it.patchBundleOrNull()?.patches?.size ?: 0 }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the bundle has been downloaded to local storage.
|
* Returns true if the bundle has been downloaded to local storage.
|
||||||
*/
|
*/
|
||||||
@ -84,9 +87,9 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil
|
|||||||
fun propsFlow() = configRepository.getProps(uid).flowOn(Dispatchers.Default)
|
fun propsFlow() = configRepository.getProps(uid).flowOn(Dispatchers.Default)
|
||||||
suspend fun getProps() = propsFlow().first()!!
|
suspend fun getProps() = propsFlow().first()!!
|
||||||
|
|
||||||
suspend fun currentVersion() = getProps().version
|
suspend fun currentVersionHash() = getProps().versionHash
|
||||||
protected suspend fun saveVersion(version: String?) =
|
protected suspend fun saveVersionHash(version: String?) =
|
||||||
configRepository.updateVersion(uid, version)
|
configRepository.updateVersionHash(uid, version)
|
||||||
|
|
||||||
suspend fun setName(name: String) {
|
suspend fun setName(name: String) {
|
||||||
configRepository.setName(uid, name)
|
configRepository.setName(uid, name)
|
||||||
|
@ -25,7 +25,7 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
saveVersion(info.version)
|
saveVersionHash(info.version)
|
||||||
reload()
|
reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
|
|||||||
|
|
||||||
suspend fun update(): Boolean = withContext(Dispatchers.IO) {
|
suspend fun update(): Boolean = withContext(Dispatchers.IO) {
|
||||||
val info = getLatestInfo()
|
val info = getLatestInfo()
|
||||||
if (hasInstalled() && info.version == currentVersion())
|
if (hasInstalled() && info.version == currentVersionHash())
|
||||||
return@withContext false
|
return@withContext false
|
||||||
|
|
||||||
download(info)
|
download(info)
|
||||||
|
@ -25,7 +25,7 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
|
|||||||
PatchBundleEntity(
|
PatchBundleEntity(
|
||||||
uid = generateUid(),
|
uid = generateUid(),
|
||||||
name = name,
|
name = name,
|
||||||
version = null,
|
versionHash = null,
|
||||||
source = source,
|
source = source,
|
||||||
autoUpdate = autoUpdate
|
autoUpdate = autoUpdate
|
||||||
).also {
|
).also {
|
||||||
@ -34,8 +34,11 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
|
|||||||
|
|
||||||
suspend fun delete(uid: Int) = dao.remove(uid)
|
suspend fun delete(uid: Int) = dao.remove(uid)
|
||||||
|
|
||||||
suspend fun updateVersion(uid: Int, version: String?) =
|
/**
|
||||||
dao.updateVersion(uid, version)
|
* Sets the version hash used for updates.
|
||||||
|
*/
|
||||||
|
suspend fun updateVersionHash(uid: Int, versionHash: String?) =
|
||||||
|
dao.updateVersionHash(uid, versionHash)
|
||||||
|
|
||||||
suspend fun setAutoUpdate(uid: Int, value: Boolean) = dao.setAutoUpdate(uid, value)
|
suspend fun setAutoUpdate(uid: Int, value: Boolean) = dao.setAutoUpdate(uid, value)
|
||||||
|
|
||||||
@ -47,7 +50,7 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
|
|||||||
val defaultSource = PatchBundleEntity(
|
val defaultSource = PatchBundleEntity(
|
||||||
uid = 0,
|
uid = 0,
|
||||||
name = "",
|
name = "",
|
||||||
version = null,
|
versionHash = null,
|
||||||
source = Source.API,
|
source = Source.API,
|
||||||
autoUpdate = false
|
autoUpdate = false
|
||||||
)
|
)
|
||||||
|
@ -76,7 +76,7 @@ class PatchOptionsRepository(db: AppDatabase) {
|
|||||||
fun getPackagesWithSavedOptions() =
|
fun getPackagesWithSavedOptions() =
|
||||||
dao.getPackagesWithOptions().map(Iterable<String>::toSet).distinctUntilChanged()
|
dao.getPackagesWithOptions().map(Iterable<String>::toSet).distinctUntilChanged()
|
||||||
|
|
||||||
suspend fun clearOptionsForPackage(packageName: String) = dao.clearForPackage(packageName)
|
suspend fun resetOptionsForPackage(packageName: String) = dao.resetOptionsForPackage(packageName)
|
||||||
suspend fun clearOptionsForPatchBundle(uid: Int) = dao.clearForPatchBundle(uid)
|
suspend fun resetOptionsForPatchBundle(uid: Int) = dao.resetOptionsForPatchBundle(uid)
|
||||||
suspend fun reset() = dao.reset()
|
suspend fun reset() = dao.reset()
|
||||||
}
|
}
|
@ -3,6 +3,8 @@ package app.revanced.manager.domain.repository
|
|||||||
import app.revanced.manager.data.room.AppDatabase
|
import app.revanced.manager.data.room.AppDatabase
|
||||||
import app.revanced.manager.data.room.AppDatabase.Companion.generateUid
|
import app.revanced.manager.data.room.AppDatabase.Companion.generateUid
|
||||||
import app.revanced.manager.data.room.selection.PatchSelection
|
import app.revanced.manager.data.room.selection.PatchSelection
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
class PatchSelectionRepository(db: AppDatabase) {
|
class PatchSelectionRepository(db: AppDatabase) {
|
||||||
private val dao = db.selectionDao()
|
private val dao = db.selectionDao()
|
||||||
@ -25,8 +27,15 @@ class PatchSelectionRepository(db: AppDatabase) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
suspend fun clearSelection(packageName: String) {
|
fun getPackagesWithSavedSelection() =
|
||||||
dao.clearForPackage(packageName)
|
dao.getPackagesWithSelection().map(Iterable<String>::toSet).distinctUntilChanged()
|
||||||
|
|
||||||
|
suspend fun resetSelectionForPackage(packageName: String) {
|
||||||
|
dao.resetForPackage(packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun resetSelectionForPatchBundle(uid: Int) {
|
||||||
|
dao.resetForPatchBundle(uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun reset() = dao.reset()
|
suspend fun reset() = dao.reset()
|
||||||
@ -34,7 +43,7 @@ class PatchSelectionRepository(db: AppDatabase) {
|
|||||||
suspend fun export(bundleUid: Int): SerializedSelection = dao.exportSelection(bundleUid)
|
suspend fun export(bundleUid: Int): SerializedSelection = dao.exportSelection(bundleUid)
|
||||||
|
|
||||||
suspend fun import(bundleUid: Int, selection: SerializedSelection) {
|
suspend fun import(bundleUid: Int, selection: SerializedSelection) {
|
||||||
dao.clearForPatchBundle(bundleUid)
|
dao.resetForPatchBundle(bundleUid)
|
||||||
dao.updateSelections(selection.entries.associate { (packageName, patches) ->
|
dao.updateSelections(selection.entries.associate { (packageName, patches) ->
|
||||||
getOrCreateSelection(bundleUid, packageName) to patches.toSet()
|
getOrCreateSelection(bundleUid, packageName) to patches.toSet()
|
||||||
})
|
})
|
||||||
|
@ -42,9 +42,8 @@ fun BundleInformationDialog(
|
|||||||
val props by remember(bundle) {
|
val props by remember(bundle) {
|
||||||
bundle.propsFlow()
|
bundle.propsFlow()
|
||||||
}.collectAsStateWithLifecycle(null)
|
}.collectAsStateWithLifecycle(null)
|
||||||
val patchCount = remember(state) {
|
val patchCount by bundle.patchCountFlow.collectAsStateWithLifecycle(0)
|
||||||
state.patchBundleOrNull()?.patches?.size ?: 0
|
val version by bundle.versionFlow.collectAsStateWithLifecycle(null)
|
||||||
}
|
|
||||||
|
|
||||||
if (viewCurrentBundlePatches) {
|
if (viewCurrentBundlePatches) {
|
||||||
BundlePatchesDialog(
|
BundlePatchesDialog(
|
||||||
@ -98,8 +97,8 @@ fun BundleInformationDialog(
|
|||||||
name = bundleName,
|
name = bundleName,
|
||||||
remoteUrl = bundle.asRemoteOrNull?.endpoint,
|
remoteUrl = bundle.asRemoteOrNull?.endpoint,
|
||||||
patchCount = patchCount,
|
patchCount = patchCount,
|
||||||
version = props?.version,
|
version = version,
|
||||||
autoUpdate = props?.autoUpdate ?: false,
|
autoUpdate = props?.autoUpdate == true,
|
||||||
onAutoUpdateChange = {
|
onAutoUpdateChange = {
|
||||||
composableScope.launch {
|
composableScope.launch {
|
||||||
bundle.asRemoteOrNull?.setAutoUpdate(it)
|
bundle.asRemoteOrNull?.setAutoUpdate(it)
|
||||||
|
@ -47,9 +47,8 @@ fun BundleItem(
|
|||||||
var showDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
|
var showDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
val state by bundle.state.collectAsStateWithLifecycle()
|
val state by bundle.state.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val version by remember(bundle) {
|
val version by bundle.versionFlow.collectAsStateWithLifecycle(null)
|
||||||
bundle.propsFlow().map { props -> props?.version }
|
val patchCount by bundle.patchCountFlow.collectAsStateWithLifecycle(0)
|
||||||
}.collectAsStateWithLifecycle(null)
|
|
||||||
val name by bundle.nameState
|
val name by bundle.nameState
|
||||||
|
|
||||||
if (viewBundleDialogPage) {
|
if (viewBundleDialogPage) {
|
||||||
@ -93,7 +92,7 @@ fun BundleItem(
|
|||||||
|
|
||||||
headlineContent = { Text(name) },
|
headlineContent = { Text(name) },
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
state.patchBundleOrNull()?.patches?.size?.let { patchCount ->
|
if (state is PatchBundleSource.State.Loaded) {
|
||||||
Text(pluralStringResource(R.plurals.patch_count, patchCount, patchCount))
|
Text(pluralStringResource(R.plurals.patch_count, patchCount, patchCount))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -13,11 +13,14 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource
|
import app.revanced.manager.domain.bundles.PatchBundleSource
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
|
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@ -54,6 +57,8 @@ fun BundleSelector(bundles: List<PatchBundleSource>, onFinish: (PatchBundleSourc
|
|||||||
}
|
}
|
||||||
bundles.forEach {
|
bundles.forEach {
|
||||||
val name by it.nameState
|
val name by it.nameState
|
||||||
|
val version by it.versionFlow.collectAsStateWithLifecycle(null)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
@ -65,7 +70,7 @@ fun BundleSelector(bundles: List<PatchBundleSource>, onFinish: (PatchBundleSourc
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
name,
|
"$name $version",
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
|
@ -9,6 +9,7 @@ import androidx.compose.material3.SwitchDefaults
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HapticSwitch(
|
fun HapticSwitch(
|
||||||
@ -20,16 +21,19 @@ fun HapticSwitch(
|
|||||||
colors: SwitchColors = SwitchDefaults.colors(),
|
colors: SwitchColors = SwitchDefaults.colors(),
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
) {
|
) {
|
||||||
|
val view = LocalView.current
|
||||||
Switch(
|
Switch(
|
||||||
checked = checked,
|
checked = checked,
|
||||||
onCheckedChange = { newChecked ->
|
onCheckedChange = { newChecked ->
|
||||||
val useNewConstants = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
|
val useNewConstants = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
|
||||||
when {
|
val hapticFeedbackType = when {
|
||||||
newChecked && useNewConstants -> HapticFeedbackConstants.TOGGLE_ON
|
newChecked && useNewConstants -> HapticFeedbackConstants.TOGGLE_ON
|
||||||
newChecked -> HapticFeedbackConstants.VIRTUAL_KEY
|
newChecked -> HapticFeedbackConstants.VIRTUAL_KEY
|
||||||
!newChecked && useNewConstants -> HapticFeedbackConstants.TOGGLE_OFF
|
!newChecked && useNewConstants -> HapticFeedbackConstants.TOGGLE_OFF
|
||||||
!newChecked -> HapticFeedbackConstants.CLOCK_TICK
|
!newChecked -> HapticFeedbackConstants.CLOCK_TICK
|
||||||
|
else -> {HapticFeedbackConstants.VIRTUAL_KEY}
|
||||||
}
|
}
|
||||||
|
view.performHapticFeedback(hapticFeedbackType)
|
||||||
onCheckedChange(newChecked)
|
onCheckedChange(newChecked)
|
||||||
},
|
},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
@ -1,6 +1,17 @@
|
|||||||
package app.revanced.manager.ui.component.settings
|
package app.revanced.manager.ui.component.settings
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
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.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ExpandLess
|
||||||
|
import androidx.compose.material.icons.filled.ExpandMore
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.ListItemColors
|
import androidx.compose.material3.ListItemColors
|
||||||
import androidx.compose.material3.ListItemDefaults
|
import androidx.compose.material3.ListItemDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -10,6 +21,10 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.material3.ListItem
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsListItem(
|
fun SettingsListItem(
|
||||||
@ -67,4 +82,48 @@ fun SettingsListItem(
|
|||||||
colors = colors,
|
colors = colors,
|
||||||
tonalElevation = tonalElevation,
|
tonalElevation = tonalElevation,
|
||||||
shadowElevation = shadowElevation
|
shadowElevation = shadowElevation
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ExpandableSettingListItem(
|
||||||
|
headlineContent: String,
|
||||||
|
supportingContent: String,
|
||||||
|
expandableContent: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.animateContentSize()
|
||||||
|
) {
|
||||||
|
SettingsListItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable{ expanded = !expanded },
|
||||||
|
headlineContent = headlineContent,
|
||||||
|
supportingContent = supportingContent,
|
||||||
|
trailingContent = {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (expanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
AnimatedVisibility(visible = expanded) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 12.dp, start = 16.dp, end = 16.dp)
|
||||||
|
.animateContentSize(
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = 500,
|
||||||
|
easing = FastOutSlowInEasing
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
expandableContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -79,7 +79,7 @@ data class BundleInfo(
|
|||||||
targetList.add(it)
|
targetList.add(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
BundleInfo(source.getName(), source.currentVersion(), source.uid, compatible, incompatible, universal)
|
BundleInfo(source.getName(), bundle.readManifestAttribute("Version"), source.uid, compatible, incompatible, universal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +62,10 @@ fun PatcherScreen(
|
|||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
viewModel: PatcherViewModel
|
viewModel: PatcherViewModel
|
||||||
) {
|
) {
|
||||||
|
fun onLeave() {
|
||||||
|
viewModel.onBack()
|
||||||
|
onBackClick()
|
||||||
|
}
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val exportApkLauncher =
|
val exportApkLauncher =
|
||||||
@ -72,7 +76,14 @@ fun PatcherScreen(
|
|||||||
var showInstallPicker by rememberSaveable { mutableStateOf(false) }
|
var showInstallPicker by rememberSaveable { mutableStateOf(false) }
|
||||||
var showDismissConfirmationDialog by rememberSaveable { mutableStateOf(false) }
|
var showDismissConfirmationDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
BackHandler(onBack = { showDismissConfirmationDialog = true })
|
fun onPageBack() {
|
||||||
|
if(patcherSucceeded == null)
|
||||||
|
showDismissConfirmationDialog = true
|
||||||
|
else
|
||||||
|
onLeave()
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler(onBack = ::onPageBack)
|
||||||
|
|
||||||
val steps by remember {
|
val steps by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@ -99,10 +110,7 @@ fun PatcherScreen(
|
|||||||
if (showDismissConfirmationDialog) {
|
if (showDismissConfirmationDialog) {
|
||||||
ConfirmDialog(
|
ConfirmDialog(
|
||||||
onDismiss = { showDismissConfirmationDialog = false },
|
onDismiss = { showDismissConfirmationDialog = false },
|
||||||
onConfirm = {
|
onConfirm = ::onLeave,
|
||||||
viewModel.onBack()
|
|
||||||
onBackClick()
|
|
||||||
},
|
|
||||||
title = stringResource(R.string.patcher_stop_confirm_title),
|
title = stringResource(R.string.patcher_stop_confirm_title),
|
||||||
description = stringResource(R.string.patcher_stop_confirm_description),
|
description = stringResource(R.string.patcher_stop_confirm_description),
|
||||||
icon = Icons.Outlined.Cancel
|
icon = Icons.Outlined.Cancel
|
||||||
@ -150,7 +158,7 @@ fun PatcherScreen(
|
|||||||
AppTopBar(
|
AppTopBar(
|
||||||
title = stringResource(R.string.patcher),
|
title = stringResource(R.string.patcher),
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
onBackClick = { showDismissConfirmationDialog = true }
|
onBackClick = ::onPageBack
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
@ -229,4 +237,4 @@ fun PatcherScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,9 +2,7 @@ package app.revanced.manager.ui.screen.settings
|
|||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@ -13,13 +11,10 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.CardDefaults
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedCard
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@ -37,7 +32,6 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -63,7 +57,6 @@ fun DownloadsSettingsScreen(
|
|||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
viewModel: DownloadsViewModel = koinViewModel()
|
viewModel: DownloadsViewModel = koinViewModel()
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val pullRefreshState = rememberPullToRefreshState()
|
val pullRefreshState = rememberPullToRefreshState()
|
||||||
val downloadedApps by viewModel.downloadedApps.collectAsStateWithLifecycle(emptyList())
|
val downloadedApps by viewModel.downloadedApps.collectAsStateWithLifecycle(emptyList())
|
||||||
val pluginStates by viewModel.downloaderPluginStates.collectAsStateWithLifecycle()
|
val pluginStates by viewModel.downloaderPluginStates.collectAsStateWithLifecycle()
|
||||||
@ -88,7 +81,7 @@ fun DownloadsSettingsScreen(
|
|||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
actions = {
|
actions = {
|
||||||
if (viewModel.appSelection.isNotEmpty()) {
|
if (viewModel.appSelection.isNotEmpty()) {
|
||||||
IconButton(onClick = { viewModel.deleteApps() }) {
|
IconButton(onClick = { showDeleteConfirmationDialog = true }) {
|
||||||
Icon(Icons.Default.Delete, stringResource(R.string.delete))
|
Icon(Icons.Default.Delete, stringResource(R.string.delete))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,20 +142,15 @@ fun DownloadsSettingsScreen(
|
|||||||
.digest(androidSignature.toByteArray())
|
.digest(androidSignature.toByteArray())
|
||||||
hash.toHexString(format = HexFormat.UpperCase)
|
hash.toHexString(format = HexFormat.UpperCase)
|
||||||
}
|
}
|
||||||
val appName = remember {
|
|
||||||
packageInfo.applicationInfo?.loadLabel(context.packageManager)
|
|
||||||
?.toString()
|
|
||||||
?: packageName
|
|
||||||
}
|
|
||||||
|
|
||||||
when (state) {
|
when (state) {
|
||||||
is DownloaderPluginState.Loaded -> TrustDialog(
|
is DownloaderPluginState.Loaded -> TrustDialog(
|
||||||
title = R.string.downloader_plugin_revoke_trust_dialog_title,
|
title = R.string.downloader_plugin_revoke_trust_dialog_title,
|
||||||
body = stringResource(
|
body = stringResource(
|
||||||
R.string.downloader_plugin_trust_dialog_body,
|
R.string.downloader_plugin_trust_dialog_body,
|
||||||
|
packageName,
|
||||||
|
signature
|
||||||
),
|
),
|
||||||
pluginName = appName,
|
|
||||||
signature = signature,
|
|
||||||
onDismiss = ::dismiss,
|
onDismiss = ::dismiss,
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
viewModel.revokePluginTrust(packageName)
|
viewModel.revokePluginTrust(packageName)
|
||||||
@ -177,20 +165,19 @@ fun DownloadsSettingsScreen(
|
|||||||
onDismiss = ::dismiss
|
onDismiss = ::dismiss
|
||||||
)
|
)
|
||||||
|
|
||||||
is DownloaderPluginState.Untrusted ->
|
is DownloaderPluginState.Untrusted -> TrustDialog(
|
||||||
TrustDialog(
|
title = R.string.downloader_plugin_trust_dialog_title,
|
||||||
title = R.string.downloader_plugin_trust_dialog_title,
|
body = stringResource(
|
||||||
body = stringResource(
|
R.string.downloader_plugin_trust_dialog_body,
|
||||||
R.string.downloader_plugin_trust_dialog_body
|
packageName,
|
||||||
),
|
signature
|
||||||
pluginName = appName,
|
),
|
||||||
signature = signature,
|
onDismiss = ::dismiss,
|
||||||
onDismiss = ::dismiss,
|
onConfirm = {
|
||||||
onConfirm = {
|
viewModel.trustPlugin(packageName)
|
||||||
viewModel.trustPlugin(packageName)
|
dismiss()
|
||||||
dismiss()
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,8 +246,6 @@ fun DownloadsSettingsScreen(
|
|||||||
private fun TrustDialog(
|
private fun TrustDialog(
|
||||||
@StringRes title: Int,
|
@StringRes title: Int,
|
||||||
body: String,
|
body: String,
|
||||||
pluginName: String,
|
|
||||||
signature: String,
|
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onConfirm: () -> Unit
|
onConfirm: () -> Unit
|
||||||
) {
|
) {
|
||||||
@ -277,35 +262,6 @@ private fun TrustDialog(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = { Text(stringResource(title)) },
|
title = { Text(stringResource(title)) },
|
||||||
text = {
|
text = { Text(body) }
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
||||||
Text(body)
|
|
||||||
Card {
|
|
||||||
Column(
|
|
||||||
Modifier.padding(12.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
stringResource(
|
|
||||||
R.string.downloader_plugin_trust_dialog_plugin,
|
|
||||||
pluginName
|
|
||||||
),
|
|
||||||
)
|
|
||||||
OutlinedCard(
|
|
||||||
colors = CardDefaults.outlinedCardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
stringResource(
|
|
||||||
R.string.downloader_plugin_trust_dialog_signature,
|
|
||||||
signature.chunked(2).joinToString(" ")
|
|
||||||
), modifier = Modifier.padding(12.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.height
|
|||||||
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.Key
|
import androidx.compose.material.icons.outlined.Key
|
||||||
|
import androidx.compose.material.icons.outlined.WarningAmber
|
||||||
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
|
||||||
@ -28,6 +29,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
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.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@ -42,11 +44,14 @@ import androidx.lifecycle.viewModelScope
|
|||||||
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
|
||||||
|
import app.revanced.manager.ui.component.ConfirmDialog
|
||||||
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.bundle.BundleSelector
|
import app.revanced.manager.ui.component.bundle.BundleSelector
|
||||||
|
import app.revanced.manager.ui.component.settings.ExpandableSettingListItem
|
||||||
import app.revanced.manager.ui.component.settings.SettingsListItem
|
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||||
import app.revanced.manager.ui.viewmodel.ImportExportViewModel
|
import app.revanced.manager.ui.viewmodel.ImportExportViewModel
|
||||||
|
import app.revanced.manager.ui.viewmodel.ResetDialogState
|
||||||
import app.revanced.manager.util.toast
|
import app.revanced.manager.util.toast
|
||||||
import app.revanced.manager.util.uiSafe
|
import app.revanced.manager.util.uiSafe
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -59,6 +64,8 @@ fun ImportExportSettingsScreen(
|
|||||||
vm: ImportExportViewModel = koinViewModel()
|
vm: ImportExportViewModel = koinViewModel()
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
var selectorDialog by rememberSaveable { mutableStateOf<(@Composable () -> Unit)?>(null) }
|
||||||
|
|
||||||
val importKeystoreLauncher =
|
val importKeystoreLauncher =
|
||||||
rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) {
|
rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) {
|
||||||
@ -70,6 +77,7 @@ fun ImportExportSettingsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val patchBundles by vm.patchBundles.collectAsStateWithLifecycle(initialValue = emptyList())
|
val patchBundles by vm.patchBundles.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||||
|
val packagesWithSelections by vm.packagesWithSelection.collectAsStateWithLifecycle(initialValue = emptySet())
|
||||||
val packagesWithOptions by vm.packagesWithOptions.collectAsStateWithLifecycle(initialValue = emptySet())
|
val packagesWithOptions by vm.packagesWithOptions.collectAsStateWithLifecycle(initialValue = emptySet())
|
||||||
|
|
||||||
vm.selectionAction?.let { action ->
|
vm.selectionAction?.let { action ->
|
||||||
@ -107,6 +115,20 @@ fun ImportExportSettingsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vm.resetDialogState?.let {
|
||||||
|
with(vm.resetDialogState!!) {
|
||||||
|
ConfirmDialog(
|
||||||
|
onDismiss = { vm.resetDialogState = null },
|
||||||
|
onConfirm = onConfirm,
|
||||||
|
title = stringResource(titleResId),
|
||||||
|
description = dialogOptionName?.let {
|
||||||
|
stringResource(descriptionResId, it)
|
||||||
|
} ?: stringResource(descriptionResId),
|
||||||
|
icon = Icons.Outlined.WarningAmber
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@ -124,28 +146,7 @@ fun ImportExportSettingsScreen(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
) {
|
) {
|
||||||
var showPackageSelector by rememberSaveable {
|
selectorDialog?.invoke()
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
var showBundleSelector by rememberSaveable {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showPackageSelector) {
|
|
||||||
PackageSelector(packages = packagesWithOptions) { selected ->
|
|
||||||
selected?.let(vm::resetOptionsForPackage)
|
|
||||||
|
|
||||||
showPackageSelector = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showBundleSelector) {
|
|
||||||
BundleSelector(bundles = patchBundles) { bundle ->
|
|
||||||
bundle?.let(vm::clearOptionsForBundle)
|
|
||||||
|
|
||||||
showBundleSelector = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GroupHeader(stringResource(R.string.import_))
|
GroupHeader(stringResource(R.string.import_))
|
||||||
GroupItem(
|
GroupItem(
|
||||||
@ -181,32 +182,126 @@ fun ImportExportSettingsScreen(
|
|||||||
|
|
||||||
GroupHeader(stringResource(R.string.reset))
|
GroupHeader(stringResource(R.string.reset))
|
||||||
GroupItem(
|
GroupItem(
|
||||||
onClick = vm::regenerateKeystore,
|
onClick = {
|
||||||
|
vm.resetDialogState = ResetDialogState.Keystore {
|
||||||
|
vm.regenerateKeystore()
|
||||||
|
}
|
||||||
|
},
|
||||||
headline = R.string.regenerate_keystore,
|
headline = R.string.regenerate_keystore,
|
||||||
description = R.string.regenerate_keystore_description
|
description = R.string.regenerate_keystore_description
|
||||||
)
|
)
|
||||||
GroupItem(
|
|
||||||
onClick = vm::resetSelection, // TODO: allow resetting selection for specific bundle or package name.
|
ExpandableSettingListItem(
|
||||||
headline = R.string.reset_patch_selection,
|
headlineContent = stringResource(R.string.reset_patch_selection),
|
||||||
description = R.string.reset_patch_selection_description
|
supportingContent = stringResource(R.string.reset_patch_selection_description),
|
||||||
|
expandableContent = {
|
||||||
|
GroupItem(
|
||||||
|
onClick = {
|
||||||
|
vm.resetDialogState = ResetDialogState.PatchSelectionAll {
|
||||||
|
vm.resetSelection()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
headline = R.string.patch_selection_reset_all,
|
||||||
|
description = R.string.patch_selection_reset_all_description
|
||||||
|
)
|
||||||
|
|
||||||
|
GroupItem(
|
||||||
|
onClick = {
|
||||||
|
selectorDialog = {
|
||||||
|
PackageSelector(packages = packagesWithSelections) { packageName ->
|
||||||
|
packageName?.also {
|
||||||
|
vm.resetDialogState =
|
||||||
|
ResetDialogState.PatchSelectionPackage(packageName) {
|
||||||
|
vm.resetSelectionForPackage(packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectorDialog = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
headline = R.string.patch_selection_reset_package,
|
||||||
|
description = R.string.patch_selection_reset_package_description
|
||||||
|
)
|
||||||
|
|
||||||
|
if (patchBundles.isNotEmpty()) {
|
||||||
|
GroupItem(
|
||||||
|
onClick = {
|
||||||
|
selectorDialog = {
|
||||||
|
BundleSelector(bundles = patchBundles) { bundle ->
|
||||||
|
bundle?.also {
|
||||||
|
coroutineScope.launch {
|
||||||
|
vm.resetDialogState =
|
||||||
|
ResetDialogState.PatchSelectionBundle(bundle.getName()) {
|
||||||
|
vm.resetSelectionForPatchBundle(bundle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectorDialog = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
headline = R.string.patch_selection_reset_bundle,
|
||||||
|
description = R.string.patch_selection_reset_bundle_description
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
GroupItem(
|
|
||||||
onClick = vm::resetOptions, // TODO: patch options import/export.
|
ExpandableSettingListItem(
|
||||||
headline = R.string.patch_options_reset_all,
|
headlineContent = stringResource(R.string.reset_patch_options),
|
||||||
description = R.string.patch_options_reset_all_description,
|
supportingContent = stringResource(R.string.reset_patch_options_description),
|
||||||
|
expandableContent = {
|
||||||
|
GroupItem(
|
||||||
|
onClick = {
|
||||||
|
vm.resetDialogState = ResetDialogState.PatchOptionsAll {
|
||||||
|
vm.resetOptions()
|
||||||
|
}
|
||||||
|
}, // TODO: patch options import/export.
|
||||||
|
headline = R.string.patch_options_reset_all,
|
||||||
|
description = R.string.patch_options_reset_all_description,
|
||||||
|
)
|
||||||
|
|
||||||
|
GroupItem(
|
||||||
|
onClick = {
|
||||||
|
selectorDialog = {
|
||||||
|
PackageSelector(packages = packagesWithOptions) { packageName ->
|
||||||
|
packageName?.also {
|
||||||
|
vm.resetDialogState =
|
||||||
|
ResetDialogState.PatchOptionPackage(packageName) {
|
||||||
|
vm.resetOptionsForPackage(packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectorDialog = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
headline = R.string.patch_options_reset_package,
|
||||||
|
description = R.string.patch_options_reset_package_description
|
||||||
|
)
|
||||||
|
|
||||||
|
if (patchBundles.isNotEmpty()) {
|
||||||
|
GroupItem(
|
||||||
|
onClick = {
|
||||||
|
selectorDialog = {
|
||||||
|
BundleSelector(bundles = patchBundles) { bundle ->
|
||||||
|
bundle?.also {
|
||||||
|
coroutineScope.launch {
|
||||||
|
vm.resetDialogState =
|
||||||
|
ResetDialogState.PatchOptionBundle(bundle.getName()) {
|
||||||
|
vm.resetOptionsForBundle(bundle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectorDialog = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
headline = R.string.patch_options_reset_bundle,
|
||||||
|
description = R.string.patch_options_reset_bundle_description,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
GroupItem(
|
|
||||||
onClick = { showPackageSelector = true },
|
|
||||||
headline = R.string.patch_options_reset_package,
|
|
||||||
description = R.string.patch_options_reset_package_description
|
|
||||||
)
|
|
||||||
if (patchBundles.size > 1) {
|
|
||||||
GroupItem(
|
|
||||||
onClick = { showBundleSelector = true },
|
|
||||||
headline = R.string.patch_options_reset_bundle,
|
|
||||||
description = R.string.patch_options_reset_bundle_description,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ fun UpdatesSettingsScreen(
|
|||||||
BooleanItem(
|
BooleanItem(
|
||||||
preference = vm.showManagerUpdateDialogOnLaunch,
|
preference = vm.showManagerUpdateDialogOnLaunch,
|
||||||
headline = R.string.show_manager_update_dialog_on_launch,
|
headline = R.string.show_manager_update_dialog_on_launch,
|
||||||
description = R.string.update_checking_manager_description
|
description = R.string.show_manager_update_dialog_on_launch_description
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import android.app.Application
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.activity.result.contract.ActivityResultContract
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -34,6 +35,59 @@ import java.nio.file.StandardCopyOption
|
|||||||
import kotlin.io.path.deleteExisting
|
import kotlin.io.path.deleteExisting
|
||||||
import kotlin.io.path.inputStream
|
import kotlin.io.path.inputStream
|
||||||
|
|
||||||
|
sealed class ResetDialogState(
|
||||||
|
@StringRes val titleResId: Int,
|
||||||
|
@StringRes val descriptionResId: Int,
|
||||||
|
val onConfirm: () -> Unit,
|
||||||
|
val dialogOptionName: String? = null
|
||||||
|
) {
|
||||||
|
class Keystore(onConfirm: () -> Unit) : ResetDialogState(
|
||||||
|
titleResId = R.string.regenerate_keystore,
|
||||||
|
descriptionResId = R.string.regenerate_keystore_dialog_description,
|
||||||
|
onConfirm = onConfirm
|
||||||
|
)
|
||||||
|
|
||||||
|
class PatchSelectionAll(onConfirm: () -> Unit) : ResetDialogState(
|
||||||
|
titleResId = R.string.patch_selection_reset_all,
|
||||||
|
descriptionResId = R.string.patch_selection_reset_all_dialog_description,
|
||||||
|
onConfirm = onConfirm
|
||||||
|
)
|
||||||
|
|
||||||
|
class PatchSelectionPackage(dialogOptionName:String, onConfirm: () -> Unit) : ResetDialogState(
|
||||||
|
titleResId = R.string.patch_selection_reset_package,
|
||||||
|
descriptionResId = R.string.patch_selection_reset_package_dialog_description,
|
||||||
|
onConfirm = onConfirm,
|
||||||
|
dialogOptionName = dialogOptionName
|
||||||
|
)
|
||||||
|
|
||||||
|
class PatchSelectionBundle(dialogOptionName: String, onConfirm: () -> Unit) : ResetDialogState(
|
||||||
|
titleResId = R.string.patch_selection_reset_bundle,
|
||||||
|
descriptionResId = R.string.patch_selection_reset_bundle_dialog_description,
|
||||||
|
onConfirm = onConfirm,
|
||||||
|
dialogOptionName = dialogOptionName
|
||||||
|
)
|
||||||
|
|
||||||
|
class PatchOptionsAll(onConfirm: () -> Unit) : ResetDialogState(
|
||||||
|
titleResId = R.string.patch_options_reset_all,
|
||||||
|
descriptionResId = R.string.patch_options_reset_all_dialog_description,
|
||||||
|
onConfirm = onConfirm
|
||||||
|
)
|
||||||
|
|
||||||
|
class PatchOptionPackage(dialogOptionName:String, onConfirm: () -> Unit) : ResetDialogState(
|
||||||
|
titleResId = R.string.patch_options_reset_package,
|
||||||
|
descriptionResId = R.string.patch_options_reset_package_dialog_description,
|
||||||
|
onConfirm = onConfirm,
|
||||||
|
dialogOptionName = dialogOptionName
|
||||||
|
)
|
||||||
|
|
||||||
|
class PatchOptionBundle(dialogOptionName: String, onConfirm: () -> Unit) : ResetDialogState(
|
||||||
|
titleResId = R.string.patch_options_reset_bundle,
|
||||||
|
descriptionResId = R.string.patch_options_reset_bundle_dialog_description,
|
||||||
|
onConfirm = onConfirm,
|
||||||
|
dialogOptionName = dialogOptionName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
class ImportExportViewModel(
|
class ImportExportViewModel(
|
||||||
private val app: Application,
|
private val app: Application,
|
||||||
@ -51,15 +105,18 @@ class ImportExportViewModel(
|
|||||||
private var keystoreImportPath by mutableStateOf<Path?>(null)
|
private var keystoreImportPath by mutableStateOf<Path?>(null)
|
||||||
val showCredentialsDialog by derivedStateOf { keystoreImportPath != null }
|
val showCredentialsDialog by derivedStateOf { keystoreImportPath != null }
|
||||||
|
|
||||||
|
var resetDialogState by mutableStateOf<ResetDialogState?>(null)
|
||||||
|
|
||||||
val packagesWithOptions = optionsRepository.getPackagesWithSavedOptions()
|
val packagesWithOptions = optionsRepository.getPackagesWithSavedOptions()
|
||||||
|
val packagesWithSelection = selectionRepository.getPackagesWithSavedSelection()
|
||||||
|
|
||||||
fun resetOptionsForPackage(packageName: String) = viewModelScope.launch {
|
fun resetOptionsForPackage(packageName: String) = viewModelScope.launch {
|
||||||
optionsRepository.clearOptionsForPackage(packageName)
|
optionsRepository.resetOptionsForPackage(packageName)
|
||||||
app.toast(app.getString(R.string.patch_options_reset_toast))
|
app.toast(app.getString(R.string.patch_options_reset_toast))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearOptionsForBundle(patchBundle: PatchBundleSource) = viewModelScope.launch {
|
fun resetOptionsForBundle(patchBundle: PatchBundleSource) = viewModelScope.launch {
|
||||||
optionsRepository.clearOptionsForPatchBundle(patchBundle.uid)
|
optionsRepository.resetOptionsForPatchBundle(patchBundle.uid)
|
||||||
app.toast(app.getString(R.string.patch_options_reset_toast))
|
app.toast(app.getString(R.string.patch_options_reset_toast))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +192,16 @@ class ImportExportViewModel(
|
|||||||
app.toast(app.getString(R.string.reset_patch_selection_success))
|
app.toast(app.getString(R.string.reset_patch_selection_success))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun resetSelectionForPackage(packageName: String) = viewModelScope.launch {
|
||||||
|
selectionRepository.resetSelectionForPackage(packageName)
|
||||||
|
app.toast(app.getString(R.string.reset_patch_selection_success))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSelectionForPatchBundle(patchBundle: PatchBundleSource) = viewModelScope.launch {
|
||||||
|
selectionRepository.resetSelectionForPatchBundle(patchBundle.uid)
|
||||||
|
app.toast(app.getString(R.string.reset_patch_selection_success))
|
||||||
|
}
|
||||||
|
|
||||||
fun executeSelectionAction(target: Uri) = viewModelScope.launch {
|
fun executeSelectionAction(target: Uri) = viewModelScope.launch {
|
||||||
val source = selectedBundle!!
|
val source = selectedBundle!!
|
||||||
val action = selectionAction!!
|
val action = selectionAction!!
|
||||||
|
@ -131,7 +131,7 @@ class SelectedAppInfoViewModel(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (!persistConfiguration) return@launch // TODO: save options for patched apps.
|
if (!persistConfiguration) return@launch // TODO: save options for patched apps.
|
||||||
|
|
||||||
state.value = withContext(Dispatchers.Default) {
|
options = withContext(Dispatchers.Default) {
|
||||||
val bundlePatches = bundleRepository.bundles.first()
|
val bundlePatches = bundleRepository.bundles.first()
|
||||||
.mapValues { (_, bundle) -> bundle.patches.associateBy { it.name } }
|
.mapValues { (_, bundle) -> bundle.patches.associateBy { it.name } }
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ class SelectedAppInfoViewModel(
|
|||||||
}
|
}
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private var selectionState by savedStateHandle.saveable {
|
private var selectionState: SelectionState by savedStateHandle.saveable {
|
||||||
if (input.patches != null)
|
if (input.patches != null)
|
||||||
return@saveable mutableStateOf(SelectionState.Customized(input.patches))
|
return@saveable mutableStateOf(SelectionState.Customized(input.patches))
|
||||||
|
|
||||||
@ -155,7 +155,7 @@ class SelectedAppInfoViewModel(
|
|||||||
|
|
||||||
val previous = selectionRepository.getSelection(packageName)
|
val previous = selectionRepository.getSelection(packageName)
|
||||||
if (previous.values.sumOf { it.size } == 0) return@launch
|
if (previous.values.sumOf { it.size } == 0) return@launch
|
||||||
selection.value = SelectionState.Customized(previous)
|
selectionState = SelectionState.Customized(previous)
|
||||||
}
|
}
|
||||||
|
|
||||||
selection
|
selection
|
||||||
@ -305,7 +305,7 @@ class SelectedAppInfoViewModel(
|
|||||||
if (!persistConfiguration) return@launch
|
if (!persistConfiguration) return@launch
|
||||||
viewModelScope.launch(Dispatchers.Default) {
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
selection?.let { selectionRepository.updateSelection(packageName, it) }
|
selection?.let { selectionRepository.updateSelection(packageName, it) }
|
||||||
?: selectionRepository.clearSelection(packageName)
|
?: selectionRepository.resetSelectionForPackage(packageName)
|
||||||
|
|
||||||
optionsRepository.saveOptions(packageName, filteredOptions)
|
optionsRepository.saveOptions(packageName, filteredOptions)
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ class PM(
|
|||||||
app.packageManager.getPackageInfo(packageName, PackageInfoFlags.of(flags.toLong()))
|
app.packageManager.getPackageInfo(packageName, PackageInfoFlags.of(flags.toLong()))
|
||||||
else
|
else
|
||||||
app.packageManager.getPackageInfo(packageName, flags)
|
app.packageManager.getPackageInfo(packageName, flags)
|
||||||
} catch (e: NameNotFoundException) {
|
} catch (_: NameNotFoundException) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +184,8 @@ class PM(
|
|||||||
get() = PackageInstaller.SessionParams(
|
get() = PackageInstaller.SessionParams(
|
||||||
PackageInstaller.SessionParams.MODE_FULL_INSTALL
|
PackageInstaller.SessionParams.MODE_FULL_INSTALL
|
||||||
).apply {
|
).apply {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||||
|
setRequestUpdateOwnership(true)
|
||||||
setInstallReason(PackageManager.INSTALL_REASON_USER)
|
setInstallReason(PackageManager.INSTALL_REASON_USER)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +118,7 @@
|
|||||||
<string name="export_keystore_success">Exported keystore</string>
|
<string name="export_keystore_success">Exported keystore</string>
|
||||||
<string name="regenerate_keystore">Regenerate keystore</string>
|
<string name="regenerate_keystore">Regenerate keystore</string>
|
||||||
<string name="regenerate_keystore_description">Generate a new keystore</string>
|
<string name="regenerate_keystore_description">Generate a new keystore</string>
|
||||||
|
<string name="regenerate_keystore_dialog_description">You are about to regenerate your keystore the manager will use during the patching process.\n\nYou will not be able to update the previously installed apps from this source.</string>
|
||||||
<string name="regenerate_keystore_success">The keystore has been successfully replaced</string>
|
<string name="regenerate_keystore_success">The keystore has been successfully replaced</string>
|
||||||
<string name="import_patch_selection">Import patch selection</string>
|
<string name="import_patch_selection">Import patch selection</string>
|
||||||
<string name="import_patch_selection_description">Import patch selection from a JSON file</string>
|
<string name="import_patch_selection_description">Import patch selection from a JSON file</string>
|
||||||
@ -129,12 +130,26 @@
|
|||||||
<string name="export_patch_selection_success">Exported patch selection</string>
|
<string name="export_patch_selection_success">Exported patch selection</string>
|
||||||
<string name="reset_patch_selection">Reset patch selection</string>
|
<string name="reset_patch_selection">Reset patch selection</string>
|
||||||
<string name="reset_patch_selection_description">Reset the stored patch selection</string>
|
<string name="reset_patch_selection_description">Reset the stored patch selection</string>
|
||||||
|
<string name="reset_patch_options">Reset patch options</string>
|
||||||
|
<string name="reset_patch_options_description">Reset the stored patch options</string>
|
||||||
<string name="reset_patch_selection_success">Patch selection has been reset</string>
|
<string name="reset_patch_selection_success">Patch selection has been reset</string>
|
||||||
|
<string name="patch_selection_reset_all">Reset all patch selection</string>
|
||||||
|
<string name="patch_selection_reset_all_dialog_description">You are about to reset all the patch selections. You will need to manually select each patch again.</string>
|
||||||
|
<string name="patch_selection_reset_all_description">Reset all the patch selections</string>
|
||||||
|
<string name="patch_selection_reset_package">Reset patch selection for app</string>
|
||||||
|
<string name="patch_selection_reset_package_dialog_description">You are about to reset the patch selection for the app \"%s\". You will have to manually select each patch again.</string>
|
||||||
|
<string name="patch_selection_reset_package_description">Resets patch selection for a single app</string>
|
||||||
|
<string name="patch_selection_reset_bundle">Resets patch selection for bundle</string>
|
||||||
|
<string name="patch_selection_reset_bundle_dialog_description">You are about to reset the patch selection for the bundle \"%s\". You will have to manually select each patch again.</string>
|
||||||
|
<string name="patch_selection_reset_bundle_description">Resets the patch selection for all patches in a bundle</string>
|
||||||
<string name="patch_options_reset_package">Reset patch options for app</string>
|
<string name="patch_options_reset_package">Reset patch options for app</string>
|
||||||
|
<string name="patch_options_reset_package_dialog_description">You are about to reset the patch options for the app \"%s\". You will have to reapply each option again.</string>
|
||||||
<string name="patch_options_reset_package_description">Resets patch options for a single app</string>
|
<string name="patch_options_reset_package_description">Resets patch options for a single app</string>
|
||||||
<string name="patch_options_reset_bundle">Resets patch options for bundle</string>
|
<string name="patch_options_reset_bundle">Resets patch options for bundle</string>
|
||||||
|
<string name="patch_options_reset_bundle_dialog_description">You are about to reset the patch options for the bundle \"%s\". You will have to reapply each option again.</string>
|
||||||
<string name="patch_options_reset_bundle_description">Resets patch options for all patches in a bundle</string>
|
<string name="patch_options_reset_bundle_description">Resets patch options for all patches in a bundle</string>
|
||||||
<string name="patch_options_reset_all">Reset patch options</string>
|
<string name="patch_options_reset_all">Reset patch options</string>
|
||||||
|
<string name="patch_options_reset_all_dialog_description">You are about to reset patch options. You will have to reapply each option again.</string>
|
||||||
<string name="patch_options_reset_all_description">Resets all patch options</string>
|
<string name="patch_options_reset_all_description">Resets all patch options</string>
|
||||||
<string name="downloader_plugins">Plugins</string>
|
<string name="downloader_plugins">Plugins</string>
|
||||||
<string name="downloader_plugin_state_trusted">Trusted</string>
|
<string name="downloader_plugin_state_trusted">Trusted</string>
|
||||||
@ -142,9 +157,7 @@
|
|||||||
<string name="downloader_plugin_state_untrusted">Untrusted</string>
|
<string name="downloader_plugin_state_untrusted">Untrusted</string>
|
||||||
<string name="downloader_plugin_trust_dialog_title">Trust plugin?</string>
|
<string name="downloader_plugin_trust_dialog_title">Trust plugin?</string>
|
||||||
<string name="downloader_plugin_revoke_trust_dialog_title">Revoke trust?</string>
|
<string name="downloader_plugin_revoke_trust_dialog_title">Revoke trust?</string>
|
||||||
<string name="downloader_plugin_trust_dialog_body">By continuing you\'ve agreed to running external plugins.\n\nDo not allow any suspicious plugin(s) to be installed as they can run arbitrary code.</string>
|
<string name="downloader_plugin_trust_dialog_body">Package name: %1$s\nSignature (SHA-256): %2$s</string>
|
||||||
<string name="downloader_plugin_trust_dialog_signature">Signature:\n\n%s</string>
|
|
||||||
<string name="downloader_plugin_trust_dialog_plugin">Plugin:\n%s</string>
|
|
||||||
<string name="downloader_plugin_delete_apps_title">Delete selected apps</string>
|
<string name="downloader_plugin_delete_apps_title">Delete selected apps</string>
|
||||||
<string name="downloader_plugin_delete_apps_description">Are you sure you want to delete the selected apps?</string>
|
<string name="downloader_plugin_delete_apps_description">Are you sure you want to delete the selected apps?</string>
|
||||||
<string name="downloader_settings_no_apps">No downloaded apps found</string>
|
<string name="downloader_settings_no_apps">No downloaded apps found</string>
|
||||||
|
@ -9,7 +9,7 @@ appcompat = "1.7.0"
|
|||||||
preferences-datastore = "1.1.2"
|
preferences-datastore = "1.1.2"
|
||||||
work-runtime = "2.10.1"
|
work-runtime = "2.10.1"
|
||||||
compose-bom = "2025.05.00"
|
compose-bom = "2025.05.00"
|
||||||
navigation = "2.9.0"
|
navigation = "2.8.6"
|
||||||
accompanist = "0.37.0"
|
accompanist = "0.37.0"
|
||||||
placeholder = "1.1.2"
|
placeholder = "1.1.2"
|
||||||
reorderable = "2.4.3"
|
reorderable = "2.4.3"
|
||||||
|
8365
package-lock.json
generated
Normal file
8365
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@anolilab/multi-semantic-release": "^1.1.10",
|
"@anolilab/multi-semantic-release": "^1.1.10",
|
||||||
"@revanced/gradle-semantic-release-plugin": "^1.10.1",
|
"gradle-semantic-release-plugin": "^1.10.1",
|
||||||
"@saithodev/semantic-release-backmerge": "^4.0.1",
|
"@saithodev/semantic-release-backmerge": "^4.0.1",
|
||||||
"@semantic-release/changelog": "^6.0.3",
|
"@semantic-release/changelog": "^6.0.3",
|
||||||
"@semantic-release/exec": "^6.0.3",
|
"@semantic-release/exec": "^6.0.3",
|
||||||
"@semantic-release/git": "^10.0.1",
|
"@semantic-release/git": "^10.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user