Added Block class and BlockingScreen
This commit is contained in:
49
app/src/main/kotlin/com/tutpro/baresip/BlockRule.kt
Normal file
49
app/src/main/kotlin/com/tutpro/baresip/BlockRule.kt
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package com.tutpro.baresip
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class BlockRule(
|
||||||
|
val pattern: String
|
||||||
|
) {
|
||||||
|
fun matches(uri: String): Boolean {
|
||||||
|
if (uri.contains(pattern, ignoreCase = true)) return true
|
||||||
|
return try {
|
||||||
|
Regex(pattern, RegexOption.IGNORE_CASE).containsMatchIn(uri)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun exists(pattern: String): Boolean {
|
||||||
|
return BaresipService.blockRules.any { it.pattern == pattern }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save() {
|
||||||
|
Log.d(TAG, "Saving ${BaresipService.blockRules.size} block rules")
|
||||||
|
val file = File(BaresipService.filesPath + "/blocking.json")
|
||||||
|
try {
|
||||||
|
val jsonString = Json.encodeToString(BaresipService.blockRules)
|
||||||
|
file.writeText(jsonString)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Serialization exception", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restore() {
|
||||||
|
val file = File(BaresipService.filesPath + "/blocking.json")
|
||||||
|
if (file.exists())
|
||||||
|
try {
|
||||||
|
val jsonString = file.readText()
|
||||||
|
BaresipService.blockRules = Json.decodeFromString<MutableList<BlockRule>>(jsonString)
|
||||||
|
Log.d(TAG, "Restored ${BaresipService.blockRules.size} block rules")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Deserialization exception: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
382
app/src/main/kotlin/com/tutpro/baresip/BlockingScreen.kt
Normal file
382
app/src/main/kotlin/com/tutpro/baresip/BlockingScreen.kt
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
package com.tutpro.baresip
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Remove
|
||||||
|
import androidx.compose.material.icons.outlined.Clear
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SmallFloatingActionButton
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavType
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.navArgument
|
||||||
|
import com.tutpro.baresip.CustomElements.AlertDialog
|
||||||
|
import com.tutpro.baresip.CustomElements.verticalScrollbar
|
||||||
|
|
||||||
|
fun NavGraphBuilder.blockingScreenRoute(navController: NavController) {
|
||||||
|
composable(
|
||||||
|
route = "blocking/{aor}",
|
||||||
|
arguments = listOf(navArgument("aor") { type = NavType.StringType })
|
||||||
|
) { backStackEntry ->
|
||||||
|
val aor = backStackEntry.arguments?.getString("aor")!!
|
||||||
|
val viewModel = viewModel<AccountViewModel>()
|
||||||
|
val ua = UserAgent.ofAor(aor)!!
|
||||||
|
BlockingScreen(navController, viewModel, ua)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun BlockingScreen(navController: NavController, viewModel: AccountViewModel, ua: UserAgent) {
|
||||||
|
val acc = ua.account
|
||||||
|
var rules by remember { mutableStateOf(BaresipService.blockRules.toList()) }
|
||||||
|
|
||||||
|
remember {
|
||||||
|
viewModel.loadAccount(acc)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier.fillMaxSize().imePadding(),
|
||||||
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
|
topBar = {
|
||||||
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
|
Spacer(Modifier.statusBarsPadding())
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(R.string.blocking), fontWeight = FontWeight.Bold)
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = { navController.navigateUp() }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
windowInsets = WindowInsets(0, 0, 0, 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bottomBar = { NewRule(onRuleAdded = { rules = BaresipService.blockRules.toList() }) },
|
||||||
|
content = { contentPadding ->
|
||||||
|
BlockingContent(
|
||||||
|
contentPadding,
|
||||||
|
viewModel,
|
||||||
|
rules,
|
||||||
|
acc,
|
||||||
|
onRuleDeleted = { rules = BaresipService.blockRules.toList() }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BlockingContent(
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
viewModel: AccountViewModel,
|
||||||
|
rules: List<BlockRule>,
|
||||||
|
acc: Account,
|
||||||
|
onRuleDeleted: () -> Unit
|
||||||
|
) {
|
||||||
|
val ctx = LocalContext.current
|
||||||
|
val alertTitle = remember { mutableStateOf("") }
|
||||||
|
val alertMessage = remember { mutableStateOf("") }
|
||||||
|
val showAlert = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (showAlert.value)
|
||||||
|
AlertDialog(
|
||||||
|
showDialog = showAlert,
|
||||||
|
title = alertTitle.value,
|
||||||
|
message = alertMessage.value,
|
||||||
|
lastButtonText = stringResource(R.string.ok),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BlockUnknown() {
|
||||||
|
val blockUnknownTitle = stringResource(R.string.block_unknown)
|
||||||
|
val blockUnknownHelp = stringResource(R.string.block_unknown_help)
|
||||||
|
val block by viewModel.blockUnknown.collectAsState()
|
||||||
|
Row(
|
||||||
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Start
|
||||||
|
) {
|
||||||
|
Text(text = blockUnknownTitle,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.clickable {
|
||||||
|
alertTitle.value = blockUnknownTitle
|
||||||
|
alertMessage.value = blockUnknownHelp
|
||||||
|
showAlert.value = true
|
||||||
|
},
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
Switch(
|
||||||
|
checked = block,
|
||||||
|
onCheckedChange = {
|
||||||
|
viewModel.blockUnknown.value = it
|
||||||
|
acc.blockUnknown = it
|
||||||
|
Account.saveAccounts()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BlockHidden(acc: Account) {
|
||||||
|
val blockHiddenTitle = stringResource(R.string.block_hidden)
|
||||||
|
val blockHiddenHelp = stringResource(R.string.block_hidden_help)
|
||||||
|
val block by viewModel.blockHidden.collectAsState()
|
||||||
|
Row(
|
||||||
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Start
|
||||||
|
) {
|
||||||
|
Text(text = blockHiddenTitle,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.clickable {
|
||||||
|
alertTitle.value = blockHiddenTitle
|
||||||
|
alertMessage.value = blockHiddenHelp
|
||||||
|
showAlert.value = true
|
||||||
|
},
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
Switch(
|
||||||
|
checked = block,
|
||||||
|
onCheckedChange = {
|
||||||
|
viewModel.blockHidden.value = it
|
||||||
|
acc.blockHidden = it
|
||||||
|
Account.saveAccounts()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val showDialog = remember { mutableStateOf(false) }
|
||||||
|
val message = remember { mutableStateOf("") }
|
||||||
|
val lastAction = remember { mutableStateOf({}) }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
showDialog = showDialog,
|
||||||
|
title = stringResource(R.string.confirmation),
|
||||||
|
message = message.value,
|
||||||
|
firstButtonText = stringResource(R.string.cancel),
|
||||||
|
lastButtonText = stringResource(R.string.delete),
|
||||||
|
onLastClicked = lastAction.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(contentPadding)
|
||||||
|
.padding(start = 16.dp, end = 4.dp, top = 8.dp, bottom = 8.dp)
|
||||||
|
) {
|
||||||
|
BlockUnknown()
|
||||||
|
BlockHidden(acc)
|
||||||
|
Spacer(Modifier.size(16.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.blocking_list),
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
|
)
|
||||||
|
if (rules.isNotEmpty()) {
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
|
LazyColumn(
|
||||||
|
state = lazyListState,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
.verticalScrollbar(state = lazyListState),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
items(rules) { rule ->
|
||||||
|
val pattern = if (Utils.checkUri(rule.pattern))
|
||||||
|
Utils.friendlyUri(ctx, rule.pattern, acc)
|
||||||
|
else
|
||||||
|
rule.pattern
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "\u2022",
|
||||||
|
modifier = Modifier.padding(start = 8.dp, end = 8.dp),
|
||||||
|
fontSize = 18.sp,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = pattern,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val deleteRuleMessage = stringResource(R.string.blocking_delete_alert)
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.padding(end = 8.dp).size(32.dp),
|
||||||
|
onClick = {
|
||||||
|
message.value = String.format(
|
||||||
|
deleteRuleMessage,
|
||||||
|
pattern
|
||||||
|
)
|
||||||
|
lastAction.value = {
|
||||||
|
BaresipService.blockRules.remove(rule)
|
||||||
|
BlockRule.save()
|
||||||
|
onRuleDeleted()
|
||||||
|
}
|
||||||
|
showDialog.value = true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Remove,
|
||||||
|
contentDescription = stringResource(R.string.delete),
|
||||||
|
tint = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier.size(32.dp).scale(scaleX = 1f, scaleY = 0.5f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NewRule(onRuleAdded: () -> Unit) {
|
||||||
|
val alertTitle = remember { mutableStateOf("") }
|
||||||
|
val alertMessage = remember { mutableStateOf("") }
|
||||||
|
val showAlert = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (showAlert.value)
|
||||||
|
AlertDialog(
|
||||||
|
showDialog = showAlert,
|
||||||
|
title = alertTitle.value,
|
||||||
|
message = alertMessage.value,
|
||||||
|
lastButtonText = stringResource(R.string.ok),
|
||||||
|
)
|
||||||
|
|
||||||
|
var pattern by remember { mutableStateOf("") }
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.padding(start = 16.dp, end = 8.dp, top = 10.dp, bottom = 16.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
val ruleHelp = stringResource(R.string.blocking_rule_help)
|
||||||
|
val newRuleTitle = stringResource(R.string.new_blocking_rule)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = pattern,
|
||||||
|
placeholder = { Text(text = stringResource(R.string.new_blocking_rule)) },
|
||||||
|
onValueChange = { pattern = it },
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
.clickable {
|
||||||
|
alertTitle.value = newRuleTitle
|
||||||
|
alertMessage.value = ruleHelp
|
||||||
|
showAlert.value = true
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
trailingIcon = {
|
||||||
|
if (pattern.isNotEmpty()) {
|
||||||
|
Icon(
|
||||||
|
Icons.Outlined.Clear,
|
||||||
|
contentDescription = "Clear",
|
||||||
|
modifier = Modifier.clickable { pattern = "" },
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = { Text(stringResource(R.string.new_blocking_rule)) },
|
||||||
|
textStyle = TextStyle(fontSize = 18.sp),
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
||||||
|
)
|
||||||
|
SmallFloatingActionButton(
|
||||||
|
modifier = Modifier.offset(y = 2.dp),
|
||||||
|
onClick = {
|
||||||
|
if (pattern.trim().isNotEmpty()) {
|
||||||
|
if (!BlockRule.exists(pattern.trim())) {
|
||||||
|
BaresipService.blockRules.add(BlockRule(pattern.trim()))
|
||||||
|
BlockRule.save()
|
||||||
|
onRuleAdded()
|
||||||
|
}
|
||||||
|
pattern = ""
|
||||||
|
focusManager.clearFocus()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
containerColor = MaterialTheme.colorScheme.secondary,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSecondary
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Add,
|
||||||
|
modifier = Modifier.size(36.dp),
|
||||||
|
contentDescription = stringResource(R.string.add)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user