add permission support for termux

this will be used in the future
This commit is contained in:
Minecon724 2024-08-01 12:39:20 +02:00
parent 046e3147d9
commit 3ae52c6638
Signed by: Minecon724
GPG key ID: 3CCC4D267742C8E8
5 changed files with 98 additions and 9 deletions

View file

@ -21,7 +21,7 @@ android {
}
// don't forget to add another "s this is counter intuitive I know but not my fault
buildConfigField("String", "VASTAI_KEY", "null")
buildConfigField("String", "VASTAI_KEY", "\"\"")
buildConfigField("String", "VASIAI_API_ENDPOINT", "\"https://cloud.vast.ai/api/v0\"")
}

View file

@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.termux.permission.RUN_COMMAND" />
<application
android:allowBackup="true"

View file

@ -0,0 +1,68 @@
package eu.m724.vastapp.activity
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
class PermissionChecker(private val context: Context) {
/**
* check if the app has a permission
* @param permission the permission
* @return whether the app has the permission? obviously
*/
fun hasPermission(permission: String): Boolean {
return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
}
/**
* check if a permission exists or if the app is installed
* @param permission the permission
* @return if the permission exists
*/
fun permissionExists(permission: String): Boolean {
try {
context.packageManager.getPermissionInfo(permission, 0)
return true
} catch (e: NameNotFoundException) {
return false
}
}
/**
* @param permission the permission
* @param activity the activity you're calling from
* @return if the permission can be asked for, that is if the user didn't check "don't ask again"
*/
fun canAskForPermission(permission: String, activity: ComponentActivity): Boolean {
return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)
}
/**
* request a permission if that permission is not granted
* @param permission the permission
* @param activity the activity you're calling from
* @param callback an Unit, the first boolean is whether the permission is granted and the second one is whether we asked for it
*/
fun requestIfNoPermission(permission: String, activity: ComponentActivity, callback: (Boolean, Boolean) -> Unit) {
val available = canAskForPermission(permission, activity)
if (hasPermission(permission)) {
callback(true, false)
} else if (available) { // no permission but can request
requestPermission(permission, activity) { callback(it, true) }
} else { // no permission and can't request
callback(false, false)
}
}
// TODO should this be private? I mean it doesn't check for other stuff so it's a waste to register an activity if we don't have to
private fun requestPermission(permission: String, activity: ComponentActivity, callback: (Boolean) -> Unit) {
activity.registerForActivityResult(
ActivityResultContracts.RequestPermission(),
callback
).launch(permission)
}
}

View file

@ -9,7 +9,6 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColor
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloat
@ -48,7 +47,6 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@ -65,12 +63,12 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope
import eu.m724.vastapp.BuildConfig
import eu.m724.vastapp.R
import eu.m724.vastapp.activity.PermissionChecker
import eu.m724.vastapp.activity.dashboard.DashboardActivity
import eu.m724.vastapp.ui.theme.VastappTheme
import eu.m724.vastapp.vastai.data.User
import kotlinx.coroutines.launch
import org.chromium.net.CronetEngine
import org.w3c.dom.Text
import java.util.concurrent.Executors
import kotlin.random.Random
@ -80,9 +78,30 @@ class LoginActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// TODO move this where and run this when we need it
val permissionChecker = PermissionChecker(applicationContext)
if (!permissionChecker.permissionExists("com.termux.permission.RUN_COMMAND")) {
Toast.makeText(
applicationContext,
R.string.no_termux,
Toast.LENGTH_SHORT
).show()
} else {
permissionChecker.requestIfNoPermission("com.termux.permission.RUN_COMMAND", this) { granted, asked ->
if (granted || !asked) return@requestIfNoPermission
Toast.makeText(
applicationContext,
getString(R.string.command_permission_denied),
Toast.LENGTH_SHORT
).show()
}
}
dashboardLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result -> finish() } // TODO re-login here
) { _ -> finish() } // TODO re-login here
val executor = Executors.newSingleThreadExecutor()
val cronetEngine = CronetEngine.Builder(baseContext).build()
@ -94,7 +113,7 @@ class LoginActivity : ComponentActivity() {
}
}
lifecycleScope.launch { // TODO I was suggested not to launch an activity from a lifecyclescope
lifecycleScope.launch { // TODO I was suggested not to launch an activity from a lifecycle scope
loginViewModel.uiState.collect { state ->
if (state is LoginUiState.Success) {
loadApp(state.user)
@ -134,12 +153,11 @@ class LoginActivity : ComponentActivity() {
@Composable
fun LoginApp(loginViewModel: LoginViewModel) {
val coroutineScope = rememberCoroutineScope()
val uiState by loginViewModel.uiState.collectAsState()
val loginErrorMessage by loginViewModel.error.observeAsState() // TODO put this in uistate
val loginErrorMessage by loginViewModel.error.observeAsState() // TODO put this in uiState
val isIdle by remember(uiState) { derivedStateOf { uiState !is LoginUiState.Loading } }
var apiKey by rememberSaveable { mutableStateOf(BuildConfig.VASTAI_KEY ?: "") }
var apiKey by rememberSaveable { mutableStateOf(BuildConfig.VASTAI_KEY) }
var advancedOpen by rememberSaveable { mutableStateOf(false) }
val transition = updateTransition(targetState = advancedOpen, label = "Advanced Menu Transition")

View file

@ -20,4 +20,6 @@
<string name="login_checkbox_20">having fun?</string>
<string name="login_checkbox_angry">checkbox is angry</string>
<string name="no_options">none yet sorry</string>
<string name="command_permission_denied">If you change your mind, do so from settings</string>
<string name="no_termux">Termuxn\'t</string>
</resources>