From d3d30100a0e51ba521611e3a8dd51e0379c64570 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Thu, 5 Dec 2024 20:25:06 +0530 Subject: [PATCH] Blacklist: Add support for import & export --- .../view/ui/commons/BlacklistFragment.kt | 75 ++++++++++++++++++- .../view/ui/commons/FavouriteFragment.kt | 8 +- .../store/viewmodel/all/BlacklistViewModel.kt | 36 +++++++++ app/src/main/res/drawable/ic_menu.xml | 9 +++ app/src/main/res/values-night/themes.xml | 1 + app/src/main/res/values/strings.xml | 6 ++ app/src/main/res/values/themes.xml | 11 ++- 7 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 app/src/main/res/drawable/ic_menu.xml diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/BlacklistFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/BlacklistFragment.kt index 7a74c5d59..978a1de75 100644 --- a/app/src/main/java/com/aurora/store/view/ui/commons/BlacklistFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/commons/BlacklistFragment.kt @@ -20,14 +20,23 @@ package com.aurora.store.view.ui.commons import android.content.pm.PackageInfo +import android.net.Uri import android.os.Bundle import android.text.Editable import android.text.TextWatcher +import android.view.ContextThemeWrapper +import android.view.MenuInflater +import android.view.MenuItem import android.view.View +import android.widget.PopupMenu +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.content.res.AppCompatResources import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController +import com.aurora.extensions.toast import com.aurora.store.AuroraApp +import com.aurora.store.R import com.aurora.store.data.event.BusEvent import com.aurora.store.databinding.FragmentGenericWithSearchBinding import com.aurora.store.view.epoxy.views.BlackListViewModel_ @@ -35,12 +44,23 @@ import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ import com.aurora.store.viewmodel.all.BlacklistViewModel import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch +import java.util.Calendar @AndroidEntryPoint class BlacklistFragment : BaseFragment() { private val viewModel: BlacklistViewModel by viewModels() + private val mimeType = "application/json" + private val startForDocumentImport = + registerForActivityResult(ActivityResultContracts.OpenDocument()) { + if (it != null) importBlacklist(it) else toast(R.string.toast_black_import_failed) + } + private val startForDocumentExport = + registerForActivityResult(ActivityResultContracts.CreateDocument(mimeType)) { + if (it != null) exportBlacklist(it) else toast(R.string.toast_black_export_failed) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -53,7 +73,15 @@ class BlacklistFragment : BaseFragment() { // Toolbar binding.layoutToolbarNative.apply { imgActionPrimary.visibility = View.VISIBLE - imgActionSecondary.visibility = View.GONE + imgActionSecondary.apply { + visibility = View.VISIBLE + setImageDrawable( + AppCompatResources.getDrawable(requireContext(), R.drawable.ic_menu) + ) + setOnClickListener { + showMenu(it) + } + } imgActionPrimary.setOnClickListener { viewModel.blacklistProvider.blacklist = viewModel.selected @@ -79,7 +107,8 @@ class BlacklistFragment : BaseFragment() { start: Int, count: Int, after: Int - ) {} + ) { + } }) } } @@ -89,6 +118,37 @@ class BlacklistFragment : BaseFragment() { viewModel.blacklistProvider.blacklist = viewModel.selected } + private fun showMenu(anchor: View) { + val popupMenu = PopupMenu( + ContextThemeWrapper( + requireContext(), + R.style.AppTheme_PopupMenu + ), anchor + ) + + val inflater: MenuInflater = popupMenu.menuInflater + inflater.inflate(R.menu.menu_import_export, popupMenu.menu) + + popupMenu.setOnMenuItemClickListener { menuItem: MenuItem -> + when (menuItem.itemId) { + R.id.action_import -> { + startForDocumentImport.launch(arrayOf(mimeType)) + true + } + + R.id.action_export -> { + startForDocumentExport.launch( + "aurora_store_blacklist_${Calendar.getInstance().time.time}.json" + ) + true + } + + else -> false + } + } + popupMenu.show() + } + private fun updateController(packages: List?) { binding.recycler.withModels { setFilterDuplicates(true) @@ -125,4 +185,15 @@ class BlacklistFragment : BaseFragment() { } } } + + private fun importBlacklist(uri: Uri) { + viewModel.importBlacklist(requireContext(), uri) + binding.recycler.requestModelBuild() + toast(R.string.toast_black_import_success) + } + + private fun exportBlacklist(uri: Uri) { + viewModel.exportBlacklist(requireContext(), uri) + toast(R.string.toast_black_export_success) + } } diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/FavouriteFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/FavouriteFragment.kt index c0df585f7..202368320 100644 --- a/app/src/main/java/com/aurora/store/view/ui/commons/FavouriteFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/commons/FavouriteFragment.kt @@ -45,11 +45,11 @@ class FavouriteFragment : BaseFragment() { private val mimeType = "application/json" private val startForDocumentImport = registerForActivityResult(ActivityResultContracts.OpenDocument()) { - if (it != null) importDeviceConfig(it) else toast(R.string.toast_fav_import_failed) + if (it != null) importFavourites(it) else toast(R.string.toast_fav_import_failed) } private val startForDocumentExport = registerForActivityResult(ActivityResultContracts.CreateDocument(mimeType)) { - if (it != null) exportDeviceConfig(it) else toast(R.string.toast_fav_export_failed) + if (it != null) exportFavourites(it) else toast(R.string.toast_fav_export_failed) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -111,13 +111,13 @@ class FavouriteFragment : BaseFragment() { } } - private fun importDeviceConfig(uri: Uri) { + private fun importFavourites(uri: Uri) { viewModel.importFavourites(requireContext(), uri) binding.recycler.requestModelBuild() toast(R.string.toast_fav_import_success) } - private fun exportDeviceConfig(uri: Uri) { + private fun exportFavourites(uri: Uri) { viewModel.exportFavourites(requireContext(), uri) toast(R.string.toast_fav_export_success) } diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt index 6a363afea..6a7d65ca8 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt @@ -21,11 +21,14 @@ package com.aurora.store.viewmodel.all import android.content.Context import android.content.pm.PackageInfo +import android.net.Uri import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.aurora.store.data.providers.BlacklistProvider import com.aurora.store.util.PackageUtil +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers @@ -37,6 +40,7 @@ import javax.inject.Inject @HiltViewModel class BlacklistViewModel @Inject constructor( val blacklistProvider: BlacklistProvider, + val gson: Gson, @ApplicationContext private val context: Context ) : ViewModel() { @@ -60,4 +64,36 @@ class BlacklistViewModel @Inject constructor( } } } + + fun importBlacklist(context: Context, uri: Uri) { + viewModelScope.launch(Dispatchers.IO) { + try { + context.contentResolver.openInputStream(uri)?.use { + val importedSet: MutableSet = gson.fromJson( + it.bufferedReader().readText(), + object : TypeToken?>() {}.type + ) + + val knownSet = blacklistProvider.blacklist + knownSet.addAll(importedSet) + + selected = knownSet + } + } catch (exception: Exception) { + Log.e(TAG, "Failed to import blacklist", exception) + } + } + } + + fun exportBlacklist(context: Context, uri: Uri) { + viewModelScope.launch(Dispatchers.IO) { + try { + context.contentResolver.openOutputStream(uri)?.use { + it.write(gson.toJson(blacklistProvider.blacklist).encodeToByteArray()) + } + } catch (exception: Exception) { + Log.e(TAG, "Failed to export blacklist", exception) + } + } + } } diff --git a/app/src/main/res/drawable/ic_menu.xml b/app/src/main/res/drawable/ic_menu.xml new file mode 100644 index 000000000..966e8e94b --- /dev/null +++ b/app/src/main/res/drawable/ic_menu.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index c70d8103a..b8ecd72e1 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -5,5 +5,6 @@ @style/Chip.Filter @style/AppTheme.PreferenceThemeOverlay @color/colorTransparent + @style/AppTheme.PopupMenu diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8c931f503..aa0a3c1a4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -437,6 +437,12 @@ Favourites imported! Favourites exported! + + Failed to import blacklist! + Failed to export blacklist! + Blacklist imported! + Blacklist exported! + File Exporter Hold on, exporting your file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 2736c408e..e4795a322 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -24,6 +24,7 @@ @style/AppTheme.PreferenceThemeOverlay @style/AppTheme.BottomSheetStyle @color/colorTransparent + @style/AppTheme.PopupMenu + +