Compare commits

..

3 commits

Author SHA1 Message Date
12273bdd17
calculate remaining time
and move opening termux to another class
2024-08-04 11:05:43 +02:00
10a4dd8f8f
move some things to opener 2024-08-04 10:53:26 +02:00
d43b02b4f2
add status to rented instance
I couldn't fix the commits
2024-08-04 10:53:02 +02:00
4 changed files with 101 additions and 35 deletions

View file

@ -1,28 +1,93 @@
package eu.m724.vastapp.activity
import android.app.PendingIntent
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.widget.Toast
import androidx.activity.ComponentActivity
import eu.m724.vastapp.activity.termux.TermuxSshActivity
class Opener {
companion object {
/**
* opens an url in another app
* usually a browser but can be another app if it's default
* @param url the url
* @param activity the activity that starts the browser activity
*/
fun openUrl(url: String, activity: ComponentActivity) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
activity.startActivity(intent)
}
/**
* opens another app
* @param packageName package name like com.termux or eu.m724.vastapp
* @param activity the activity that starts the package
*/
fun openApp(packageName: String, activity: ComponentActivity) {
val intent = activity.packageManager.getLaunchIntentForPackage(packageName)
activity.startActivity(intent)
}
/**
* copies text to clipboard without toast
* @param text the text to copy
* @param label what are you copying
* @param context the context
*/
fun copyToClipboard(text: String, label: String, context: Context) {
copyToClipboard(text, label, null, context)
}
/**
* copies text to clipboard with toast
* @param text the text to copy
* @param label what are you copying
* @param toast text of toast that will appear if android < 12
* @param context the context
*/
fun copyToClipboard(text: String, label: String, toast: String?, context: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
Toast.makeText(
context,
toast,
Toast.LENGTH_SHORT
).show()
}
val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipData = ClipData.newPlainText(label, text)
clipboardManager.setPrimaryClip(clipData)
}
/**
* starts termux with command
* it will start [TermuxSshActivity] on finish
* @param context the context
* @param command the command, first entry is the executable and then arguments
*/
fun startTermux(context: Context, command: Array<String>) {
val noSshIntent = Intent(context, TermuxSshActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
context,
0,
noSshIntent,
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_MUTABLE
)
val intent = Intent()
intent.setClassName("com.termux", "com.termux.app.RunCommandService")
intent.setAction("com.termux.RUN_COMMAND")
intent.putExtra("com.termux.RUN_COMMAND_PATH", command[0])
intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", command.drop(1).toTypedArray())
intent.putExtra("com.termux.RUN_COMMAND_PENDING_INTENT", pendingIntent)
context.startForegroundService(intent)
}
}
}

View file

