feat: Compatibility status

This commit is contained in:
Mathew Tkachuk committed 2025-03-08 16:33:46 +03:00
1 parent 479deff61a
commit 3fbd6b61bd
7 files changed
+247 -56

No files matched your search

@@ -8,4 +8,9 @@
<string name="cant_update_overport">overport libraries cannot be downloaded.</string> <string name="cant_update_overport">overport libraries cannot be downloaded.</string>
<string name="unknown_error">Unknown error has been occurred.</string> <string name="unknown_error">Unknown error has been occurred.</string>
<string name="update_available">Update available!</string> <string name="update_available">Update available!</string>
<string name="status_unknown">Unknown</string>
<string name="status_playable">Playable</string>
<string name="status_has_problems">Has problems</string>
<string name="status_not_working">Not working</string>
<string name="status_loading">Loading…</string>
</resources> </resources>
@@ -0,0 +1,32 @@
package moe.crx.ovrport.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GithubIssueLabel(
@SerialName("id")
val id: Long,
@SerialName("name")
val name: String,
@SerialName("color")
val color: String,
)
@Serializable
data class GithubIssue(
@SerialName("html_url")
var htmlUrl: String,
@SerialName("title")
val title: String,
@SerialName("created_at")
val createdAt: String,
@SerialName("labels")
val labels: List<GithubIssueLabel>
)
@Serializable
data class GithubIssues(
@SerialName("items")
val items: List<GithubIssue>
)
@@ -1,9 +1,130 @@
package moe.crx.ovrport.patch package moe.crx.ovrport.patch
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import kotlinx.serialization.json.Json
import moe.crx.ovrport.model.GithubIssues
import moe.crx.ovrport.patch.Constants.UNKNOWN_COMPATIBILITY
import moe.crx.ovrport.utils.HttpUtil.download
import org.jetbrains.compose.resources.StringResource
import overportapp.composeapp.generated.resources.*
import overportapp.composeapp.generated.resources.Res
import overportapp.composeapp.generated.resources.status_has_problems
import overportapp.composeapp.generated.resources.status_not_working
import overportapp.composeapp.generated.resources.status_unknown
import java.net.URLEncoder
private val json = Json { ignoreUnknownKeys = true }
object Constants { object Constants {
const val APK_MIMETYPE = "application/vnd.android.package-archive" const val APK_MIMETYPE = "application/vnd.android.package-archive"
const val OVRPORT_VERSION = "3.2.2" const val OVRPORT_VERSION = "3.2.2"
const val LIBRARIES_URL = "https://files.crx.moe/ovrport/tracker/releases/download/$OVRPORT_VERSION/libraries.zip" const val LIBRARIES_URL = "https://files.crx.moe/ovrport/tracker/releases/download/$OVRPORT_VERSION/libraries.zip"
const val LIBRARIES_DIR = "libraries/$OVRPORT_VERSION" const val LIBRARIES_DIR = "libraries/$OVRPORT_VERSION"
const val RELEASES_URL = "https://api.github.com/repos/ovrport/app/releases" const val RELEASES_URL = "https://api.github.com/repos/ovrport/app/releases"
const val ISSUES_URL = "https://api.github.com/search/issues?q=%s+repo:ovrport/tracker"
private const val TRACKER_URL = "https://github.com/ovrport/tracker/issues"
val UNKNOWN_COMPATIBILITY = CompatibilityStatusInfo(CompatibilityStatus.UNKNOWN, TRACKER_URL)
val LOADING_COMPATIBILITY = CompatibilityStatusInfo(CompatibilityStatus.LOADING, TRACKER_URL)
}
enum class CompatibilityStatus(
val labelName: String,
val icon: ImageVector,
val nameResource: StringResource,
val cardColor: Color,
val contentColor: Color,
) {
LOADING(
"Loading",
Icons.Default.Sync,
Res.string.status_loading,
Color(0xFF483f77),
Color(0xFFe6deff),
),
UNKNOWN(
"Unknown",
Icons.Default.QuestionMark,
Res.string.status_unknown,
Color(0xFF354479),
Color(0xFFDCE1FF),
),
NOT_WORKING(
"Not working",
Icons.Default.Close,
Res.string.status_not_working,
Color(0xFF73332f),
Color(0xFFffdad7),
),
HAS_PROBLEMS(
"Has problems",
Icons.Default.Warning,
Res.string.status_has_problems,
Color(0xFF5b4300),
Color(0xFFffdf9f),
),
PLAYABLE(
"Playable",
Icons.Default.Check,
Res.string.status_playable,
Color(0xFF2d4f1d),
Color(0xFFc4efac),
),
}
data class CompatibilityStatusInfo(
val status: CompatibilityStatus,
val url: String,
)
fun getCompatibilityStatus(
applicationName: String?,
applicationPackage: String?
): CompatibilityStatusInfo {
val pkg = applicationName ?: ""
val name = applicationPackage ?: ""
runCatching {
val issue = json
.decodeFromString<GithubIssues>(
String(
download(
Constants.ISSUES_URL.format(
URLEncoder.encode(
pkg,
Charsets.UTF_8
)
)
)
)
)
.items.firstOrNull { it.title.contains(pkg) || it.title.contains(name) }
?: json
.decodeFromString<GithubIssues>(
String(
download(
Constants.ISSUES_URL.format(
URLEncoder.encode(
name,
Charsets.UTF_8
)
)
)
)
)
.items.firstOrNull { it.title.contains(pkg) || it.title.contains(name) }
issue?.labels?.forEach { label ->
CompatibilityStatus.entries.forEach { status ->
if (status.labelName == label.name) {
return CompatibilityStatusInfo(status, issue.htmlUrl)
}
}
}
}
return UNKNOWN_COMPATIBILITY
} }
@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CloudDownload import androidx.compose.material.icons.filled.CloudDownload
import androidx.compose.material.icons.filled.Download
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -79,7 +78,6 @@ fun AppContent(
Constants.OVRPORT_VERSION, Constants.OVRPORT_VERSION,
modifier = Modifier.padding(8.dp), modifier = Modifier.padding(8.dp),
fontSize = 18.sp, fontSize = 18.sp,
color = MaterialTheme.colorScheme.secondary
) )
} }
}, },
@@ -103,7 +101,6 @@ fun AppContent(
Text( Text(
stringResource(Res.string.update_available), stringResource(Res.string.update_available),
fontSize = 18.sp, fontSize = 18.sp,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.padding(0.dp, 8.dp, 8.dp, 8.dp), modifier = Modifier.padding(0.dp, 8.dp, 8.dp, 8.dp),
) )
} }
@@ -2,43 +2,110 @@ package moe.crx.ovrport.ui
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.runtime.Composable import androidx.compose.material.icons.filled.Widgets
import androidx.compose.runtime.mutableStateListOf import androidx.compose.material3.*
import androidx.compose.runtime.remember import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import moe.crx.ovrport.patch.Constants.LOADING_COMPATIBILITY
import moe.crx.ovrport.patch.Patch import moe.crx.ovrport.patch.Patch
import moe.crx.ovrport.patch.PatchStore import moe.crx.ovrport.patch.PatchStore
import org.jetbrains.compose.ui.tooling.preview.Preview import moe.crx.ovrport.patch.getCompatibilityStatus
import androidx.compose.material.icons.filled.Widgets
import androidx.compose.material3.*
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import overportapp.composeapp.generated.resources.Res import overportapp.composeapp.generated.resources.Res
import overportapp.composeapp.generated.resources.unknown_package import overportapp.composeapp.generated.resources.unknown_package
@Composable
fun ApplicationInfo(
applicationName: String? = "Application",
applicationPackage: String? = "com.company.application",
applicationVersion: String? = "1.0.0",
applicationIcon: ImageBitmap? = null,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
if (applicationIcon == null) {
Icon(
Icons.Default.Widgets,
contentDescription = null,
modifier = Modifier.size(48.dp)
)
} else {
Image(
applicationIcon,
contentDescription = null,
modifier = Modifier.size(48.dp).clip(RoundedCornerShape(8.dp))
)
}
Column(
modifier = Modifier.height(48.dp),
verticalArrangement = Arrangement.Center
) {
Text("$applicationName ($applicationVersion)")
Text(
applicationPackage ?: stringResource(Res.string.unknown_package),
)
}
}
}
@Composable
fun ApplicationStatus(
applicationName: String? = "Application",
applicationPackage: String? = "",
) {
var statusInfo by remember(applicationPackage) { mutableStateOf(LOADING_COMPATIBILITY) }
val urlHandler = LocalUriHandler.current
LaunchedEffect(applicationPackage) {
withContext(Dispatchers.IO) {
statusInfo = getCompatibilityStatus(applicationName, applicationPackage)
}
}
Card(colors = CardDefaults.cardColors(statusInfo.status.cardColor, statusInfo.status.contentColor)) {
Row(
verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clip(
RoundedCornerShape(16.dp)
).clickable {
urlHandler.openUri(statusInfo.url)
}) {
Icon(
statusInfo.status.icon,
contentDescription = null,
modifier = Modifier.padding(16.dp),
)
Text(
stringResource(statusInfo.status.nameResource),
fontSize = 18.sp,
modifier = Modifier.padding(0.dp, 16.dp, 16.dp, 16.dp),
)
}
}
}
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
@Preview @Preview
fun ApplicationInfoCard( fun ApplicationInfoCard(
innerPadding: PaddingValues = PaddingValues(16.dp), innerPadding: PaddingValues = PaddingValues(16.dp),
applicationName: String? = "Application", applicationName: String? = "Application",
applicationPackage: String? = "com.company.application", applicationPackage: String? = "",
applicationVersion: String? = "1.0.0", applicationVersion: String? = "1.0.0",
applicationIcon: ImageBitmap? = null, applicationIcon: ImageBitmap? = null,
onCancel: () -> Unit = {}, onCancel: () -> Unit = {},
@@ -49,32 +116,11 @@ fun ApplicationInfoCard(
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
FlowRow( FlowRow(
modifier = Modifier.padding(16.dp), modifier = Modifier.fillMaxWidth().padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.SpaceBetween
) { ) {
if (applicationIcon == null) { ApplicationInfo(applicationName, applicationPackage, applicationVersion, applicationIcon)
Icon( ApplicationStatus(applicationName, applicationPackage)
Icons.Default.Widgets,
contentDescription = null,
modifier = Modifier.size(48.dp)
)
} else {
Image(
applicationIcon,
contentDescription = null,
modifier = Modifier.size(48.dp).clip(RoundedCornerShape(8.dp))
)
}
Column(
modifier = Modifier.height(48.dp),
verticalArrangement = Arrangement.Center
) {
Text("$applicationName ($applicationVersion)")
Text(
applicationPackage ?: stringResource(Res.string.unknown_package),
color = MaterialTheme.colorScheme.secondary
)
}
} }
val patches = remember { mutableStateListOf(*PatchStore.PATCHES.toTypedArray()) } val patches = remember { mutableStateListOf(*PatchStore.PATCHES.toTypedArray()) }
@@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddCircle
import androidx.compose.material.icons.filled.Bolt import androidx.compose.material.icons.filled.Bolt
import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Download
import androidx.compose.material3.Card import androidx.compose.material3.Card
@@ -5,6 +5,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
private val DarkColorScheme = darkColorScheme( private val DarkColorScheme = darkColorScheme(
primary = Purple80, primary = Purple80,
@@ -16,16 +17,6 @@ private val LightColorScheme = lightColorScheme(
primary = Purple40, primary = Purple40,
secondary = PurpleGrey40, secondary = PurpleGrey40,
tertiary = Pink40, 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 @Composable