Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/apply-translations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ jobs:
sf.write(new_content)
EOF

python3 scripts/validate_strings.py --fix

AUTHOR_EMAIL="$CONTRIBUTOR_EMAIL"
if [ -z "$AUTHOR_EMAIL" ]; then
AUTHOR_EMAIL="${CONTRIBUTOR}@users.noreply.github.com"
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/validate-translations.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Validate Translations

on:
pull_request:
paths:
- 'app/src/main/res/values*/**'
- 'scripts/validate_strings.py'
workflow_dispatch:

jobs:
validate-strings:
name: Validate Android Strings XML
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Run Translation Strings Validator
run: |
python3 scripts/validate_strings.py
57 changes: 34 additions & 23 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose)
}


import org . jetbrains . kotlin . gradle . dsl . JvmTarget

kotlin {
compilerOptions {
Expand All @@ -25,15 +26,25 @@ android {
namespace = "com.sameerasw.essentials"
compileSdk = 37

androidResources {
localeFilters += listOf(
"en", "ach", "af", "ar", "bn", "bn-rBD", "ca", "cs", "da", "de", "el", "es", "fi", "fil", "fil-rPH",
"fr", "he", "hi", "hi-rIN", "hu", "id", "in", "in-rID", "it", "iw", "ja", "ko", "ml", "ml-rIN",
"ne", "ne-rNP", "nl", "no", "pl", "pt", "pt-rBR", "pt-rPT", "ro", "ru", "si", "sk", "sk-rSK",
"sr", "sv", "ta", "ta-rIN", "tr", "uk", "vi", "zh", "zh-rCN", "zh-rTW"
)
}

defaultConfig {
applicationId = "com.sameerasw.essentials"
minSdk = 26
targetSdk = 37
versionCode = 55
versionName = "16.3"
versionCode = 56
versionName = "16.4"

val whatsNewCounter = 2
buildConfigField("int", "WHATS_NEW_COUNTER", whatsNewCounter.toString())
buildConfigField("int", "REQUIRED_WEAR_VERSION_CODE", "5")

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -88,19 +99,19 @@ dependencies {
implementation(platform(libs.androidx.compose.bom))

// Android 12+ SplashScreen API with backward compatibility attributes
implementation("androidx.core:core-splashscreen:1.0.1")
implementation(libs.androidx.core.splashscreen)

// Force latest Material3 1.5.0-alpha17 for new MaterialShapes
implementation("androidx.compose.material3:material3:1.5.0-alpha19")
implementation(libs.androidx.compose.material3.v150alpha24)

implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.appcompat)
implementation("androidx.biometric:biometric:1.2.0-alpha05")
implementation(libs.androidx.biometric)

implementation("io.coil-kt:coil-compose:2.5.0")
implementation("io.coil-kt:coil-gif:2.5.0")
implementation(libs.coil.compose)
implementation(libs.coil.gif)
implementation(libs.androidx.compose.foundation.layout)
implementation(libs.androidx.ui)
implementation(libs.androidx.compose.foundation)
Expand All @@ -123,15 +134,15 @@ dependencies {
implementation(libs.hiddenapibypass)

// Gson for JSON serialization
implementation("com.google.code.gson:gson:2.10.1")
implementation("androidx.palette:palette:1.0.0")
implementation(libs.gson.v2140)
implementation(libs.androidx.palette)

// Reorderable library
implementation("sh.calvin.reorderable:reorderable:3.0.0")
implementation(libs.reorderable)

// Volume Long Press
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")
implementation("dev.rikka.shizuku:api:13.1.5")
implementation(libs.kotlinx.coroutines.android)
implementation(libs.api.v1315)

// Google Maps & Location
implementation(libs.play.services.location)
Expand All @@ -144,34 +155,34 @@ dependencies {
implementation(kotlin("reflect"))

// SymSpell for word suggestions
implementation("com.darkrockstudios:symspellkt:3.4.0")
implementation(libs.symspellkt)

implementation("androidx.glance:glance-appwidget:1.3.0-alpha01")
implementation("androidx.glance:glance-material3:1.3.0-alpha01")
implementation(libs.androidx.glance.appwidget)
implementation(libs.androidx.glance.material3)

// Watermark dependencies
implementation("androidx.exifinterface:exifinterface:1.3.7")
implementation("androidx.compose.material:material-icons-extended:1.7.0") // Compatible with Compose BOM
implementation(libs.androidx.exifinterface)
implementation(libs.androidx.compose.material.icons.extended) // Compatible with Compose BOM

// GSMArena Parsing
implementation("org.jsoup:jsoup:1.15.3")
implementation(libs.jsoup)
implementation(libs.sentry.android)
implementation(libs.androidx.graphics.shapes)

// AutoUpdater
implementation("com.github.CSAbhiOnline:AutoUpdater:1.0.1")
implementation(libs.autoupdater)

// Media3 for Live Wallpaper
implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.media3.common)

// RemoteIntent support
implementation("androidx.wear:wear-remote-interactions:1.1.0-alpha02")
implementation(libs.androidx.wear.remote.interactions.v110alpha02)

// tandard wearable library
implementation("com.google.android.gms:play-services-wearable:19.0.0")
implementation(libs.play.services.wearable.v1900)

// Lottie for animations
implementation("com.airbnb.android:lottie-compose:6.4.0")
implementation(libs.lottie.compose)

}
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,14 @@ class EssentialsWearableListenerService : WearableListenerService() {
if (data != null && data.size >= 2) {
val adbWifiEnabled = data[0].toInt() == 1
val secureSettingsGranted = data[1].toInt() == 1
val version = if (data.size >= 3) data[2].toInt() else 0
val prefs = getSharedPreferences("essentials_prefs", MODE_PRIVATE)
prefs.edit(commit = true) {
putBoolean("watch_adb_wifi_enabled", adbWifiEnabled)
putBoolean("watch_write_secure_settings_granted", secureSettingsGranted)
if (version > 0) {
putInt("watch_version_code", version)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,18 @@ object StringLoader {

fun getTranslationsForKey(context: Context, key: String): Map<String, String> {
val all = getAllTranslations(context)
return all[key] ?: emptyMap()
val map = all[key] ?: return emptyMap()
val result = mutableMapOf<String, String>()
result.putAll(map)
for ((loc, valStr) in map) {
if (loc.contains("-")) {
val base = loc.split("-")[0]
if (!result.containsKey(base)) {
result[base] = valStr
}
}
}
return result
}

@Synchronized
Expand Down Expand Up @@ -54,12 +65,45 @@ object StringLoader {
val assets = context.assets
// Common bundled locale subfolders in res/values-*
val knownLocales = listOf(
"values-ach", "values-af", "values-ar", "values-ca", "values-cs", "values-da",
"values-de", "values-el", "values-en", "values-es", "values-fi", "values-fr",
"values-he", "values-hu", "values-in-rID", "values-it", "values-iw-rIL", "values-ja",
"values-ko", "values-nl", "values-no", "values-pl", "values-pt-rBR", "values-pt-rPT",
"values-ro", "values-ru", "values-si", "values-sr", "values-sv", "values-tr",
"values-uk", "values-vi", "values-zh-rCN", "values-zh-rTW"
"values-ach", "values-ach-rUG",
"values-af", "values-af-rZA",
"values-ar", "values-ar-rSA",
"values-bn-rBD",
"values-ca", "values-ca-rES",
"values-cs", "values-cs-rCZ",
"values-da", "values-da-rDK",
"values-de", "values-de-rDE",
"values-el", "values-el-rGR",
"values-en", "values-en-rUS",
"values-es", "values-es-rES",
"values-fi", "values-fi-rFI",
"values-fil-rPH",
"values-fr", "values-fr-rFR",
"values-he",
"values-hi-rIN",
"values-hu", "values-hu-rHU",
"values-id", "values-in-rID",
"values-it", "values-it-rIT",
"values-iw-rIL",
"values-ja", "values-ja-rJP",
"values-ko", "values-ko-rKR",
"values-ml-rIN",
"values-ne-rNP",
"values-nl", "values-nl-rNL",
"values-no", "values-no-rNO",
"values-pl", "values-pl-rPL",
"values-pt", "values-pt-rBR", "values-pt-rPT",
"values-ro", "values-ro-rRO",
"values-ru", "values-ru-rRU",
"values-si", "values-si-rLK",
"values-sk-rSK",
"values-sr", "values-sr-rSP",
"values-sv", "values-sv-rSE",
"values-ta-rIN",
"values-tr", "values-tr-rTR",
"values-uk", "values-uk-rUA",
"values-vi", "values-vi-rVN",
"values-zh", "values-zh-rCN", "values-zh-rTW"
)
list.addAll(knownLocales)
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.ui.graphics.toArgb
import com.sameerasw.essentials.R
import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenu
import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenuItem
Expand Down Expand Up @@ -63,7 +66,8 @@ fun FeatureCard(
onHelpClick: (() -> Unit)? = null,
additionalMenuItems: (@Composable (onDismiss: () -> Unit) -> Unit)? = null,
customTrailingContent: (@Composable () -> Unit)? = null,
iconPainter: androidx.compose.ui.graphics.painter.Painter? = null
iconPainter: androidx.compose.ui.graphics.painter.Painter? = null,
hasBadge: Boolean = false
) {
val view = LocalView.current
var showMenu by remember { mutableStateOf(false) }
Expand Down Expand Up @@ -119,44 +123,83 @@ fun FeatureCard(
.blur(blurRadius),
leadingContent = if (iconPainter != null || iconRes != null) {
{
Box(
modifier = Modifier
.size(40.dp)
.background(
color = ColorUtil.getPastelColorFor(resolvedTitle),
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
if (iconPainter != null) {
androidx.compose.foundation.Image(
painter = iconPainter,
contentDescription = resolvedTitle,
modifier = Modifier.size(24.dp)
)
} else if (iconRes != null) {
val context = LocalContext.current
val isValid = remember(iconRes) {
try {
val value = android.util.TypedValue()
context.resources.getValue(iconRes, value, true)
val path = value.string?.toString() ?: ""
!path.endsWith(".gif", ignoreCase = true)
} catch (e: Throwable) {
false
Box {
Box(
modifier = Modifier
.size(40.dp)
.background(
color = ColorUtil.getPastelColorFor(resolvedTitle),
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
if (iconPainter != null) {
androidx.compose.foundation.Image(
painter = iconPainter,
contentDescription = resolvedTitle,
modifier = Modifier.size(24.dp)
)
} else if (iconRes != null) {
val context = LocalContext.current
val isValid = remember(iconRes) {
try {
val value = android.util.TypedValue()
context.resources.getValue(iconRes, value, true)
val path = value.string?.toString() ?: ""
!path.endsWith(".gif", ignoreCase = true)
} catch (e: Throwable) {
false
}
}
val painter = if (isValid) {
painterResource(id = iconRes)
} else {
painterResource(id = R.drawable.rounded_settings_accessibility_24)
}
Icon(
painter = painter,
contentDescription = resolvedTitle,
modifier = Modifier.size(24.dp),
tint = ColorUtil.getVibrantColorFor(resolvedTitle)
)
}
val painter = if (isValid) {
painterResource(id = iconRes)
} else {
painterResource(id = R.drawable.rounded_settings_accessibility_24)
}

if (hasBadge) {
androidx.compose.material3.Badge(
modifier = Modifier
.align(Alignment.TopEnd)
.offset(x = 4.dp, y = (-4).dp)
.size(18.dp),
containerColor = MaterialTheme.colorScheme.error,
contentColor = MaterialTheme.colorScheme.onError
) {
val composition by com.airbnb.lottie.compose.rememberLottieComposition(com.airbnb.lottie.compose.LottieCompositionSpec.RawRes(R.raw.update_motion))
val progress by com.airbnb.lottie.compose.animateLottieCompositionAsState(
composition = composition,
iterations = com.airbnb.lottie.compose.LottieConstants.IterateForever
)
val onErrorColor = MaterialTheme.colorScheme.onError
val dynamicProperties = com.airbnb.lottie.compose.rememberLottieDynamicProperties(
com.airbnb.lottie.compose.rememberLottieDynamicProperty(
property = com.airbnb.lottie.LottieProperty.COLOR_FILTER,
value = android.graphics.PorterDuffColorFilter(
onErrorColor.toArgb(),
android.graphics.PorterDuff.Mode.SRC_ATOP
),
keyPath = arrayOf("**")
)
)

com.airbnb.lottie.compose.LottieAnimation(
composition = composition,
progress = { progress },
dynamicProperties = dynamicProperties,
modifier = Modifier
.fillMaxSize()
.padding(1.dp)
)
}
Icon(
painter = painter,
contentDescription = resolvedTitle,
modifier = Modifier.size(24.dp),
tint = ColorUtil.getVibrantColorFor(resolvedTitle)
)
}
}
}
Expand Down
Loading
Loading