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 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_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_EXPORT = "NOTIFICATION_CHANNEL_EXPORT"
|
||||||
const val NOTIFICATION_CHANNEL_INSTALL = "NOTIFICATION_CHANNEL_INSTALL"
|
const val NOTIFICATION_CHANNEL_INSTALL = "NOTIFICATION_CHANNEL_INSTALL"
|
||||||
@@ -56,4 +57,6 @@ object Constants {
|
|||||||
const val PAGE_TYPE = "PAGE_TYPE"
|
const val PAGE_TYPE = "PAGE_TYPE"
|
||||||
const val TOP_CHART_TYPE = "TOP_CHART_TYPE"
|
const val TOP_CHART_TYPE = "TOP_CHART_TYPE"
|
||||||
const val TOP_CHART_CATEGORY = "TOP_CHART_CATEGORY"
|
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 android.os.Parcelable
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import com.aurora.gplayapi.data.models.App
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -16,6 +17,18 @@ data class Favourite(
|
|||||||
val mode: Mode
|
val mode: Mode
|
||||||
) : Parcelable {
|
) : 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 {
|
enum class Mode {
|
||||||
MANUAL,
|
MANUAL,
|
||||||
IMPORT
|
IMPORT
|
||||||
|
|||||||
@@ -19,15 +19,25 @@
|
|||||||
|
|
||||||
package com.aurora.store.view.ui.all
|
package com.aurora.store.view.ui.all
|
||||||
|
|
||||||
|
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.Constants
|
||||||
|
import com.aurora.extensions.toast
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.store.AuroraApp
|
import com.aurora.store.AuroraApp
|
||||||
|
import com.aurora.store.R
|
||||||
import com.aurora.store.data.event.InstallerEvent
|
import com.aurora.store.data.event.InstallerEvent
|
||||||
import com.aurora.store.data.model.MinimalApp
|
import com.aurora.store.data.model.MinimalApp
|
||||||
import com.aurora.store.databinding.FragmentGenericWithSearchBinding
|
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 com.aurora.store.viewmodel.all.InstalledViewModel
|
||||||
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 AppsGamesFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
class AppsGamesFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||||
|
|
||||||
private val viewModel: InstalledViewModel by viewModels()
|
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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
@@ -69,9 +85,17 @@ class AppsGamesFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
|||||||
// Toolbar
|
// Toolbar
|
||||||
binding.layoutToolbarNative.apply {
|
binding.layoutToolbarNative.apply {
|
||||||
imgActionPrimary.visibility = View.VISIBLE
|
imgActionPrimary.visibility = View.VISIBLE
|
||||||
imgActionSecondary.visibility = View.GONE
|
imgActionSecondary.visibility = View.VISIBLE
|
||||||
|
|
||||||
imgActionPrimary.setOnClickListener { findNavController().navigateUp() }
|
imgActionPrimary.setOnClickListener { findNavController().navigateUp() }
|
||||||
|
imgActionSecondary.apply {
|
||||||
|
setImageDrawable(
|
||||||
|
AppCompatResources.getDrawable(requireContext(), R.drawable.ic_menu)
|
||||||
|
)
|
||||||
|
setOnClickListener {
|
||||||
|
showMenu(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inputSearch.addTextChangedListener(object : TextWatcher {
|
inputSearch.addTextChangedListener(object : TextWatcher {
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
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>?) {
|
private fun updateController(packages: List<App>?) {
|
||||||
binding.recycler.withModels {
|
binding.recycler.withModels {
|
||||||
setFilterDuplicates(true)
|
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.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import com.aurora.Constants
|
||||||
import com.aurora.extensions.toast
|
import com.aurora.extensions.toast
|
||||||
import com.aurora.store.AuroraApp
|
import com.aurora.store.AuroraApp
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
@@ -51,13 +52,12 @@ class BlacklistFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
|||||||
|
|
||||||
private val viewModel: BlacklistViewModel by viewModels()
|
private val viewModel: BlacklistViewModel by viewModels()
|
||||||
|
|
||||||
private val mimeType = "application/json"
|
|
||||||
private val startForDocumentImport =
|
private val startForDocumentImport =
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) {
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||||
if (it != null) importBlacklist(it) else toast(R.string.toast_black_import_failed)
|
if (it != null) importBlacklist(it) else toast(R.string.toast_black_import_failed)
|
||||||
}
|
}
|
||||||
private val startForDocumentExport =
|
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)
|
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 ->
|
popupMenu.setOnMenuItemClickListener { menuItem: MenuItem ->
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
R.id.action_import -> {
|
R.id.action_import -> {
|
||||||
startForDocumentImport.launch(arrayOf(mimeType))
|
startForDocumentImport.launch(arrayOf(Constants.JSON_MIME_TYPE))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
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.Constants
|
||||||
import com.aurora.extensions.toast
|
import com.aurora.extensions.toast
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.data.room.favourite.Favourite
|
import com.aurora.store.data.room.favourite.Favourite
|
||||||
@@ -42,13 +43,12 @@ import java.util.Calendar
|
|||||||
class FavouriteFragment : BaseFragment<FragmentFavouriteBinding>() {
|
class FavouriteFragment : BaseFragment<FragmentFavouriteBinding>() {
|
||||||
private val viewModel: FavouriteViewModel by viewModels()
|
private val viewModel: FavouriteViewModel by viewModels()
|
||||||
|
|
||||||
private val mimeType = "application/json"
|
|
||||||
private val startForDocumentImport =
|
private val startForDocumentImport =
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) {
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||||
if (it != null) importFavourites(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(Constants.JSON_MIME_TYPE)) {
|
||||||
if (it != null) exportFavourites(it) else toast(R.string.toast_fav_export_failed)
|
if (it != null) exportFavourites(it) else toast(R.string.toast_fav_export_failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ class FavouriteFragment : BaseFragment<FragmentFavouriteBinding>() {
|
|||||||
binding.toolbar.apply {
|
binding.toolbar.apply {
|
||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener {
|
||||||
when (it.itemId) {
|
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 -> {
|
R.id.action_export -> {
|
||||||
startForDocumentExport.launch(
|
startForDocumentExport.launch(
|
||||||
"aurora_store_favourites_${Calendar.getInstance().time.time}.json"
|
"aurora_store_favourites_${Calendar.getInstance().time.time}.json"
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package com.aurora.store.viewmodel.all
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
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.room.favourite.Favourite
|
import com.aurora.store.data.room.favourite.Favourite
|
||||||
@@ -39,26 +40,38 @@ class FavouriteViewModel @Inject constructor(
|
|||||||
private val favouriteDao: FavouriteDao,
|
private val favouriteDao: FavouriteDao,
|
||||||
private val gson: Gson
|
private val gson: Gson
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
private val TAG = FavouriteViewModel::class.java.simpleName
|
||||||
|
|
||||||
val favouritesList = favouriteDao.favourites()
|
val favouritesList = favouriteDao.favourites()
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||||
|
|
||||||
fun importFavourites(context: Context, uri: Uri) {
|
fun importFavourites(context: Context, uri: Uri) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
context.contentResolver.openInputStream(uri)?.use {
|
try {
|
||||||
val importExport =
|
context.contentResolver.openInputStream(uri)?.use {
|
||||||
gson.fromJson(it.bufferedReader().readText(), ImportExport::class.java)
|
val importExport = gson.fromJson(
|
||||||
favouriteDao.insertAll(
|
it.bufferedReader().readText(),
|
||||||
importExport.favourites.map { fav -> fav.copy(mode = Favourite.Mode.IMPORT) }
|
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) {
|
fun exportFavourites(context: Context, uri: Uri) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
context.contentResolver.openOutputStream(uri)?.use {
|
try {
|
||||||
it.write(gson.toJson(ImportExport(favouritesList.value!!)).encodeToByteArray())
|
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
|
package com.aurora.store.viewmodel.all
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
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.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.gplayapi.helpers.web.WebAppDetailsHelper
|
import com.aurora.gplayapi.helpers.web.WebAppDetailsHelper
|
||||||
import com.aurora.store.data.providers.BlacklistProvider
|
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.aurora.store.util.PackageUtil
|
||||||
|
import com.google.gson.Gson
|
||||||
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
|
||||||
@@ -39,6 +43,7 @@ import javax.inject.Inject
|
|||||||
class InstalledViewModel @Inject constructor(
|
class InstalledViewModel @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val blacklistProvider: BlacklistProvider,
|
private val blacklistProvider: BlacklistProvider,
|
||||||
|
private val gson: Gson
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val TAG = InstalledViewModel::class.java.simpleName
|
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? {
|
private fun getLatestExodusReport(packageName: String): Report? {
|
||||||
val headers: MutableMap<String, String> = mutableMapOf()
|
val headers: MutableMap<String, String> = mutableMapOf()
|
||||||
headers["Content-Type"] = "application/json"
|
headers["Content-Type"] = Constants.JSON_MIME_TYPE
|
||||||
headers["Accept"] = "application/json"
|
headers["Accept"] = Constants.JSON_MIME_TYPE
|
||||||
headers["Authorization"] = "Token ${BuildConfig.EXODUS_API_KEY}"
|
headers["Authorization"] = "Token ${BuildConfig.EXODUS_API_KEY}"
|
||||||
|
|
||||||
val url = Constants.EXODUS_SEARCH_URL + packageName
|
val url = Constants.EXODUS_SEARCH_URL + packageName
|
||||||
|
|||||||
Reference in New Issue
Block a user