Compare commits

..

No commits in common. "12273bdd1713b481bbe9b896e7249f0993434d3a" and "19ab656b7c3b20fc023c23e867c9521efee14641" have entirely different histories.

4 changed files with 35 additions and 101 deletions

View file

@ -1,93 +1,28 @@
package eu.m724.vastapp.activity package eu.m724.vastapp.activity
import android.app.PendingIntent
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import eu.m724.vastapp.activity.termux.TermuxSshActivity
class Opener { class Opener {
companion object { 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) { fun openUrl(url: String, activity: ComponentActivity) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
activity.startActivity(intent) 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) { fun openApp(packageName: String, activity: ComponentActivity) {
val intent = activity.packageManager.getLaunchIntentForPackage(packageName) val intent = activity.packageManager.getLaunchIntentForPackage(packageName)
activity.startActivity(intent) 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) { 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 clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipData = ClipData.newPlainText(label, text) val clipData = ClipData.newPlainText(label, text)
clipboardManager.setPrimaryClip(clipData) 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,13 +1,17 @@
package eu.m724.vastapp.activity.dashboard package eu.m724.vastapp.activity.dashboard
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent
import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import eu.m724.vastapp.R import eu.m724.vastapp.R
import eu.m724.vastapp.activity.Opener import eu.m724.vastapp.activity.Opener
import eu.m724.vastapp.activity.PermissionChecker import eu.m724.vastapp.activity.PermissionChecker
import eu.m724.vastapp.activity.termux.TermuxSshActivity
import eu.m724.vastapp.vastai.ApiRoute import eu.m724.vastapp.vastai.ApiRoute
import eu.m724.vastapp.vastai.VastApi import eu.m724.vastapp.vastai.VastApi
import eu.m724.vastapp.vastai.api.InstancesUrlRequestCallback import eu.m724.vastapp.vastai.api.InstancesUrlRequestCallback
@ -21,7 +25,7 @@ import kotlinx.coroutines.flow.update
class DashboardViewModel( class DashboardViewModel(
initialUser: User, private val initialUser: User,
private val vastApi: VastApi private val vastApi: VastApi
) : ViewModel() { // TODO do something with the user ) : ViewModel() { // TODO do something with the user
@ -36,9 +40,6 @@ class DashboardViewModel(
private val _user: MutableStateFlow<User> = MutableStateFlow(initialUser) private val _user: MutableStateFlow<User> = MutableStateFlow(initialUser)
val user: StateFlow<User> = _user.asStateFlow() 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()) private val _refreshError: MutableStateFlow<List<String>> = MutableStateFlow(emptyList())
val refreshError: StateFlow<List<String>> = _refreshError.asStateFlow() val refreshError: StateFlow<List<String>> = _refreshError.asStateFlow()
@ -71,22 +72,6 @@ class DashboardViewModel(
_uiState.update { _uiState.update {
it.copy(refreshing = it.refreshing - 1) 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 -> }, { apiFailure ->
_refreshError.update { it + apiFailure.errorMessage!! } _refreshError.update { it + apiFailure.errorMessage!! }
_uiState.update { _uiState.update {
@ -114,7 +99,6 @@ class DashboardViewModel(
// TODO I don't like this function especially the last line // TODO I don't like this function especially the last line
} }
@SuppressLint("SdCardPath")
fun sshButtonClick(activity: ComponentActivity, rentedInstance: RentedInstance) { fun sshButtonClick(activity: ComponentActivity, rentedInstance: RentedInstance) {
val sshCommand = "ssh -p ${rentedInstance.sshProxyPort} root@${rentedInstance.sshProxyHost}" val sshCommand = "ssh -p ${rentedInstance.sshProxyPort} root@${rentedInstance.sshProxyHost}"
val context = activity.applicationContext val context = activity.applicationContext
@ -126,11 +110,10 @@ class DashboardViewModel(
) { granted, asked -> ) { granted, asked ->
if (granted) { if (granted) {
val arguments = arrayOf( val arguments = arrayOf(
"/data/data/com.termux/files/usr/bin/ssh",
"-p", rentedInstance.sshProxyPort.toString(), "-p", rentedInstance.sshProxyPort.toString(),
"root@" + rentedInstance.sshProxyHost "root@" + rentedInstance.sshProxyHost
) )
Opener.startTermux(context, arguments) startTermux(context, arguments)
Thread.sleep(100) Thread.sleep(100)
println(activity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) println(activity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED))
} else { } else {
@ -144,12 +127,33 @@ class DashboardViewModel(
} }
private fun copyToClipboard(context: Context, text: String) { private fun copyToClipboard(context: Context, text: String) {
Opener.copyToClipboard( Toast.makeText(
text, context,
"ssh command",
context.getString(R.string.copied_to_clipboard), context.getString(R.string.copied_to_clipboard),
context 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
) )
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,7 +26,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@ -46,7 +48,7 @@ fun DashboardScreen(dashboardViewModel: DashboardViewModel) {
val user by dashboardViewModel.user.collectAsState() val user by dashboardViewModel.user.collectAsState()
val rentedInstances by dashboardViewModel.rentedInstances.collectAsState() val rentedInstances by dashboardViewModel.rentedInstances.collectAsState()
val remainingTime by dashboardViewModel.remainingTime.collectAsState() val remainingTime by rememberSaveable { mutableIntStateOf(6000000) }
val isRefreshing by remember(uiState) { derivedStateOf { uiState.refreshing > 0 } } val isRefreshing by remember(uiState) { derivedStateOf { uiState.refreshing > 0 } }
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
@ -168,7 +170,7 @@ fun balanceColor(balance: Double, warningThreshold: Double): Color {
@Composable @Composable
fun formatTime(seconds: Int): String { fun formatTime(seconds: Int): String {
if (seconds <= 0) if (seconds <= 0)
return "" return stringResource(id = R.string.time_minutes_short, 0.0)
val minutes: Double = seconds / 60.0 val minutes: Double = seconds / 60.0
if (minutes < 60) if (minutes < 60)

View file

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