diff --git a/.editorconfig b/.editorconfig index efce1d8a8a87..7a82f7c4eb36 100644 --- a/.editorconfig +++ b/.editorconfig @@ -91,3 +91,7 @@ ij_kotlin_while_on_new_line = false ij_kotlin_wrap_elvis_expressions = 1 ij_kotlin_wrap_expression_body_functions = 1 ij_kotlin_wrap_first_method_in_call_chain = false +ktlint_standard_no-wildcard-imports = disabled +ktlint_standard_filename = disabled +ktlint_standard_package-name = disabled +ktlint_standard_annotation = disabled diff --git a/.githooks/pre-commit b/.githooks/pre-commit index e4fdce27070d..12719f1fa0a1 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -2,6 +2,5 @@ #!/bin/bash echo "Running spotless check" ./gradlew spotlessApply - ./gradlew formatKotlin git add `git diff --name-only --cached` exit 0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b55b82aa3706..98947505ffcc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: + fetch-depth: 0 # required due to setting Spotless ratchetFrom submodules: recursive - name: Set up JDK version @@ -39,7 +40,7 @@ jobs: uses: gradle/actions/setup-gradle@v3 - name: Run Code Formatting Checks - run: ./gradlew code_format_checks + run: ./gradlew spotlessCheck unit_tests: name: Unit tests diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a6b5efc71e5a..ae05a351d4a9 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -18,6 +18,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: + fetch-depth: 0 # required due to setting Spotless ratchetFrom submodules: recursive - name: Set up JDK version @@ -30,7 +31,7 @@ jobs: uses: gradle/actions/setup-gradle@v3 - name: Run Code Formatting Checks - run: ./gradlew code_format_checks + run: ./gradlew spotlessCheck unit_tests: name: Unit tests diff --git a/.gitignore b/.gitignore index fe3087c55bb1..8bb8d8797bcb 100644 --- a/.gitignore +++ b/.gitignore @@ -41,9 +41,10 @@ captures/ # Intellij *.iml -.idea/ -.idea/workspace.xml +.idea/* !.idea/icon.svg +!.idea/copyright/ +.idea/workspace.xml # Keystore files *.jks diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4ba9fe4ef84c..7c4b98aef9ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,6 +50,5 @@ If your PR is failing because of that, please make sure that you follow our [sty You can also trigger an automatic code formatting of the code by executing: ``` -./gradleW code_format_checks -./gradleW formatKotlin +./gradleW app:spotlessApply ``` diff --git a/STYLEGUIDE.md b/STYLEGUIDE.md index 1149d370a5af..a32fb8905ada 100644 --- a/STYLEGUIDE.md +++ b/STYLEGUIDE.md @@ -5,8 +5,8 @@ ### Code formatting -You can check the code formatting correctness by running `./gradleW code_format_checks`. -To adhere to codestyle, please run `./gradleW formatKotlin` and `./gradleW spotlessApply` to autoformat in order to fix any CI issues. +You can check the code formatting correctness by running `./gradleW spotlessCheck`. +To adhere to codestyle, please run `./gradleW spotlessApply` to autoformat and fix any CI issues. If you want to do this automatically upon commit we recommend the existing [pre-commit hook](.githooks/pre-commit): - Pull develop branch diff --git a/app-tracking-protection/vpn-impl/src/main/AndroidManifest.xml b/app-tracking-protection/vpn-impl/src/main/AndroidManifest.xml index 4f14a9aea91b..eae0b3d96127 100644 --- a/app-tracking-protection/vpn-impl/src/main/AndroidManifest.xml +++ b/app-tracking-protection/vpn-impl/src/main/AndroidManifest.xml @@ -28,7 +28,7 @@ android:screenOrientation="portrait" /> renderViewState(viewState) } - .launchIn(findViewTreeLifecycleOwner()?.lifecycleScope!!) + conflatedJob += + viewModel.viewStateFlow + .onEach { viewState -> renderViewState(viewState) } + .launchIn(findViewTreeLifecycleOwner()?.lifecycleScope!!) deviceShieldPixels.didShowNewTabSummary() @@ -141,49 +139,56 @@ class AppTrackingProtectionStateView @JvmOverloads constructor( val lastTrackingApp = trackerBlocked.latestApp val otherApps = trackerBlocked.otherAppsSize - val textToStyle = if (trackersBlocked == 1) { - when (otherApps) { - 0 -> resources.getString( - R.string.atp_DailyLastCompanyBlockedHomeTabOneTimeZeroOtherApps, - trackersBlocked, - lastTrackingApp, - ) - - 1 -> resources.getString( - R.string.atp_DailyLastCompanyBlockedHomeTabOneTimeOneOtherApp, - trackersBlocked, - lastTrackingApp, - ) - - else -> resources.getString( - R.string.atp_DailyLastCompanyBlockedHomeTabOneTimeMoreOtherApps, - trackersBlocked, - lastTrackingApp, - otherApps, - ) + val textToStyle = + if (trackersBlocked == 1) { + when (otherApps) { + 0 -> + resources.getString( + R.string.atp_DailyLastCompanyBlockedHomeTabOneTimeZeroOtherApps, + trackersBlocked, + lastTrackingApp, + ) + + 1 -> + resources.getString( + R.string.atp_DailyLastCompanyBlockedHomeTabOneTimeOneOtherApp, + trackersBlocked, + lastTrackingApp, + ) + + else -> + resources.getString( + R.string.atp_DailyLastCompanyBlockedHomeTabOneTimeMoreOtherApps, + trackersBlocked, + lastTrackingApp, + otherApps, + ) + } + } else { + when (otherApps) { + 0 -> + resources.getString( + R.string.atp_DailyLastCompanyBlockedHomeTabOtherTimesZeroOtherApps, + trackersBlocked, + lastTrackingApp, + ) + + 1 -> + resources.getString( + R.string.atp_DailyLastCompanyBlockedHomeTabOtherTimesOneOtherApp, + trackersBlocked, + lastTrackingApp, + ) + + else -> + resources.getString( + R.string.atp_DailyLastCompanyBlockedHomeTabOtherTimesMoreOtherApps, + trackersBlocked, + lastTrackingApp, + otherApps, + ) + } } - } else { - when (otherApps) { - 0 -> resources.getString( - R.string.atp_DailyLastCompanyBlockedHomeTabOtherTimesZeroOtherApps, - trackersBlocked, - lastTrackingApp, - ) - - 1 -> resources.getString( - R.string.atp_DailyLastCompanyBlockedHomeTabOtherTimesOneOtherApp, - trackersBlocked, - lastTrackingApp, - ) - - else -> resources.getString( - R.string.atp_DailyLastCompanyBlockedHomeTabOtherTimesMoreOtherApps, - trackersBlocked, - lastTrackingApp, - otherApps, - ) - } - } binding.deviceShieldCtaHeader.text = HtmlCompat.fromHtml(textToStyle, HtmlCompat.FROM_HTML_MODE_LEGACY) binding.deviceShieldCtaImage.setImageResource(R.drawable.ic_apptp_default) @@ -202,9 +207,7 @@ class AppTrackingProtectionNewTabPageSectionPlugin @Inject constructor( ) : NewTabPageSectionPlugin { override val name = NewTabPageSection.APP_TRACKING_PROTECTION.name - override fun getView(context: Context): View { - return AppTrackingProtectionStateView(context) - } + override fun getView(context: Context): View = AppTrackingProtectionStateView(context) override suspend fun isUserEnabled(): Boolean { if (vpnFeatureRemover.isFeatureRemoved()) { diff --git a/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/report/DeviceShieldAppTrackersInfo.kt b/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/privacyreport/DeviceShieldAppTrackersInfo.kt similarity index 88% rename from app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/report/DeviceShieldAppTrackersInfo.kt rename to app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/privacyreport/DeviceShieldAppTrackersInfo.kt index 7825961728ae..c840a4f6bbfc 100644 --- a/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/report/DeviceShieldAppTrackersInfo.kt +++ b/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/privacyreport/DeviceShieldAppTrackersInfo.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 DuckDuckGo + * Copyright (c) 2025 DuckDuckGo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.duckduckgo.mobile.android.vpn.ui.report +package com.duckduckgo.mobile.android.vpn.ui.privacyreport import android.content.Context import android.content.Intent @@ -29,7 +29,6 @@ import javax.inject.Inject @InjectWith(ActivityScope::class) class DeviceShieldAppTrackersInfo : DuckDuckGoActivity() { - @Inject lateinit var deviceShieldPixels: DeviceShieldPixels @@ -55,9 +54,6 @@ class DeviceShieldAppTrackersInfo : DuckDuckGoActivity() { } companion object { - - internal fun intent(context: Context): Intent { - return Intent(context, DeviceShieldAppTrackersInfo::class.java) - } + internal fun intent(context: Context): Intent = Intent(context, DeviceShieldAppTrackersInfo::class.java) } } diff --git a/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/report/DeviceShieldFragment.kt b/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/privacyreport/DeviceShieldFragment.kt similarity index 71% rename from app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/report/DeviceShieldFragment.kt rename to app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/privacyreport/DeviceShieldFragment.kt index 4716f2b34341..fdcbe3ae605d 100644 --- a/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/report/DeviceShieldFragment.kt +++ b/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/privacyreport/DeviceShieldFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 DuckDuckGo + * Copyright (c) 2025 DuckDuckGo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.duckduckgo.mobile.android.vpn.ui.report +package com.duckduckgo.mobile.android.vpn.ui.privacyreport import android.app.ActivityOptions import android.os.Bundle @@ -38,15 +38,15 @@ import com.duckduckgo.mobile.android.vpn.R import com.duckduckgo.mobile.android.vpn.pixels.DeviceShieldPixels import com.duckduckgo.mobile.android.vpn.state.VpnStateMonitor.VpnRunningState.ENABLED import com.duckduckgo.mobile.android.vpn.state.VpnStateMonitor.VpnStopReason.REVOKED -import com.duckduckgo.mobile.android.vpn.ui.report.PrivacyReportViewModel.PrivacyReportView.ViewState +import com.duckduckgo.mobile.android.vpn.ui.privacyreport.PrivacyReportViewModel.PrivacyReportView.TrackersBlocked +import com.duckduckgo.mobile.android.vpn.ui.privacyreport.PrivacyReportViewModel.PrivacyReportView.ViewState import com.duckduckgo.mobile.android.vpn.ui.tracker_activity.DeviceShieldTrackerActivity -import javax.inject.Inject import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import javax.inject.Inject @InjectWith(FragmentScope::class) class DeviceShieldFragment : DuckDuckGoFragment() { - @Inject lateinit var viewModelFactory: FragmentViewModelFactory @@ -57,8 +57,7 @@ class DeviceShieldFragment : DuckDuckGoFragment() { private lateinit var deviceShieldCtaHeaderTextView: TextView private lateinit var deviceShieldCtaImageView: ImageView - private inline fun bindViewModel() = - lazy { ViewModelProvider(this, viewModelFactory).get(V::class.java) } + private inline fun bindViewModel() = lazy { ViewModelProvider(this, viewModelFactory).get(V::class.java) } private val viewModel: PrivacyReportViewModel by bindViewModel() @@ -139,50 +138,57 @@ class DeviceShieldFragment : DuckDuckGoFragment() { deviceShieldCtaImageView.setImageResource(R.drawable.ic_apptp_warning) } - private fun renderTrackersBlockedWhenEnabled(trackerBlocked: PrivacyReportViewModel.PrivacyReportView.TrackersBlocked) { + private fun renderTrackersBlockedWhenEnabled(trackerBlocked: TrackersBlocked) { val trackersBlocked = trackerBlocked.trackers val lastTrackingApp = trackerBlocked.latestApp val otherApps = trackerBlocked.otherAppsSize - val textToStyle = if (trackersBlocked == 1) { - when (otherApps) { - 0 -> resources.getString( - R.string.atp_DailyLastCompanyBlockedHomeTabOneTimeZeroOtherApps, - trackersBlocked, - lastTrackingApp, - ) - 1 -> resources.getString( - R.string.atp_DailyLastCompanyBlockedHomeTabOneTimeOneOtherApp, - trackersBlocked, - lastTrackingApp, - ) - else -> resources.getString( - R.string.atp_DailyLastCompanyBlockedHomeTabOneTimeMoreOtherApps, - trackersBlocked, - lastTrackingApp, - otherApps, - ) + val textToStyle = + if (trackersBlocked == 1) { + when (otherApps) { + 0 -> + resources.getString( + R.string.atp_DailyLastCompanyBlockedHomeTabOneTimeZeroOtherApps, + trackersBlocked, + lastTrackingApp, + ) + 1 -> + resources.getString( + R.string.atp_DailyLastCompanyBlockedHomeTabOneTimeOneOtherApp, + trackersBlocked, + lastTrackingApp, + ) + else -> + resources.getString( + R.string.atp_DailyLastCompanyBlockedHomeTabOneTimeMoreOtherApps, + trackersBlocked, + lastTrackingApp, + otherApps, + ) + } + } else { + when (otherApps) { + 0 -> + resources.getString( + R.string.atp_DailyLastCompanyBlockedHomeTabOtherTimesZeroOtherApps, + trackersBlocked, + lastTrackingApp, + ) + 1 -> + resources.getString( + R.string.atp_DailyLastCompanyBlockedHomeTabOtherTimesOneOtherApp, + trackersBlocked, + lastTrackingApp, + ) + else -> + resources.getString( + R.string.atp_DailyLastCompanyBlockedHomeTabOtherTimesMoreOtherApps, + trackersBlocked, + lastTrackingApp, + otherApps, + ) + } } - } else { - when (otherApps) { - 0 -> resources.getString( - R.string.atp_DailyLastCompanyBlockedHomeTabOtherTimesZeroOtherApps, - trackersBlocked, - lastTrackingApp, - ) - 1 -> resources.getString( - R.string.atp_DailyLastCompanyBlockedHomeTabOtherTimesOneOtherApp, - trackersBlocked, - lastTrackingApp, - ) - else -> resources.getString( - R.string.atp_DailyLastCompanyBlockedHomeTabOtherTimesMoreOtherApps, - trackersBlocked, - lastTrackingApp, - otherApps, - ) - } - } deviceShieldCtaHeaderTextView.text = HtmlCompat.fromHtml(textToStyle, HtmlCompat.FROM_HTML_MODE_LEGACY) deviceShieldCtaImageView.setImageResource(R.drawable.ic_apptp_default) diff --git a/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/report/PrivacyReportViewModel.kt b/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/privacyreport/PrivacyReportViewModel.kt similarity index 75% rename from app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/report/PrivacyReportViewModel.kt rename to app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/privacyreport/PrivacyReportViewModel.kt index d3fbb1d6cf68..654aa807d3e1 100644 --- a/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/report/PrivacyReportViewModel.kt +++ b/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/privacyreport/PrivacyReportViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 DuckDuckGo + * Copyright (c) 2025 DuckDuckGo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.duckduckgo.mobile.android.vpn.ui.report +package com.duckduckgo.mobile.android.vpn.ui.privacyreport import androidx.annotation.VisibleForTesting import androidx.lifecycle.ViewModel @@ -28,11 +28,13 @@ import com.duckduckgo.mobile.android.vpn.state.VpnStateMonitor import com.duckduckgo.mobile.android.vpn.state.VpnStateMonitor.VpnState import com.duckduckgo.mobile.android.vpn.stats.AppTrackerBlockingStatsRepository import com.duckduckgo.mobile.android.vpn.ui.onboarding.VpnStore -import javax.inject.Inject +import com.duckduckgo.mobile.android.vpn.ui.privacyreport.PrivacyReportViewModel.PrivacyReportView.TrackersBlocked +import com.duckduckgo.mobile.android.vpn.ui.privacyreport.PrivacyReportViewModel.PrivacyReportView.ViewState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext +import javax.inject.Inject @ContributesViewModel(ViewScope::class) class PrivacyReportViewModel @Inject constructor( @@ -42,35 +44,33 @@ class PrivacyReportViewModel @Inject constructor( vpnStateMonitor: VpnStateMonitor, private val dispatchers: DispatcherProvider, ) : ViewModel() { - - val viewStateFlow = vpnStateMonitor.getStateFlow(AppTpVpnFeature.APPTP_VPN).combine(getReport()) { vpnState, trackersBlocked -> - PrivacyReportView.ViewState(vpnState, trackersBlocked, shouldShowCTA()) - } + val viewStateFlow = + vpnStateMonitor.getStateFlow(AppTpVpnFeature.APPTP_VPN).combine(getReport()) { vpnState, trackersBlocked -> + ViewState(vpnState, trackersBlocked, shouldShowCTA()) + } @VisibleForTesting - fun getReport(): Flow { - return repository.getVpnTrackers({ dateOfLastHour() }).map { trackers -> + fun getReport(): Flow = + repository.getVpnTrackers({ dateOfLastHour() }).map { trackers -> if (trackers.isEmpty()) { - PrivacyReportView.TrackersBlocked("", 0, 0) + TrackersBlocked("", 0, 0) } else { val perApp = trackers.groupBy { it.trackingApp }.toList().sortedByDescending { it.second.sumOf { t -> t.count } } val otherAppsSize = (perApp.size - 1).coerceAtLeast(0) val latestApp = perApp.first().first.appDisplayName - PrivacyReportView.TrackersBlocked(latestApp, otherAppsSize, trackers.sumOf { it.count }) + TrackersBlocked(latestApp, otherAppsSize, trackers.sumOf { it.count }) } } - } - private suspend fun shouldShowCTA(): Boolean { - return withContext(dispatchers.io()) { + private suspend fun shouldShowCTA(): Boolean = + withContext(dispatchers.io()) { if (vpnFeatureRemover.isFeatureRemoved()) { false } else { vpnStore.didShowOnboarding() } } - } object PrivacyReportView { data class ViewState( diff --git a/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/tracker_activity/DeviceShieldTrackerActivity.kt b/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/tracker_activity/DeviceShieldTrackerActivity.kt index 864e05d0e7d8..df1987345559 100644 --- a/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/tracker_activity/DeviceShieldTrackerActivity.kt +++ b/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/tracker_activity/DeviceShieldTrackerActivity.kt @@ -68,7 +68,7 @@ import com.duckduckgo.mobile.android.vpn.state.VpnStateMonitor.VpnRunningState import com.duckduckgo.mobile.android.vpn.state.VpnStateMonitor.VpnState import com.duckduckgo.mobile.android.vpn.ui.AppBreakageCategory import com.duckduckgo.mobile.android.vpn.ui.alwayson.AlwaysOnAlertDialogFragment -import com.duckduckgo.mobile.android.vpn.ui.report.DeviceShieldAppTrackersInfo +import com.duckduckgo.mobile.android.vpn.ui.privacyreport.DeviceShieldAppTrackersInfo import com.duckduckgo.mobile.android.vpn.ui.tracker_activity.DeviceShieldTrackerActivityViewModel.ViewEvent import com.duckduckgo.mobile.android.vpn.ui.tracker_activity.DeviceShieldTrackerActivityViewModel.ViewEvent.StartVpn import com.duckduckgo.mobile.android.vpn.ui.tracker_activity.view.DisableVpnDialogOptions @@ -76,9 +76,6 @@ import com.duckduckgo.mobile.android.vpn.ui.tracker_activity.view.message.AppTPS import com.duckduckgo.mobile.android.vpn.ui.tracker_activity.view.message.AppTPStateMessagePlugin.DefaultAppTPMessageAction import com.duckduckgo.navigation.api.GlobalActivityStarter import com.google.android.material.snackbar.Snackbar -import java.util.concurrent.TimeUnit -import javax.inject.Inject -import javax.inject.Provider import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.combine @@ -88,13 +85,15 @@ import kotlinx.coroutines.launch import logcat.logcat import nl.dionsegijn.konfetti.models.Shape import nl.dionsegijn.konfetti.models.Size +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Provider @InjectWith(ActivityScope::class) @ContributeToActivityStarter(AppTrackerActivityWithEmptyParams::class, screenName = "apptp.main") class DeviceShieldTrackerActivity : DuckDuckGoActivity(), DeviceShieldActivityFeedFragment.DeviceShieldActivityFeedListener { - @Inject lateinit var deviceShieldPixels: DeviceShieldPixels @@ -125,20 +124,22 @@ class DeviceShieldTrackerActivity : // we might get an update before options menu has been populated; temporarily cache value to use when menu populated private var vpnCachedState: VpnState? = null - private val feedConfig = DeviceShieldActivityFeedFragment.ActivityFeedConfig( - maxRows = MIN_ROWS_FOR_ALL_ACTIVITY, - timeWindow = 7, - timeWindowUnits = TimeUnit.DAYS, - showTimeWindowHeadings = false, - ) + private val feedConfig = + DeviceShieldActivityFeedFragment.ActivityFeedConfig( + maxRows = MIN_ROWS_FOR_ALL_ACTIVITY, + timeWindow = 7, + timeWindowUnits = TimeUnit.DAYS, + showTimeWindowHeadings = false, + ) private val viewModel: DeviceShieldTrackerActivityViewModel by bindViewModel() private lateinit var reportBreakage: ActivityResultLauncher - private val enableAppTPSwitchListener = CompoundButton.OnCheckedChangeListener { _, isChecked -> - viewModel.onAppTPToggleSwitched(isChecked) - } + private val enableAppTPSwitchListener = + CompoundButton.OnCheckedChangeListener { _, isChecked -> + viewModel.onAppTPToggleSwitched(isChecked) + } private val onInfoMessageClick = fun(action: DefaultAppTPMessageAction) { when (action) { @@ -153,11 +154,12 @@ class DeviceShieldTrackerActivity : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - reportBreakage = registerForActivityResult(reportBreakageContract.get()) { - if (!it.isEmpty()) { - Snackbar.make(binding.root, R.string.atp_ReportBreakageSent, Snackbar.LENGTH_LONG).show() + reportBreakage = + registerForActivityResult(reportBreakageContract.get()) { + if (!it.isEmpty()) { + Snackbar.make(binding.root, R.string.atp_ReportBreakageSent, Snackbar.LENGTH_LONG).show() + } } - } setContentView(binding.root) setupToolbar(binding.includeToolbar.trackersToolbar) @@ -231,31 +233,31 @@ class DeviceShieldTrackerActivity : } private fun showDeviceShieldActivity() { - supportFragmentManager.beginTransaction() + supportFragmentManager + .beginTransaction() .replace( R.id.activity_list, DeviceShieldActivityFeedFragment.newInstance(feedConfig), - ) - .commitNow() + ).commitNow() } @OptIn(FlowPreview::class) private fun observeViewModel() { lifecycleScope.launch { - viewModel.getBlockedTrackersCount() + viewModel + .getBlockedTrackersCount() .combine(viewModel.getTrackingAppsCount()) { trackers, apps -> DeviceShieldTrackerActivityViewModel.TrackerCountInfo(trackers, apps) - } - .combine(viewModel.getRunningState()) { trackerCountInfo, runningState -> + }.combine(viewModel.getRunningState()) { trackerCountInfo, runningState -> DeviceShieldTrackerActivityViewModel.TrackerActivityViewState(trackerCountInfo, runningState) - } - .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) + }.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) .collect { renderViewState(it) } } lifecycleScope.launch { // This is a one-shot check as soon as the screen is shown - viewModel.getRunningState() + viewModel + .getRunningState() .map { it.alwaysOnState } .debounce(500) // give a bit of time so that pop doesn't just suddenly pops up .take(1) @@ -266,7 +268,8 @@ class DeviceShieldTrackerActivity : } } - viewModel.commands() + viewModel + .commands() .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) .onEach { processCommand(it) } .launchIn(lifecycleScope) @@ -348,8 +351,7 @@ class DeviceShieldTrackerActivity : } } }, - ) - .show() + ).show() } private fun launchRemoveFeatureConfirmationDialog() { @@ -370,8 +372,7 @@ class DeviceShieldTrackerActivity : deviceShieldPixels.didChooseToCancelRemoveTrakcingProtectionDialog() } }, - ) - .show() + ).show() } private fun showVpnConflictDialog() { @@ -392,8 +393,7 @@ class DeviceShieldTrackerActivity : onVpnConflictDialogDismiss() } }, - ) - .show() + ).show() } private fun showAlwaysOnConflictDialog() { @@ -413,42 +413,43 @@ class DeviceShieldTrackerActivity : onVpnConflictDialogDismiss() } }, - ) - .show() + ).show() } private fun launchAlwaysOnPromotionDialog() { val dialog = supportFragmentManager.findFragmentByTag(TAG_APPTP_PROMOTE_ALWAYS_ON_DIALOG) as? AlwaysOnAlertDialogFragment dialog?.dismiss() - AlwaysOnAlertDialogFragment.newAlwaysOnDialog( - object : AlwaysOnAlertDialogFragment.Listener { - override fun onGoToSettingsClicked() { - viewModel.onViewEvent(ViewEvent.PromoteAlwaysOnOpenSettings) - } + AlwaysOnAlertDialogFragment + .newAlwaysOnDialog( + object : AlwaysOnAlertDialogFragment.Listener { + override fun onGoToSettingsClicked() { + viewModel.onViewEvent(ViewEvent.PromoteAlwaysOnOpenSettings) + } - override fun onCanceled() { - viewModel.onViewEvent(ViewEvent.PromoteAlwaysOnCancelled) - } - }, - ).show(supportFragmentManager, TAG_APPTP_PROMOTE_ALWAYS_ON_DIALOG) + override fun onCanceled() { + viewModel.onViewEvent(ViewEvent.PromoteAlwaysOnCancelled) + } + }, + ).show(supportFragmentManager, TAG_APPTP_PROMOTE_ALWAYS_ON_DIALOG) } private fun launchAlwaysOnLockdownEnabledDialog() { val dialog = supportFragmentManager.findFragmentByTag(TAG_APPTP_PROMOTE_ALWAYS_ON_DIALOG) as? AlwaysOnAlertDialogFragment dialog?.dismiss() - AlwaysOnAlertDialogFragment.newAlwaysOnLockdownDialog( - object : AlwaysOnAlertDialogFragment.Listener { - override fun onGoToSettingsClicked() { - viewModel.onViewEvent(ViewEvent.PromoteAlwaysOnOpenSettings) - } + AlwaysOnAlertDialogFragment + .newAlwaysOnLockdownDialog( + object : AlwaysOnAlertDialogFragment.Listener { + override fun onGoToSettingsClicked() { + viewModel.onViewEvent(ViewEvent.PromoteAlwaysOnOpenSettings) + } - override fun onCanceled() { - viewModel.onViewEvent(ViewEvent.PromoteAlwaysOnCancelled) - } - }, - ).show(supportFragmentManager, TAG_APPTP_PROMOTE_ALWAYS_ON_DIALOG) + override fun onCanceled() { + viewModel.onViewEvent(ViewEvent.PromoteAlwaysOnCancelled) + } + }, + ).show(supportFragmentManager, TAG_APPTP_PROMOTE_ALWAYS_ON_DIALOG) } fun onOpenAppProtection() { @@ -562,18 +563,20 @@ class DeviceShieldTrackerActivity : private suspend fun updateRunningState(runningState: VpnState) { if (!binding.deviceShieldTrackerNotifyMe.isVisible) { var newActivePlugin: ActivePlugin? = null - appTPStateMessagePluginPoint.getPlugins().firstNotNullOfOrNull { - it.getView(this, runningState, onInfoMessageClick)?.apply { - newActivePlugin = it - } - }?.let { - if (currenActivePlugin == null || newActivePlugin != currenActivePlugin) { - currenActivePlugin = newActivePlugin - binding.deviceShieldTrackerMessageContainer.show() - binding.deviceShieldTrackerMessageContainer.removeAllViews() - binding.deviceShieldTrackerMessageContainer.addView(it) - } - } ?: { + appTPStateMessagePluginPoint + .getPlugins() + .firstNotNullOfOrNull { + it.getView(this, runningState, onInfoMessageClick)?.apply { + newActivePlugin = it + } + }?.let { + if (currenActivePlugin == null || newActivePlugin != currenActivePlugin) { + currenActivePlugin = newActivePlugin + binding.deviceShieldTrackerMessageContainer.show() + binding.deviceShieldTrackerMessageContainer.removeAllViews() + binding.deviceShieldTrackerMessageContainer.addView(it) + } + } ?: { currenActivePlugin = null binding.deviceShieldTrackerMessageContainer.gone() } @@ -626,7 +629,10 @@ class DeviceShieldTrackerActivity : private sealed class VpnPermissionStatus { object Granted : VpnPermissionStatus() - data class Denied(val intent: Intent) : VpnPermissionStatus() + + data class Denied( + val intent: Intent, + ) : VpnPermissionStatus() } private fun showAppTpEnabledCta() { @@ -634,11 +640,12 @@ class DeviceShieldTrackerActivity : return } - val dialog = TypewriterDaxDialog.newInstance( - daxText = getString(R.string.atp_ActivityAppTpEnabledCtaText), - primaryButtonText = getString(R.string.atp_ActivityAppTpEnabledCtaButtonLabel), - hideButtonText = "", - ) + val dialog = + TypewriterDaxDialog.newInstance( + daxText = getString(R.string.atp_ActivityAppTpEnabledCtaText), + primaryButtonText = getString(R.string.atp_ActivityAppTpEnabledCtaButtonLabel), + hideButtonText = "", + ) dialog.setDaxDialogListener( object : DaxDialogListener { @@ -674,7 +681,8 @@ class DeviceShieldTrackerActivity : val displayWidth = resources.displayMetrics.widthPixels - binding.appTpEnabledKonfetti.build() + binding.appTpEnabledKonfetti + .build() .addColors(magenta, blue, purple, green, yellow) .setDirection(0.0, 359.0) .setSpeed(4f, 9f) @@ -699,10 +707,9 @@ class DeviceShieldTrackerActivity : internal fun intent( context: Context, onLaunchCallback: ResultReceiver? = null, - ): Intent { - return Intent(context, DeviceShieldTrackerActivity::class.java).apply { + ): Intent = + Intent(context, DeviceShieldTrackerActivity::class.java).apply { putExtra(RESULT_RECEIVER_EXTRA, onLaunchCallback) } - } } } diff --git a/app-tracking-protection/vpn-impl/src/main/res/layout/activity_app_trackers_info.xml b/app-tracking-protection/vpn-impl/src/main/res/layout/activity_app_trackers_info.xml index 7dca0b2c6bc6..24909105fe02 100644 --- a/app-tracking-protection/vpn-impl/src/main/res/layout/activity_app_trackers_info.xml +++ b/app-tracking-protection/vpn-impl/src/main/res/layout/activity_app_trackers_info.xml @@ -24,7 +24,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - tools:context=".ui.report.DeviceShieldAppTrackersInfo"> + tools:context=".ui.privacyreport.DeviceShieldAppTrackersInfo">