diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt index 2ec2684ac..ea6e9cfc8 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt @@ -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 diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/ExodusScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/ExodusScreen.kt index b8bbe2041..6ee06b031 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/ExodusScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/ExodusScreen.kt @@ -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 = 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) + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/details/ExodusViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/details/ExodusViewModel.kt index 828f3c105..18816a8c3 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/details/ExodusViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/details/ExodusViewModel.kt @@ -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>(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() diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml new file mode 100644 index 000000000..f448bafd0 --- /dev/null +++ b/app/src/main/res/drawable/ic_scan.xml @@ -0,0 +1,14 @@ + + + + +