diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Instances.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Instances.kt index f3c71c0..921c78f 100644 --- a/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Instances.kt +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Instances.kt @@ -49,7 +49,7 @@ fun InstancesScreen(dashboardViewModel: DashboardViewModel) { // TODO actually get instances - if (rentedInstances.size > 0) { + if (rentedInstances.isNotEmpty()) { ContextualFlowRow( modifier = Modifier .fillMaxWidth() @@ -66,6 +66,12 @@ fun InstancesScreen(dashboardViewModel: DashboardViewModel) { sshButtonClick = { dashboardViewModel.sshButtonClick(activity, it) }, + actionButtonClick = { + dashboardViewModel.toggleInstance(it) + }, + deleteButtonClick = { + dashboardViewModel.deleteInstance(it) + } ) } } else { @@ -89,6 +95,8 @@ fun RentedInstanceCard( rentedInstance: RentedInstance, termuxAvailable: Int, sshButtonClick: (RentedInstance) -> Unit, + actionButtonClick: (RentedInstance) -> Unit, + deleteButtonClick: (RentedInstance) -> Unit, ) { val instance by remember(rentedInstance) { derivedStateOf { rentedInstance.instance } } val label by remember(instance) { derivedStateOf { @@ -103,27 +111,66 @@ fun RentedInstanceCard( Column( horizontalAlignment = Alignment.End ) { - Button( // TODO consider other buttons - modifier = Modifier.height(24.dp), - contentPadding = PaddingValues(0.dp), - onClick = { sshButtonClick(rentedInstance) } - ) { - if (termuxAvailable > -1) { + Row { + Button( + modifier = Modifier.size(24.dp), + contentPadding = PaddingValues(0.dp), + onClick = { deleteButtonClick(rentedInstance) }, + ) { Icon( - painter = painterResource(id = R.drawable.termux_icon), - contentDescription = "Run in Termux" + modifier = Modifier.size(16.dp), + painter = painterResource(id = R.drawable.baseline_delete_24), + contentDescription = "Delete instance" ) - Text("ssh") - Spacer(modifier = Modifier.size(4.dp)) // necessary because TODO the termux icon has padding - } else { - Spacer(modifier = Modifier.size(1.dp)) // TODO make this not needed? - Icon( - modifier = Modifier.size(12.dp), - painter = painterResource(id = R.drawable.copy_regular), // TODO copy icon here - contentDescription = "Copy command" - ) - Spacer(modifier = Modifier.size(6.dp)) - Text("ssh") + } + + Spacer(modifier = Modifier.width(4.dp)) + + Button( + modifier = Modifier.size(24.dp), + contentPadding = PaddingValues(0.dp), + onClick = { actionButtonClick(rentedInstance) }, + enabled = rentedInstance.status == rentedInstance.targetStatus + ) { + if (rentedInstance.status == "running") { + Icon( + modifier = Modifier.size(16.dp), + painter = painterResource(id = R.drawable.baseline_stop_24), + contentDescription = "Stop instance" + ) + } else { + Icon( + modifier = Modifier.size(16.dp), + painter = painterResource(id = R.drawable.baseline_play_arrow_24), + contentDescription = "Start instance" + ) + } + } + + Spacer(modifier = Modifier.width(4.dp)) + + Button( + modifier = Modifier.height(24.dp), + contentPadding = PaddingValues(0.dp), + onClick = { sshButtonClick(rentedInstance) } + ) { + if (termuxAvailable > -1) { + Icon( + painter = painterResource(id = R.drawable.termux_icon), + contentDescription = "Run in Termux" + ) + Text("ssh") + Spacer(modifier = Modifier.size(4.dp)) // necessary because TODO the termux icon has padding + } else { + Spacer(modifier = Modifier.size(1.dp)) // TODO make this not needed? + Icon( + modifier = Modifier.size(12.dp), + painter = painterResource(id = R.drawable.copy_regular), // TODO copy icon here + contentDescription = "Copy command" + ) + Spacer(modifier = Modifier.size(6.dp)) + Text("ssh") + } } } diff --git a/app/src/main/java/eu/m724/vastapp/vastai/api/JsonUrlRequestCallback.kt b/app/src/main/java/eu/m724/vastapp/vastai/api/JsonUrlRequestCallback.kt new file mode 100644 index 0000000..807ea35 --- /dev/null +++ b/app/src/main/java/eu/m724/vastapp/vastai/api/JsonUrlRequestCallback.kt @@ -0,0 +1,16 @@ +package eu.m724.vastapp.vastai.api + +import eu.m724.vastapp.vastai.ApiFailure +import org.json.JSONObject + +class JsonUrlRequestCallback( + onSuccess: (JSONObject) -> Unit, + onFailure: (ApiFailure) -> Unit +) : StringUrlRequestCallback({ stringResponse -> + try { + val jsonResponse = JSONObject(stringResponse) + onSuccess(jsonResponse) + } catch (e: Exception) { + onFailure(ApiFailure(e.message)) + } +}, onFailure) \ No newline at end of file diff --git a/app/src/main/java/eu/m724/vastapp/vastai/api/StringUrlRequestCallback.kt b/app/src/main/java/eu/m724/vastapp/vastai/api/StringUrlRequestCallback.kt new file mode 100644 index 0000000..dd9085d --- /dev/null +++ b/app/src/main/java/eu/m724/vastapp/vastai/api/StringUrlRequestCallback.kt @@ -0,0 +1,51 @@ +package eu.m724.vastapp.vastai.api + +import eu.m724.vastapp.vastai.ApiFailure +import org.chromium.net.CronetException +import org.chromium.net.UrlRequest +import org.chromium.net.UrlResponseInfo +import java.nio.ByteBuffer +import java.nio.charset.CodingErrorAction + +open class StringUrlRequestCallback( + val onSuccess: (String) -> Unit, + val onFailure: (ApiFailure) -> Unit +) : UrlRequest.Callback() { + protected val stringResponse = StringBuilder() + + override fun onRedirectReceived( + request: UrlRequest?, + info: UrlResponseInfo?, + newLocationUrl: String? + ) { + request?.followRedirect() + } + + override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo?) { + request?.read(ByteBuffer.allocateDirect(102400)) + } + + override fun onReadCompleted( + request: UrlRequest?, + info: UrlResponseInfo?, + byteBuffer: ByteBuffer? + ) { + byteBuffer?.clear() + request?.read(byteBuffer) + + stringResponse.append(Charsets.UTF_8.newDecoder().onUnmappableCharacter(CodingErrorAction.IGNORE).decode(byteBuffer)) + } + + override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) { + if (info?.httpStatusCode == 200) { + onSuccess(stringResponse.toString()) + } else { + onFailure(ApiFailure("${info?.httpStatusCode} ${info?.httpStatusText}")) + println("API error: ${stringResponse.toString()}") + } + } + + override fun onFailed(request: UrlRequest?, info: UrlResponseInfo?, error: CronetException?) { + onFailure(ApiFailure("Network error: ${error?.message ?: "Unknown"}")) + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/m724/vastapp/vastai/api/upload/JsonUploadDataProvider.kt b/app/src/main/java/eu/m724/vastapp/vastai/api/upload/JsonUploadDataProvider.kt new file mode 100644 index 0000000..e14bbf7 --- /dev/null +++ b/app/src/main/java/eu/m724/vastapp/vastai/api/upload/JsonUploadDataProvider.kt @@ -0,0 +1,9 @@ +package eu.m724.vastapp.vastai.api.upload + +import org.json.JSONObject + +class JsonUploadDataProvider( + jsonObject: JSONObject +) : StringUploadDataProvider( + jsonObject.toString() +) \ No newline at end of file diff --git a/app/src/main/java/eu/m724/vastapp/vastai/api/upload/StringUploadDataProvider.kt b/app/src/main/java/eu/m724/vastapp/vastai/api/upload/StringUploadDataProvider.kt new file mode 100644 index 0000000..1724866 --- /dev/null +++ b/app/src/main/java/eu/m724/vastapp/vastai/api/upload/StringUploadDataProvider.kt @@ -0,0 +1,25 @@ +package eu.m724.vastapp.vastai.api.upload + +import org.chromium.net.UploadDataProvider +import org.chromium.net.UploadDataSink +import java.nio.ByteBuffer +import java.nio.charset.StandardCharsets + +open class StringUploadDataProvider( + val data: String +) : UploadDataProvider() { + override fun getLength(): Long { + return data.length.toLong() + } + + override fun read(uploadDataSink: UploadDataSink?, byteBuffer: ByteBuffer?) { + byteBuffer!!.put(data.toByteArray(StandardCharsets.UTF_8)) + uploadDataSink!!.onReadSucceeded(false) + } + + override fun rewind(uploadDataSink: UploadDataSink?) { + // TODO look into it + uploadDataSink!!.onRewindSucceeded() + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/baseline_delete_24.xml b/app/src/main/res/drawable/baseline_delete_24.xml new file mode 100644 index 0000000..883bcaa --- /dev/null +++ b/app/src/main/res/drawable/baseline_delete_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_play_arrow_24.xml b/app/src/main/res/drawable/baseline_play_arrow_24.xml new file mode 100644 index 0000000..b176182 --- /dev/null +++ b/app/src/main/res/drawable/baseline_play_arrow_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_stop_24.xml b/app/src/main/res/drawable/baseline_stop_24.xml new file mode 100644 index 0000000..817d57b --- /dev/null +++ b/app/src/main/res/drawable/baseline_stop_24.xml @@ -0,0 +1,5 @@ + + + + +