diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 18311db..5aad826 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,7 +14,19 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.Vastapp" + android:name=".VastApplication" tools:targetApi="34"> + + + + + + + - - - - - diff --git a/app/src/main/java/eu/m724/vastapp/VastApplication.kt b/app/src/main/java/eu/m724/vastapp/VastApplication.kt new file mode 100644 index 0000000..2e40f7f --- /dev/null +++ b/app/src/main/java/eu/m724/vastapp/VastApplication.kt @@ -0,0 +1,38 @@ +package eu.m724.vastapp + +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import eu.m724.vastapp.vastai.Account +import eu.m724.vastapp.vastai.VastApi +import org.chromium.net.CronetEngine +import java.util.concurrent.Executors + +class VastApplication : Application() { + private lateinit var cronetEngine: CronetEngine + lateinit var vastApi: VastApi // TODO maybe make private? + + private lateinit var loginSharedPreferences: SharedPreferences + + var account: Account? = null + + override fun onCreate() { + super.onCreate() + cronetEngine = CronetEngine.Builder(baseContext) + .enableBrotli(true) + .build() // http3 is not supported on cloud.vast.ai + vastApi = VastApi("", cronetEngine, Executors.newSingleThreadExecutor()) + loginSharedPreferences = applicationContext.getSharedPreferences("login", Context.MODE_PRIVATE) + } + + fun submitKey(apiKey: String) { + with (loginSharedPreferences.edit()) { + putString("apiKey", apiKey) // TODO encrypt + apply() + } + } + + fun loadKey(): String? { + return loginSharedPreferences.getString("apiKey", null) + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardActivity.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardActivity.kt index ecc2842..19ea987 100644 --- a/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardActivity.kt +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardActivity.kt @@ -36,26 +36,18 @@ import eu.m724.vastapp.activity.dashboard.screen.HelpScreen import eu.m724.vastapp.activity.dashboard.screen.InstancesScreen import eu.m724.vastapp.activity.dashboard.screen.Screen import eu.m724.vastapp.ui.theme.VastappTheme -import eu.m724.vastapp.vastai.VastApi -import eu.m724.vastapp.vastai.data.User import kotlinx.coroutines.launch -import org.chromium.net.CronetEngine -import java.util.concurrent.Executors class DashboardActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val user = intent.getParcelableExtra("user")!! // TODO null check - - val executor = Executors.newSingleThreadExecutor() - val cronetEngine = CronetEngine.Builder(baseContext).enableBrotli(true).build() - val vastApi = VastApi(user.apiKey, cronetEngine, executor) // TODO use that from login activity - - val dashboardViewModel = DashboardViewModel(user, vastApi) + val dashboardViewModel = DashboardViewModel(application) + if (intent.getBooleanExtra("direct", false).not()) { + dashboardViewModel.refresh(this) + } lifecycleScope.launch { - dashboardViewModel.refresh(this@DashboardActivity) repeatOnLifecycle(Lifecycle.State.STARTED) { dashboardViewModel.refreshError.collect { it.forEach { errorMsg -> diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardViewModel.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardViewModel.kt index 21b65de..fbb1ff8 100644 --- a/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardViewModel.kt @@ -1,19 +1,19 @@ package eu.m724.vastapp.activity.dashboard import android.annotation.SuppressLint +import android.app.Application import android.content.Context import androidx.activity.ComponentActivity +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.Lifecycle -import androidx.lifecycle.ViewModel import eu.m724.vastapp.R +import eu.m724.vastapp.VastApplication import eu.m724.vastapp.activity.Opener import eu.m724.vastapp.activity.PermissionChecker import eu.m724.vastapp.vastai.ApiRoute -import eu.m724.vastapp.vastai.VastApi import eu.m724.vastapp.vastai.api.InstancesUrlRequestCallback import eu.m724.vastapp.vastai.api.UserUrlRequestCallback import eu.m724.vastapp.vastai.data.RentedInstance -import eu.m724.vastapp.vastai.data.User import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -21,30 +21,24 @@ import kotlinx.coroutines.flow.update class DashboardViewModel( - initialUser: User, - private val vastApi: VastApi -) : ViewModel() { // TODO do something with the user + application: Application +) : AndroidViewModel(application) { // TODO do something with the user private val _uiState: MutableStateFlow = MutableStateFlow(DashboardUiState(0)) val uiState: StateFlow = _uiState.asStateFlow() - private val _rentedInstances: MutableStateFlow> = MutableStateFlow(emptyList()) - val rentedInstances: StateFlow> = _rentedInstances.asStateFlow() - - private val _user: MutableStateFlow = MutableStateFlow(initialUser) - val user: StateFlow = _user.asStateFlow() - - private val _remainingTime: MutableStateFlow = MutableStateFlow(-1) - var remainingTime: StateFlow = _remainingTime.asStateFlow() - private val _refreshError: MutableStateFlow> = MutableStateFlow(emptyList()) val refreshError: StateFlow> = _refreshError.asStateFlow() private val _termuxAvailable: MutableStateFlow = MutableStateFlow(0) val termuxAvailable: StateFlow = _termuxAvailable.asStateFlow() + private val application = getApplication() + private val vastApi = this.application.vastApi + val account = this.application.account!! + fun refresh(activity: ComponentActivity) { _uiState.value = _uiState.value.copy(refreshing = 2) _refreshError.value = emptyList() @@ -52,7 +46,7 @@ class DashboardViewModel( val userRequest = vastApi.buildRequest( ApiRoute.SHOW_USER, UserUrlRequestCallback({ newUser -> - _user.value = newUser + account.updateUser(newUser) _uiState.update { it.copy(refreshing = it.refreshing - 1) // TODO I don't like how this looks } @@ -67,33 +61,17 @@ class DashboardViewModel( val instancesRequest = vastApi.buildRequest( ApiRoute.GET_INSTANCES, InstancesUrlRequestCallback({ instances -> - _rentedInstances.value = instances // TODO better way? + account.updateRentedInstances(instances) _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 { it.copy(refreshing = it.refreshing - 1) } }) - ) + ) // TODO move all that refreshing to some shared place userRequest.start() instancesRequest.start() @@ -111,7 +89,7 @@ class DashboardViewModel( } } else -1 // not available - // TODO I don't like this function especially the last line + // TODO I don't like this function especially the last line. I think it should be moved to application } @SuppressLint("SdCardPath") @@ -123,7 +101,7 @@ class DashboardViewModel( PermissionChecker.requestIfNoPermission( "com.termux.permission.RUN_COMMAND", activity, 0 - ) { granted, asked -> + ) { granted, _ -> if (granted) { val arguments = arrayOf( "/data/data/com.termux/files/usr/bin/ssh", diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/LoadingActivity.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/LoadingActivity.kt new file mode 100644 index 0000000..7f0f942 --- /dev/null +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/LoadingActivity.kt @@ -0,0 +1,76 @@ +package eu.m724.vastapp.activity.dashboard.loading + +import android.content.Intent +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import eu.m724.vastapp.R +import eu.m724.vastapp.activity.dashboard.DashboardActivity +import eu.m724.vastapp.activity.dashboard.loading.ui.theme.VastappTheme +import eu.m724.vastapp.activity.login.LoginActivity + +class LoadingActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val viewModel = LoadingViewModel(application) { result -> + println(result.loggedIn) + val intent = + if (result.loggedIn) { + Intent(this, DashboardActivity::class.java) + .putExtra("direct", true) + } else { + Intent(this, LoginActivity::class.java) + .putExtra("error", result.error!!) + } + + this.startActivity(intent) + finish() + } + + viewModel.init() + + enableEdgeToEdge() + setContent { + VastappTheme { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.fillMaxHeight().weight(1f)) + Box( + modifier = Modifier + .fillMaxHeight() + .weight(1f), + contentAlignment = Alignment.Center + ) { + Icon( + modifier = Modifier.fillMaxSize(), + painter = painterResource(id = R.drawable.ic_launcher_foreground), + contentDescription = "app icon" + ) + } + Box( + modifier = Modifier + .fillMaxHeight() + .weight(1f), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/LoadingResult.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/LoadingResult.kt new file mode 100644 index 0000000..ff03242 --- /dev/null +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/LoadingResult.kt @@ -0,0 +1,6 @@ +package eu.m724.vastapp.activity.dashboard.loading + +data class LoadingResult( + val loggedIn: Boolean, + val error: String? +) \ No newline at end of file diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/LoadingViewModel.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/LoadingViewModel.kt new file mode 100644 index 0000000..5bbb4e0 --- /dev/null +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/LoadingViewModel.kt @@ -0,0 +1,54 @@ +package eu.m724.vastapp.activity.dashboard.loading + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import eu.m724.vastapp.VastApplication +import eu.m724.vastapp.vastai.Account +import eu.m724.vastapp.vastai.ApiRoute +import eu.m724.vastapp.vastai.api.InstancesUrlRequestCallback +import eu.m724.vastapp.vastai.api.UserUrlRequestCallback + +class LoadingViewModel( + application: Application, + private val onEnded: (LoadingResult) -> Unit, +) : AndroidViewModel(application) { + private val application = application as VastApplication + + fun init() { + val vastApi = application.vastApi + val apiKey = application.loadKey() + + if (apiKey != null) { + vastApi.apiKey = apiKey + + val request = vastApi.buildRequest( + ApiRoute.SHOW_USER, + UserUrlRequestCallback({ user -> + application.account = Account(user) + loadInstances() + }, { apiFailure -> + onEnded(LoadingResult(false, apiFailure.errorMessage)) + }) + ) + + request.start() + } + } + + private fun loadInstances() { + val vastApi = application.vastApi + + val instancesRequest = vastApi.buildRequest( + ApiRoute.GET_INSTANCES, + InstancesUrlRequestCallback({ instances -> + application.account!!.updateRentedInstances(instances) + onEnded(LoadingResult(true, null)) + }, { apiFailure -> + // TODO I don't know what to do yet + onEnded(LoadingResult(true, null)) + }) + ) + + instancesRequest.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/ui/theme/Color.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/ui/theme/Color.kt new file mode 100644 index 0000000..b383a21 --- /dev/null +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package eu.m724.vastapp.activity.dashboard.loading.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/ui/theme/Theme.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/ui/theme/Theme.kt new file mode 100644 index 0000000..10e5b06 --- /dev/null +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/ui/theme/Theme.kt @@ -0,0 +1,58 @@ +package eu.m724.vastapp.activity.dashboard.loading.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun VastappTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/ui/theme/Type.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/ui/theme/Type.kt new file mode 100644 index 0000000..09e2472 --- /dev/null +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/loading/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package eu.m724.vastapp.activity.dashboard.loading.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Billing.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Billing.kt index eb5dd2b..c693e32 100644 --- a/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Billing.kt +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Billing.kt @@ -29,7 +29,7 @@ import eu.m724.vastapp.activity.dashboard.DashboardViewModel fun BillingScreen(dashboardViewModel: DashboardViewModel) { val uiState by dashboardViewModel.uiState.collectAsState() - val user by dashboardViewModel.user.collectAsState() + val user by dashboardViewModel.account.user.collectAsState() Column( modifier = Modifier.fillMaxWidth(), diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Dashboard.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Dashboard.kt index fadbaed..ba882a5 100644 --- a/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Dashboard.kt +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Dashboard.kt @@ -44,9 +44,9 @@ fun DashboardScreen(dashboardViewModel: DashboardViewModel) { val context = LocalContext.current val uiState by dashboardViewModel.uiState.collectAsState() - val user by dashboardViewModel.user.collectAsState() - val rentedInstances by dashboardViewModel.rentedInstances.collectAsState() - val remainingTime by dashboardViewModel.remainingTime.collectAsState() + val user by dashboardViewModel.account.user.collectAsState() + val rentedInstances by dashboardViewModel.account.rentedInstances.collectAsState() + val remainingTime by dashboardViewModel.account.remainingTime.collectAsState() val isRefreshing by remember(uiState) { derivedStateOf { uiState.refreshing > 0 } } val scrollState = rememberScrollState() 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 882734c..3f7882d 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 @@ -42,7 +42,7 @@ fun InstancesScreen(dashboardViewModel: DashboardViewModel) { val activity = LocalContext.current as ComponentActivity val uiState by dashboardViewModel.uiState.collectAsState() - val rentedInstances by dashboardViewModel.rentedInstances.collectAsState() + val rentedInstances by dashboardViewModel.account.rentedInstances.collectAsState() val termuxAvailable by dashboardViewModel.termuxAvailable.collectAsState() // TODO actually get instances diff --git a/app/src/main/java/eu/m724/vastapp/activity/login/LoginActivity.kt b/app/src/main/java/eu/m724/vastapp/activity/login/LoginActivity.kt index 1045384..0aac63f 100644 --- a/app/src/main/java/eu/m724/vastapp/activity/login/LoginActivity.kt +++ b/app/src/main/java/eu/m724/vastapp/activity/login/LoginActivity.kt @@ -55,7 +55,6 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation 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.dashboard.DashboardActivity import eu.m724.vastapp.ui.theme.VastappTheme @@ -84,6 +83,11 @@ class LoginActivity : ComponentActivity() { } } + val loadingError = intent.getStringExtra("error") + if (loadingError != null) { + Toast.makeText(baseContext, loadingError, Toast.LENGTH_SHORT).show() + } + lifecycleScope.launch { // TODO I was suggested not to launch an activity from a lifecycle scope loginViewModel.uiState.collect { state -> if (state is LoginUiState.Success) { @@ -92,9 +96,6 @@ class LoginActivity : ComponentActivity() { } } - if (BuildConfig.AUTO_LOGIN) - loginViewModel.loadKey() - enableEdgeToEdge() setContent { VastappTheme { diff --git a/app/src/main/java/eu/m724/vastapp/activity/login/LoginViewModel.kt b/app/src/main/java/eu/m724/vastapp/activity/login/LoginViewModel.kt index a079008..6be7c72 100644 --- a/app/src/main/java/eu/m724/vastapp/activity/login/LoginViewModel.kt +++ b/app/src/main/java/eu/m724/vastapp/activity/login/LoginViewModel.kt @@ -1,12 +1,11 @@ package eu.m724.vastapp.activity.login import android.app.Application -import android.content.Context import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import eu.m724.vastapp.VastApplication import eu.m724.vastapp.vastai.ApiRoute -import eu.m724.vastapp.vastai.VastApi import eu.m724.vastapp.vastai.api.UserUrlRequestCallback import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -34,35 +33,16 @@ class LoginViewModel( private val _fullscreenLoading = MutableStateFlow(false) var fullscreenLoading: StateFlow = _fullscreenLoading.asStateFlow() - private val applicationContext = getApplication().applicationContext - private val sharedPreferences = applicationContext.getSharedPreferences("login", Context.MODE_PRIVATE) - - - private fun saveKey() { - with (sharedPreferences.edit()) { - putString("apiKey", apiKey.value) // TODO encrypt - apply() - } - } - - fun loadKey() { - val apiKey = sharedPreferences.getString("apiKey", null) - - if (apiKey != null) { - _apiKey.value = apiKey - _fullscreenLoading.value = true - tryLogin() - } - } + private val application = getApplication() fun tryLogin() { - val apiKey = apiKey.value + val vastApi = application.vastApi + vastApi.apiKey = apiKey.value - val vastApi = VastApi(apiKey, cronetEngine, executor) val request = vastApi.buildRequest( ApiRoute.SHOW_USER, UserUrlRequestCallback({ user -> - saveKey() // TODO toggle for this + application.submitKey(apiKey.value) // TODO toggle for this _uiState.value = LoginUiState.Success(user) }, { apiFailure -> _uiState.value = LoginUiState.Idle diff --git a/app/src/main/java/eu/m724/vastapp/vastai/Account.kt b/app/src/main/java/eu/m724/vastapp/vastai/Account.kt new file mode 100644 index 0000000..a846009 --- /dev/null +++ b/app/src/main/java/eu/m724/vastapp/vastai/Account.kt @@ -0,0 +1,49 @@ +package eu.m724.vastapp.vastai + +import eu.m724.vastapp.vastai.data.RentedInstance +import eu.m724.vastapp.vastai.data.User +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class Account( + initialUser: User +) { + private val _rentedInstances: MutableStateFlow> = MutableStateFlow(emptyList()) + val rentedInstances: StateFlow> = _rentedInstances.asStateFlow() + + private val _user: MutableStateFlow = MutableStateFlow(initialUser) + val user: StateFlow = _user.asStateFlow() + + private val _remainingTime: MutableStateFlow = MutableStateFlow(-1) + var remainingTime: StateFlow = _remainingTime.asStateFlow() + + fun updateUser(user: User) { + _user.value = user + } + + fun updateRentedInstances(rentedInstances: List) { // TODO better way? + _rentedInstances.value = rentedInstances + calculateRemainingTime() + } + + fun calculateRemainingTime() { + val rentedInstances = rentedInstances.value + + if (rentedInstances.isEmpty()) { + _remainingTime.value = -1 + } else { + var totalDph = 0.0 + + rentedInstances.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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/m724/vastapp/vastai/VastApi.kt b/app/src/main/java/eu/m724/vastapp/vastai/VastApi.kt index 9edb9a5..920a174 100644 --- a/app/src/main/java/eu/m724/vastapp/vastai/VastApi.kt +++ b/app/src/main/java/eu/m724/vastapp/vastai/VastApi.kt @@ -1,16 +1,14 @@ package eu.m724.vastapp.vastai -import android.os.Parcel -import android.os.Parcelable import eu.m724.vastapp.BuildConfig import org.chromium.net.CronetEngine import org.chromium.net.UrlRequest import java.util.concurrent.Executor class VastApi( - val apiKey: String, - val cronetEngine: CronetEngine, - val executor: Executor + var apiKey: String, // TODO make private? + private val cronetEngine: CronetEngine, + private val executor: Executor ) { /** * build an api request diff --git a/app/src/main/java/eu/m724/vastapp/vastai/api/UserUrlRequestCallback.kt b/app/src/main/java/eu/m724/vastapp/vastai/api/UserUrlRequestCallback.kt index 7bf693e..ddef734 100644 --- a/app/src/main/java/eu/m724/vastapp/vastai/api/UserUrlRequestCallback.kt +++ b/app/src/main/java/eu/m724/vastapp/vastai/api/UserUrlRequestCallback.kt @@ -43,16 +43,23 @@ class UserUrlRequestCallback( override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) { println(stringResponse) // TODO don't do that if (info?.httpStatusCode == 200) { - val jsonResponse = JSONObject(stringResponse.toString()) - onSuccess(User( - id = jsonResponse.getString("id"), - username = jsonResponse.getString("username"), - email = jsonResponse.getString("email"), - apiKey = jsonResponse.getString("api_key"), - credit = jsonResponse.getDouble("credit"), - balanceThreshold = jsonResponse.getDouble("balance_threshold"), - balanceThresholdEnabled = jsonResponse.getBoolean("balance_threshold_enabled"), - )) + try { + val jsonResponse = JSONObject(stringResponse.toString()) + onSuccess( + User( + id = jsonResponse.getString("id"), + username = jsonResponse.getString("username"), + email = jsonResponse.getString("email"), + apiKey = jsonResponse.getString("api_key"), + credit = jsonResponse.getDouble("credit"), + balanceThreshold = jsonResponse.getDouble("balance_threshold"), + balanceThresholdEnabled = jsonResponse.getBoolean("balance_threshold_enabled"), + ) + ) + } catch (e: Exception) { + onFailure(ApiFailure(e.message)) + println("API response error: $stringResponse") + } } else { onFailure(ApiFailure("${info?.httpStatusCode} ${info?.httpStatusText}")) println("API error: $stringResponse") diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6adf639..7c12a4d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,8 @@ vast.app Dashboard - vast.app + Login + vast.app Dashboard Billing Instances @@ -21,14 +22,13 @@ checkbox is angry none yet sorry If you change your mind, do so from settings - Termuxn\'t - TermuxSshActivity + Termux Error No ssh client on termux, install dropbear or openssh package Copied command to clipboard Install Dropbear with: Open Termux Termux is not configured for usage with other apps. Open instructions on github.com - An error occured: + An error occurred: (this will be a webview) \ No newline at end of file