Skip to content

Commit 4934326

Browse files
author
MasterDNS Dev
committed
feat(settings): add Version Info section to Global Settings tab
- Show App Version (from BuildConfig.VERSION_NAME) in Global Settings - Show Upstream Engine version (from strings.xml engine_version resource) - engine_version can be updated per release in strings.xml - Enabled buildConfig = true in build.gradle.kts to expose BuildConfig
1 parent b9512a5 commit 4934326

3 files changed

Lines changed: 17 additions & 113 deletions

File tree

android/app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ android {
7676

7777
buildFeatures {
7878
compose = true
79+
buildConfig = true
7980
}
8081

8182
splits {

android/app/src/main/java/com/masterdns/vpn/ui/settings/GlobalSettingsScreen.kt

Lines changed: 15 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,14 @@ import androidx.compose.ui.text.font.FontWeight
5555
import androidx.compose.ui.text.style.TextDecoration
5656
import androidx.compose.ui.text.style.TextOverflow
5757
import androidx.compose.ui.unit.dp
58+
import androidx.compose.ui.text.style.TextAlign
59+
import com.masterdns.vpn.BuildConfig
5860
import androidx.compose.ui.window.Dialog
5961
import androidx.core.graphics.drawable.toBitmap
6062
import androidx.lifecycle.viewmodel.compose.viewModel
6163
import com.masterdns.vpn.R
6264
import com.masterdns.vpn.util.GlobalSettings
6365
import kotlinx.coroutines.launch
64-
6566
@OptIn(ExperimentalMaterial3Api::class)
6667
@Composable
6768
fun GlobalSettingsScreen(vm: GlobalSettingsViewModel = viewModel()) {
@@ -77,7 +78,6 @@ fun GlobalSettingsScreen(vm: GlobalSettingsViewModel = viewModel()) {
7778
val snackbarHostState = remember { SnackbarHostState() }
7879
val scope = rememberCoroutineScope()
7980
val uriHandler = LocalUriHandler.current
80-
8181
Scaffold(
8282
topBar = { TopAppBar(title = { Text("Settings") }) },
8383
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
@@ -118,17 +118,14 @@ fun GlobalSettingsScreen(vm: GlobalSettingsViewModel = viewModel()) {
118118
draft = draft.copy(connectionMode = mode)
119119
modeExpanded = false
120120
}
121-
)
122121
}
123122
}
124123
}
125-
126124
RowSwitch(
127125
title = "Split Tunneling",
128126
checked = draft.splitTunnelingEnabled,
129127
onChecked = { draft = draft.copy(splitTunnelingEnabled = it) }
130128
)
131-
132129
if (draft.splitTunnelingEnabled) {
133130
Card(
134131
onClick = {
@@ -137,33 +134,23 @@ fun GlobalSettingsScreen(vm: GlobalSettingsViewModel = viewModel()) {
137134
selectedQuery = ""
138135
activeTab = "AVAILABLE"
139136
showAppPicker = true
140-
},
141137
modifier = Modifier.fillMaxWidth()
142138
) {
143139
Column(modifier = Modifier.padding(12.dp)) {
144140
Text("Split Tunnel Apps")
145141
Text(
146142
"${parseCsv(draft.splitPackagesCsv).size} selected apps - tap to choose",
147143
style = MaterialTheme.typography.bodySmall
148-
)
149-
}
150-
}
151-
}
152-
153144
Button(
154145
onClick = {
155146
vm.save(normalize(draft))
156147
scope.launch { snackbarHostState.showSnackbar("Global settings saved and applied") }
157148
},
158149
modifier = Modifier.fillMaxWidth()
159-
) {
160150
Text("Save Global Settings")
161-
}
162151
}
163152
}
164153
}
165-
item {
166-
Card(colors = CardDefaults.cardColors()) {
167154
Column(
168155
modifier = Modifier.padding(12.dp),
169156
verticalArrangement = Arrangement.spacedBy(8.dp)
@@ -176,41 +163,38 @@ fun GlobalSettingsScreen(vm: GlobalSettingsViewModel = viewModel()) {
176163
title = "Main GitHub:",
177164
link = mainGithubLink,
178165
onOpen = { uriHandler.openUri("https://$mainGithubLink") }
179-
)
180-
LinkRow(
181166
title = "Main Telegram:",
182167
link = mainTelegramLink,
183168
onOpen = { uriHandler.openUri("https://$mainTelegramLink") }
184-
)
185-
LinkRow(
186169
title = "MDV-HN Android Client:",
187170
link = androidClientGithubLink,
188171
onOpen = { uriHandler.openUri("https://$androidClientGithubLink") }
189-
)
190-
}
191-
}
192-
}
172+
val engineVersion = stringResource(R.string.engine_version)
173+
modifier = Modifier
174+
.fillMaxWidth()
175+
.padding(12.dp),
176+
verticalArrangement = Arrangement.spacedBy(4.dp)
177+
Text("Version Info", style = MaterialTheme.typography.titleMedium)
178+
Row(
179+
modifier = Modifier.fillMaxWidth(),
180+
horizontalArrangement = Arrangement.SpaceBetween
181+
Text("App Version", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
182+
Text(BuildConfig.VERSION_NAME, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium)
183+
Text("Upstream Engine", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
184+
Text(engineVersion, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium)
193185
}
194186
}
195-
196187
if (showAppPicker) {
197188
val selectedApps = installedApps.filter { draftAppSelection.contains(it.packageName) }
198189
val availableApps = installedApps.filterNot { draftAppSelection.contains(it.packageName) }
199-
200190
val selectedFiltered = selectedApps.filter {
201191
val q = selectedQuery.trim().lowercase()
202192
q.isEmpty() ||
203193
it.label.lowercase().contains(q) ||
204194
it.packageName.lowercase().contains(q)
205195
}.sortedWith(compareBy({ it.label.lowercase() }, { it.packageName }))
206-
207196
val availableFiltered = availableApps.filter {
208197
val q = availableQuery.trim().lowercase()
209-
q.isEmpty() ||
210-
it.label.lowercase().contains(q) ||
211-
it.packageName.lowercase().contains(q)
212-
}.sortedWith(compareBy({ it.label.lowercase() }, { it.packageName }))
213-
214198
Dialog(onDismissRequest = { showAppPicker = false }) {
215199
Surface(
216200
modifier = Modifier
@@ -231,86 +215,54 @@ fun GlobalSettingsScreen(vm: GlobalSettingsViewModel = viewModel()) {
231215
style = MaterialTheme.typography.bodySmall,
232216
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
233217
)
234-
235218
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
236219
FilterChip(
237220
selected = activeTab == "SELECTED",
238221
onClick = { activeTab = "SELECTED" },
239222
label = { Text("Selected ${selectedApps.size}") }
240-
)
241-
FilterChip(
242223
selected = activeTab == "AVAILABLE",
243224
onClick = { activeTab = "AVAILABLE" },
244225
label = { Text("Available ${availableApps.size}") }
245-
)
246-
}
247-
248226
if (activeTab == "SELECTED") {
249227
OutlinedTextField(
250228
value = selectedQuery,
251229
onValueChange = { selectedQuery = it },
252230
label = { Text("Search selected apps") },
253-
modifier = Modifier.fillMaxWidth()
254-
)
255231
} else {
256-
OutlinedTextField(
257232
value = availableQuery,
258233
onValueChange = { availableQuery = it },
259234
label = { Text("Search available apps") },
260-
modifier = Modifier.fillMaxWidth()
261-
)
262-
}
263-
264-
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
265235
OutlinedButton(
266-
onClick = {
267236
draftAppSelection = draftAppSelection.toMutableSet().apply {
268237
addAll(availableFiltered.map { it.packageName })
269-
}
270-
},
271238
modifier = Modifier.weight(1f)
272-
) {
273239
Text("Select Visible")
274-
}
275-
OutlinedButton(
276240
onClick = { draftAppSelection = mutableSetOf() },
277-
modifier = Modifier.weight(1f)
278-
) {
279241
Text("Select None")
280-
}
281-
}
282-
283242
Surface(
284243
modifier = Modifier.fillMaxWidth(),
285244
shape = RoundedCornerShape(12.dp),
286245
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)
287-
) {
288246
Column(
289247
modifier = Modifier
290248
.fillMaxWidth()
291249
.padding(10.dp)
292-
) {
293250
val appsToShow = if (activeTab == "SELECTED") selectedFiltered else availableFiltered
294251
val emptyText = if (activeTab == "SELECTED") {
295252
"No selected app matches your search"
296253
} else {
297254
"No available app matches your search"
298-
}
299-
300255
Text(
301256
if (activeTab == "SELECTED") "Selected Apps" else "Available Apps",
302257
style = MaterialTheme.typography.labelLarge,
303258
color = MaterialTheme.colorScheme.primary
304-
)
305-
306259
if (appsToShow.isEmpty()) {
307260
Text(
308261
emptyText,
309262
style = MaterialTheme.typography.bodySmall,
310263
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
311264
modifier = Modifier.padding(top = 8.dp)
312265
)
313-
} else {
314266
LazyColumn(
315267
modifier = Modifier
316268
.fillMaxWidth()
@@ -327,34 +279,14 @@ fun GlobalSettingsScreen(vm: GlobalSettingsViewModel = viewModel()) {
327279
}
328280
)
329281
}
330-
}
331-
}
332-
}
333-
}
334-
335282
Row(
336-
modifier = Modifier.fillMaxWidth(),
337283
horizontalArrangement = Arrangement.End
338-
) {
339284
TextButton(onClick = { showAppPicker = false }) {
340285
Text("Cancel")
341-
}
342-
Button(
343-
onClick = {
344286
draft = draft.copy(splitPackagesCsv = draftAppSelection.sorted().joinToString(","))
345287
showAppPicker = false
346-
}
347-
) {
348288
Text("Apply")
349-
}
350-
}
351-
}
352-
}
353-
}
354-
}
355289
}
356-
357-
@Composable
358290
private fun LinkRow(title: String, link: String, onOpen: () -> Unit) {
359291
Column(
360292
modifier = Modifier
@@ -367,7 +299,6 @@ private fun LinkRow(title: String, link: String, onOpen: () -> Unit) {
367299
style = MaterialTheme.typography.bodyMedium,
368300
color = MaterialTheme.colorScheme.onSurfaceVariant
369301
)
370-
Text(
371302
text = link,
372303
style = MaterialTheme.typography.bodyMedium.copy(
373304
textDecoration = TextDecoration.Underline,
@@ -376,11 +307,6 @@ private fun LinkRow(title: String, link: String, onOpen: () -> Unit) {
376307
color = MaterialTheme.colorScheme.primary,
377308
maxLines = 3,
378309
overflow = TextOverflow.Ellipsis
379-
)
380-
}
381-
}
382-
383-
@Composable
384310
private fun AppRow(
385311
app: GlobalSettingsViewModel.AppEntry,
386312
checked: Boolean,
@@ -391,19 +317,14 @@ private fun AppRow(
391317
runCatching {
392318
context.packageManager.getApplicationIcon(app.packageName).toBitmap(48, 48)
393319
}.getOrNull()
394-
}
395320
Row(
396-
modifier = Modifier
397-
.fillMaxWidth()
398321
.clickable { onToggle() }
399322
.padding(vertical = 4.dp),
400323
horizontalArrangement = Arrangement.SpaceBetween,
401324
verticalAlignment = Alignment.CenterVertically
402-
) {
403325
Row(
404326
modifier = Modifier.weight(1f),
405327
verticalAlignment = Alignment.CenterVertically
406-
) {
407328
if (appIconBitmap != null) {
408329
Image(
409330
bitmap = appIconBitmap.asImageBitmap(),
@@ -413,41 +334,23 @@ private fun AppRow(
413334
} else {
414335
Icon(
415336
imageVector = Icons.Filled.ArrowDropDown,
416-
contentDescription = null,
417-
modifier = Modifier.size(24.dp)
418-
)
419-
}
420337
Spacer(modifier = Modifier.size(8.dp))
421338
Column {
422339
Text(text = app.label)
423340
Text(text = app.packageName)
424-
}
425-
}
426341
Checkbox(
427342
checked = checked,
428343
onCheckedChange = { onToggle() }
429-
)
430-
}
431-
}
432-
433-
@Composable
434344
private fun RowSwitch(title: String, checked: Boolean, onChecked: (Boolean) -> Unit) {
435-
Row(
436345
modifier = Modifier.fillMaxWidth(),
437346
horizontalArrangement = Arrangement.SpaceBetween
438-
) {
439347
Text(title)
440348
Switch(checked = checked, onCheckedChange = onChecked)
441-
}
442-
}
443-
444349
private fun parseCsv(value: String): Set<String> {
445350
return value.split(",")
446351
.map { it.trim() }
447352
.filter { it.isNotBlank() }
448353
.toSet()
449-
}
450-
451354
private fun normalize(settings: GlobalSettings): GlobalSettings {
452355
return settings.copy(
453356
connectionMode = settings.connectionMode.uppercase(),
@@ -458,4 +361,3 @@ private fun normalize(settings: GlobalSettings): GlobalSettings {
458361
.distinct()
459362
.joinToString(",")
460363
)
461-
}

android/app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
<string name="project_main_github">github.com/masterking32/MasterDnsVPN</string>
88
<string name="project_main_telegram">t.me/masterdnsvpn</string>
99
<string name="project_android_client_github">github.com/Hidden-Node/MasterDnsVPN-AndroidClient</string>
10+
<string name="engine_version">v2026.04.07</string>
1011
</resources>

0 commit comments

Comments
 (0)