mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2026-01-19 01:03:56 +00:00
Compare commits
7 Commits
v1.26.0-de
...
v1.26.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2805ac6540 | ||
|
|
b16931ca79 | ||
|
|
dfeca09d00 | ||
|
|
44c06e2197 | ||
|
|
df31b39cc8 | ||
|
|
25d82e869c | ||
|
|
9d9a0e81db |
43
.github/workflows/pull_strings.yml
vendored
Normal file
43
.github/workflows/pull_strings.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
name: Pull strings
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * 0"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pull:
|
||||||
|
name: Pull strings
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
ref: dev
|
||||||
|
clean: true
|
||||||
|
|
||||||
|
- name: Pull strings
|
||||||
|
uses: crowdin/github-action@v2
|
||||||
|
with:
|
||||||
|
config: crowdin.yml
|
||||||
|
upload_sources: false
|
||||||
|
download_translations: true
|
||||||
|
skip_ref_checkout: true
|
||||||
|
localization_branch_name: feat/translations
|
||||||
|
create_pull_request: false
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||||
|
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||||
|
|
||||||
|
- name: Open pull request
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
uses: repo-sync/pull-request@v2
|
||||||
|
with:
|
||||||
|
source_branch: feat/translations
|
||||||
|
destination_branch: dev
|
||||||
|
pr_title: "chore: Sync translations"
|
||||||
|
pr_body: "Sync translations from [crowdin.com/project/revanced](https://crowdin.com/project/revanced)"
|
||||||
26
.github/workflows/push_strings.yml
vendored
Normal file
26
.github/workflows/push_strings.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Push strings
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
paths:
|
||||||
|
- app/src/main/res/values/strings.xml
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
push:
|
||||||
|
name: Push strings
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Push strings
|
||||||
|
uses: crowdin/github-action@v2
|
||||||
|
with:
|
||||||
|
config: crowdin.yml
|
||||||
|
upload_sources: true
|
||||||
|
env:
|
||||||
|
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||||
|
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||||
@@ -1,3 +1,22 @@
|
|||||||
|
# app [1.26.0-dev.18](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.17...v1.26.0-dev.18) (2026-01-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Prevent trailing comma when no locales are generated ([b16931c](https://github.com/ReVanced/revanced-manager/commit/b16931ca79d5ce4d17c75f6dd3bf6f976b8ff7be))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add language settings ([#2913](https://github.com/ReVanced/revanced-manager/issues/2913)) ([df31b39](https://github.com/ReVanced/revanced-manager/commit/df31b39cc8c1fbf00bc3301468e8e7e4b283caf2))
|
||||||
|
|
||||||
|
# app [1.26.0-dev.17](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.16...v1.26.0-dev.17) (2026-01-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* allow updating patches on metered networks ([9d9a0e8](https://github.com/ReVanced/revanced-manager/commit/9d9a0e81dbc9e73e6e3181f6bea9cabb69e49ea8))
|
||||||
|
|
||||||
# app [1.26.0-dev.16](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.15...v1.26.0-dev.16) (2025-12-30)
|
# app [1.26.0-dev.16](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.15...v1.26.0-dev.16) (2025-12-30)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -149,7 +149,6 @@ android {
|
|||||||
debug {
|
debug {
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
resValue("string", "app_name", "ReVanced Manager (Debug)")
|
resValue("string", "app_name", "ReVanced Manager (Debug)")
|
||||||
isPseudoLocalesEnabled = true
|
|
||||||
|
|
||||||
buildConfigField("long", "BUILD_ID", "${Random.nextLong()}L")
|
buildConfigField("long", "BUILD_ID", "${Random.nextLong()}L")
|
||||||
}
|
}
|
||||||
@@ -243,6 +242,8 @@ android {
|
|||||||
version = "3.22.1"
|
version = "3.22.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceSets["main"].kotlin.srcDir(layout.buildDirectory.dir("generated/source/locales"))
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
@@ -250,6 +251,46 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
val generateSupportedLocales by registering {
|
||||||
|
description = "Generate list of supported locales from resource directories"
|
||||||
|
|
||||||
|
val resDir = file("src/main/res")
|
||||||
|
val outputDir = layout.buildDirectory.dir("generated/source/locales")
|
||||||
|
|
||||||
|
inputs.dir(resDir)
|
||||||
|
outputs.dir(outputDir)
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
val locales = resDir.listFiles()
|
||||||
|
.orEmpty()
|
||||||
|
.filter { it.isDirectory && it.name.matches(Regex("values-[a-z]{2}(-r[A-Z]{2})?")) }
|
||||||
|
.map { it.name.removePrefix("values-").replace("-r", "-") }
|
||||||
|
.sorted()
|
||||||
|
.joinToString("\n ") { "Locale.forLanguageTag(\"$it\")," }
|
||||||
|
|
||||||
|
val output = outputDir.get().asFile.resolve("app/revanced/manager/util/GeneratedLocales.kt")
|
||||||
|
output.parentFile.mkdirs()
|
||||||
|
output.writeText(
|
||||||
|
"""
|
||||||
|
|package app.revanced.manager.util
|
||||||
|
|
|
||||||
|
|import java.util.Locale
|
||||||
|
|
|
||||||
|
|object GeneratedLocales {
|
||||||
|
| val SUPPORTED_LOCALES = listOf(
|
||||||
|
| Locale.ENGLISH,$locales
|
||||||
|
| )
|
||||||
|
|}
|
||||||
|
""".trimMargin()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preBuild {
|
||||||
|
dependsOn(generateSupportedLocales)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Needed by gradle-semantic-release-plugin.
|
// Needed by gradle-semantic-release-plugin.
|
||||||
// Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435.
|
// Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435.
|
||||||
val publish by registering {
|
val publish by registering {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
version = 1.26.0-dev.16
|
version = 1.26.0-dev.18
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package app.revanced.manager
|
|||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.animation.slideInHorizontally
|
import androidx.compose.animation.slideInHorizontally
|
||||||
import androidx.compose.animation.slideOutHorizontally
|
import androidx.compose.animation.slideOutHorizontally
|
||||||
@@ -63,7 +63,7 @@ import org.koin.androidx.compose.navigation.koinNavViewModel
|
|||||||
import org.koin.core.parameter.parametersOf
|
import org.koin.core.parameter.parametersOf
|
||||||
import org.koin.androidx.viewmodel.ext.android.getViewModel as getActivityViewModel
|
import org.koin.androidx.viewmodel.ext.android.getViewModel as getActivityViewModel
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ class NetworkInfo(app: Application) {
|
|||||||
/**
|
/**
|
||||||
* Returns true if it is safe to download large files.
|
* Returns true if it is safe to download large files.
|
||||||
*/
|
*/
|
||||||
fun isSafe() = isConnected() && isUnmetered()
|
fun isSafe(ignoreMetered: Boolean) = isConnected() && (ignoreMetered || isUnmetered())
|
||||||
}
|
}
|
||||||
@@ -34,4 +34,6 @@ class PreferencesManager(
|
|||||||
val acknowledgedDownloaderPlugins = stringSetPreference("acknowledged_downloader_plugins", emptySet())
|
val acknowledgedDownloaderPlugins = stringSetPreference("acknowledged_downloader_plugins", emptySet())
|
||||||
|
|
||||||
val showDeveloperSettings = booleanPreference("show_developer_settings", context.isDebuggable)
|
val showDeveloperSettings = booleanPreference("show_developer_settings", context.isDebuggable)
|
||||||
|
|
||||||
|
val allowMeteredNetworks = booleanPreference("allow_metered_networks", false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -286,28 +286,29 @@ class PatchBundleRepository(
|
|||||||
State(sources.toPersistentMap(), info.toPersistentMap())
|
State(sources.toPersistentMap(), info.toPersistentMap())
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createLocal(createStream: suspend () -> InputStream) = dispatchAction("Add bundle") {
|
suspend fun createLocal(createStream: suspend () -> InputStream) =
|
||||||
with(createEntity("", SourceInfo.Local).load() as LocalPatchBundle) {
|
dispatchAction("Add bundle") {
|
||||||
try {
|
with(createEntity("", SourceInfo.Local).load() as LocalPatchBundle) {
|
||||||
createStream().use { patches -> replace(patches) }
|
try {
|
||||||
} catch (e: Exception) {
|
createStream().use { patches -> replace(patches) }
|
||||||
if (e is CancellationException) throw e
|
} catch (e: Exception) {
|
||||||
Log.e(tag, "Got exception while importing bundle", e)
|
if (e is CancellationException) throw e
|
||||||
withContext(Dispatchers.Main) {
|
Log.e(tag, "Got exception while importing bundle", e)
|
||||||
app.toast(app.getString(R.string.patches_replace_fail, e.simpleMessage()))
|
withContext(Dispatchers.Main) {
|
||||||
|
app.toast(app.getString(R.string.patches_replace_fail, e.simpleMessage()))
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteLocalFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteLocalFile()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
doReload()
|
doReload()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createRemote(url: String, autoUpdate: Boolean) =
|
suspend fun createRemote(url: String, autoUpdate: Boolean) =
|
||||||
dispatchAction("Add bundle ($url)") { state ->
|
dispatchAction("Add bundle ($url)") { state ->
|
||||||
val src = createEntity("", SourceInfo.from(url), autoUpdate).load() as RemotePatchBundle
|
val src = createEntity("", SourceInfo.from(url), autoUpdate).load() as RemotePatchBundle
|
||||||
update(src)
|
update(src, force = true)
|
||||||
state.copy(sources = state.sources.put(src.uid, src))
|
state.copy(sources = state.sources.put(src.uid, src))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,32 +330,38 @@ class PatchBundleRepository(
|
|||||||
state.copy(sources = state.sources.put(uid, newSrc))
|
state.copy(sources = state.sources.put(uid, newSrc))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun update(vararg sources: RemotePatchBundle, showToast: Boolean = false) {
|
suspend fun update(
|
||||||
|
vararg sources: RemotePatchBundle,
|
||||||
|
showToast: Boolean = false,
|
||||||
|
force: Boolean = false
|
||||||
|
) {
|
||||||
val uids = sources.map { it.uid }.toSet()
|
val uids = sources.map { it.uid }.toSet()
|
||||||
store.dispatch(Update(showToast = showToast) { it.uid in uids })
|
store.dispatch(Update(showToast = showToast, force = force) { it.uid in uids })
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun redownloadRemoteBundles() = store.dispatch(Update(force = true))
|
suspend fun redownloadRemoteBundles() = store.dispatch(Update(force = true, redownload = true))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates all bundles that should be automatically updated.
|
* Updates all bundles that should be automatically updated.
|
||||||
*/
|
*/
|
||||||
suspend fun updateCheck() = store.dispatch(Update { it.autoUpdate })
|
suspend fun updateCheck() =
|
||||||
|
store.dispatch(Update(force = prefs.allowMeteredNetworks.get()) { it.autoUpdate })
|
||||||
|
|
||||||
private inner class Update(
|
private inner class Update(
|
||||||
private val force: Boolean = false,
|
private val force: Boolean = false,
|
||||||
|
private val redownload: Boolean = false,
|
||||||
private val showToast: Boolean = false,
|
private val showToast: Boolean = false,
|
||||||
private val predicate: (bundle: RemotePatchBundle) -> Boolean = { true },
|
private val predicate: (bundle: RemotePatchBundle) -> Boolean = { true },
|
||||||
) : Action<State> {
|
) : Action<State> {
|
||||||
private suspend fun toast(@StringRes id: Int, vararg args: Any?) =
|
private suspend fun toast(@StringRes id: Int, vararg args: Any?) =
|
||||||
withContext(Dispatchers.Main) { app.toast(app.getString(id, *args)) }
|
withContext(Dispatchers.Main) { app.toast(app.getString(id, *args)) }
|
||||||
|
|
||||||
override fun toString() = if (force) "Redownload remote bundles" else "Update check"
|
override fun toString() = if (redownload) "Redownload remote bundles" else "Update check"
|
||||||
|
|
||||||
override suspend fun ActionContext.execute(
|
override suspend fun ActionContext.execute(
|
||||||
current: State
|
current: State
|
||||||
) = coroutineScope {
|
) = coroutineScope {
|
||||||
if (!networkInfo.isSafe()) {
|
if (!networkInfo.isSafe(force)) {
|
||||||
Log.d(tag, "Skipping update check because the network is down or metered.")
|
Log.d(tag, "Skipping update check because the network is down or metered.")
|
||||||
return@coroutineScope current
|
return@coroutineScope current
|
||||||
}
|
}
|
||||||
@@ -367,7 +374,7 @@ class PatchBundleRepository(
|
|||||||
Log.d(tag, "Updating patch bundle: ${it.name}")
|
Log.d(tag, "Updating patch bundle: ${it.name}")
|
||||||
|
|
||||||
val newVersion = with(it) {
|
val newVersion = with(it) {
|
||||||
if (force) downloadLatest() else update()
|
if (redownload) downloadLatest() else update()
|
||||||
} ?: return@async null
|
} ?: return@async null
|
||||||
|
|
||||||
it to newVersion
|
it to newVersion
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import androidx.compose.foundation.layout.Row
|
|||||||
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
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FilledTonalButton
|
import androidx.compose.material3.FilledTonalButton
|
||||||
@@ -19,6 +21,7 @@ import androidx.compose.material3.rememberTopAppBarState
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
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.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -38,6 +41,7 @@ import app.revanced.manager.ui.theme.Theme
|
|||||||
import app.revanced.manager.ui.viewmodel.GeneralSettingsViewModel
|
import app.revanced.manager.ui.viewmodel.GeneralSettingsViewModel
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import org.koin.compose.koinInject
|
import org.koin.compose.koinInject
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -48,6 +52,7 @@ fun GeneralSettingsScreen(
|
|||||||
val prefs = viewModel.prefs
|
val prefs = viewModel.prefs
|
||||||
val coroutineScope = viewModel.viewModelScope
|
val coroutineScope = viewModel.viewModelScope
|
||||||
var showThemePicker by rememberSaveable { mutableStateOf(false) }
|
var showThemePicker by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var showLanguagePicker by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
if (showThemePicker) {
|
if (showThemePicker) {
|
||||||
ThemePicker(
|
ThemePicker(
|
||||||
@@ -55,6 +60,17 @@ fun GeneralSettingsScreen(
|
|||||||
onConfirm = { viewModel.setTheme(it) }
|
onConfirm = { viewModel.setTheme(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showLanguagePicker) {
|
||||||
|
LanguagePicker(
|
||||||
|
supportedLocales = viewModel.getSupportedLocales(),
|
||||||
|
currentLocale = viewModel.getCurrentLocale(),
|
||||||
|
onDismiss = { showLanguagePicker = false },
|
||||||
|
onConfirm = { viewModel.setLocale(it) },
|
||||||
|
getDisplayName = { viewModel.getLocaleDisplayName(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -74,6 +90,24 @@ fun GeneralSettingsScreen(
|
|||||||
) {
|
) {
|
||||||
GroupHeader(stringResource(R.string.appearance))
|
GroupHeader(stringResource(R.string.appearance))
|
||||||
|
|
||||||
|
val currentLocale = viewModel.getCurrentLocale()
|
||||||
|
val currentLanguageDisplay = remember(currentLocale) {
|
||||||
|
currentLocale?.let { viewModel.getLocaleDisplayName(it) }
|
||||||
|
}
|
||||||
|
SettingsListItem(
|
||||||
|
modifier = Modifier.clickable { showLanguagePicker = true },
|
||||||
|
headlineContent = stringResource(R.string.language),
|
||||||
|
supportingContent = stringResource(R.string.language_description),
|
||||||
|
trailingContent = {
|
||||||
|
FilledTonalButton(onClick = { showLanguagePicker = true }) {
|
||||||
|
Text(
|
||||||
|
currentLanguageDisplay
|
||||||
|
?: stringResource(R.string.language_system_default)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
val theme by prefs.theme.getAsState()
|
val theme by prefs.theme.getAsState()
|
||||||
SettingsListItem(
|
SettingsListItem(
|
||||||
modifier = Modifier.clickable { showThemePicker = true },
|
modifier = Modifier.clickable { showThemePicker = true },
|
||||||
@@ -105,6 +139,14 @@ fun GeneralSettingsScreen(
|
|||||||
description = R.string.pure_black_theme_description
|
description = R.string.pure_black_theme_description
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GroupHeader(stringResource(R.string.networking))
|
||||||
|
BooleanItem(
|
||||||
|
preference = prefs.allowMeteredNetworks,
|
||||||
|
coroutineScope = coroutineScope,
|
||||||
|
headline = R.string.allow_metered_networks,
|
||||||
|
description = R.string.allow_metered_networks_description
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,3 +191,63 @@ private fun ThemePicker(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LanguagePicker(
|
||||||
|
supportedLocales: List<Locale>,
|
||||||
|
currentLocale: Locale?,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onConfirm: (Locale?) -> Unit,
|
||||||
|
getDisplayName: (Locale) -> String
|
||||||
|
) {
|
||||||
|
var selectedLocale by remember { mutableStateOf(currentLocale) }
|
||||||
|
val systemDefaultString = stringResource(R.string.language_system_default)
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text(stringResource(R.string.language)) },
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { selectedLocale = null },
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
HapticRadioButton(
|
||||||
|
selected = selectedLocale == null,
|
||||||
|
onClick = { selectedLocale = null }
|
||||||
|
)
|
||||||
|
Text(systemDefaultString)
|
||||||
|
}
|
||||||
|
|
||||||
|
supportedLocales.forEach { locale ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { selectedLocale = locale },
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
HapticRadioButton(
|
||||||
|
selected = selectedLocale == locale,
|
||||||
|
onClick = { selectedLocale = locale }
|
||||||
|
)
|
||||||
|
Text(getDisplayName(locale))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
onConfirm(selectedLocale)
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.apply))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -54,6 +54,7 @@ class BundleListViewModel : ViewModel(), KoinComponent {
|
|||||||
patchBundleRepository.update(
|
patchBundleRepository.update(
|
||||||
*getSelectedSources().filterIsInstance<RemotePatchBundle>().toTypedArray(),
|
*getSelectedSources().filterIsInstance<RemotePatchBundle>().toTypedArray(),
|
||||||
showToast = true,
|
showToast = true,
|
||||||
|
force = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +66,7 @@ class BundleListViewModel : ViewModel(), KoinComponent {
|
|||||||
fun update(src: PatchBundleSource) = viewModelScope.launch {
|
fun update(src: PatchBundleSource) = viewModelScope.launch {
|
||||||
if (src !is RemotePatchBundle) return@launch
|
if (src !is RemotePatchBundle) return@launch
|
||||||
|
|
||||||
patchBundleRepository.update(src, showToast = true)
|
patchBundleRepository.update(src, showToast = true, force = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Event {
|
enum class Event {
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
package app.revanced.manager.ui.viewmodel
|
package app.revanced.manager.ui.viewmodel
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.revanced.manager.domain.manager.PreferencesManager
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
import app.revanced.manager.ui.theme.Theme
|
import app.revanced.manager.ui.theme.Theme
|
||||||
|
import app.revanced.manager.util.SupportedLocales
|
||||||
import app.revanced.manager.util.resetListItemColorsCached
|
import app.revanced.manager.util.resetListItemColorsCached
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class GeneralSettingsViewModel(
|
class GeneralSettingsViewModel(
|
||||||
|
private val app: Application,
|
||||||
val prefs: PreferencesManager
|
val prefs: PreferencesManager
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
fun setTheme(theme: Theme) = viewModelScope.launch {
|
fun setTheme(theme: Theme) = viewModelScope.launch {
|
||||||
prefs.theme.update(theme)
|
prefs.theme.update(theme)
|
||||||
resetListItemColorsCached()
|
resetListItemColorsCached()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSupportedLocales() = SupportedLocales.getSupportedLocales(app)
|
||||||
|
fun getCurrentLocale() = SupportedLocales.getCurrentLocale()
|
||||||
|
fun setLocale(locale: Locale?) = SupportedLocales.setLocale(locale)
|
||||||
|
fun getLocaleDisplayName(locale: Locale) = SupportedLocales.getDisplayName(locale)
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ class UpdateViewModel(
|
|||||||
uiSafe(app, R.string.failed_to_download_update, "Failed to download update") {
|
uiSafe(app, R.string.failed_to_download_update, "Failed to download update") {
|
||||||
val release = releaseInfo!!
|
val release = releaseInfo!!
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
if (!networkInfo.isSafe() && !ignoreInternetCheck) {
|
if (!networkInfo.isSafe(false) && !ignoreInternetCheck) {
|
||||||
showInternetCheckDialog = true
|
showInternetCheckDialog = true
|
||||||
} else {
|
} else {
|
||||||
state = State.DOWNLOADING
|
state = State.DOWNLOADING
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package app.revanced.manager.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.LocaleList
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.core.os.LocaleListCompat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
object SupportedLocales {
|
||||||
|
fun getSupportedLocales(context: Context): List<Locale> {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
runCatching {
|
||||||
|
android.app.LocaleConfig(context).supportedLocales?.toList()
|
||||||
|
}.getOrNull() ?: GeneratedLocales.SUPPORTED_LOCALES
|
||||||
|
} else {
|
||||||
|
GeneratedLocales.SUPPORTED_LOCALES
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentLocale(): Locale? =
|
||||||
|
AppCompatDelegate.getApplicationLocales().takeIf { !it.isEmpty }?.get(0)
|
||||||
|
|
||||||
|
fun setLocale(locale: Locale?) = AppCompatDelegate.setApplicationLocales(
|
||||||
|
locale?.let { LocaleListCompat.create(it) } ?: LocaleListCompat.getEmptyLocaleList()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getDisplayName(locale: Locale) =
|
||||||
|
locale.getDisplayName(locale).replaceFirstChar { it.uppercase(locale) }
|
||||||
|
|
||||||
|
private fun LocaleList.toList() = (0 until size()).map { get(it) }
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ Second \"item\" text"</string>
|
|||||||
-->
|
-->
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">ReVanced Manager</string>
|
<string name="app_name">ReVanced Manager</string>
|
||||||
<string name="patcher">Patcher</string>
|
<string name="patcher">Patcher test</string>
|
||||||
<string name="patches">Patches</string>
|
<string name="patches">Patches</string>
|
||||||
<string name="cli">CLI</string>
|
<string name="cli">CLI</string>
|
||||||
<string name="manager">Manager</string>
|
<string name="manager">Manager</string>
|
||||||
@@ -83,7 +83,7 @@ Second \"item\" text"</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>
|
||||||
|
|
||||||
<string name="general">General</string>
|
<string name="general">General</string>
|
||||||
<string name="general_description">Theme, dynamic color</string>
|
<string name="general_description">Language, theme, dynamic color</string>
|
||||||
<string name="updates">Updates</string>
|
<string name="updates">Updates</string>
|
||||||
<string name="updates_description">Check for updates and view changelogs</string>
|
<string name="updates_description">Check for updates and view changelogs</string>
|
||||||
<string name="downloads">Downloads</string>
|
<string name="downloads">Downloads</string>
|
||||||
@@ -104,6 +104,9 @@ Second \"item\" text"</string>
|
|||||||
<string name="pure_black_theme_description">Use pure black backgrounds for dark theme</string>
|
<string name="pure_black_theme_description">Use pure black backgrounds for dark theme</string>
|
||||||
<string name="theme">Theme</string>
|
<string name="theme">Theme</string>
|
||||||
<string name="theme_description">Choose between light or dark theme</string>
|
<string name="theme_description">Choose between light or dark theme</string>
|
||||||
|
<string name="language">Language</string>
|
||||||
|
<string name="language_description">Choose the app display language</string>
|
||||||
|
<string name="language_system_default">System default</string>
|
||||||
<string name="safeguards">Safeguards</string>
|
<string name="safeguards">Safeguards</string>
|
||||||
<string name="patch_compat_check">Disable version compatibility check</string>
|
<string name="patch_compat_check">Disable version compatibility check</string>
|
||||||
<string name="patch_compat_check_description">Do not restrict patches to compatible app versions</string>
|
<string name="patch_compat_check_description">Do not restrict patches to compatible app versions</string>
|
||||||
@@ -217,6 +220,10 @@ You will not be able to update the previously installed apps from this source."<
|
|||||||
<string name="light">Light</string>
|
<string name="light">Light</string>
|
||||||
<string name="dark">Dark</string>
|
<string name="dark">Dark</string>
|
||||||
<string name="appearance">Appearance</string>
|
<string name="appearance">Appearance</string>
|
||||||
|
<string name="networking">Networking</string>
|
||||||
|
<string name="allow_metered_networks">Allow metered networks</string>
|
||||||
|
<string name="allow_metered_networks_description">Permits automatic updates on metered networks.
|
||||||
|
The application might still warn about metered networks for manual operations.</string>
|
||||||
<string name="downloaded_apps">Downloaded apps</string>
|
<string name="downloaded_apps">Downloaded apps</string>
|
||||||
<string name="process_runtime">Run Patcher in another process (experimental)</string>
|
<string name="process_runtime">Run Patcher in another process (experimental)</string>
|
||||||
<string name="process_runtime_description">This is faster and allows Patcher to use more memory</string>
|
<string name="process_runtime_description">This is faster and allows Patcher to use more memory</string>
|
||||||
|
|||||||
9
crowdin.yml
Normal file
9
crowdin.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
project_id_env: "CROWDIN_PROJECT_ID"
|
||||||
|
api_token_env: "CROWDIN_PERSONAL_TOKEN"
|
||||||
|
|
||||||
|
preserve_hierarchy: true
|
||||||
|
files:
|
||||||
|
- source: app/src/main/res/values/strings.xml
|
||||||
|
dest: manager.xml
|
||||||
|
translation: app/src/main/res/values-%android_code%/strings.xml
|
||||||
|
skip_untranslated_strings: true
|
||||||
Reference in New Issue
Block a user