AppDetailsFragment: Add data safety section
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -22,6 +22,8 @@ package com.aurora.store.view.custom.layouts
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
|
import androidx.appcompat.widget.AppCompatImageView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.databinding.ViewDevInfoBinding
|
import com.aurora.store.databinding.ViewDevInfoBinding
|
||||||
|
|
||||||
@@ -29,6 +31,16 @@ class DevInfoLayout : RelativeLayout {
|
|||||||
|
|
||||||
private lateinit var binding: ViewDevInfoBinding
|
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) {
|
constructor(context: Context) : super(context) {
|
||||||
init(context, null)
|
init(context, null)
|
||||||
}
|
}
|
||||||
@@ -64,8 +76,15 @@ class DevInfoLayout : RelativeLayout {
|
|||||||
typedArray.recycle()
|
typedArray.recycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setTxtTitle(text: String?) {
|
||||||
|
binding.txtTitle.text = text
|
||||||
|
binding.txtTitle.isVisible = text != null
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
fun setTxtSubtitle(text: String?) {
|
fun setTxtSubtitle(text: String?) {
|
||||||
binding.txtSubtitle.text = text
|
binding.txtSubtitle.text = text
|
||||||
|
binding.txtSubtitle.isVisible = text != null
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import com.aurora.gplayapi.data.models.File
|
|||||||
import com.aurora.gplayapi.data.models.Review
|
import com.aurora.gplayapi.data.models.Review
|
||||||
import com.aurora.gplayapi.data.models.StreamBundle
|
import com.aurora.gplayapi.data.models.StreamBundle
|
||||||
import com.aurora.gplayapi.data.models.StreamCluster
|
import com.aurora.gplayapi.data.models.StreamCluster
|
||||||
|
import com.aurora.gplayapi.data.models.datasafety.EntryType
|
||||||
import com.aurora.store.AppStreamStash
|
import com.aurora.store.AppStreamStash
|
||||||
import com.aurora.store.AuroraApp
|
import com.aurora.store.AuroraApp
|
||||||
import com.aurora.store.PermissionType
|
import com.aurora.store.PermissionType
|
||||||
@@ -89,6 +90,7 @@ import kotlinx.coroutines.flow.filter
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import com.aurora.gplayapi.data.models.datasafety.Report as DataSafetyReport
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||||
@@ -264,6 +266,11 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Data Safety Report
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewModel.dataSafetyReport.collect { updateDataSafetyViews(it) }
|
||||||
|
}
|
||||||
|
|
||||||
// Exodus Privacy Report
|
// Exodus Privacy Report
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
viewModel.exodusReport.collect { report ->
|
viewModel.exodusReport.collect { report ->
|
||||||
@@ -687,6 +694,7 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
|||||||
inflateAppDescription(app)
|
inflateAppDescription(app)
|
||||||
inflateAppRatingAndReviews(app)
|
inflateAppRatingAndReviews(app)
|
||||||
inflateAppDevInfo(app)
|
inflateAppDevInfo(app)
|
||||||
|
inflateAppDataSafety(app)
|
||||||
inflateAppPrivacy(app)
|
inflateAppPrivacy(app)
|
||||||
inflateAppPermission(app)
|
inflateAppPermission(app)
|
||||||
|
|
||||||
@@ -814,6 +822,10 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun inflateAppDataSafety(app: App) {
|
||||||
|
viewModel.fetchAppDataSafetyReport(app.packageName)
|
||||||
|
}
|
||||||
|
|
||||||
private fun inflateAppPrivacy(app: App) {
|
private fun inflateAppPrivacy(app: App) {
|
||||||
viewModel.fetchAppReport(app.packageName)
|
viewModel.fetchAppReport(app.packageName)
|
||||||
}
|
}
|
||||||
@@ -953,6 +965,32 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 */
|
/* App Review Helpers */
|
||||||
|
|
||||||
private fun addAvgReviews(number: Int, max: Long, rating: Long): RelativeLayout {
|
private fun addAvgReviews(number: Int, max: Long, rating: Long): RelativeLayout {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.aurora.gplayapi.data.models.Review
|
|||||||
import com.aurora.gplayapi.data.models.details.TestingProgramStatus
|
import com.aurora.gplayapi.data.models.details.TestingProgramStatus
|
||||||
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||||
import com.aurora.gplayapi.helpers.ReviewsHelper
|
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.ExodusReport
|
||||||
import com.aurora.store.data.model.Report
|
import com.aurora.store.data.model.Report
|
||||||
import com.aurora.store.data.network.HttpClient
|
import com.aurora.store.data.network.HttpClient
|
||||||
@@ -28,6 +29,7 @@ import kotlinx.coroutines.launch
|
|||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import com.aurora.gplayapi.data.models.datasafety.Report as DataSafetyReport
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AppDetailsViewModel @Inject constructor(
|
class AppDetailsViewModel @Inject constructor(
|
||||||
@@ -58,6 +60,10 @@ class AppDetailsViewModel @Inject constructor(
|
|||||||
private val _userReview = MutableSharedFlow<Review>()
|
private val _userReview = MutableSharedFlow<Review>()
|
||||||
val userReview = _userReview.asSharedFlow()
|
val userReview = _userReview.asSharedFlow()
|
||||||
|
|
||||||
|
private val dataSafetyReportStash = mutableMapOf<String, DataSafetyReport>()
|
||||||
|
private val _dataSafetyReport = MutableSharedFlow<DataSafetyReport>()
|
||||||
|
val dataSafetyReport = _dataSafetyReport.asSharedFlow()
|
||||||
|
|
||||||
private val exodusReportStash = mutableMapOf<String, Report?>()
|
private val exodusReportStash = mutableMapOf<String, Report?>()
|
||||||
private val _exodusReport = MutableSharedFlow<Report?>()
|
private val _exodusReport = MutableSharedFlow<Report?>()
|
||||||
val exodusReport = _exodusReport.asSharedFlow()
|
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) {
|
fun fetchAppReport(packageName: String) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
11
app/src/main/res/drawable/ic_cloud_upload.xml
Normal file
11
app/src/main/res/drawable/ic_cloud_upload.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M260,800Q169,800 104.5,737Q40,674 40,583Q40,505 87,444Q134,383 210,366Q235,274 310,217Q385,160 480,160Q597,160 678.5,241.5Q760,323 760,440L760,440L760,440Q829,448 874.5,499.5Q920,551 920,620Q920,695 867.5,747.5Q815,800 740,800L520,800Q487,800 463.5,776.5Q440,753 440,720L440,514L376,576L320,520L480,360L640,520L584,576L520,514L520,720Q520,720 520,720Q520,720 520,720L740,720Q782,720 811,691Q840,662 840,620Q840,578 811,549Q782,520 740,520L680,520L680,440Q680,357 621.5,298.5Q563,240 480,240Q397,240 338.5,298.5Q280,357 280,440L260,440Q202,440 161,481Q120,522 120,580Q120,638 161,679Q202,720 260,720L360,720L360,800L260,800ZM480,520L480,520L480,520Q480,520 480,520Q480,520 480,520L480,520L480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520L480,520L480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520L480,520Z"/>
|
||||||
|
</vector>
|
||||||
@@ -103,6 +103,10 @@
|
|||||||
android:id="@+id/layout_details_permissions"
|
android:id="@+id/layout_details_permissions"
|
||||||
layout="@layout/layout_details_permissions" />
|
layout="@layout/layout_details_permissions" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/layout_details_data_safety"
|
||||||
|
layout="@layout/layout_details_data_safety" />
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/layout_details_privacy"
|
android:id="@+id/layout_details_privacy"
|
||||||
layout="@layout/layout_details_privacy" />
|
layout="@layout/layout_details_privacy" />
|
||||||
|
|||||||
35
app/src/main/res/layout/layout_details_data_safety.xml
Normal file
35
app/src/main/res/layout/layout_details_data_safety.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:divider="@drawable/divider"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="@dimen/padding_small"
|
||||||
|
android:paddingEnd="@dimen/padding_small"
|
||||||
|
android:showDividers="middle">
|
||||||
|
|
||||||
|
<com.aurora.store.view.custom.layouts.ActionHeaderLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:headerSubtitle="@string/details_data_safety_subtitle"
|
||||||
|
app:headerTitle="@string/details_data_safety_title" />
|
||||||
|
|
||||||
|
<com.aurora.store.view.custom.layouts.DevInfoLayout
|
||||||
|
android:id="@+id/data_collect"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:imgIcon="@drawable/ic_cloud_upload"
|
||||||
|
tools:txtSubtitle="Personal info, Financial info and 3 others"
|
||||||
|
tools:txtTitle="This app may collect these data types" />
|
||||||
|
|
||||||
|
<com.aurora.store.view.custom.layouts.DevInfoLayout
|
||||||
|
android:id="@+id/data_share"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:imgIcon="@drawable/ic_share"
|
||||||
|
tools:txtSubtitle="Learn more about how developers declare sharing"
|
||||||
|
tools:txtTitle="No data shared with third parties" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -127,6 +127,8 @@
|
|||||||
<string name="details_no_apps_on_sale">"No apps found on sale"</string>
|
<string name="details_no_apps_on_sale">"No apps found on sale"</string>
|
||||||
<string name="details_paid">"Paid"</string>
|
<string name="details_paid">"Paid"</string>
|
||||||
<string name="details_permission">"Permission"</string>
|
<string name="details_permission">"Permission"</string>
|
||||||
|
<string name="details_data_safety_title">Data safety</string>
|
||||||
|
<string name="details_data_safety_subtitle">Data privacy and security practices declared by developer</string>
|
||||||
<string name="details_privacy">"Privacy"</string>
|
<string name="details_privacy">"Privacy"</string>
|
||||||
<string name="details_ratings">"Rating and reviews"</string>
|
<string name="details_ratings">"Rating and reviews"</string>
|
||||||
<string name="details_ratings_title_hint">"Review title"</string>
|
<string name="details_ratings_title_hint">"Review title"</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user