compose: exodus: Add back button to request new anaylsis

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2025-12-03 13:29:02 +08:00
parent 9d405ce20a
commit 6767ed7ea7
4 changed files with 115 additions and 43 deletions

View File

@@ -393,7 +393,7 @@ private fun ScreenContentApp(
Privacy(
report = exodusReport,
onNavigateToDetailsExodus = if (!exodusReport?.trackers.isNullOrEmpty()) {
onNavigateToDetailsExodus = if (exodusReport?.id != -1) {
{ showExtraPane(ExtraScreen.Exodus) }
} else {
null

View File

@@ -9,31 +9,38 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.aurora.Constants.EXODUS_REPORT_URL
import com.aurora.Constants.EXODUS_SUBMIT_PAGE
import com.aurora.extensions.adaptiveNavigationIcon
import com.aurora.extensions.browse
import com.aurora.extensions.isWindowCompact
import com.aurora.gplayapi.data.models.App
import com.aurora.store.R
import com.aurora.store.compose.composable.Error
import com.aurora.store.compose.composable.Header
import com.aurora.store.compose.composable.TopAppBar
import com.aurora.store.compose.composable.details.ExodusListItem
import com.aurora.store.compose.preview.AppPreviewProvider
import com.aurora.store.compose.preview.PreviewTemplate
import com.aurora.store.data.model.ExodusTracker
import com.aurora.store.data.model.Report
import com.aurora.store.viewmodel.details.AppDetailsViewModel
import com.aurora.store.viewmodel.details.ExodusViewModel
@@ -42,39 +49,52 @@ fun ExodusScreen(
packageName: String,
onNavigateUp: () -> Unit,
appDetailsViewModel: AppDetailsViewModel = hiltViewModel(key = packageName),
exodusViewModel: ExodusViewModel = hiltViewModel(
key = "$packageName/exodus",
creationCallback = { factory: ExodusViewModel.Factory ->
factory.create(appDetailsViewModel.exodusReport.value!!)
}
),
exodusViewModel: ExodusViewModel = hiltViewModel(key = "$packageName/exodus"),
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
) {
val context = LocalContext.current
val app by appDetailsViewModel.app.collectAsStateWithLifecycle()
val exodusReport by appDetailsViewModel.exodusReport.collectAsStateWithLifecycle()
val report by appDetailsViewModel.exodusReport.collectAsStateWithLifecycle()
val trackers by exodusViewModel.trackers.collectAsStateWithLifecycle()
LaunchedEffect(key1 = Unit) {
report?.let { exodusViewModel.getExodusTrackersFromReport(it) }
}
val topAppBarTitle = when {
windowAdaptiveInfo.isWindowCompact -> app!!.displayName
else -> stringResource(R.string.details_privacy)
}
ScreenContent(
topAppBarTitle = topAppBarTitle,
id = exodusReport!!.id,
version = exodusReport!!.version,
trackers = trackers,
onNavigateUp = onNavigateUp,
)
when (report) {
null -> {
ScreenContentError(
topAppBarTitle = topAppBarTitle,
onNavigateUp = onNavigateUp,
onRequestAnalysis = { context.browse("${EXODUS_SUBMIT_PAGE}${app!!.packageName}") }
)
}
else -> {
ScreenContentReport(
topAppBarTitle = topAppBarTitle,
report = report,
trackers = trackers,
onNavigateUp = onNavigateUp,
onRequestAnalysis = { context.browse("${EXODUS_SUBMIT_PAGE}${app!!.packageName}") }
)
}
}
}
@Composable
private fun ScreenContent(
private fun ScreenContentReport(
topAppBarTitle: String? = null,
id: Int = -1,
version: String = String(),
report: Report? = null,
trackers: List<ExodusTracker> = emptyList(),
onNavigateUp: () -> Unit = {},
onRequestAnalysis: () -> Unit = {},
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
) {
val context = LocalContext.current
@@ -86,6 +106,14 @@ private fun ScreenContent(
navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon,
onNavigateUp = onNavigateUp
)
},
floatingActionButton = {
FloatingActionButton(onClick = onRequestAnalysis) {
Icon(
painter = painterResource(R.drawable.ic_scan),
contentDescription = stringResource(R.string.action_request_analysis)
)
}
}
) { paddingValues ->
LazyColumn(
@@ -96,9 +124,17 @@ private fun ScreenContent(
) {
stickyHeader {
Header(
title = stringResource(R.string.exodus_report_trackers, trackers.size, version),
title = if (report?.trackers.isNullOrEmpty()) {
stringResource(R.string.exodus_no_tracker)
} else {
stringResource(
R.string.exodus_report_trackers,
report.trackers.size,
report.version
)
},
subtitle = stringResource(R.string.exodus_view_report),
onClick = { context.browse(EXODUS_REPORT_URL + id) }
onClick = { context.browse(EXODUS_REPORT_URL + report!!.id) }
)
}
@@ -109,13 +145,47 @@ private fun ScreenContent(
}
}
@Preview
/**
* Composable to display errors related to fetching app details
*/
@Composable
private fun ExodusScreenPreview(@PreviewParameter(AppPreviewProvider::class) app: App) {
PreviewTemplate {
ScreenContent(
topAppBarTitle = app.displayName,
version = app.versionName
private fun ScreenContentError(
topAppBarTitle: String? = null,
onNavigateUp: () -> Unit = {},
onRequestAnalysis: () -> Unit = {},
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
) {
Scaffold(
topBar = {
TopAppBar(
title = topAppBarTitle,
navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon,
onNavigateUp = onNavigateUp
)
},
) { paddingValues ->
Error(
modifier = Modifier.padding(paddingValues),
painter = painterResource(R.drawable.ic_disclaimer),
message = stringResource(R.string.failed_to_fetch_report),
actionMessage = stringResource(R.string.action_request_analysis),
onAction = onRequestAnalysis
)
}
}
@Preview
@Composable
private fun ExodusScreenPreviewReport(@PreviewParameter(AppPreviewProvider::class) app: App) {
PreviewTemplate {
ScreenContentReport(topAppBarTitle = app.displayName)
}
}
@Preview
@Composable
private fun ExodusScreenPreviewError(@PreviewParameter(AppPreviewProvider::class) app: App) {
PreviewTemplate {
ScreenContentError(topAppBarTitle = app.displayName)
}
}

View File

@@ -9,37 +9,25 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.store.data.model.ExodusTracker
import com.aurora.store.data.model.Report
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.json.JSONObject
import javax.inject.Inject
@HiltViewModel(assistedFactory = ExodusViewModel.Factory::class)
class ExodusViewModel @AssistedInject constructor(
@Assisted private val exodusReport: Report,
@HiltViewModel
class ExodusViewModel @Inject constructor(
private val exodusTrackers: JSONObject
) : ViewModel() {
@AssistedFactory
interface Factory {
fun create(exodusReport: Report): ExodusViewModel
}
private val _trackers = MutableStateFlow<List<ExodusTracker>>(emptyList())
val trackers = _trackers.asStateFlow()
init {
getExodusTrackersFromReport()
}
private fun getExodusTrackersFromReport() {
fun getExodusTrackersFromReport(report: Report) {
viewModelScope.launch(Dispatchers.IO) {
val trackerObjects = exodusReport.trackers.map {
val trackerObjects = report.trackers.map {
exodusTrackers.getJSONObject(it.toString())
}.toList()

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: Material Design Authors / Google LLC
~ SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,680L240,680L240,800Q240,800 240,800Q240,800 240,800L720,800Q720,800 720,800Q720,800 720,800L720,680L800,680L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM160,440L160,160Q160,127 183.5,103.5Q207,80 240,80L560,80L800,320L800,440L720,440L720,360L520,360L520,160L240,160Q240,160 240,160Q240,160 240,160L240,440L160,440ZM40,600L40,520L920,520L920,600L40,600ZM480,440L480,440L480,440L480,440L480,440Q480,440 480,440Q480,440 480,440ZM480,680L480,680Q480,680 480,680Q480,680 480,680L480,680Q480,680 480,680Q480,680 480,680L480,680Z"/>
</vector>