From 8aeec0c0827e096672fcafcc9fbda8198ef43241 Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Mon, 29 Jul 2024 23:42:00 +0700 Subject: [PATCH] AppDetailsFragment: Add data safety section Signed-off-by: Aayush Gupta --- .../view/custom/layouts/DevInfoLayout.kt | 19 ++++++++++ .../view/ui/details/AppDetailsFragment.kt | 38 +++++++++++++++++++ .../viewmodel/details/AppDetailsViewModel.kt | 21 ++++++++++ app/src/main/res/drawable/ic_cloud_upload.xml | 11 ++++++ app/src/main/res/layout/fragment_details.xml | 4 ++ .../res/layout/layout_details_data_safety.xml | 35 +++++++++++++++++ app/src/main/res/values/strings.xml | 2 + 7 files changed, 130 insertions(+) create mode 100644 app/src/main/res/drawable/ic_cloud_upload.xml create mode 100644 app/src/main/res/layout/layout_details_data_safety.xml diff --git a/app/src/main/java/com/aurora/store/view/custom/layouts/DevInfoLayout.kt b/app/src/main/java/com/aurora/store/view/custom/layouts/DevInfoLayout.kt index 1c3c69124..4ce3fcbe1 100644 --- a/app/src/main/java/com/aurora/store/view/custom/layouts/DevInfoLayout.kt +++ b/app/src/main/java/com/aurora/store/view/custom/layouts/DevInfoLayout.kt @@ -22,6 +22,8 @@ package com.aurora.store.view.custom.layouts import android.content.Context import android.util.AttributeSet import android.widget.RelativeLayout +import androidx.appcompat.widget.AppCompatImageView +import androidx.core.view.isVisible import com.aurora.store.R import com.aurora.store.databinding.ViewDevInfoBinding @@ -29,6 +31,16 @@ class DevInfoLayout : RelativeLayout { private lateinit var binding: ViewDevInfoBinding + val icon: AppCompatImageView get() = binding.img + + var title: String + get() = binding.txtTitle.text.toString() + set(value) = setTxtTitle(value) + + var subTitle: String? + get() = binding.txtSubtitle.text.toString() + set(value) = setTxtSubtitle(value) + constructor(context: Context) : super(context) { init(context, null) } @@ -64,8 +76,15 @@ class DevInfoLayout : RelativeLayout { typedArray.recycle() } + fun setTxtTitle(text: String?) { + binding.txtTitle.text = text + binding.txtTitle.isVisible = text != null + invalidate() + } + fun setTxtSubtitle(text: String?) { binding.txtSubtitle.text = text + binding.txtSubtitle.isVisible = text != null invalidate() } } diff --git a/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsFragment.kt b/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsFragment.kt index 6d4210741..b986a2c21 100644 --- a/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsFragment.kt @@ -51,6 +51,7 @@ import com.aurora.gplayapi.data.models.File import com.aurora.gplayapi.data.models.Review import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.gplayapi.data.models.datasafety.EntryType import com.aurora.store.AppStreamStash import com.aurora.store.AuroraApp import com.aurora.store.PermissionType @@ -89,6 +90,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import java.util.Locale import javax.inject.Inject +import com.aurora.gplayapi.data.models.datasafety.Report as DataSafetyReport @AndroidEntryPoint class AppDetailsFragment : BaseFragment() { @@ -264,6 +266,11 @@ class AppDetailsFragment : BaseFragment() { } } + // Data Safety Report + viewLifecycleOwner.lifecycleScope.launch { + viewModel.dataSafetyReport.collect { updateDataSafetyViews(it) } + } + // Exodus Privacy Report viewLifecycleOwner.lifecycleScope.launch { viewModel.exodusReport.collect { report -> @@ -687,6 +694,7 @@ class AppDetailsFragment : BaseFragment() { inflateAppDescription(app) inflateAppRatingAndReviews(app) inflateAppDevInfo(app) + inflateAppDataSafety(app) inflateAppPrivacy(app) inflateAppPermission(app) @@ -814,6 +822,10 @@ class AppDetailsFragment : BaseFragment() { } } + private fun inflateAppDataSafety(app: App) { + viewModel.fetchAppDataSafetyReport(app.packageName) + } + private fun inflateAppPrivacy(app: App) { viewModel.fetchAppReport(app.packageName) } @@ -953,6 +965,32 @@ class AppDetailsFragment : BaseFragment() { } } + private fun updateDataSafetyViews(report: DataSafetyReport) { + report.entries.groupBy { it.type }.forEach { (type, entries) -> + when (type) { + EntryType.DATA_COLLECTED -> { + binding.layoutDetailsDataSafety.dataCollect.title = HtmlCompat.fromHtml( + entries.first().description, + HtmlCompat.FROM_HTML_MODE_COMPACT + ).toString() + binding.layoutDetailsDataSafety.dataCollect.subTitle = + entries.first().subEntries.joinToString(", ") { it.name }.ifBlank { null } + } + + EntryType.DATA_SHARED -> { + binding.layoutDetailsDataSafety.dataShare.title = HtmlCompat.fromHtml( + entries.first().description, + HtmlCompat.FROM_HTML_MODE_COMPACT + ).toString() + binding.layoutDetailsDataSafety.dataShare.subTitle = + entries.first().subEntries.joinToString(", ") { it.name }.ifBlank { null } + } + + else -> {} + } + } + } + /* App Review Helpers */ private fun addAvgReviews(number: Int, max: Long, rating: Long): RelativeLayout { diff --git a/app/src/main/java/com/aurora/store/viewmodel/details/AppDetailsViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/details/AppDetailsViewModel.kt index 54b2c67b2..af1737e07 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/details/AppDetailsViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/details/AppDetailsViewModel.kt @@ -9,6 +9,7 @@ import com.aurora.gplayapi.data.models.Review import com.aurora.gplayapi.data.models.details.TestingProgramStatus import com.aurora.gplayapi.helpers.AppDetailsHelper import com.aurora.gplayapi.helpers.ReviewsHelper +import com.aurora.gplayapi.helpers.web.WebDataSafetyHelper import com.aurora.store.data.model.ExodusReport import com.aurora.store.data.model.Report import com.aurora.store.data.network.HttpClient @@ -28,6 +29,7 @@ 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 @HiltViewModel class AppDetailsViewModel @Inject constructor( @@ -58,6 +60,10 @@ class AppDetailsViewModel @Inject constructor( private val _userReview = MutableSharedFlow() val userReview = _userReview.asSharedFlow() + private val dataSafetyReportStash = mutableMapOf() + private val _dataSafetyReport = MutableSharedFlow() + val dataSafetyReport = _dataSafetyReport.asSharedFlow() + private val exodusReportStash = mutableMapOf() private val _exodusReport = MutableSharedFlow() val exodusReport = _exodusReport.asSharedFlow() @@ -103,6 +109,21 @@ class AppDetailsViewModel @Inject constructor( } } + fun fetchAppDataSafetyReport(packageName: String) { + viewModelScope.launch(Dispatchers.IO) { + try { + val report = dataSafetyReportStash.getOrPut(packageName) { + WebDataSafetyHelper() + .using(httpClient) + .fetch(packageName) + } + _dataSafetyReport.emit(report) + } catch (exception: Exception) { + Log.e(TAG, "Failed to fetch data safety report", exception) + } + } + } + fun fetchAppReport(packageName: String) { viewModelScope.launch(Dispatchers.IO) { try { diff --git a/app/src/main/res/drawable/ic_cloud_upload.xml b/app/src/main/res/drawable/ic_cloud_upload.xml new file mode 100644 index 000000000..ecf35e1d8 --- /dev/null +++ b/app/src/main/res/drawable/ic_cloud_upload.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index ae87ad01b..77ae231ef 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -103,6 +103,10 @@ android:id="@+id/layout_details_permissions" layout="@layout/layout_details_permissions" /> + + diff --git a/app/src/main/res/layout/layout_details_data_safety.xml b/app/src/main/res/layout/layout_details_data_safety.xml new file mode 100644 index 000000000..4caabe496 --- /dev/null +++ b/app/src/main/res/layout/layout_details_data_safety.xml @@ -0,0 +1,35 @@ + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c36ec7db4..e6526d024 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -127,6 +127,8 @@ "No apps found on sale" "Paid" "Permission" + Data safety + Data privacy and security practices declared by developer "Privacy" "Rating and reviews" "Review title"