diff --git a/app/src/main/java/com/sergay/hhu67/pw/app/data/models/AnswerModels.kt b/app/src/main/java/com/sergay/hhu67/pw/app/data/models/AnswerModels.kt index 19ca87f..d056824 100644 --- a/app/src/main/java/com/sergay/hhu67/pw/app/data/models/AnswerModels.kt +++ b/app/src/main/java/com/sergay/hhu67/pw/app/data/models/AnswerModels.kt @@ -43,3 +43,10 @@ data class AnswerBotList( @SerializedName("total") val total: Int, @SerializedName("items") val items: List? ) + +data class ReleaseInfo( + @SerializedName("tag_name") val tagName: String, + @SerializedName("name") val name: String?, + @SerializedName("body") val body: String?, + @SerializedName("html_url") val htmlUrl: String +) diff --git a/app/src/main/java/com/sergay/hhu67/pw/app/data/network/ApiService.kt b/app/src/main/java/com/sergay/hhu67/pw/app/data/network/ApiService.kt index 839e969..8f7513f 100644 --- a/app/src/main/java/com/sergay/hhu67/pw/app/data/network/ApiService.kt +++ b/app/src/main/java/com/sergay/hhu67/pw/app/data/network/ApiService.kt @@ -18,6 +18,11 @@ interface IpService { suspend fun getIpInfo(): IpInfo } +interface ForgejoService { + @GET("api/v1/repos/hhu67/sergay-app/releases/latest") + suspend fun getLatestRelease(): ReleaseInfo +} + interface ApiService { @FormUrlEncoded @POST("api/login") @@ -116,5 +121,13 @@ interface ApiService { .build() .create(IpService::class.java) } + + fun createForgejoService(): ForgejoService { + return Retrofit.Builder() + .baseUrl("https://code.hhu67.pw/") + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(ForgejoService::class.java) + } } } diff --git a/app/src/main/java/com/sergay/hhu67/pw/app/ui/screens/MainAppScreen.kt b/app/src/main/java/com/sergay/hhu67/pw/app/ui/screens/MainAppScreen.kt index fdb8ef1..6eff2f8 100644 --- a/app/src/main/java/com/sergay/hhu67/pw/app/ui/screens/MainAppScreen.kt +++ b/app/src/main/java/com/sergay/hhu67/pw/app/ui/screens/MainAppScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties @@ -41,6 +42,7 @@ import kotlin.system.exitProcess @Composable fun MainAppScreen(vm: MainViewModel) { val ctx = LocalContext.current + val uriHandler = LocalUriHandler.current val scope = rememberCoroutineScope() val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) @@ -178,6 +180,41 @@ fun MainAppScreen(vm: MainViewModel) { icon = { Icon(Icons.Default.BrightnessLow, contentDescription = null, tint = Color.Blue) }, modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding) ) + Spacer(Modifier.weight(1f)) + HorizontalDivider() + NavigationDrawerItem( + label = { + Column { + Text("Проверить обновления") + if (vm.updateCheckMessage.isNotEmpty()) { + Text(vm.updateCheckMessage, style = MaterialTheme.typography.labelSmall, color = Color.Gray) + } + } + }, + selected = false, + onClick = { + vm.checkForUpdates("1.0") + }, + icon = { + if (vm.isCheckingUpdates) { + CircularProgressIndicator(modifier = Modifier.size(24.dp), strokeWidth = 2.dp) + } else { + Icon(Icons.Default.Update, contentDescription = null) + } + }, + modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding) + ) + vm.updateInfo?.let { info -> + NavigationDrawerItem( + label = { Text("Скачать ${info.tagName}", color = MaterialTheme.colorScheme.primary) }, + selected = false, + onClick = { + uriHandler.openUri(info.htmlUrl) + }, + icon = { Icon(Icons.Default.Download, contentDescription = null, tint = MaterialTheme.colorScheme.primary) }, + modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding) + ) + } } } ) { diff --git a/app/src/main/java/com/sergay/hhu67/pw/app/ui/viewmodel/MainViewModel.kt b/app/src/main/java/com/sergay/hhu67/pw/app/ui/viewmodel/MainViewModel.kt index 3668b09..9f98f88 100644 --- a/app/src/main/java/com/sergay/hhu67/pw/app/ui/viewmodel/MainViewModel.kt +++ b/app/src/main/java/com/sergay/hhu67/pw/app/ui/viewmodel/MainViewModel.kt @@ -1,8 +1,13 @@ package com.sergay.hhu67.pw.app.ui.viewmodel import android.content.Context +import android.content.Intent import android.content.SharedPreferences +import android.net.Uri +import android.os.Build +import android.provider.Settings import androidx.compose.runtime.* +import androidx.core.content.FileProvider import androidx.core.content.edit import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -10,10 +15,17 @@ import androidx.lifecycle.viewModelScope import com.sergay.hhu67.pw.app.data.models.* import com.sergay.hhu67.pw.app.data.network.ApiService import com.sergay.hhu67.pw.app.data.network.globalCookieJar +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody +import okhttp3.OkHttpClient +import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody +import java.io.File +import java.io.FileOutputStream import kotlin.math.ceil enum class AppTab(val title: String) { @@ -65,6 +77,12 @@ class MainViewModel(appContext: Context) : ViewModel() { var inoagentModeActive by mutableStateOf(false) var showInoagentDialog by mutableStateOf(false) + var updateInfo by mutableStateOf(null) + var isCheckingUpdates by mutableStateOf(false) + var updateCheckMessage by mutableStateOf("") + + private val forgejo = ApiService.createForgejoService() + init { if (isAuthenticated) { checkRoleAndRestore() @@ -290,6 +308,27 @@ class MainViewModel(appContext: Context) : ViewModel() { inoagentModeActive = true } + fun checkForUpdates(currentVersion: String) { + viewModelScope.launch { + isCheckingUpdates = true + updateCheckMessage = "" + updateInfo = null + try { + val latest = forgejo.getLatestRelease() + if (latest.tagName != currentVersion) { + updateInfo = latest + updateCheckMessage = "Доступно обновление: ${latest.tagName}" + } else { + updateCheckMessage = "У вас последняя версия" + } + } catch (e: Exception) { + updateCheckMessage = "Ошибка проверки: ${e.localizedMessage}" + } finally { + isCheckingUpdates = false + } + } + } + fun deleteBotPhrase(id: Int) { viewModelScope.launch { try {