From 8aeec0c0827e096672fcafcc9fbda8198ef43241 Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Mon, 29 Jul 2024 23:42:00 +0700 Subject: [PATCH 1/6] 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" From 0a25a7e75526814af1402e933a7f9ee6cbe28503 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Mon, 29 Jul 2024 23:53:12 +0530 Subject: [PATCH 2/6] Address button color issues --- .../java/com/aurora/extensions/Commons.kt | 50 +++++++++++++++++++ .../custom/layouts/button/UpdateButton.kt | 16 +++++- .../view/epoxy/views/UpdateHeaderView.kt | 3 ++ app/src/main/res/layout/view_app_update.xml | 1 + .../main/res/layout/view_header_update.xml | 3 +- app/src/main/res/layout/view_state_button.xml | 8 +-- .../main/res/layout/view_update_button.xml | 8 ++- 7 files changed, 78 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/aurora/extensions/Commons.kt b/app/src/main/java/com/aurora/extensions/Commons.kt index af88aebbf..90c2b48a4 100644 --- a/app/src/main/java/com/aurora/extensions/Commons.kt +++ b/app/src/main/java/com/aurora/extensions/Commons.kt @@ -19,11 +19,14 @@ package com.aurora.extensions +import android.graphics.Color import android.text.format.DateFormat +import androidx.annotation.ColorInt import java.io.PrintWriter import java.io.StringWriter import java.util.Calendar import java.util.Locale +import javax.annotation.Nullable fun Long.toDate(): String { val calendar = Calendar.getInstance(Locale.getDefault()) @@ -42,4 +45,51 @@ fun Throwable.stackTraceToString(): String { fun isValidPackageName(packageName: String): Boolean { val packageRegex = "^[a-zA-Z][a-zA-Z0-9_]*(\\.[a-zA-Z][a-zA-Z0-9_]*)*$".toRegex() return packageName.matches(packageRegex) +} + +/** + * Computes a darker color from the given color. + * @param color The color to darken. + * @param factor The factor to darken the color by. + * - higher factor values will result in a lighter color. + * @return The darker color. + */ +fun darkenColor(@ColorInt color: Int, factor: Float = 0.25f): Int { + val a = Color.alpha(color) + val r = (Color.red(color) * factor).coerceIn(0f, 255f).toInt() + val g = (Color.green(color) * factor).coerceIn(0f, 255f).toInt() + val b = (Color.blue(color) * factor).coerceIn(0f, 255f).toInt() + + return Color.argb(a, r, g, b) +} + +/** + * Computes a lighter color from the given color. + * @param color The color to lighten. + * @param factor The factor to lighten the color by. + * - higher factor values will result in a lighter color. + * @param alpha The alpha value to use for the lighter color. + * @return The lighter color. + */ +fun lightenColor(@ColorInt color: Int, factor: Float = 0.5f, @Nullable alpha: Int? = null): Int { + val a = alpha ?: Color.alpha(color) + val r = (Color.red(color) + (255 - Color.red(color)) * factor).toInt() + val g = (Color.green(color) + (255 - Color.green(color)) * factor).toInt() + val b = (Color.blue(color) + (255 - Color.blue(color)) * factor).toInt() + + return Color.argb(a, r, g, b) +} + +/** + * Computes a contrasting color from the given color. + * @param color The color to contrast. + * @return The contrasting color. + */ +fun contrastingColor(@ColorInt color: Int): Int { + val red = Color.red(color) + val green = Color.green(color) + val blue = Color.blue(color) + val yiq = ((red * 299) + (green * 587) + (blue * 114)) / 1000 + + return if (yiq >= 128) Color.BLACK else Color.WHITE } \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/layouts/button/UpdateButton.kt b/app/src/main/java/com/aurora/store/view/custom/layouts/button/UpdateButton.kt index 49ba8a8fe..95a7dcff3 100644 --- a/app/src/main/java/com/aurora/store/view/custom/layouts/button/UpdateButton.kt +++ b/app/src/main/java/com/aurora/store/view/custom/layouts/button/UpdateButton.kt @@ -20,9 +20,13 @@ package com.aurora.store.view.custom.layouts.button import android.content.Context +import android.content.res.ColorStateList import android.util.AttributeSet import android.widget.RelativeLayout +import com.aurora.extensions.accentColor +import com.aurora.extensions.darkenColor import com.aurora.extensions.getString +import com.aurora.extensions.lightenColor import com.aurora.extensions.runOnUiThread import com.aurora.store.R import com.aurora.store.data.model.DownloadStatus @@ -51,6 +55,15 @@ class UpdateButton : RelativeLayout { private fun init(context: Context) { val view = inflate(context, R.layout.view_update_button, this) binding = ViewUpdateButtonBinding.bind(view) + + // Apply primaryColor tint to all buttons with alpha + val alphaAccent = lightenColor(context.accentColor(), alpha = 200) + binding.btnPositive.backgroundTintList = ColorStateList.valueOf(alphaAccent) + binding.btnNegative.backgroundTintList = ColorStateList.valueOf(alphaAccent) + + val textColor = darkenColor(context.accentColor()) + binding.btnPositive.setTextColor(textColor) + binding.btnNegative.setTextColor(textColor) } fun setText(text: String) { @@ -65,9 +78,8 @@ class UpdateButton : RelativeLayout { fun updateState(downloadStatus: DownloadStatus) { val displayChild = when (downloadStatus) { - DownloadStatus.QUEUED, + DownloadStatus.QUEUED -> 1 DownloadStatus.DOWNLOADING -> 2 - else -> 0 } diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/UpdateHeaderView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/UpdateHeaderView.kt index 8faaf1a90..0452a4ebd 100644 --- a/app/src/main/java/com/aurora/store/view/epoxy/views/UpdateHeaderView.kt +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/UpdateHeaderView.kt @@ -25,6 +25,8 @@ import com.airbnb.epoxy.CallbackProp import com.airbnb.epoxy.ModelProp import com.airbnb.epoxy.ModelView import com.airbnb.epoxy.OnViewRecycled +import com.aurora.extensions.accentColor +import com.aurora.extensions.contrastingColor import com.aurora.store.databinding.ViewHeaderUpdateBinding @@ -46,6 +48,7 @@ class UpdateHeaderView @JvmOverloads constructor( @ModelProp fun action(action: String) { binding.btnAction.text = action + binding.btnAction.setTextColor(contrastingColor(context.accentColor())) } @CallbackProp diff --git a/app/src/main/res/layout/view_app_update.xml b/app/src/main/res/layout/view_app_update.xml index 04368ff7b..8e421cd53 100644 --- a/app/src/main/res/layout/view_app_update.xml +++ b/app/src/main/res/layout/view_app_update.xml @@ -106,6 +106,7 @@ android:layout_alignTop="@id/txt_line1" android:layout_toStartOf="@id/btn_action" android:contentDescription="@string/details_changelog" + app:iconTint="?colorControlNormal" app:icon="@drawable/ic_arrow_down" /> + android:text="@string/action_update_all" + android:textAppearance="@style/TextAppearance.Aurora.Line1" /> diff --git a/app/src/main/res/layout/view_state_button.xml b/app/src/main/res/layout/view_state_button.xml index ba6bdd0db..5d0637c62 100644 --- a/app/src/main/res/layout/view_state_button.xml +++ b/app/src/main/res/layout/view_state_button.xml @@ -25,14 +25,16 @@ diff --git a/app/src/main/res/layout/view_update_button.xml b/app/src/main/res/layout/view_update_button.xml index 18a0bccbe..366a5efb0 100644 --- a/app/src/main/res/layout/view_update_button.xml +++ b/app/src/main/res/layout/view_update_button.xml @@ -36,6 +36,7 @@ - - - - - From 1124a100d8b802e6482730d00bfe6aca2000f4c1 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Tue, 30 Jul 2024 01:36:30 +0530 Subject: [PATCH 3/6] Address more button text color issues --- .../aurora/store/view/epoxy/views/app/NoAppView.kt | 3 +++ .../store/view/ui/details/AppDetailsFragment.kt | 14 +++++++++++--- app/src/main/res/layout/view_no_app.xml | 5 +++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppView.kt index 9b563075f..65e25dddd 100644 --- a/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppView.kt +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppView.kt @@ -26,6 +26,8 @@ import androidx.core.view.isVisible import com.airbnb.epoxy.CallbackProp import com.airbnb.epoxy.ModelProp import com.airbnb.epoxy.ModelView +import com.aurora.extensions.accentColor +import com.aurora.extensions.contrastingColor import com.aurora.store.databinding.ViewNoAppBinding import com.aurora.store.view.epoxy.views.BaseModel import com.aurora.store.view.epoxy.views.BaseView @@ -62,6 +64,7 @@ class NoAppView @JvmOverloads constructor( @ModelProp fun actionMessage(message: String = String()) { binding.button.text = message + binding.button.setTextColor(contrastingColor(context.accentColor())) } @JvmOverloads 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 b986a2c21..366ab07fc 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 @@ -39,7 +39,9 @@ import coil.load import coil.transform.RoundedCornersTransformation import com.aurora.Constants import com.aurora.Constants.EXODUS_SUBMIT_PAGE +import com.aurora.extensions.accentColor import com.aurora.extensions.browse +import com.aurora.extensions.contrastingColor import com.aurora.extensions.getString import com.aurora.extensions.hide import com.aurora.extensions.runOnUiThread @@ -317,7 +319,10 @@ class AppDetailsFragment : BaseFragment() { viewLifecycleOwner.lifecycleScope.launch { viewModel.testingProgramStatus.collect { if (it != null) { - binding.layoutDetailsBeta.btnBetaAction.isEnabled = true + binding.layoutDetailsBeta.btnBetaAction.apply { + isEnabled = true + setTextColor(contrastingColor(requireContext().accentColor())) + } if (it.subscribed) { updateBetaActions(true) } @@ -349,8 +354,11 @@ class AppDetailsFragment : BaseFragment() { } // Misc Bindings - binding.layoutDetailsPrivacy.btnRequestAnalysis.setOnClickListener { - it.context.browse("${EXODUS_SUBMIT_PAGE}${app.packageName}") + binding.layoutDetailsPrivacy.btnRequestAnalysis.apply { + setOnClickListener { + it.context.browse("${EXODUS_SUBMIT_PAGE}${app.packageName}") + } + setTextColor(contrastingColor(requireContext().accentColor())) } binding.layoutDetailsInstall.progressDownload.clipToOutline = true binding.layoutDetailsInstall.imgCancel.setOnClickListener { diff --git a/app/src/main/res/layout/view_no_app.xml b/app/src/main/res/layout/view_no_app.xml index b30269ada..c3c1aee25 100644 --- a/app/src/main/res/layout/view_no_app.xml +++ b/app/src/main/res/layout/view_no_app.xml @@ -41,12 +41,13 @@ android:textAppearance="@style/TextAppearance.Aurora.Line1" tools:text="No updates available" /> -