diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 312b58b..fed4743 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -77,6 +77,7 @@ dependencies {
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.accompanist.drawablepainter)
+ implementation(libs.accompanist.permissions)
implementation(libs.androidx.material3)
implementation(libs.androidx.navigation.compose)
implementation(libs.shizuku.provider)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 99b7dcf..5ca5af9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -20,6 +20,8 @@
+
+
= 24 && (deviceOwner || dpm.isOrgProfile(receiver))) {
- FunctionItem(R.string.wifi_mac_address, "", R.drawable.wifi_fill0) { wifiMacDialog = true }
- }
+ if(!dhizuku) FunctionItem(R.string.wifi, "", R.drawable.wifi_fill0) { navCtrl.navigate("Wifi") }
if(VERSION.SDK_INT >= 30) {
FunctionItem(R.string.options, "", R.drawable.tune_fill0) { navCtrl.navigate("NetworkOptions") }
}
- FunctionItem(R.string.add_wifi, "", R.drawable.wifi_add_fill0) { navCtrl.navigate("AddWifi") }
- if(VERSION.SDK_INT >= 33 && (deviceOwner || dpm.isOrgProfile(receiver))) {
- FunctionItem(R.string.min_wifi_security_level, "", R.drawable.wifi_password_fill0) { navCtrl.navigate("MinWifiSecurityLevel") }
- }
- if(VERSION.SDK_INT >= 33 && (deviceOwner || dpm.isOrgProfile(receiver))) {
- FunctionItem(R.string.wifi_ssid_policy, "", R.drawable.wifi_fill0) { navCtrl.navigate("WifiSsidPolicy") }
- }
if(VERSION.SDK_INT >= 29 && deviceOwner) {
FunctionItem(R.string.private_dns, "", R.drawable.dns_fill0) { navCtrl.navigate("PrivateDNS") }
}
- if(VERSION.SDK_INT >= 24 && (deviceOwner || profileOwner)) {
+ if(VERSION.SDK_INT >= 24) {
FunctionItem(R.string.always_on_vpn, "", R.drawable.vpn_key_fill0) { navCtrl.navigate("AlwaysOnVpn") }
}
if(deviceOwner) {
@@ -157,39 +173,16 @@ fun Network(navCtrl:NavHostController) {
if(VERSION.SDK_INT >= 26 && !dhizuku && (deviceOwner || (profileOwner && dpm.isManagedProfile(receiver)))) {
FunctionItem(R.string.network_logging, "", R.drawable.description_fill0) { navCtrl.navigate("NetworkLog") }
}
- if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) {
+ if(VERSION.SDK_INT >= 31) {
FunctionItem(R.string.wifi_auth_keypair, "", R.drawable.key_fill0) { navCtrl.navigate("WifiAuthKeypair") }
}
- if(VERSION.SDK_INT >= 33 && (deviceOwner || profileOwner)) {
+ if(VERSION.SDK_INT >= 33) {
FunctionItem(R.string.preferential_network_service, "", R.drawable.globe_fill0) { navCtrl.navigate("PreferentialNetworkService") }
}
if(VERSION.SDK_INT >= 28 && deviceOwner) {
FunctionItem(R.string.override_apn_settings, "", R.drawable.cell_tower_fill0) { navCtrl.navigate("OverrideAPN") }
}
}
- if(wifiMacDialog && VERSION.SDK_INT >= 24) {
- val context = LocalContext.current
- val dpm = context.getDPM()
- val receiver = context.getReceiver()
- AlertDialog(
- onDismissRequest = { wifiMacDialog = false },
- confirmButton = { TextButton(onClick = { wifiMacDialog = false }) { Text(stringResource(R.string.confirm)) } },
- title = { Text(stringResource(R.string.wifi_mac_address)) },
- text = {
- val mac = dpm.getWifiMacAddress(receiver)
- OutlinedTextField(
- value = mac ?: stringResource(R.string.none),
- onValueChange = {}, readOnly = true, modifier = Modifier.fillMaxWidth(), textStyle = typography.bodyLarge,
- trailingIcon = {
- if(mac != null) IconButton(onClick = { writeClipBoard(context, mac) }) {
- Icon(painter = painterResource(R.drawable.content_copy_fill0), contentDescription = stringResource(R.string.copy))
- }
- }
- )
- },
- modifier = Modifier.fillMaxWidth()
- )
- }
}
@Composable
@@ -216,16 +209,265 @@ fun NetworkOptions(navCtrl: NavHostController) {
)
}
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun Wifi(navCtrl: NavHostController) {
+ val context = LocalContext.current
+ val coroutine = rememberCoroutineScope()
+ val pagerState = rememberPagerState { 3 }
+ var tabIndex by rememberSaveable { mutableIntStateOf(0) }
+ tabIndex = pagerState.currentPage
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text(stringResource(R.string.wifi)) },
+ navigationIcon = { NavIcon { navCtrl.navigateUp() } }
+ )
+ }
+ ) { paddingValues ->
+ var wifiMacDialog by remember { mutableStateOf(false) }
+ Column(
+ modifier = Modifier.fillMaxSize().padding(paddingValues)
+ ) {
+ TabRow(tabIndex) {
+ Tab(
+ selected = tabIndex == 0, onClick = { tabIndex = 0; coroutine.launch { pagerState.animateScrollToPage(tabIndex) } },
+ text = { Text(stringResource(R.string.overview)) }
+ )
+ Tab(
+ selected = tabIndex == 1, onClick = { tabIndex = 1; coroutine.launch { pagerState.animateScrollToPage(tabIndex) } },
+ text = { Text(stringResource(R.string.saved_networks)) }
+ )
+ Tab(
+ selected = tabIndex == 2, onClick = { tabIndex = 2; coroutine.launch { pagerState.animateScrollToPage(tabIndex) } },
+ text = { Text(stringResource(R.string.add_network)) }
+ )
+ }
+ HorizontalPager(state = pagerState, verticalAlignment = Alignment.Top) { page ->
+ if(page == 0) {
+ val wm = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
+ val deviceOwner = context.isDeviceOwner
+ val orgProfileOwner = context.getDPM().isOrgProfile(context.getReceiver())
+ @Suppress("DEPRECATION") Column(
+ modifier = Modifier.fillMaxSize().padding(top = 12.dp)
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.Center,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Button(
+ onClick = { context.showOperationResultToast(wm.setWifiEnabled(true)) },
+ modifier = Modifier.padding(end = 8.dp)
+ ) {
+ Text(stringResource(R.string.enable))
+ }
+ Button(onClick = { context.showOperationResultToast(wm.setWifiEnabled(false)) }) {
+ Text(stringResource(R.string.disable))
+ }
+ }
+ Row(
+ horizontalArrangement = Arrangement.Center,
+ modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)
+ ) {
+ Button(
+ onClick = { context.showOperationResultToast(wm.disconnect()) },
+ modifier = Modifier.padding(end = 8.dp)
+ ) {
+ Text(stringResource(R.string.disconnect))
+ }
+ Button(onClick = { context.showOperationResultToast(wm.reconnect()) }) {
+ Text(stringResource(R.string.reconnect))
+ }
+ }
+ if(VERSION.SDK_INT >= 24 && (deviceOwner || orgProfileOwner)) {
+ FunctionItem(R.string.wifi_mac_address, "", null) { wifiMacDialog = true }
+ }
+ if(VERSION.SDK_INT >= 33 && (deviceOwner || orgProfileOwner)) {
+ FunctionItem(R.string.min_wifi_security_level, "", null) { navCtrl.navigate("MinWifiSecurityLevel") }
+ FunctionItem(R.string.wifi_ssid_policy, "", null) { navCtrl.navigate("WifiSsidPolicy") }
+ }
+ }
+ } else if(page == 1) {
+ SavedNetworks(navCtrl)
+ } else {
+ AddNetwork()
+ }
+ }
+ }
+ if(wifiMacDialog && VERSION.SDK_INT >= 24) {
+ val context = LocalContext.current
+ val dpm = context.getDPM()
+ val receiver = context.getReceiver()
+ AlertDialog(
+ onDismissRequest = { wifiMacDialog = false },
+ confirmButton = { TextButton(onClick = { wifiMacDialog = false }) { Text(stringResource(R.string.confirm)) } },
+ text = {
+ val mac = dpm.getWifiMacAddress(receiver)
+ OutlinedTextField(
+ value = mac ?: stringResource(R.string.none), label = { Text(stringResource(R.string.wifi_mac_address)) },
+ onValueChange = {}, readOnly = true, modifier = Modifier.fillMaxWidth(), textStyle = typography.bodyLarge,
+ trailingIcon = {
+ if(mac != null) IconButton(onClick = { writeClipBoard(context, mac) }) {
+ Icon(painter = painterResource(R.drawable.content_copy_fill0), contentDescription = stringResource(R.string.copy))
+ }
+ }
+ )
+ },
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+ }
+}
+
+@Suppress("DEPRECATION")
+@OptIn(ExperimentalPermissionsApi::class)
+@Composable
+private fun SavedNetworks(navCtrl: NavHostController) {
+ val context = LocalContext.current
+ val wm = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
+ val configuredNetworks = remember { mutableStateListOf() }
+ var networkDetailsDialog by remember { mutableIntStateOf(-1) } // -1:Hidden, 0+:Index of configuredNetworks
+ fun refresh() {
+ configuredNetworks.clear()
+ wm.configuredNetworks.forEach { network ->
+ if(configuredNetworks.none { it.networkId == network.networkId }) configuredNetworks += network
+ }
+ }
+ LaunchedEffect(Unit) { refresh() }
+ Column(
+ modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(start = 8.dp, end = 8.dp, bottom = 60.dp)
+ ) {
+ val locationPermission = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
+ val requestPermissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) {
+ if(it) refresh()
+ }
+ if(!locationPermission.status.isGranted) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(12.dp)
+ .clip(RoundedCornerShape(15))
+ .background(MaterialTheme.colorScheme.primaryContainer)
+ .clickable { requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) }
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.LocationOn, contentDescription = null,
+ tint = MaterialTheme.colorScheme.onPrimaryContainer,
+ modifier = Modifier.padding(start = 8.dp, end = 4.dp))
+ Text(
+ text = stringResource(R.string.request_location_permission_description),
+ color = MaterialTheme.colorScheme.onPrimaryContainer,
+ modifier = Modifier.padding(8.dp)
+ )
+ }
+ }
+ configuredNetworks.forEachIndexed { index, network ->
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth().padding(start = 8.dp, top = 8.dp)
+ ) {
+ Text(text = network.SSID.removeSurrounding("\""), style = typography.titleLarge)
+ IconButton(onClick = { networkDetailsDialog = index }) {
+ Icon(painter = painterResource(R.drawable.more_horiz_fill0), contentDescription = null)
+ }
+ }
+ }
+ }
+ if(networkDetailsDialog != -1) AlertDialog(
+ text = {
+ val network = configuredNetworks[networkDetailsDialog]
+ val statusText = when(network.status) {
+ WifiConfiguration.Status.CURRENT -> R.string.current
+ WifiConfiguration.Status.DISABLED -> R.string.disabled
+ WifiConfiguration.Status.ENABLED -> R.string.enabled
+ else -> R.string.place_holder
+ }
+ Column {
+ Text(stringResource(R.string.network_id) + ": " + network.networkId.toString())
+ SelectionContainer {
+ Text("SSID: " + network.SSID)
+ if(network.BSSID != null) Text("BSSID: " + network.BSSID)
+ }
+ Text(stringResource(R.string.status) + ": " + stringResource(statusText))
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth().padding(top = 12.dp)
+ ) {
+ Button(
+ onClick = {
+ val success = wm.enableNetwork(network.networkId, false)
+ Toast.makeText(context, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
+ networkDetailsDialog = -1
+ refresh()
+ },
+ modifier = Modifier.fillMaxWidth(0.49F)
+ ) {
+ Text(stringResource(R.string.enable))
+ }
+ Button(
+ onClick = {
+ val success = wm.disableNetwork(network.networkId)
+ Toast.makeText(context, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
+ networkDetailsDialog = -1
+ refresh()
+ },
+ modifier = Modifier.fillMaxWidth(0.96F)
+ ) {
+ Text(stringResource(R.string.disable))
+ }
+ }
+ Button(
+ onClick = {
+ networkDetailsDialog = -1
+ val dest = navCtrl.graph.findNode("UpdateNetwork")
+ if(dest != null)
+ navCtrl.navigate(dest.id, bundleOf("wifi_configuration" to network))
+ },
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text(stringResource(R.string.edit))
+ }
+ TextButton(
+ onClick = {
+ val success = wm.removeNetwork(network.networkId)
+ Toast.makeText(context, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
+ networkDetailsDialog = -1
+ refresh()
+ },
+ colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error),
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text(stringResource(R.string.remove))
+ }
+ }
+ },
+ confirmButton = {
+ TextButton(onClick = { networkDetailsDialog = -1 }) {
+ Text(stringResource(R.string.confirm))
+ }
+ },
+ onDismissRequest = { networkDetailsDialog = -1 }
+ )
+}
+
+@Composable
+fun UpdateNetwork(arguments: Bundle, navCtrl: NavHostController) {
+ MyScaffold(R.string.update_network, 0.dp, navCtrl, false) {
+ AddNetwork(arguments.getParcelable("wifi_configuration"), navCtrl)
+ }
+}
+
@Suppress("DEPRECATION")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun AddNetwork(navCtrl: NavHostController) {
+private fun AddNetwork(wifiConfig: WifiConfiguration? = null, navCtrl: NavHostController? = null) {
val context = LocalContext.current
var resultDialog by remember { mutableStateOf(false) }
var createdNetworkId by remember { mutableIntStateOf(-1) }
- var createNetworkResult by remember {mutableIntStateOf(0)}
+ var createNetworkResult by remember { mutableIntStateOf(0) }
var dropdownMenu by remember { mutableIntStateOf(0) } // 0: None, 1:Status, 2:Security, 3:MAC randomization, 4:Static IP, 5:Proxy
- var networkId by remember { mutableStateOf("") }
var status by remember { mutableIntStateOf(WifiConfiguration.Status.ENABLED) }
var ssid by remember { mutableStateOf("") }
var hiddenSsid by remember { mutableStateOf(false) }
@@ -240,16 +482,19 @@ fun AddNetwork(navCtrl: NavHostController) {
var httpProxyHost by remember { mutableStateOf("") }
var httpProxyPort by remember { mutableStateOf("") }
var httpProxyExclList by remember { mutableStateOf("") }
- MyScaffold(R.string.add_wifi, 8.dp, navCtrl) {
- OutlinedTextField(
- value = networkId, onValueChange = { networkId = it }, label = { Text(stringResource(R.string.network_id)) },
- keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
- isError = networkId != "" && (try { networkId.toInt(); false } catch(_: NumberFormatException) { true }),
- modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)
- )
+ LaunchedEffect(Unit) {
+ if(wifiConfig != null) {
+ status = wifiConfig.status
+ if(wifiConfig.status == WifiConfiguration.Status.CURRENT) status = WifiConfiguration.Status.ENABLED
+ ssid = wifiConfig.SSID.removeSurrounding("\"")
+ }
+ }
+ Column(
+ modifier = (if(wifiConfig == null) Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(bottom = 60.dp) else Modifier)
+ .padding(start = 8.dp, end = 8.dp, top = 12.dp)
+ ) {
ExposedDropdownMenuBox(dropdownMenu == 1, { dropdownMenu = if(it) 1 else 0 }) {
val statusText = when(status) {
- WifiConfiguration.Status.CURRENT -> R.string.current
WifiConfiguration.Status.DISABLED -> R.string.disabled
WifiConfiguration.Status.ENABLED -> R.string.enabled
else -> R.string.place_holder
@@ -261,13 +506,6 @@ fun AddNetwork(navCtrl: NavHostController) {
modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth().padding(bottom = 16.dp)
)
ExposedDropdownMenu(dropdownMenu == 1, { dropdownMenu = 0 }) {
- DropdownMenuItem(
- text = { Text(stringResource(R.string.current)) },
- onClick = {
- status = WifiConfiguration.Status.CURRENT
- dropdownMenu = 0
- }
- )
DropdownMenuItem(
text = { Text(stringResource(R.string.disabled)) },
onClick = {
@@ -413,7 +651,6 @@ fun AddNetwork(navCtrl: NavHostController) {
val wm = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
try {
val config = WifiConfiguration()
- if(networkId != "") config.networkId = networkId.toInt()
config.status = status
config.SSID = '"' + ssid + '"'
config.hiddenSSID = hiddenSsid
@@ -435,12 +672,17 @@ fun AddNetwork(navCtrl: NavHostController) {
if(VERSION.SDK_INT >= 26 && useHttpProxy) {
config.httpProxy = ProxyInfo.buildDirectProxy(httpProxyHost, httpProxyPort.toInt(), httpProxyExclList.lines())
}
- if(VERSION.SDK_INT >= 31) {
- val result = wm.addNetworkPrivileged(config)
- createdNetworkId = result.networkId
- createNetworkResult = result.statusCode
+ if(wifiConfig != null) {
+ config.networkId = wifiConfig.networkId
+ createdNetworkId = wm.updateNetwork(config)
} else {
- createdNetworkId = wm.addNetwork(config)
+ if(VERSION.SDK_INT >= 31) {
+ val result = wm.addNetworkPrivileged(config)
+ createdNetworkId = result.networkId
+ createNetworkResult = result.statusCode
+ } else {
+ createdNetworkId = wm.addNetwork(config)
+ }
}
resultDialog = true
} catch(e: Exception) {
@@ -454,7 +696,7 @@ fun AddNetwork(navCtrl: NavHostController) {
},
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)
) {
- Text(stringResource(R.string.add))
+ Text(stringResource(if(wifiConfig != null) R.string.update else R.string.add))
}
if(resultDialog) AlertDialog(
text = {
@@ -467,7 +709,12 @@ fun AddNetwork(navCtrl: NavHostController) {
Text(stringResource(statusText) + "\n" + stringResource(R.string.network_id) + ": " + createdNetworkId)
},
confirmButton = {
- TextButton(onClick = { resultDialog = false }) {
+ TextButton(
+ onClick = {
+ resultDialog = false
+ if(createdNetworkId != -1) navCtrl?.navigateUp()
+ }
+ ) {
Text(stringResource(R.string.confirm))
}
},
diff --git a/app/src/main/res/drawable/wifi_add_fill0.xml b/app/src/main/res/drawable/wifi_add_fill0.xml
deleted file mode 100644
index a815f40..0000000
--- a/app/src/main/res/drawable/wifi_add_fill0.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index b8e39f2..376bc56 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -217,7 +217,12 @@
Сеть
MAC-адрес Wi-Fi
- Add Wi-Fi
+ Disconnect
+ Reconnect
+ Saved networks
+ This app need location permission to get saved networks, your geographic location will not be read.\nClick to request the permission.
+ Add network
+ Update network
Current
Hidden SSID
IP settings
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 0006a03..f376da7 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -218,7 +218,12 @@
Ağ
Wi-Fi MAC adresi
- Add Wi-Fi
+ Disconnect
+ Reconnect
+ Saved networks
+ This app need location permission to get saved networks, your geographic location will not be read.\nClick to request the permission.
+ Add network
+ Update network
Current
Hidden SSID
IP settings
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 6f6804e..e2085f3 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -210,7 +210,12 @@
网络
Wi-Fi MAC地址
- 添加Wi-Fi
+ 断开连接
+ 重新连接
+ 已保存的网络
+ 此app需要定位权限以获取已保存的网络,你的地理位置将不会被读取。点击以请求权限
+ 添加网络
+ 更新网络
当前
隐藏的SSID
IP设置
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c8e4020..1d9ab20 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -67,6 +67,8 @@
API
Error
Status
+ Edit
+ Overview
Click to activate
@@ -221,8 +223,14 @@
Network
- Wi-Fi Mac address
- Add Wi-Fi
+ Wi-Fi MAC address
+ Wi-Fi
+ Disconnect
+ Reconnect
+ Saved networks
+ This app need location permission to get saved networks, your geographic location will not be read.\nClick to request the permission.
+ Add network
+ Update network
Current
Hidden SSID
IP settings
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 05db652..0728b0e 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -5,6 +5,7 @@ kotlin = "2.0.21"
navigation-compose = "2.8.5"
composeBom = "2024.12.01"
accompanist-drawablepainter = "0.35.0-alpha"
+accompanist-permissions = "0.37.0"
shizuku = "13.1.5"
biometric = "1.2.0-alpha05"
fragment = "1.8.5"
@@ -19,6 +20,7 @@ androidx-material3 = { module = "androidx.compose.material3:material3" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" }
accompanist-drawablepainter = { module = "com.google.accompanist:accompanist-drawablepainter", version.ref = "accompanist-drawablepainter" }
+accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanist-permissions" }
androidx-biometric = { group = "androidx.biometric", name = "biometric", version.ref = "biometric" }
shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizuku" }