Blacklist: Add support for import & export

This commit is contained in:
Rahul Patel
2024-12-05 20:25:06 +05:30
parent b989030e7b
commit d3d30100a0
7 changed files with 138 additions and 8 deletions

View File

@@ -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<FragmentGenericWithSearchBinding>() {
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<FragmentGenericWithSearchBinding>() {
// 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<FragmentGenericWithSearchBinding>() {
start: Int,
count: Int,
after: Int
) {}
) {
}
})
}
}
@@ -89,6 +118,37 @@ class BlacklistFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
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<PackageInfo>?) {
binding.recycler.withModels {
setFilterDuplicates(true)
@@ -125,4 +185,15 @@ class BlacklistFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
}
}
}
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)
}
}

View File

@@ -45,11 +45,11 @@ class FavouriteFragment : BaseFragment<FragmentFavouriteBinding>() {
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<FragmentFavouriteBinding>() {
}
}
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)
}

View File

@@ -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<String> = gson.fromJson(
it.bufferedReader().readText(),
object : TypeToken<MutableSet<String?>?>() {}.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)
}
}
}
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M480,800q-33,0 -56.5,-23.5T400,720q0,-33 23.5,-56.5T480,640q33,0 56.5,23.5T560,720q0,33 -23.5,56.5T480,800ZM480,560q-33,0 -56.5,-23.5T400,480q0,-33 23.5,-56.5T480,400q33,0 56.5,23.5T560,480q0,33 -23.5,56.5T480,560ZM480,320q-33,0 -56.5,-23.5T400,240q0,-33 23.5,-56.5T480,160q33,0 56.5,23.5T560,240q0,33 -23.5,56.5T480,320Z"
android:fillColor="#e8eaed"/>
</vector>

View File

@@ -5,5 +5,6 @@
<item name="chipStyle">@style/Chip.Filter</item>
<item name="preferenceTheme">@style/AppTheme.PreferenceThemeOverlay</item>
<item name="android:statusBarColor">@color/colorTransparent</item>
<item name="popupMenuStyle">@style/AppTheme.PopupMenu</item>
</style>
</resources>

View File

@@ -437,6 +437,12 @@
<string name="toast_fav_import_success">Favourites imported!</string>
<string name="toast_fav_export_success">Favourites exported!</string>
<!-- BlacklistFragment-->
<string name="toast_black_import_failed">Failed to import blacklist!</string>
<string name="toast_black_export_failed">Failed to export blacklist!</string>
<string name="toast_black_import_success">Blacklist imported!</string>
<string name="toast_black_export_success">Blacklist exported!</string>
<!-- ExportWorker -->
<string name="export_app_title">File Exporter</string>
<string name="export_app_summary">Hold on, exporting your file</string>

View File

@@ -24,6 +24,7 @@
<item name="preferenceTheme">@style/AppTheme.PreferenceThemeOverlay</item>
<item name="bottomSheetStyle">@style/AppTheme.BottomSheetStyle</item>
<item name="android:statusBarColor">@color/colorTransparent</item>
<item name="popupMenuStyle">@style/AppTheme.PopupMenu</item>
</style>
<style name="AppTheme.Translucent" parent="Theme.Material3.DynamicColors.DayNight.NoActionBar">
@@ -47,17 +48,23 @@
<item name="widgetLayout">@layout/preference_material_switch</item>
</style>
<style name="AppTheme.PopupMenu" parent="Widget.Material3.PopupMenu">
<item name="android:popupBackground">?attr/colorSurfaceContainer</item>
<item name="android:textColor">?attr/colorOnSurface</item>
<item name="android:elevation">4dp</item>
</style>
<style name="Chip.Filter" parent="@style/Widget.Material3.Chip.Filter" />
<style name="Chip.TopChart" parent="@style/Widget.Material3.Chip.Filter">
<item name="android:textAppearance">@style/TextAppearance.Aurora.Line1</item>
<item name="chipStrokeColor">?colorControlHighlight</item>
<item name="chipStrokeColor">?attr/colorControlHighlight</item>
</style>
<style name="Chip.Tag" parent="@style/Widget.Material3.Chip.Assist.Elevated">
<item name="android:clickable">false</item>
<item name="android:textAppearance">@style/TextAppearance.Aurora.Line2</item>
<item name="chipBackgroundColor">?colorControlHighlight</item>
<item name="chipBackgroundColor">?attr/colorControlHighlight</item>
<item name="chipStrokeWidth">0dp</item>
</style>
</resources>