|
@@ -0,0 +1,402 @@
|
|
|
|
+package com.vpn.fastestvpnservice.screensTV
|
|
|
|
+
|
|
|
|
+import android.Manifest
|
|
|
|
+import android.app.Activity
|
|
|
|
+import android.content.Context
|
|
|
|
+import android.content.pm.ApplicationInfo
|
|
|
|
+import android.content.pm.PackageManager
|
|
|
|
+import android.util.Log
|
|
|
|
+import android.widget.Toast
|
|
|
|
+import androidx.compose.foundation.background
|
|
|
|
+import androidx.compose.foundation.clickable
|
|
|
|
+import androidx.compose.foundation.focusable
|
|
|
|
+import androidx.compose.foundation.layout.Arrangement
|
|
|
|
+import androidx.compose.foundation.layout.Box
|
|
|
|
+import androidx.compose.foundation.layout.BoxScope
|
|
|
|
+import androidx.compose.foundation.layout.Column
|
|
|
|
+import androidx.compose.foundation.layout.ColumnScope
|
|
|
|
+import androidx.compose.foundation.layout.Row
|
|
|
|
+import androidx.compose.foundation.layout.Spacer
|
|
|
|
+import androidx.compose.foundation.layout.fillMaxHeight
|
|
|
|
+import androidx.compose.foundation.layout.fillMaxSize
|
|
|
|
+import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
|
+import androidx.compose.foundation.layout.height
|
|
|
|
+import androidx.compose.foundation.layout.padding
|
|
|
|
+import androidx.compose.foundation.layout.size
|
|
|
|
+import androidx.compose.foundation.lazy.LazyColumn
|
|
|
|
+import androidx.compose.foundation.lazy.itemsIndexed
|
|
|
|
+import androidx.compose.foundation.selection.selectable
|
|
|
|
+import androidx.compose.material.Icon
|
|
|
|
+import androidx.compose.material.IconButton
|
|
|
|
+import androidx.compose.material.Surface
|
|
|
|
+import androidx.compose.material.Text
|
|
|
|
+import androidx.compose.material3.MaterialTheme
|
|
|
|
+import androidx.compose.material3.RadioButton
|
|
|
|
+import androidx.compose.material3.RadioButtonDefaults
|
|
|
|
+import androidx.compose.runtime.Composable
|
|
|
|
+import androidx.compose.runtime.LaunchedEffect
|
|
|
|
+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.alpha
|
|
|
|
+import androidx.compose.ui.focus.FocusRequester
|
|
|
|
+import androidx.compose.ui.focus.focusRequester
|
|
|
|
+import androidx.compose.ui.focus.onFocusChanged
|
|
|
|
+import androidx.compose.ui.graphics.Color
|
|
|
|
+import androidx.compose.ui.graphics.toArgb
|
|
|
|
+import androidx.compose.ui.platform.LocalContext
|
|
|
|
+import androidx.compose.ui.platform.LocalView
|
|
|
|
+import androidx.compose.ui.res.colorResource
|
|
|
|
+import androidx.compose.ui.res.painterResource
|
|
|
|
+import androidx.compose.ui.unit.dp
|
|
|
|
+import androidx.navigation.NavHostController
|
|
|
|
+import com.google.gson.Gson
|
|
|
|
+import com.google.gson.reflect.TypeToken
|
|
|
|
+import com.vpn.fastestvpnservice.R
|
|
|
|
+import com.vpn.fastestvpnservice.beans.SelectApplicationEntry
|
|
|
|
+import com.vpn.fastestvpnservice.constants.splitList
|
|
|
|
+import com.vpn.fastestvpnservice.customItems.SplitTunnelingItem
|
|
|
|
+import com.vpn.fastestvpnservice.helpers.BasePreferenceHelper
|
|
|
|
+import com.vpn.fastestvpnservice.screens.settingsScreenAll.getInstalledApps
|
|
|
|
+import java.util.SortedSet
|
|
|
|
+import java.util.TreeSet
|
|
|
|
+
|
|
|
|
+@Composable
|
|
|
|
+fun SplitTunnelingTV(navHostController: NavHostController) {
|
|
|
|
+ Box(
|
|
|
|
+ modifier = Modifier
|
|
|
|
+ .fillMaxSize()
|
|
|
|
+ .background(colorResource(id = R.color.background_color_gray))
|
|
|
|
+ ) {
|
|
|
|
+ val view = LocalView.current
|
|
|
|
+ val window = (view.context as Activity).window
|
|
|
|
+ window.statusBarColor = Color.Transparent.toArgb()
|
|
|
|
+ window.navigationBarColor = Color.Transparent.toArgb()
|
|
|
|
+
|
|
|
|
+ HeaderRowSTTV(navHostController = navHostController)
|
|
|
|
+
|
|
|
|
+ Column(
|
|
|
|
+ verticalArrangement = Arrangement.Top,
|
|
|
|
+ horizontalAlignment = Alignment.Start,
|
|
|
|
+ modifier = Modifier
|
|
|
|
+ .padding(top = 60.dp, start = 0.dp, end = 0.dp)
|
|
|
|
+ .fillMaxSize()
|
|
|
|
+ .background(Color.Transparent)
|
|
|
|
+ ) {
|
|
|
|
+ var isExpanded by remember { mutableStateOf(true) }
|
|
|
|
+ val icon = if (isExpanded) R.drawable.dragarrow3x
|
|
|
|
+ else R.drawable.downarrow3x
|
|
|
|
+// val rotationState by animateFloatAsState(
|
|
|
|
+// targetValue = if (isExpanded) 180f else 0f,
|
|
|
|
+// label = if (isExpanded) "Show Less" else "Show More")
|
|
|
|
+
|
|
|
|
+ Row(
|
|
|
|
+ horizontalArrangement = Arrangement.SpaceBetween,
|
|
|
|
+ verticalAlignment = Alignment.CenterVertically,
|
|
|
|
+ modifier = Modifier
|
|
|
|
+ .padding(start = 16.dp, end = 28.dp)
|
|
|
|
+ .fillMaxWidth()
|
|
|
|
+ ) {
|
|
|
|
+ Text(text = "While VPN is Connected",
|
|
|
|
+ style = MaterialTheme.typography.titleSmall,
|
|
|
|
+ color = colorResource(id = R.color.dark_blue_gray_text)
|
|
|
|
+ )
|
|
|
|
+// IconButton(onClick = {
|
|
|
|
+// isExpanded = !isExpanded
|
|
|
|
+// },
|
|
|
|
+// modifier = Modifier
|
|
|
|
+// .background(Color.Transparent)
|
|
|
|
+//// .rotate(rotationState)
|
|
|
|
+// .size(12.dp, 7.dp)
|
|
|
|
+// ) {
|
|
|
|
+// Icon(
|
|
|
|
+// painter = painterResource(
|
|
|
|
+// id = icon),
|
|
|
|
+// contentDescription = "Arrow-DropDown",
|
|
|
|
+// tint = MaterialTheme.colorScheme.primary
|
|
|
|
+// )
|
|
|
|
+//
|
|
|
|
+// }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+// if (isExpanded) { }
|
|
|
|
+ ShowRadioButtonsTV()
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@Composable
|
|
|
|
+fun BoxScope.HeaderRowSTTV(navHostController: NavHostController) {
|
|
|
|
+ val focusRequester1 = remember { FocusRequester() }
|
|
|
|
+ var isButtonFocused by remember { mutableStateOf(false) }
|
|
|
|
+// LaunchedEffect(key1 = Unit) {
|
|
|
|
+// focusRequester1.requestFocus()
|
|
|
|
+// }
|
|
|
|
+// IconButton(
|
|
|
|
+// onClick = {
|
|
|
|
+// navHostController.popBackStack()
|
|
|
|
+//// navHostController.navigate(BottomBarScreen.Help.route)
|
|
|
|
+// },
|
|
|
|
+// modifier = Modifier
|
|
|
|
+// .align(Alignment.TopStart)
|
|
|
|
+// .padding(top = 50.dp)
|
|
|
|
+// .padding(start = 16.dp)
|
|
|
|
+// .size(30.dp, 32.dp)
|
|
|
|
+// .background(if (isButtonFocused) Color.LightGray else Color.Transparent)
|
|
|
|
+// .focusRequester(focusRequester1)
|
|
|
|
+// .onFocusChanged {
|
|
|
|
+// isButtonFocused = it.isFocused
|
|
|
|
+// }
|
|
|
|
+// .focusable()
|
|
|
|
+// .clickable {
|
|
|
|
+// navHostController.popBackStack()
|
|
|
|
+// }
|
|
|
|
+// ) {
|
|
|
|
+// Icon(
|
|
|
|
+// painter = painterResource(id = R.drawable.backarrow3x),
|
|
|
|
+// contentDescription = "Arrow-Back",
|
|
|
|
+// tint = colorResource(id = R.color.dark_blue_gray_text),
|
|
|
|
+// modifier = Modifier.size(18.dp, 12.dp)
|
|
|
|
+// )
|
|
|
|
+// }
|
|
|
|
+ Surface(
|
|
|
|
+ modifier = Modifier
|
|
|
|
+ .padding(start = 14.dp, top = 16.dp)
|
|
|
|
+ .height(32.dp)
|
|
|
|
+ .align(Alignment.TopStart)
|
|
|
|
+// .padding(5.dp),
|
|
|
|
+ ,
|
|
|
|
+ color = colorResource(id = R.color.transparent)
|
|
|
|
+ ) {
|
|
|
|
+ Text(text = "Split Tunneling",
|
|
|
|
+ color = colorResource(id = R.color.dark_blue_gray_text),
|
|
|
|
+ style = MaterialTheme.typography.titleMedium,
|
|
|
|
+ modifier = Modifier.fillMaxHeight()
|
|
|
|
+
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@Composable
|
|
|
|
+fun ColumnScope.ShowSplitTunnelListTV(list: List<SelectApplicationEntry>) {
|
|
|
|
+
|
|
|
|
+ Box(modifier = Modifier
|
|
|
|
+ .padding(top = 5.dp)
|
|
|
|
+ .fillMaxSize()
|
|
|
|
+ .background(Color.Transparent)
|
|
|
|
+ ) {
|
|
|
|
+ LazyColumn() {
|
|
|
|
+ itemsIndexed(items = list) {position, app ->
|
|
|
|
+ SplitTunnelingItem(app, list, position)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@Composable
|
|
|
|
+fun ColumnScope.ShowRadioButtonsTV() {
|
|
|
|
+ Log.d("test_split_compose", "ShowRadioButtons()")
|
|
|
|
+ val context = LocalContext.current
|
|
|
|
+ val prefHelper = BasePreferenceHelper(context = context)
|
|
|
|
+ var apps = ArrayList<SelectApplicationEntry>()
|
|
|
|
+
|
|
|
|
+ var selectedList by remember { mutableStateOf(prefHelper.getSplitPosition()) }
|
|
|
|
+ var focusedList by remember { mutableStateOf(splitList[0]) }
|
|
|
|
+ var isButtonFocused by remember { mutableStateOf(false) }
|
|
|
|
+ var isFocusedAnyItem by remember { mutableStateOf(false) }
|
|
|
|
+
|
|
|
|
+ Spacer(modifier = Modifier.height(10.dp))
|
|
|
|
+
|
|
|
|
+ splitList.forEachIndexed { index, list ->
|
|
|
|
+ isButtonFocused = focusedList == list
|
|
|
|
+ Row(
|
|
|
|
+ modifier = Modifier
|
|
|
|
+ .fillMaxWidth()
|
|
|
|
+ .background(if (isButtonFocused && isFocusedAnyItem) Color.LightGray else Color.Transparent)
|
|
|
|
+ .padding(top = 0.dp)
|
|
|
|
+ .onFocusChanged {
|
|
|
|
+ if (it.isFocused) {
|
|
|
|
+ focusedList = list
|
|
|
|
+ }
|
|
|
|
+ isFocusedAnyItem = it.isFocused
|
|
|
|
+ }
|
|
|
|
+ .focusable()
|
|
|
|
+ .selectable(
|
|
|
|
+ selected = selectedList == list,
|
|
|
|
+ onClick = {
|
|
|
|
+ selectedList = list
|
|
|
|
+ prefHelper.saveSplitPosition(list)
|
|
|
|
+ apps = getInstalledAppsTV(context)
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+ )
|
|
|
|
+// .indication(
|
|
|
|
+// indication = null,
|
|
|
|
+// interactionSource = remember {
|
|
|
|
+// MutableInteractionSource()
|
|
|
|
+// }
|
|
|
|
+// )
|
|
|
|
+ ,
|
|
|
|
+ verticalAlignment = Alignment.CenterVertically
|
|
|
|
+ ) {
|
|
|
|
+ RadioButton(
|
|
|
|
+ modifier = Modifier,
|
|
|
|
+ selected = list == selectedList,
|
|
|
|
+ onClick = {
|
|
|
|
+ selectedList = list
|
|
|
|
+ prefHelper.saveSplitPosition(list)
|
|
|
|
+ apps = getInstalledAppsTV(context)
|
|
|
|
+ },
|
|
|
|
+ colors = RadioButtonDefaults.colors(
|
|
|
|
+ selectedColor = colorResource(id = R.color.radio_button_blue),
|
|
|
|
+ unselectedColor = colorResource(id = R.color.gray_icon)
|
|
|
|
+ )
|
|
|
|
+ )
|
|
|
|
+ Text(
|
|
|
|
+ text = list,
|
|
|
|
+ modifier = Modifier.padding(start = 12.dp),
|
|
|
|
+ style = MaterialTheme.typography.bodySmall,
|
|
|
|
+ color = colorResource(id = R.color.dark_blue_gray_text)
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Surface(
|
|
|
|
+ modifier = Modifier
|
|
|
|
+ .padding(start = 16.dp, end = 16.dp, top = 15.dp)
|
|
|
|
+ .height(1.dp)
|
|
|
|
+ .fillMaxWidth()
|
|
|
|
+ .alpha(0.6F)
|
|
|
|
+ ,
|
|
|
|
+ color = colorResource(id = R.color.gray_icon)
|
|
|
|
+ ) {}
|
|
|
|
+
|
|
|
|
+ apps = getInstalledApps(context)
|
|
|
|
+ Log.d("test_split_compose", "apps called : ${apps.size}")
|
|
|
|
+
|
|
|
|
+ val type = object : TypeToken<SortedSet<String?>?>() {}.type
|
|
|
|
+ val appsAllow = Gson().fromJson<SortedSet<String>>(
|
|
|
|
+ prefHelper.getSplitTunneledApps(), type
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ appsAllow?.let {
|
|
|
|
+ for (app in appsAllow) {
|
|
|
|
+ Log.d("test_split_compose", "apps allow : $app")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (selectedList == splitList[1]) {
|
|
|
|
+ ShowSplitTunnelListTV(apps)
|
|
|
|
+ }
|
|
|
|
+ else if (selectedList == splitList[2]) {
|
|
|
|
+ ShowSplitTunnelListTV(apps)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+fun getInstalledAppsTV(context: Context): ArrayList<SelectApplicationEntry> {
|
|
|
|
+ val prefHelper = BasePreferenceHelper(context)
|
|
|
|
+ val apps: ArrayList<SelectApplicationEntry> = ArrayList()
|
|
|
|
+// val apps2: ArrayList<SelectApplicationEntry> = ArrayList()
|
|
|
|
+ val seen: SortedSet<String> = TreeSet()
|
|
|
|
+ var selectedAppsList = ArrayList<String>()
|
|
|
|
+ var selectedAppsListNoVpn = ArrayList<String>()
|
|
|
|
+
|
|
|
|
+ if (!prefHelper.getSplitTunneledApps().equals("")) {
|
|
|
|
+ val type = object : TypeToken<ArrayList<String>>() {}.type
|
|
|
|
+ selectedAppsList = Gson().fromJson(prefHelper.getSplitTunneledApps(), type)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!prefHelper.getSplitTunneledAppsNoVpn().equals("")) {
|
|
|
|
+ val type = object : TypeToken<ArrayList<String>>() {}.type
|
|
|
|
+ selectedAppsListNoVpn = Gson().fromJson(prefHelper.getSplitTunneledAppsNoVpn(), type)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ val mPackageManager: PackageManager = context.packageManager
|
|
|
|
+
|
|
|
|
+ for (info in mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA)) { /* skip apps that can't access the network anyway */
|
|
|
|
+
|
|
|
|
+ if (info.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP != 0) {
|
|
|
|
+ Log.d("--package names !0", "" + info.packageName)
|
|
|
|
+ if (mPackageManager.checkPermission(
|
|
|
|
+ Manifest.permission.INTERNET, info.packageName
|
|
|
|
+ ) == PackageManager.PERMISSION_GRANTED
|
|
|
|
+ ) {
|
|
|
|
+ if (info.packageName == context.packageName) continue
|
|
|
|
+ val entry = SelectApplicationEntry(mPackageManager, info, false)
|
|
|
|
+
|
|
|
|
+ prefHelper.getSplitPosition().let {
|
|
|
|
+ when (it){
|
|
|
|
+ splitList[1] -> {
|
|
|
|
+ if (!prefHelper.getSplitTunneledApps().equals("")) {
|
|
|
|
+ entry.setSelected(mutableStateOf(selectedAppsList.contains(info.packageName))) //= selectedAppsList!!.contains(info.packageName)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ splitList[2] -> {
|
|
|
|
+ if (!prefHelper.getSplitTunneledAppsNoVpn().equals("")) {
|
|
|
|
+ entry.setSelected(mutableStateOf(selectedAppsListNoVpn.contains(info.packageName))) //= selectedAppsList!!.contains(info.packageName)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Log.d("test_splittunnel_data", "${entry.getInfo()?.name}")
|
|
|
|
+
|
|
|
|
+ Log.d("entry_split_FUSA","$entry")
|
|
|
|
+
|
|
|
|
+ apps.add(entry)
|
|
|
|
+// apps2.add(entry)
|
|
|
|
+ seen.add(info.packageName)
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (info.flags and ApplicationInfo.FLAG_SYSTEM == 0) {
|
|
|
|
+ Log.d("--package names 0 ", "" + info.packageName)
|
|
|
|
+ if (mPackageManager.checkPermission(
|
|
|
|
+ Manifest.permission.INTERNET, info.packageName
|
|
|
|
+ ) == PackageManager.PERMISSION_GRANTED
|
|
|
|
+ ) {
|
|
|
|
+ if (info.packageName == context.packageName) continue
|
|
|
|
+ val entry = SelectApplicationEntry(mPackageManager, info, false)
|
|
|
|
+
|
|
|
|
+ prefHelper.getSplitPosition().let {
|
|
|
|
+ when (it){
|
|
|
|
+ splitList[1] -> {
|
|
|
|
+ if (!prefHelper.getSplitTunneledApps().equals("")) {
|
|
|
|
+ entry.setSelected(mutableStateOf(selectedAppsList.contains(info.packageName))) //= selectedAppsList!!.contains(info.packageName)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ splitList[2] -> {
|
|
|
|
+ if (!prefHelper.getSplitTunneledAppsNoVpn().equals("")) {
|
|
|
|
+ entry.setSelected(mutableStateOf(selectedAppsListNoVpn.contains(info.packageName))) //= selectedAppsList!!.contains(info.packageName)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ apps.add(entry)
|
|
|
|
+// apps2.add(entry)
|
|
|
|
+ seen.add(info.packageName)
|
|
|
|
+
|
|
|
|
+ Log.d("entry_split_FS","$entry")
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ apps.sort()
|
|
|
|
+// apps2.sort()
|
|
|
|
+
|
|
|
|
+ apps.forEach {
|
|
|
|
+ Log.d("test_split_apps", "$it")
|
|
|
|
+ }
|
|
|
|
+// apps2.forEach {
|
|
|
|
+// Log.d("test_split_apps 2", "$$it ${it.getIcon()}")
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+ return apps
|
|
|
|
+}
|