diff options
author | Guillaume Jacquart <guillaume.jacquart@hoodbrains.com> | 2023-06-09 06:34:10 +0000 |
---|---|---|
committer | Guillaume Jacquart <guillaume.jacquart@hoodbrains.com> | 2023-06-09 06:34:10 +0000 |
commit | 8b1855bce1313ad84df8f96efdbb62e2acf7ff33 (patch) | |
tree | 94e19efed58b931139a86c7c9c8aced91767e6c3 /app/src/main | |
parent | 333623483246398c76bed4aa5ee5b43c843f65cd (diff) | |
parent | 74b9860784913c097ae59e58b0958da7744ebc2e (diff) |
Merge branch '1227-navigation_graph' into 'main'
1227: use navigation graph component, avoid view (fragments) duplications
See merge request e/os/advanced-privacy!136
Diffstat (limited to 'app/src/main')
17 files changed, 227 insertions, 287 deletions
diff --git a/app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt b/app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt index 91e2f44..5664515 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt @@ -41,7 +41,7 @@ import foundation.e.advancedprivacy.features.dashboard.DashboardViewModel import foundation.e.advancedprivacy.features.internetprivacy.InternetPrivacyViewModel import foundation.e.advancedprivacy.features.location.FakeLocationViewModel import foundation.e.advancedprivacy.features.trackers.TrackersViewModel -import foundation.e.advancedprivacy.features.trackers.apptrackers.AppTrackersFragment +import foundation.e.advancedprivacy.features.trackers.apptrackers.AppTrackersFragmentArgs import foundation.e.advancedprivacy.features.trackers.apptrackers.AppTrackersViewModel import foundation.e.privacymodules.fakelocation.FakeLocationModule import foundation.e.privacymodules.ipscrambler.IpScramblerModule @@ -172,8 +172,8 @@ class ViewModelsFactory( override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T { return when (modelClass) { AppTrackersViewModel::class.java -> { - val app = extras[DEFAULT_ARGS_KEY]?.getInt(AppTrackersFragment.PARAM_APP_UID)?.let { - appListUseCase.getApp(it) + val app = extras[DEFAULT_ARGS_KEY]?.let { + appListUseCase.getApp(AppTrackersFragmentArgs.fromBundle(it).appUid) } ?: appListUseCase.dummySystemApp AppTrackersViewModel( diff --git a/app/src/main/java/foundation/e/advancedprivacy/Notifications.kt b/app/src/main/java/foundation/e/advancedprivacy/Notifications.kt index 68c4bd3..291f9bc 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/Notifications.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/Notifications.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 MURENA SAS * Copyright (C) 2022 MURENA SAS * * This program is free software: you can redistribute it and/or modify @@ -21,7 +22,6 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context -import android.content.Intent import androidx.annotation.StringRes import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat @@ -54,8 +54,9 @@ object Notifications { icon = R.drawable.ic_notification_logo, title = R.string.first_notification_title, description = R.string.first_notification_summary, - destinationIntent = - context.packageManager.getLaunchIntentForPackage(context.packageName) + pendingIntent = MainActivity.deepLinkBuilder(context) + .setDestination(R.id.dashboardFragment) + .createPendingIntent() ) ) .setAutoCancel(true) @@ -140,7 +141,9 @@ object Notifications { icon = R.drawable.ic_fmd_bad, title = R.string.notifications_fake_location_title, description = R.string.notifications_fake_location_content, - destinationIntent = MainActivity.createFakeLocationIntent(context), + pendingIntent = MainActivity.deepLinkBuilder(context) + .addDestination(R.id.fakeLocationFragment) + .createPendingIntent() ) ) MainFeatures.IP_SCRAMBLING -> showFlagNotification( @@ -151,7 +154,9 @@ object Notifications { icon = R.drawable.ic_language, title = R.string.notifications_ipscrambling_title, description = R.string.notifications_ipscrambling_content, - destinationIntent = MainActivity.createIpScramblingIntent(context), + pendingIntent = MainActivity.deepLinkBuilder(context) + .addDestination(R.id.internetPrivacyFragment) + .createPendingIntent() ) ) else -> {} @@ -184,7 +189,7 @@ object Notifications { val icon: Int, val title: Int, val description: Int, - val destinationIntent: Intent? + val pendingIntent: PendingIntent? ) private fun notificationBuilder( @@ -196,14 +201,7 @@ object Notifications { .setPriority(NotificationCompat.PRIORITY_LOW) .setContentTitle(context.getString(content.title)) .setStyle(NotificationCompat.BigTextStyle().bigText(context.getString(content.description))) - - content.destinationIntent?.let { - it.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - val pendingIntent: PendingIntent = PendingIntent.getActivity( - context, 0, it, PendingIntent.FLAG_IMMUTABLE - ) - builder.setContentIntent(pendingIntent) - } + content.pendingIntent?.let { builder.setContentIntent(it) } return builder } diff --git a/app/src/main/java/foundation/e/advancedprivacy/common/NavToolbarFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/common/NavToolbarFragment.kt index 1417977..cdb6a4c 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/common/NavToolbarFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/common/NavToolbarFragment.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 MURENA SAS * Copyright (C) 2021 E FOUNDATION * * This program is free software: you can redistribute it and/or modify @@ -17,17 +18,39 @@ package foundation.e.advancedprivacy.common +import android.os.Bundle +import android.view.View import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController import com.google.android.material.appbar.MaterialToolbar +import foundation.e.advancedprivacy.R -abstract class NavToolbarFragment(@LayoutRes contentLayoutId: Int) : ToolbarFragment(contentLayoutId) { +abstract class NavToolbarFragment(@LayoutRes contentLayoutId: Int) : Fragment(contentLayoutId) { - override fun setupToolbar(toolbar: MaterialToolbar) { - super.setupToolbar(toolbar) - toolbar.apply { - setNavigationOnClickListener { - requireActivity().onBackPressed() - } + /** + * @return title to be used in toolbar + */ + open fun getTitle(): CharSequence { + return findNavController().currentDestination?.label ?: "" + } + + fun setTitle(title: CharSequence?) { + getToolbar()?.title = title + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupToolbar(view.findViewById(R.id.toolbar)) + } + + open fun setupToolbar(toolbar: MaterialToolbar) { + toolbar.title = getTitle() + toolbar.setNavigationOnClickListener { + requireActivity().onBackPressed() } } + + fun getToolbar(): MaterialToolbar? = view?.findViewById(R.id.toolbar) } diff --git a/app/src/main/java/foundation/e/advancedprivacy/common/ToolbarFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/common/ToolbarFragment.kt deleted file mode 100644 index fb3ea14..0000000 --- a/app/src/main/java/foundation/e/advancedprivacy/common/ToolbarFragment.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -package foundation.e.advancedprivacy.common - -import android.os.Bundle -import android.view.View -import androidx.annotation.LayoutRes -import androidx.fragment.app.Fragment -import com.google.android.material.appbar.MaterialToolbar -import foundation.e.advancedprivacy.R - -abstract class ToolbarFragment(@LayoutRes contentLayoutId: Int) : Fragment(contentLayoutId) { - - /** - * @return title to be used in toolbar - */ - abstract fun getTitle(): String - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupToolbar(view.findViewById(R.id.toolbar)) - } - - open fun setupToolbar(toolbar: MaterialToolbar) { - toolbar.title = getTitle() - } - - fun getToolbar(): MaterialToolbar? = view?.findViewById(R.id.toolbar) -} diff --git a/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt b/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt index 98deeb1..3f3f66c 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 MURENA SAS * Copyright (C) 2022 E FOUNDATION * * This program is free software: you can redistribute it and/or modify @@ -120,7 +121,10 @@ class WarningDialog : Activity() { if (feature == TRACKERS_CONTROL) { builder.setNeutralButton(R.string.warningdialog_trackers_secondary_cta) { _, _ -> - startActivity(MainActivity.createTrackersIntent(this)) + MainActivity.deepLinkBuilder(this) + .setDestination(R.id.trackersFragment) + .createPendingIntent().send() + finish() } } diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt index 9b99b95..5a16308 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 MURENA SAS * Copyright (C) 2021 E FOUNDATION * * This program is free software: you can redistribute it and/or modify @@ -144,7 +145,7 @@ class FakeLocationStateUseCase( } } - // Deprecated since API 29, never called. + @Deprecated("Deprecated since API 29, never called.") override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {} override fun onProviderEnabled(provider: String) { diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt index 02fd1ad..6ca9792 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 MURENA SAS * Copyright (C) 2021 E FOUNDATION * * This program is free software: you can redistribute it and/or modify @@ -17,21 +18,19 @@ package foundation.e.advancedprivacy.features.dashboard -import android.content.Intent import android.os.Bundle import android.text.Html import android.text.Html.FROM_HTML_MODE_LEGACY import android.view.View import android.widget.Toast import androidx.core.content.ContextCompat.getColor -import androidx.core.os.bundleOf import androidx.core.view.isVisible -import androidx.fragment.app.commit -import androidx.fragment.app.replace import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs import foundation.e.advancedprivacy.AdvancedPrivacyApplication import foundation.e.advancedprivacy.DependencyContainer import foundation.e.advancedprivacy.R @@ -44,20 +43,9 @@ import foundation.e.advancedprivacy.domain.entities.QuickPrivacyState import foundation.e.advancedprivacy.domain.entities.TrackerMode import foundation.e.advancedprivacy.features.dashboard.DashboardViewModel.Action import foundation.e.advancedprivacy.features.dashboard.DashboardViewModel.SingleEvent -import foundation.e.advancedprivacy.features.internetprivacy.InternetPrivacyFragment -import foundation.e.advancedprivacy.features.location.FakeLocationFragment -import foundation.e.advancedprivacy.features.trackers.TrackersFragment -import foundation.e.advancedprivacy.features.trackers.apptrackers.AppTrackersFragment import kotlinx.coroutines.launch class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { - companion object { - private const val PARAM_HIGHLIGHT_INDEX = "PARAM_HIGHLIGHT_INDEX" - fun buildArgs(highlightIndex: Int): Bundle = bundleOf( - PARAM_HIGHLIGHT_INDEX to highlightIndex - ) - } - private val dependencyContainer: DependencyContainer by lazy { (this.requireActivity().application as AdvancedPrivacyApplication).dependencyContainer } @@ -73,10 +61,11 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { private var highlightIndexOnStart: Int? = null + private val args: DashboardFragmentArgs by navArgs() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - highlightIndexOnStart = arguments?.getInt(PARAM_HIGHLIGHT_INDEX, -1) + highlightIndexOnStart = args.highlightLeaks } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -134,45 +123,6 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.singleEvents.collect { event -> when (event) { - is SingleEvent.NavigateToLocationSingleEvent -> { - requireActivity().supportFragmentManager.commit { - replace<FakeLocationFragment>(R.id.container) - setReorderingAllowed(true) - addToBackStack("dashboard") - } - } - is SingleEvent.NavigateToInternetActivityPrivacySingleEvent -> { - requireActivity().supportFragmentManager.commit { - replace<InternetPrivacyFragment>(R.id.container) - setReorderingAllowed(true) - addToBackStack("dashboard") - } - } - is SingleEvent.NavigateToPermissionsSingleEvent -> { - val intent = Intent("android.intent.action.MANAGE_PERMISSIONS") - requireActivity().startActivity(intent) - } - SingleEvent.NavigateToTrackersSingleEvent -> { - requireActivity().supportFragmentManager.commit { - replace<TrackersFragment>(R.id.container) - setReorderingAllowed(true) - addToBackStack("dashboard") - } - } - is SingleEvent.NavigateToAppDetailsEvent -> { - requireActivity().supportFragmentManager.commit { - replace<AppTrackersFragment>( - R.id.container, - args = AppTrackersFragment.buildArgs( - event.appDesc.label.toString(), - event.appDesc.packageName, - event.appDesc.uid - ) - ) - setReorderingAllowed(true) - addToBackStack("dashboard") - } - } is SingleEvent.ToastMessageSingleEvent -> Toast.makeText( requireContext(), @@ -183,6 +133,11 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { } } } + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.navigate.collect(findNavController()::navigate) + } + } viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -191,10 +146,6 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { } } - override fun getTitle(): String { - return getString(R.string.dashboard_title) - } - private fun render(state: DashboardState) { binding.stateLabel.text = getString( when (state.quickPrivacyState) { diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt index cc1263b..8259c89 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt @@ -1,5 +1,5 @@ /* -* Copyright (C) 2023 MURENA SAS + * Copyright (C) 2023 MURENA SAS * Copyright (C) 2021 E FOUNDATION * * This program is free software: you can redistribute it and/or modify @@ -21,10 +21,10 @@ package foundation.e.advancedprivacy.features.dashboard import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.navigation.NavDirections import foundation.e.advancedprivacy.R import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase -import foundation.e.privacymodules.permissions.data.ApplicationDescription import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -51,6 +51,8 @@ class DashboardViewModel( private val _singleEvents = MutableSharedFlow<SingleEvent>() val singleEvents = _singleEvents.asSharedFlow() + private val _navigate = MutableSharedFlow<NavDirections>() + val navigate = _navigate.asSharedFlow() init { viewModelScope.launch(Dispatchers.IO) { trackersStatisticsUseCase.initAppList() } } @@ -98,13 +100,13 @@ class DashboardViewModel( is Action.ToggleIpScrambling -> getPrivacyStateUseCase.toggleIpScrambling(action.enabled) is Action.ShowFakeMyLocationAction -> - _singleEvents.emit(SingleEvent.NavigateToLocationSingleEvent) + _navigate.emit(DashboardFragmentDirections.gotoFakeLocationFragment()) is Action.ShowAppsPermissions -> - _singleEvents.emit(SingleEvent.NavigateToPermissionsSingleEvent) + _navigate.emit(DashboardFragmentDirections.gotoSettingsPermissionsActivity()) is Action.ShowInternetActivityPrivacyAction -> - _singleEvents.emit(SingleEvent.NavigateToInternetActivityPrivacySingleEvent) + _navigate.emit(DashboardFragmentDirections.gotoInternetPrivacyFragment()) is Action.ShowTrackers -> - _singleEvents.emit(SingleEvent.NavigateToTrackersSingleEvent) + _navigate.emit(DashboardFragmentDirections.gotoTrackersFragment()) is Action.ShowMostLeakedApp -> actionShowMostLeakedApp() } } @@ -127,19 +129,14 @@ class DashboardViewModel( } private suspend fun actionShowMostLeakedApp() = withContext(Dispatchers.IO) { - _singleEvents.emit( + _navigate.emit( trackersStatisticsUseCase.getMostLeakedApp()?.let { - SingleEvent.NavigateToAppDetailsEvent(appDesc = it) - } ?: SingleEvent.NavigateToTrackersSingleEvent + DashboardFragmentDirections.gotoAppTrackersFragment(appUid = it.uid) + } ?: DashboardFragmentDirections.gotoTrackersFragment() ) } sealed class SingleEvent { - object NavigateToTrackersSingleEvent : SingleEvent() - object NavigateToInternetActivityPrivacySingleEvent : SingleEvent() - object NavigateToLocationSingleEvent : SingleEvent() - object NavigateToPermissionsSingleEvent : SingleEvent() - data class NavigateToAppDetailsEvent(val appDesc: ApplicationDescription) : SingleEvent() data class ToastMessageSingleEvent( @StringRes val message: Int, val args: List<Any> = emptyList() diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyFragment.kt index 07da82a..35fc1d4 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyFragment.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 MURENA SAS * Copyright (C) 2021 E FOUNDATION * * This program is free software: you can redistribute it and/or modify @@ -141,8 +142,6 @@ class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_ac } } - override fun getTitle(): String = getString(R.string.ipscrambling_title) - private fun render(state: InternetPrivacyState) { binding.radioUseHiddenIp.radiobutton.apply { isChecked = state.mode in listOf( diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt index 9934713..09409f2 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 MURENA SAS * Copyright (C) 2021 E FOUNDATION * * This program is free software: you can redistribute it and/or modify @@ -100,8 +101,6 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location) Mapbox.getInstance(requireContext(), getString(R.string.mapbox_key), WellKnownTileServer.Mapbox) } - override fun getTitle(): String = getString(R.string.location_title) - private fun displayToast(message: String) { Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT) .show() diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersFragment.kt index 3e17334..f486114 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersFragment.kt @@ -1,5 +1,6 @@ /* - * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS + * Copyright (C) 2022-2023 MURENA SAS + * Copyright (C) 2021 E FOUNDATION * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,12 +31,11 @@ import android.view.View import android.widget.Toast import androidx.core.content.ContextCompat import androidx.core.view.isVisible -import androidx.fragment.app.commit -import androidx.fragment.app.replace import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import foundation.e.advancedprivacy.AdvancedPrivacyApplication import foundation.e.advancedprivacy.DependencyContainer @@ -47,11 +47,9 @@ import foundation.e.advancedprivacy.common.setToolTipForAsterisk import foundation.e.advancedprivacy.databinding.FragmentTrackersBinding import foundation.e.advancedprivacy.databinding.TrackersItemGraphBinding import foundation.e.advancedprivacy.domain.entities.TrackersPeriodicStatistics -import foundation.e.advancedprivacy.features.trackers.apptrackers.AppTrackersFragment import kotlinx.coroutines.launch -class TrackersFragment : - NavToolbarFragment(R.layout.fragment_trackers) { +class TrackersFragment : NavToolbarFragment(R.layout.fragment_trackers) { private val dependencyContainer: DependencyContainer by lazy { (this.requireActivity().application as AdvancedPrivacyApplication).dependencyContainer @@ -134,20 +132,6 @@ class TrackersFragment : is TrackersViewModel.SingleEvent.ErrorEvent -> { displayToast(event.error) } - is TrackersViewModel.SingleEvent.OpenAppDetailsEvent -> { - requireActivity().supportFragmentManager.commit { - replace<AppTrackersFragment>( - R.id.container, - args = AppTrackersFragment.buildArgs( - event.appDesc.label.toString(), - event.appDesc.packageName, - event.appDesc.uid - ) - ) - setReorderingAllowed(true) - addToBackStack("apptrackers") - } - } is TrackersViewModel.SingleEvent.OpenUrl -> { try { startActivity(Intent(Intent.ACTION_VIEW, event.url)) @@ -166,6 +150,12 @@ class TrackersFragment : viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.navigate.collect(findNavController()::navigate) + } + } + + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.doOnStartedState() } } @@ -176,8 +166,6 @@ class TrackersFragment : .show() } - override fun getTitle() = getString(R.string.trackers_title) - private fun render(state: TrackersState) { state.dayStatistics?.let { renderGraph(it, dayGraphHolder!!, binding.graphDay) } state.monthStatistics?.let { renderGraph(it, monthGraphHolder!!, binding.graphMonth) } diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersViewModel.kt index bcb4df8..8a5d0f0 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersViewModel.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersViewModel.kt @@ -1,5 +1,6 @@ /* - * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS + * Copyright (C) 2022-2023 MURENA SAS + * Copyright (C) 2021 E FOUNDATION * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,7 +21,7 @@ package foundation.e.advancedprivacy.features.trackers import android.net.Uri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import foundation.e.advancedprivacy.domain.entities.AppWithCounts +import androidx.navigation.NavDirections import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow @@ -48,6 +49,9 @@ class TrackersViewModel( private val _singleEvents = MutableSharedFlow<SingleEvent>() val singleEvents = _singleEvents.asSharedFlow() + private val _navigate = MutableSharedFlow<NavDirections>() + val navigate = _navigate.asSharedFlow() + suspend fun doOnStartedState() = withContext(Dispatchers.IO) { merge( trackersStatisticsUseCase.listenUpdates().map { @@ -78,13 +82,12 @@ class TrackersViewModel( private suspend fun actionClickApp(action: Action.ClickAppAction) { state.value.apps?.find { it.uid == action.appUid }?.let { - _singleEvents.emit(SingleEvent.OpenAppDetailsEvent(it)) + _navigate.emit(TrackersFragmentDirections.gotoAppTrackersFragment(appUid = it.uid)) } } sealed class SingleEvent { data class ErrorEvent(val error: String) : SingleEvent() - data class OpenAppDetailsEvent(val appDesc: AppWithCounts) : SingleEvent() data class OpenUrl(val url: Uri) : SingleEvent() } diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersFragment.kt index 2bb53d6..457a02a 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersFragment.kt @@ -23,7 +23,6 @@ import android.content.Intent import android.os.Bundle import android.view.View import android.widget.Toast -import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle @@ -39,19 +38,6 @@ import foundation.e.advancedprivacy.databinding.ApptrackersFragmentBinding import kotlinx.coroutines.launch class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) { - companion object { - private val PARAM_LABEL = "PARAM_LABEL" - private val PARAM_PACKAGE_NAME = "PARAM_PACKAGE_NAME" - - const val PARAM_APP_UID = "PARAM_APP_UID" - - fun buildArgs(label: String, packageName: String, appUid: Int): Bundle = bundleOf( - PARAM_LABEL to label, - PARAM_PACKAGE_NAME to packageName, - PARAM_APP_UID to appUid - ) - } - private val dependencyContainer: DependencyContainer by lazy { (this.requireActivity().application as AdvancedPrivacyApplication).dependencyContainer } @@ -63,13 +49,8 @@ class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) { private var _binding: ApptrackersFragmentBinding? = null private val binding get() = _binding!! - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - if (arguments == null || - requireArguments().getInt(PARAM_APP_UID, Int.MIN_VALUE) == Int.MIN_VALUE - ) { - activity?.supportFragmentManager?.popBackStack() - } + override fun getTitle(): CharSequence { + return "" } private fun displayToast(message: String) { @@ -77,8 +58,6 @@ class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) { .show() } - override fun getTitle(): String = requireArguments().getString(PARAM_LABEL) ?: "" - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) _binding = ApptrackersFragmentBinding.bind(view) @@ -144,6 +123,7 @@ class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) { } private fun render(state: AppTrackersState) { + setTitle(state.appDesc?.label) binding.trackersCountSummary.text = if (state.getTrackersCount() == 0) "" else getString( R.string.apptrackers_trackers_count_summary, diff --git a/app/src/main/java/foundation/e/advancedprivacy/main/MainActivity.kt b/app/src/main/java/foundation/e/advancedprivacy/main/MainActivity.kt index 277031a..fbe5cf8 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/main/MainActivity.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/main/MainActivity.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 MURENA SAS * Copyright (C) 2021 E FOUNDATION * * This program is free software: you can redistribute it and/or modify @@ -17,91 +18,22 @@ package foundation.e.advancedprivacy.main -import android.app.Activity import android.content.Context import android.content.Intent -import android.os.Bundle -import android.util.Log import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.add -import androidx.fragment.app.commit +import androidx.navigation.NavDeepLinkBuilder +import androidx.navigation.findNavController import foundation.e.advancedprivacy.R -import foundation.e.advancedprivacy.features.dashboard.DashboardFragment -import foundation.e.advancedprivacy.features.internetprivacy.InternetPrivacyFragment -import foundation.e.advancedprivacy.features.location.FakeLocationFragment -import foundation.e.advancedprivacy.features.trackers.TrackersFragment - -open class MainActivity : FragmentActivity(R.layout.activity_main) { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - if (savedInstanceState == null) handleIntent(intent) - } +class MainActivity : FragmentActivity(R.layout.activity_main) { override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - handleIntent(intent) - } - - open fun handleIntent(intent: Intent) { - supportFragmentManager.commit { - setReorderingAllowed(true) - when (intent.action) { - ACTION_HIGHLIGHT_LEAKS -> add<DashboardFragment>( - containerViewId = R.id.container, - args = intent.extras - ) - ACTION_VIEW_TRACKERS -> { - add<TrackersFragment>(R.id.container) - } - ACTION_VIEW_FAKE_LOCATION -> { - add<FakeLocationFragment>(R.id.container) - } - ACTION_VIEW_IPSCRAMBLING -> { - add<InternetPrivacyFragment>(R.id.container) - } - else -> add<DashboardFragment>(R.id.container) - } - disallowAddToBackStack() - } - } - - override fun finishAfterTransition() { - val resultData = Intent() - val result = onPopulateResultIntent(resultData) - setResult(result, resultData) - - super.finishAfterTransition() + findNavController(R.id.nav_host_fragment).handleDeepLink(intent) } - open fun onPopulateResultIntent(intent: Intent): Int = Activity.RESULT_OK - companion object { - private const val ACTION_HIGHLIGHT_LEAKS = "ACTION_HIGHLIGHT_LEAKS" - private const val ACTION_VIEW_TRACKERS = "ACTION_VIEW_TRACKERS" - private const val ACTION_VIEW_FAKE_LOCATION = "ACTION_VIEW_FAKE_LOCATION" - private const val ACTION_VIEW_IPSCRAMBLING = "ACTION_VIEW_IPSCRAMBLING" - - fun createHighlightLeaksIntent(context: Context, highlightIndex: Int) = - Intent(context, MainActivity::class.java).apply { - action = ACTION_HIGHLIGHT_LEAKS - putExtras(DashboardFragment.buildArgs(highlightIndex)) - } - - fun createTrackersIntent(context: Context) = - Intent(context, MainActivity::class.java).apply { - action = ACTION_VIEW_TRACKERS - } - - fun createFakeLocationIntent(context: Context): Intent { - return Intent(context, MainActivity::class.java).apply { - action = ACTION_VIEW_FAKE_LOCATION - } - } - - fun createIpScramblingIntent(context: Context): Intent { - return Intent(context, MainActivity::class.java).apply { - action = ACTION_VIEW_IPSCRAMBLING - } - } + fun deepLinkBuilder(context: Context) = NavDeepLinkBuilder(context) + .setGraph(R.navigation.nav_graph) + .setComponentName(MainActivity::class.java) } } diff --git a/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetUI.kt b/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetUI.kt index f1edb36..bfd7d1a 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetUI.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetUI.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 MURENA SAS * Copyright (C) 2022 E FOUNDATION * * This program is free software: you can redistribute it and/or modify @@ -33,6 +34,7 @@ import foundation.e.advancedprivacy.common.extensions.dpToPxF import foundation.e.advancedprivacy.domain.entities.InternetPrivacyMode import foundation.e.advancedprivacy.domain.entities.QuickPrivacyState import foundation.e.advancedprivacy.domain.entities.TrackerMode +import foundation.e.advancedprivacy.features.dashboard.DashboardFragmentArgs import foundation.e.advancedprivacy.main.MainActivity import foundation.e.advancedprivacy.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_IPSCRAMBLING import foundation.e.advancedprivacy.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_LOCATION @@ -179,12 +181,9 @@ fun render( setViewVisibility(R.id.graph_legend_values, View.VISIBLE) setViewVisibility(R.id.graph_view_trackers_btn, View.VISIBLE) - val pIntent = PendingIntent.getActivity( - context, - REQUEST_CODE_TRACKERS, - MainActivity.createTrackersIntent(context), - FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT - ) + val pIntent = MainActivity.deepLinkBuilder(context) + .setDestination(R.id.trackersFragment) + .createPendingIntent() setOnClickPendingIntent(R.id.graph_view_trackers_btn, pIntent) @@ -205,11 +204,10 @@ fun render( val topPadding = graphHeightPx - (blocked + leaked) * ratio setViewPadding(leakedBarIds[index], 0, topPadding.toInt(), 0, 0) - val highlightPIntent = PendingIntent.getActivity( - context, REQUEST_CODE_HIGHLIGHT + index, - MainActivity.createHighlightLeaksIntent(context, index), - FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT - ) + val highlightPIntent = MainActivity.deepLinkBuilder(context) + .setDestination(R.id.dashboardFragment) + .setArguments(DashboardFragmentArgs(highlightLeaks = index).toBundle()) + .createPendingIntent() setOnClickPendingIntent(containerBarIds[index], highlightPIntent) } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2627a32..a5816f4 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,6 +1,27 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2023 MURENA SAS + ~ Copyright (C) 2021 E FOUNDATION + ~ + ~ This program is free software: you can redistribute it and/or modify + ~ it under the terms of the GNU General Public License as published by + ~ the Free Software Foundation, either version 3 of the License, or + ~ (at your option) any later version. + ~ + ~ This program is distributed in the hope that it will be useful, + ~ but WITHOUT ANY WARRANTY; without even the implied warranty of + ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ~ GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License + ~ along with this program. If not, see <https://www.gnu.org/licenses/>. + --> <androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/container" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/nav_host_fragment" + android:name="androidx.navigation.fragment.NavHostFragment" android:layout_height="match_parent" android:layout_width="match_parent" - />
\ No newline at end of file + + app:defaultNavHost="true" + app:navGraph="@navigation/nav_graph" + /> diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml new file mode 100644 index 0000000..52a1677 --- /dev/null +++ b/app/src/main/res/navigation/nav_graph.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 MURENA SAS + ~ + ~ This program is free software: you can redistribute it and/or modify + ~ it under the terms of the GNU General Public License as published by + ~ the Free Software Foundation, either version 3 of the License, or + ~ (at your option) any later version. + ~ + ~ This program is distributed in the hope that it will be useful, + ~ but WITHOUT ANY WARRANTY; without even the implied warranty of + ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ~ GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License + ~ along with this program. If not, see <https://www.gnu.org/licenses/>. + --> +<navigation xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/nav_graph" + app:startDestination="@id/dashboardFragment" + > + + <fragment + android:id="@+id/dashboardFragment" + android:name="foundation.e.advancedprivacy.features.dashboard.DashboardFragment" + android:label="@string/app_name" + > + <action + android:id="@+id/goto_trackersFragment" + app:destination="@id/trackersFragment" + /> + <action + android:id="@+id/goto_fakeLocationFragment" + app:destination="@id/fakeLocationFragment" + /> + <action + android:id="@+id/goto_internetPrivacyFragment" + app:destination="@id/internetPrivacyFragment" + /> + <action + android:id="@+id/goto_appTrackersFragment" + app:destination="@id/appTrackersFragment" + /> + <action + android:id="@+id/goto_settingsPermissionsActivity" + app:destination="@id/settingsPermissionsActivity" + /> + <argument + android:name="highlightLeaks" + app:argType="integer" + android:defaultValue="-1" + /> + </fragment> + <fragment + android:id="@+id/trackersFragment" + android:name="foundation.e.advancedprivacy.features.trackers.TrackersFragment" + android:label="@string/trackers_title" + > + <action + android:id="@+id/goto_appTrackersFragment" + app:destination="@id/appTrackersFragment" + /> + </fragment> + <fragment + android:id="@+id/appTrackersFragment" + android:name="foundation.e.advancedprivacy.features.trackers.apptrackers.AppTrackersFragment" + android:label="AppTrackersFragment" + > + <argument + android:name="appUid" + app:argType="integer" + android:defaultValue="1000" + /> + </fragment> + <fragment + android:id="@+id/fakeLocationFragment" + android:name="foundation.e.advancedprivacy.features.location.FakeLocationFragment" + android:label="@string/location_title" + /> + <fragment + android:id="@+id/internetPrivacyFragment" + android:name="foundation.e.advancedprivacy.features.internetprivacy.InternetPrivacyFragment" + android:label="@string/ipscrambling_title" + /> + <activity + android:id="@+id/settingsPermissionsActivity" + android:label="@string/dashboard_apps_permissions_title" + app:action="android.intent.action.MANAGE_PERMISSIONS" + /> +</navigation> |