AppDetailsFragment: Implement compatibility section using plexus
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
42
app/src/main/java/com/aurora/store/data/model/Plexus.kt
Normal file
42
app/src/main/java/com/aurora/store/data/model/Plexus.kt
Normal file
@@ -0,0 +1,42 @@
|
||||
package com.aurora.store.data.model
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.aurora.store.R
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class PlexusReport(
|
||||
@SerializedName("data")
|
||||
val report: Data?
|
||||
)
|
||||
|
||||
data class Data(
|
||||
val name: String,
|
||||
val scores: Scores,
|
||||
@SerializedName("updated_at")
|
||||
val updatedAt: String
|
||||
)
|
||||
|
||||
data class Scores(
|
||||
@SerializedName("micro_g")
|
||||
val microG: Rating,
|
||||
@SerializedName("native")
|
||||
val aosp: Rating
|
||||
)
|
||||
|
||||
data class Rating(
|
||||
val denominator: Float,
|
||||
val numerator: Float,
|
||||
val rating_type: String,
|
||||
val total_count: Long
|
||||
) {
|
||||
private val fraction get() = numerator / denominator
|
||||
|
||||
@get:StringRes
|
||||
val status: Int
|
||||
get() = when {
|
||||
fraction == 0F -> R.string.details_compatibility_status_unknown
|
||||
fraction >= 0.90 -> R.string.details_compatibility_status_compatible
|
||||
fraction >= 0.50 -> R.string.details_compatibility_status_limited
|
||||
else -> R.string.details_compatibility_status_unsupported
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,7 @@ object OkHttpClientModule {
|
||||
.add("auroraoss.com", "sha256/mEflZT5enoR1FuXLgYYGqnVEoZvmf9c2bVBpiOjYQ0c=") // GTS Root R4
|
||||
.add("*.exodus-privacy.eu.org", "sha256/C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=") // ISRG Root X1
|
||||
.add("gitlab.com", "sha256/x4QzPSC810K5/cMjb05Qm4k3Bw5zBn4lTdO/nEW/Td4=") // USERTrust RSA Certification Authority
|
||||
.add("plexus.techlore.tech", "sha256/C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=") // ISRG Root X1
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ object PackageUtil {
|
||||
|
||||
private const val TAG = "PackageUtil"
|
||||
|
||||
private const val PACKAGE_NAME_MICRO_G = "com.google.android.gms"
|
||||
const val PACKAGE_NAME_GMS = "com.google.android.gms"
|
||||
private const val VERSION_CODE_MICRO_G = 240913402
|
||||
private const val VERSION_CODE_MICRO_G_HUAWEI = 240913007
|
||||
|
||||
@@ -66,15 +66,15 @@ object PackageUtil {
|
||||
}
|
||||
|
||||
fun hasSupportedMicroG(context: Context): Boolean {
|
||||
val isMicroG = CertUtil.isMicroGGMS(context, PACKAGE_NAME_MICRO_G)
|
||||
val isMicroG = CertUtil.isMicroGGMS(context, PACKAGE_NAME_GMS)
|
||||
|
||||
// Do not proceed if MicroG variant is not installed
|
||||
if (!isMicroG) return false
|
||||
|
||||
return if (isHuawei) {
|
||||
isInstalled(context, PACKAGE_NAME_MICRO_G, VERSION_CODE_MICRO_G_HUAWEI)
|
||||
isInstalled(context, PACKAGE_NAME_GMS, VERSION_CODE_MICRO_G_HUAWEI)
|
||||
} else {
|
||||
isInstalled(context, PACKAGE_NAME_MICRO_G, VERSION_CODE_MICRO_G)
|
||||
isInstalled(context, PACKAGE_NAME_GMS, VERSION_CODE_MICRO_G)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,9 @@ package com.aurora.store.view.custom.layouts
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.ViewDevInfoBinding
|
||||
@@ -87,4 +89,12 @@ class DevInfoLayout : RelativeLayout {
|
||||
binding.txtSubtitle.isVisible = text != null
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun setTitleColor(@ColorRes color: Int) {
|
||||
binding.txtTitle.setTextColor(ContextCompat.getColor(context, color))
|
||||
}
|
||||
|
||||
fun setSubtitleColor(@ColorRes color: Int) {
|
||||
binding.txtSubtitle.setTextColor(ContextCompat.getColor(context, color))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
updateToolbar(app)
|
||||
updateAppHeader(app) // Re-inflate the app details, as web data may vary.
|
||||
updateExtraDetails(app)
|
||||
updateCompatibilityInfo()
|
||||
|
||||
if (app.versionCode == 0) {
|
||||
warnAppUnavailable(app)
|
||||
@@ -331,6 +332,27 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
// Plexus Report
|
||||
viewModel.plexusReport.onEach { plexusReport ->
|
||||
if (plexusReport?.report?.scores == null) {
|
||||
binding.layoutDetailsCompatibility.txtStatusMicroG.subTitle =
|
||||
getString(R.string.details_compatibility_status_unknown)
|
||||
|
||||
binding.layoutDetailsCompatibility.txtStatusAosp.subTitle =
|
||||
getString(R.string.details_compatibility_status_unknown)
|
||||
} else {
|
||||
binding.layoutDetailsCompatibility.headerCompatibility.addClickListener {
|
||||
requireContext().browse("${Constants.PLEXUS_SEARCH_URL}${app.packageName}")
|
||||
}
|
||||
|
||||
binding.layoutDetailsCompatibility.txtStatusMicroG.subTitle =
|
||||
getString(plexusReport.report.scores.microG.status)
|
||||
|
||||
binding.layoutDetailsCompatibility.txtStatusAosp.subTitle =
|
||||
getString(plexusReport.report.scores.microG.status)
|
||||
}
|
||||
}.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
// Beta program
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.testingProgramStatus.collect {
|
||||
@@ -1013,6 +1035,26 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCompatibilityInfo() {
|
||||
if (app.dependencies.dependentPackages.contains(PackageUtil.PACKAGE_NAME_GMS)) {
|
||||
viewModel.fetchPlexusReport(app.packageName)
|
||||
|
||||
binding.layoutDetailsCompatibility.txtGmsDependency.apply {
|
||||
title = getString(R.string.details_compatibility_gms_required_title)
|
||||
subTitle = getString(R.string.details_compatibility_gms_required_subtitle)
|
||||
setTitleColor(R.color.colorRed)
|
||||
}
|
||||
|
||||
binding.layoutDetailsCompatibility.compatibilityStatusLayout.isVisible = true
|
||||
} else {
|
||||
binding.layoutDetailsCompatibility.txtGmsDependency.apply {
|
||||
title = getString(R.string.details_compatibility_gms_not_required_title)
|
||||
subTitle = getString(R.string.details_compatibility_gms_not_required_subtitle)
|
||||
setTitleColor(R.color.colorGreen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun warnAppUnavailable(app: App) {
|
||||
AuroraApp.events.send(InstallerEvent.Failed(app.packageName).apply {
|
||||
error = getString(R.string.status_unavailable)
|
||||
|
||||
@@ -13,16 +13,16 @@ import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||
import com.aurora.gplayapi.helpers.ReviewsHelper
|
||||
import com.aurora.gplayapi.helpers.web.WebDataSafetyHelper
|
||||
import com.aurora.gplayapi.network.IHttpClient
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.helper.DownloadHelper
|
||||
import com.aurora.store.data.model.ExodusReport
|
||||
import com.aurora.store.data.model.PlexusReport
|
||||
import com.aurora.store.data.model.Report
|
||||
import com.aurora.store.data.room.favourite.Favourite
|
||||
import com.aurora.store.data.room.favourite.FavouriteDao
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.Gson
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -35,7 +35,6 @@ import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.json.JSONObject
|
||||
import java.lang.reflect.Modifier
|
||||
import javax.inject.Inject
|
||||
import com.aurora.gplayapi.data.models.datasafety.Report as DataSafetyReport
|
||||
|
||||
@@ -47,7 +46,8 @@ class AppDetailsViewModel @Inject constructor(
|
||||
private val webDataSafetyHelper: WebDataSafetyHelper,
|
||||
private val downloadHelper: DownloadHelper,
|
||||
private val favouriteDao: FavouriteDao,
|
||||
private val httpClient: IHttpClient
|
||||
private val httpClient: IHttpClient,
|
||||
private val gson: Gson
|
||||
) : ViewModel() {
|
||||
|
||||
private val TAG = AppDetailsViewModel::class.java.simpleName
|
||||
@@ -72,6 +72,10 @@ class AppDetailsViewModel @Inject constructor(
|
||||
private val _exodusReport = MutableSharedFlow<Report?>()
|
||||
val exodusReport = _exodusReport.asSharedFlow()
|
||||
|
||||
private val plexusReportStash = mutableMapOf<String, PlexusReport?>()
|
||||
private val _plexusReport = MutableSharedFlow<PlexusReport?>()
|
||||
val plexusReport = _plexusReport.asSharedFlow()
|
||||
|
||||
private val testProgramStatusStash = mutableMapOf<String, TestingProgramStatus?>()
|
||||
private val _testingProgramStatus = MutableSharedFlow<TestingProgramStatus?>()
|
||||
val testingProgramStatus = _testingProgramStatus.asSharedFlow()
|
||||
@@ -147,6 +151,24 @@ class AppDetailsViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchPlexusReport(packageName: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val plexusReport = plexusReportStash.getOrPut(packageName) {
|
||||
val url = "${Constants.PLEXUS_API_URL}/${packageName}/?scores=true"
|
||||
val playResponse = httpClient.get(url, emptyMap())
|
||||
gson.fromJson(String(playResponse.responseBytes), PlexusReport::class.java)
|
||||
}
|
||||
|
||||
_plexusReport.emit(plexusReport)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to fetch compatibility report", exception)
|
||||
plexusReportStash[packageName] = null
|
||||
_plexusReport.emit(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchTestingProgramStatus(packageName: String, subscribe: Boolean) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -258,10 +280,6 @@ class AppDetailsViewModel @Inject constructor(
|
||||
|
||||
private fun parseExodusResponse(response: String, packageName: String): List<Report> {
|
||||
try {
|
||||
val gson = GsonBuilder()
|
||||
.excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC)
|
||||
.create()
|
||||
|
||||
val jsonObject = JSONObject(response)
|
||||
val exodusObject = jsonObject.getJSONObject(packageName)
|
||||
val exodusReport: ExodusReport = gson.fromJson(
|
||||
|
||||
Reference in New Issue
Block a user