Blacklist: Add support for import & export
This commit is contained in:
@@ -20,14 +20,23 @@
|
|||||||
package com.aurora.store.view.ui.commons
|
package com.aurora.store.view.ui.commons
|
||||||
|
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
|
import android.view.ContextThemeWrapper
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
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.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import com.aurora.extensions.toast
|
||||||
import com.aurora.store.AuroraApp
|
import com.aurora.store.AuroraApp
|
||||||
|
import com.aurora.store.R
|
||||||
import com.aurora.store.data.event.BusEvent
|
import com.aurora.store.data.event.BusEvent
|
||||||
import com.aurora.store.databinding.FragmentGenericWithSearchBinding
|
import com.aurora.store.databinding.FragmentGenericWithSearchBinding
|
||||||
import com.aurora.store.view.epoxy.views.BlackListViewModel_
|
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 com.aurora.store.viewmodel.all.BlacklistViewModel
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class BlacklistFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
class BlacklistFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||||
|
|
||||||
private val viewModel: BlacklistViewModel by viewModels()
|
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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
@@ -53,7 +73,15 @@ class BlacklistFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
|||||||
// Toolbar
|
// Toolbar
|
||||||
binding.layoutToolbarNative.apply {
|
binding.layoutToolbarNative.apply {
|
||||||
imgActionPrimary.visibility = View.VISIBLE
|
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 {
|
imgActionPrimary.setOnClickListener {
|
||||||
viewModel.blacklistProvider.blacklist = viewModel.selected
|
viewModel.blacklistProvider.blacklist = viewModel.selected
|
||||||
@@ -79,7 +107,8 @@ class BlacklistFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
|||||||
start: Int,
|
start: Int,
|
||||||
count: Int,
|
count: Int,
|
||||||
after: Int
|
after: Int
|
||||||
) {}
|
) {
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,6 +118,37 @@ class BlacklistFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
|||||||
viewModel.blacklistProvider.blacklist = viewModel.selected
|
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>?) {
|
private fun updateController(packages: List<PackageInfo>?) {
|
||||||
binding.recycler.withModels {
|
binding.recycler.withModels {
|
||||||
setFilterDuplicates(true)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,11 +45,11 @@ class FavouriteFragment : BaseFragment<FragmentFavouriteBinding>() {
|
|||||||
private val mimeType = "application/json"
|
private val mimeType = "application/json"
|
||||||
private val startForDocumentImport =
|
private val startForDocumentImport =
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) {
|
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 =
|
private val startForDocumentExport =
|
||||||
registerForActivityResult(ActivityResultContracts.CreateDocument(mimeType)) {
|
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?) {
|
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)
|
viewModel.importFavourites(requireContext(), uri)
|
||||||
binding.recycler.requestModelBuild()
|
binding.recycler.requestModelBuild()
|
||||||
toast(R.string.toast_fav_import_success)
|
toast(R.string.toast_fav_import_success)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exportDeviceConfig(uri: Uri) {
|
private fun exportFavourites(uri: Uri) {
|
||||||
viewModel.exportFavourites(requireContext(), uri)
|
viewModel.exportFavourites(requireContext(), uri)
|
||||||
toast(R.string.toast_fav_export_success)
|
toast(R.string.toast_fav_export_success)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,14 @@ package com.aurora.store.viewmodel.all
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.aurora.store.data.providers.BlacklistProvider
|
import com.aurora.store.data.providers.BlacklistProvider
|
||||||
import com.aurora.store.util.PackageUtil
|
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.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -37,6 +40,7 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class BlacklistViewModel @Inject constructor(
|
class BlacklistViewModel @Inject constructor(
|
||||||
val blacklistProvider: BlacklistProvider,
|
val blacklistProvider: BlacklistProvider,
|
||||||
|
val gson: Gson,
|
||||||
@ApplicationContext private val context: Context
|
@ApplicationContext private val context: Context
|
||||||
) : ViewModel() {
|
) : 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
app/src/main/res/drawable/ic_menu.xml
Normal file
9
app/src/main/res/drawable/ic_menu.xml
Normal 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>
|
||||||
@@ -5,5 +5,6 @@
|
|||||||
<item name="chipStyle">@style/Chip.Filter</item>
|
<item name="chipStyle">@style/Chip.Filter</item>
|
||||||
<item name="preferenceTheme">@style/AppTheme.PreferenceThemeOverlay</item>
|
<item name="preferenceTheme">@style/AppTheme.PreferenceThemeOverlay</item>
|
||||||
<item name="android:statusBarColor">@color/colorTransparent</item>
|
<item name="android:statusBarColor">@color/colorTransparent</item>
|
||||||
|
<item name="popupMenuStyle">@style/AppTheme.PopupMenu</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -437,6 +437,12 @@
|
|||||||
<string name="toast_fav_import_success">Favourites imported!</string>
|
<string name="toast_fav_import_success">Favourites imported!</string>
|
||||||
<string name="toast_fav_export_success">Favourites exported!</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 -->
|
<!-- ExportWorker -->
|
||||||
<string name="export_app_title">File Exporter</string>
|
<string name="export_app_title">File Exporter</string>
|
||||||
<string name="export_app_summary">Hold on, exporting your file</string>
|
<string name="export_app_summary">Hold on, exporting your file</string>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
<item name="preferenceTheme">@style/AppTheme.PreferenceThemeOverlay</item>
|
<item name="preferenceTheme">@style/AppTheme.PreferenceThemeOverlay</item>
|
||||||
<item name="bottomSheetStyle">@style/AppTheme.BottomSheetStyle</item>
|
<item name="bottomSheetStyle">@style/AppTheme.BottomSheetStyle</item>
|
||||||
<item name="android:statusBarColor">@color/colorTransparent</item>
|
<item name="android:statusBarColor">@color/colorTransparent</item>
|
||||||
|
<item name="popupMenuStyle">@style/AppTheme.PopupMenu</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppTheme.Translucent" parent="Theme.Material3.DynamicColors.DayNight.NoActionBar">
|
<style name="AppTheme.Translucent" parent="Theme.Material3.DynamicColors.DayNight.NoActionBar">
|
||||||
@@ -47,17 +48,23 @@
|
|||||||
<item name="widgetLayout">@layout/preference_material_switch</item>
|
<item name="widgetLayout">@layout/preference_material_switch</item>
|
||||||
</style>
|
</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.Filter" parent="@style/Widget.Material3.Chip.Filter" />
|
||||||
|
|
||||||
<style name="Chip.TopChart" 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="android:textAppearance">@style/TextAppearance.Aurora.Line1</item>
|
||||||
<item name="chipStrokeColor">?colorControlHighlight</item>
|
<item name="chipStrokeColor">?attr/colorControlHighlight</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Chip.Tag" parent="@style/Widget.Material3.Chip.Assist.Elevated">
|
<style name="Chip.Tag" parent="@style/Widget.Material3.Chip.Assist.Elevated">
|
||||||
<item name="android:clickable">false</item>
|
<item name="android:clickable">false</item>
|
||||||
<item name="android:textAppearance">@style/TextAppearance.Aurora.Line2</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>
|
<item name="chipStrokeWidth">0dp</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user