AppsGamesFragment: Allow exporting list as favourites
- This will allow isers to export there current installed app & then do bulk import as favourites - Probably allowing a bulk install in favourites later would be a great addition
This commit is contained in:
@@ -35,7 +35,8 @@ object Constants {
|
||||
const val SHARE_URL = "https://play.google.com/store/apps/details?id="
|
||||
|
||||
const val UPDATE_URL_STABLE = "https://gitlab.com/AuroraOSS/AuroraStore/raw/master/updates.json"
|
||||
const val UPDATE_URL_NIGHTLY = "https://auroraoss.com/downloads/AuroraStore/Feeds/nightly_feed.json"
|
||||
const val UPDATE_URL_NIGHTLY =
|
||||
"https://auroraoss.com/downloads/AuroraStore/Feeds/nightly_feed.json"
|
||||
|
||||
const val NOTIFICATION_CHANNEL_EXPORT = "NOTIFICATION_CHANNEL_EXPORT"
|
||||
const val NOTIFICATION_CHANNEL_INSTALL = "NOTIFICATION_CHANNEL_INSTALL"
|
||||
@@ -56,4 +57,6 @@ object Constants {
|
||||
const val PAGE_TYPE = "PAGE_TYPE"
|
||||
const val TOP_CHART_TYPE = "TOP_CHART_TYPE"
|
||||
const val TOP_CHART_CATEGORY = "TOP_CHART_CATEGORY"
|
||||
|
||||
const val JSON_MIME_TYPE = "application/json"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.aurora.store.data.room.favourite
|
||||
import android.os.Parcelable
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@@ -16,6 +17,18 @@ data class Favourite(
|
||||
val mode: Mode
|
||||
) : Parcelable {
|
||||
|
||||
companion object {
|
||||
fun fromApp(app: App, mode: Mode): Favourite {
|
||||
return Favourite(
|
||||
packageName = app.packageName,
|
||||
displayName = app.displayName,
|
||||
iconURL = app.iconArtwork.url,
|
||||
added = System.currentTimeMillis(),
|
||||
mode = mode
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
MANUAL,
|
||||
IMPORT
|
||||
|
||||
@@ -19,15 +19,25 @@
|
||||
|
||||
package com.aurora.store.view.ui.all
|
||||
|
||||
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.Constants
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.model.MinimalApp
|
||||
import com.aurora.store.databinding.FragmentGenericWithSearchBinding
|
||||
@@ -38,12 +48,18 @@ import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.all.InstalledViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Calendar
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AppsGamesFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||
|
||||
private val viewModel: InstalledViewModel by viewModels()
|
||||
|
||||
private val startForDocumentExport =
|
||||
registerForActivityResult(ActivityResultContracts.CreateDocument(Constants.JSON_MIME_TYPE)) {
|
||||
if (it != null) exportInstalledApps(it) else toast(R.string.toast_fav_export_failed)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
@@ -69,9 +85,17 @@ class AppsGamesFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||
// Toolbar
|
||||
binding.layoutToolbarNative.apply {
|
||||
imgActionPrimary.visibility = View.VISIBLE
|
||||
imgActionSecondary.visibility = View.GONE
|
||||
imgActionSecondary.visibility = View.VISIBLE
|
||||
|
||||
imgActionPrimary.setOnClickListener { findNavController().navigateUp() }
|
||||
imgActionSecondary.apply {
|
||||
setImageDrawable(
|
||||
AppCompatResources.getDrawable(requireContext(), R.drawable.ic_menu)
|
||||
)
|
||||
setOnClickListener {
|
||||
showMenu(it)
|
||||
}
|
||||
}
|
||||
|
||||
inputSearch.addTextChangedListener(object : TextWatcher {
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
@@ -97,6 +121,35 @@ class AppsGamesFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
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.menu.removeItem(R.id.action_import)
|
||||
|
||||
popupMenu.setOnMenuItemClickListener { menuItem: MenuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.action_export -> {
|
||||
startForDocumentExport.launch(
|
||||
"aurora_store_apps_${Calendar.getInstance().time.time}.json"
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
popupMenu.show()
|
||||
}
|
||||
|
||||
private fun updateController(packages: List<App>?) {
|
||||
binding.recycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
@@ -137,4 +190,9 @@ class AppsGamesFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportInstalledApps(uri: Uri) {
|
||||
viewModel.exportApps(requireContext(), uri)
|
||||
toast(R.string.toast_fav_export_success)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.R
|
||||
@@ -51,13 +52,12 @@ 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)) {
|
||||
registerForActivityResult(ActivityResultContracts.CreateDocument(Constants.JSON_MIME_TYPE)) {
|
||||
if (it != null) exportBlacklist(it) else toast(R.string.toast_black_export_failed)
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ class BlacklistFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||
popupMenu.setOnMenuItemClickListener { menuItem: MenuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.action_import -> {
|
||||
startForDocumentImport.launch(arrayOf(mimeType))
|
||||
startForDocumentImport.launch(arrayOf(Constants.JSON_MIME_TYPE))
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.room.favourite.Favourite
|
||||
@@ -42,13 +43,12 @@ import java.util.Calendar
|
||||
class FavouriteFragment : BaseFragment<FragmentFavouriteBinding>() {
|
||||
private val viewModel: FavouriteViewModel by viewModels()
|
||||
|
||||
private val mimeType = "application/json"
|
||||
private val startForDocumentImport =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||
if (it != null) importFavourites(it) else toast(R.string.toast_fav_import_failed)
|
||||
}
|
||||
private val startForDocumentExport =
|
||||
registerForActivityResult(ActivityResultContracts.CreateDocument(mimeType)) {
|
||||
registerForActivityResult(ActivityResultContracts.CreateDocument(Constants.JSON_MIME_TYPE)) {
|
||||
if (it != null) exportFavourites(it) else toast(R.string.toast_fav_export_failed)
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class FavouriteFragment : BaseFragment<FragmentFavouriteBinding>() {
|
||||
binding.toolbar.apply {
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_import -> startForDocumentImport.launch(arrayOf(mimeType))
|
||||
R.id.action_import -> startForDocumentImport.launch(arrayOf(Constants.JSON_MIME_TYPE))
|
||||
R.id.action_export -> {
|
||||
startForDocumentExport.launch(
|
||||
"aurora_store_favourites_${Calendar.getInstance().time.time}.json"
|
||||
|
||||
@@ -21,6 +21,7 @@ package com.aurora.store.viewmodel.all
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.store.data.room.favourite.Favourite
|
||||
@@ -39,26 +40,38 @@ class FavouriteViewModel @Inject constructor(
|
||||
private val favouriteDao: FavouriteDao,
|
||||
private val gson: Gson
|
||||
) : ViewModel() {
|
||||
private val TAG = FavouriteViewModel::class.java.simpleName
|
||||
|
||||
val favouritesList = favouriteDao.favourites()
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||
|
||||
fun importFavourites(context: Context, uri: Uri) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
context.contentResolver.openInputStream(uri)?.use {
|
||||
val importExport =
|
||||
gson.fromJson(it.bufferedReader().readText(), ImportExport::class.java)
|
||||
favouriteDao.insertAll(
|
||||
importExport.favourites.map { fav -> fav.copy(mode = Favourite.Mode.IMPORT) }
|
||||
)
|
||||
try {
|
||||
context.contentResolver.openInputStream(uri)?.use {
|
||||
val importExport = gson.fromJson(
|
||||
it.bufferedReader().readText(),
|
||||
ImportExport::class.java
|
||||
)
|
||||
|
||||
favouriteDao.insertAll(
|
||||
importExport.favourites.map { fav -> fav.copy(mode = Favourite.Mode.IMPORT) }
|
||||
)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to import favourites", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun exportFavourites(context: Context, uri: Uri) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
context.contentResolver.openOutputStream(uri)?.use {
|
||||
it.write(gson.toJson(ImportExport(favouritesList.value!!)).encodeToByteArray())
|
||||
try {
|
||||
context.contentResolver.openOutputStream(uri)?.use {
|
||||
it.write(gson.toJson(ImportExport(favouritesList.value!!)).encodeToByteArray())
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to export favourites", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,17 @@
|
||||
package com.aurora.store.viewmodel.all
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.helpers.web.WebAppDetailsHelper
|
||||
import com.aurora.store.data.providers.BlacklistProvider
|
||||
import com.aurora.store.data.room.favourite.Favourite
|
||||
import com.aurora.store.data.room.favourite.ImportExport
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.google.gson.Gson
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -39,6 +43,7 @@ import javax.inject.Inject
|
||||
class InstalledViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val blacklistProvider: BlacklistProvider,
|
||||
private val gson: Gson
|
||||
) : ViewModel() {
|
||||
|
||||
private val TAG = InstalledViewModel::class.java.simpleName
|
||||
@@ -69,4 +74,19 @@ class InstalledViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun exportApps(context: Context, uri: Uri) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val favourites: List<Favourite> = apps.value!!.map { app ->
|
||||
Favourite.fromApp(app, Favourite.Mode.IMPORT)
|
||||
}
|
||||
context.contentResolver.openOutputStream(uri)?.use {
|
||||
it.write(gson.toJson(ImportExport(favourites)).encodeToByteArray())
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to installed apps", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,8 +236,8 @@ class AppDetailsViewModel @Inject constructor(
|
||||
|
||||
private fun getLatestExodusReport(packageName: String): Report? {
|
||||
val headers: MutableMap<String, String> = mutableMapOf()
|
||||
headers["Content-Type"] = "application/json"
|
||||
headers["Accept"] = "application/json"
|
||||
headers["Content-Type"] = Constants.JSON_MIME_TYPE
|
||||
headers["Accept"] = Constants.JSON_MIME_TYPE
|
||||
headers["Authorization"] = "Token ${BuildConfig.EXODUS_API_KEY}"
|
||||
|
||||
val url = Constants.EXODUS_SEARCH_URL + packageName
|
||||
|
||||
Reference in New Issue
Block a user