@ -1,17 +1,13 @@
package eu.m724.vastapp.activity.dashboard
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModel
import eu.m724.vastapp.R
import eu.m724.vastapp.activity.Opener
import eu.m724.vastapp.activity.PermissionChecker
import eu.m724.vastapp.activity.termux.TermuxSshActivity
import eu.m724.vastapp.vastai.ApiRoute
import eu.m724.vastapp.vastai.VastApi
import eu.m724.vastapp.vastai.api.InstancesUrlRequestCallback
@ -25,7 +21,7 @@ import kotlinx.coroutines.flow.update
class DashboardViewModel(
private val initialUser: User,
initialUser: User,
private val vastApi: VastApi
) : ViewModel() { // TODO do something with the user
@ -40,6 +36,9 @@ class DashboardViewModel(
private val _user: MutableStateFlow<User> = MutableStateFlow(initialUser)
val user: StateFlow<User> = _user.asStateFlow()
private val _remainingTime: MutableStateFlow<Int> = MutableStateFlow(-1)
var remainingTime: StateFlow<Int> = _remainingTime.asStateFlow()
private val _refreshError: MutableStateFlow<List<String>> = MutableStateFlow(emptyList())
val refreshError: StateFlow<List<String>> = _refreshError.asStateFlow()
@ -72,6 +71,22 @@ class DashboardViewModel(
_uiState.update {
it.copy(refreshing = it.refreshing - 1)
}
if (instances.isEmpty()) { // TODO move this
_remainingTime.value = -1
} else {
var totalDph = 0.0
instances.forEach {
if (it.status == "running")
totalDph += it.instance.pricing.dphTotal!!
else
totalDph += it.instance.pricing.dphTotal!! - it.instance.pricing.dphBase
// TODO make this ideal
}
_remainingTime.value = (user.value.credit / totalDph * 3600).toInt()
}
}, { apiFailure ->
_refreshError.update { it + apiFailure.errorMessage!! }
_uiState.update {
@ -99,6 +114,7 @@ class DashboardViewModel(
// TODO I don't like this function especially the last line
}
@SuppressLint("SdCardPath")
fun sshButtonClick(activity: ComponentActivity, rentedInstance: RentedInstance) {
val sshCommand = "ssh -p ${rentedInstance.sshProxyPort} root@${rentedInstance.sshProxyHost}"
val context = activity.applicationContext
@ -110,10 +126,11 @@ class DashboardViewModel(
) { granted, asked ->
if (granted) {
val arguments = arrayOf(
"/data/data/com.termux/files/usr/bin/ssh",
"-p", rentedInstance.sshProxyPort.toString(),
"root@" + rentedInstance.sshProxyHost
)
startTermux(context, arguments)
Opener.startTermux(context, arguments)
Thread.sleep(100)
println(activity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED))
} else {
@ -127,33 +144,12 @@ class DashboardViewModel(
}
private fun copyToClipboard(context: Context, text: String) {
Toast.makeText(
context,
Opener.copyToClipboard(
text,
"ssh command",
context.getString(R.string.copied_to_clipboard),
Toast.LENGTH_SHORT
).show() // TODO hide on a12
Opener.copyToClipboard(text, "ssh command", context)
}
@SuppressLint("SdCardPath")
private fun startTermux(context: Context, arguments: Array<String>) {
val noSshIntent = Intent(context, TermuxSshActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
context,
0,
noSshIntent,
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_MUTABLE
context
)
val intent = Intent()
intent.setClassName("com.termux", "com.termux.app.RunCommandService")
intent.setAction("com.termux.RUN_COMMAND")
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/ssh")
intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", arguments)
intent.putExtra("com.termux.RUN_COMMAND_PENDING_INTENT", pendingIntent)
context.startForegroundService(intent)
}
}

View file

@ -26,9 +26,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -48,7 +46,7 @@ fun DashboardScreen(dashboardViewModel: DashboardViewModel) {
val user by dashboardViewModel.user.collectAsState()
val rentedInstances by dashboardViewModel.rentedInstances.collectAsState()
val remainingTime by rememberSaveable { mutableIntStateOf(6000000) }
val remainingTime by dashboardViewModel.remainingTime.collectAsState()
val isRefreshing by remember(uiState) { derivedStateOf { uiState.refreshing > 0 } }
val scrollState = rememberScrollState()
@ -170,7 +168,7 @@ fun balanceColor(balance: Double, warningThreshold: Double): Color {
@Composable
fun formatTime(seconds: Int): String {
if (seconds <= 0)
return stringResource(id = R.string.time_minutes_short, 0.0)
return ""
val minutes: Double = seconds / 60.0
if (minutes < 60)

View file

@ -34,6 +34,11 @@ data class RentedInstance(
val sshProxyHost: String,
val sshProxyPort: Int,
/** exited or running */
val status: String,
/** "running" if the machine is starting or "stopped" if stopping */
val targetStatus: String,
/** the docker image that runs on the instance */
val image: String,
/** label set by user, null if not set */
@ -59,6 +64,8 @@ data class RentedInstance(
json.optDouble("inet_up_billed").toInt(),
"ssh${json.getInt("ssh_idx")}.vast.ai", // TODO
json.getInt("ssh_port"),
json.getString("actual_status"),
json.getString("intended_status"),
json.getString("image_uuid"),
json.optString("label").takeUnless { it == "null" || it.isBlank() },
json.getString("local_ipaddrs").split(" ").filterNot { it == "\n" }