compose: favourite: Initial migration

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2025-09-22 14:46:38 +08:00
parent 78d029b8e8
commit e8a1e18750
17 changed files with 330 additions and 348 deletions

View File

@@ -27,6 +27,7 @@ import com.aurora.store.compose.ui.commons.PermissionRationaleScreen
import com.aurora.store.compose.ui.details.AppDetailsScreen import com.aurora.store.compose.ui.details.AppDetailsScreen
import com.aurora.store.compose.ui.dev.DevProfileScreen import com.aurora.store.compose.ui.dev.DevProfileScreen
import com.aurora.store.compose.ui.downloads.DownloadsScreen import com.aurora.store.compose.ui.downloads.DownloadsScreen
import com.aurora.store.compose.ui.favourite.FavouriteScreen
import com.aurora.store.compose.ui.search.SearchScreen import com.aurora.store.compose.ui.search.SearchScreen
/** /**
@@ -114,6 +115,15 @@ fun NavDisplay(startDestination: NavKey) {
entry<Screen.About> { entry<Screen.About> {
AboutScreen(onNavigateUp = { onNavigateUp() }) AboutScreen(onNavigateUp = { onNavigateUp() })
} }
entry<Screen.Favourite> {
FavouriteScreen(
onNavigateUp = { onNavigateUp() },
onNavigateToAppDetails = { packageName ->
backstack.add(Screen.AppDetails(packageName))
}
)
}
} }
) )
} }

View File

@@ -47,4 +47,7 @@ sealed class Screen : NavKey, Parcelable {
@Serializable @Serializable
data object About : Screen() data object About : Screen()
@Serializable
data object Favourite : Screen()
} }

View File

@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.preview
import androidx.compose.runtime.Composable
import androidx.paging.PagingData
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import kotlinx.coroutines.flow.flowOf
/**
* Empty lazy paging item flow for optional methods
*/
@Composable
fun <T: Any> emptyPagingItems(): LazyPagingItems<T> {
return flowOf(PagingData.empty<T>()).collectAsLazyPagingItems()
}

View File

@@ -49,6 +49,7 @@ import com.aurora.store.compose.composables.TopAppBarComposable
import com.aurora.store.compose.composables.details.ReviewComposable import com.aurora.store.compose.composables.details.ReviewComposable
import com.aurora.store.compose.preview.ReviewPreviewProvider import com.aurora.store.compose.preview.ReviewPreviewProvider
import com.aurora.store.compose.preview.coilPreviewProvider import com.aurora.store.compose.preview.coilPreviewProvider
import com.aurora.store.compose.preview.emptyPagingItems
import com.aurora.store.viewmodel.details.AppDetailsViewModel import com.aurora.store.viewmodel.details.AppDetailsViewModel
import com.aurora.store.viewmodel.details.ReviewViewModel import com.aurora.store.viewmodel.details.ReviewViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -87,7 +88,7 @@ fun ReviewScreen(
private fun ScreenContent( private fun ScreenContent(
topAppBarTitle: String? = null, topAppBarTitle: String? = null,
onNavigateUp: () -> Unit = {}, onNavigateUp: () -> Unit = {},
reviews: LazyPagingItems<Review>, reviews: LazyPagingItems<Review> = emptyPagingItems(),
onFilter: (filter: Review.Filter) -> Unit = {}, onFilter: (filter: Review.Filter) -> Unit = {},
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo() windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
) { ) {

View File

@@ -25,7 +25,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.paging.LoadState import androidx.paging.LoadState
import androidx.paging.PagingData
import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey import androidx.paging.compose.itemKey
@@ -35,11 +34,11 @@ import com.aurora.store.compose.composables.DownloadComposable
import com.aurora.store.compose.composables.ErrorComposable import com.aurora.store.compose.composables.ErrorComposable
import com.aurora.store.compose.composables.ProgressComposable import com.aurora.store.compose.composables.ProgressComposable
import com.aurora.store.compose.composables.TopAppBarComposable import com.aurora.store.compose.composables.TopAppBarComposable
import com.aurora.store.compose.preview.emptyPagingItems
import com.aurora.store.compose.ui.downloads.menu.DownloadsMenu import com.aurora.store.compose.ui.downloads.menu.DownloadsMenu
import com.aurora.store.compose.ui.downloads.menu.MenuItem import com.aurora.store.compose.ui.downloads.menu.MenuItem
import com.aurora.store.data.room.download.Download import com.aurora.store.data.room.download.Download
import com.aurora.store.viewmodel.downloads.DownloadsViewModel import com.aurora.store.viewmodel.downloads.DownloadsViewModel
import kotlinx.coroutines.flow.flowOf
@Composable @Composable
fun DownloadsScreen( fun DownloadsScreen(
@@ -85,7 +84,7 @@ fun DownloadsScreen(
@Composable @Composable
private fun ScreenContent( private fun ScreenContent(
downloads: LazyPagingItems<Download> = flowOf(PagingData.empty<Download>()).collectAsLazyPagingItems(), downloads: LazyPagingItems<Download> = emptyPagingItems(),
onNavigateUp: () -> Unit = {}, onNavigateUp: () -> Unit = {},
onNavigateToAppDetails: (packageName: String) -> Unit = {}, onNavigateToAppDetails: (packageName: String) -> Unit = {},
onCancel: (packageName: String) -> Unit = {}, onCancel: (packageName: String) -> Unit = {},

View File

@@ -0,0 +1,169 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.ui.favourite
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import com.aurora.Constants.JSON_MIME_TYPE
import com.aurora.extensions.toast
import com.aurora.store.R
import com.aurora.store.compose.composables.ErrorComposable
import com.aurora.store.compose.composables.FavouriteComposable
import com.aurora.store.compose.composables.ProgressComposable
import com.aurora.store.compose.composables.TopAppBarComposable
import com.aurora.store.compose.preview.emptyPagingItems
import com.aurora.store.compose.ui.favourite.menu.FavouriteMenu
import com.aurora.store.compose.ui.favourite.menu.MenuItem
import com.aurora.store.data.room.favourite.Favourite
import com.aurora.store.viewmodel.all.FavouriteViewModel
import java.util.Calendar
@Composable
fun FavouriteScreen(
onNavigateUp: () -> Unit,
onNavigateToAppDetails: (packageName: String) -> Unit,
viewModel: FavouriteViewModel = hiltViewModel()
) {
val context = LocalContext.current
val favourites = viewModel.favourites.collectAsLazyPagingItems()
val documentOpenLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
onResult = {
if (it != null) {
viewModel.importFavourites(it)
context.toast(R.string.toast_fav_import_success)
} else {
context.toast(R.string.toast_fav_import_failed)
}
}
)
val documentCreateLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument(JSON_MIME_TYPE),
onResult = {
if (it != null) {
viewModel.exportFavourites(it)
context.toast(R.string.toast_fav_export_success)
} else {
context.toast(R.string.toast_fav_export_failed)
}
}
)
ScreenContent(
favourites = favourites,
onNavigateUp = onNavigateUp,
onNavigateToAppDetails = onNavigateToAppDetails,
onRemoveFavourite = { packageName -> viewModel.removeFavourite(packageName) },
onImportFavourites = {
documentOpenLauncher.launch(arrayOf(JSON_MIME_TYPE))
},
onExportFavourites = {
documentCreateLauncher.launch(
"aurora_store_favourites_${Calendar.getInstance().time.time}.json"
)
}
)
}
@Composable
private fun ScreenContent(
favourites: LazyPagingItems<Favourite> = emptyPagingItems(),
onNavigateUp: () -> Unit = {},
onNavigateToAppDetails: (packageName: String) -> Unit = {},
onRemoveFavourite: (packageName: String) -> Unit = {},
onImportFavourites: () -> Unit = {},
onExportFavourites: () -> Unit = {}
) {
@Composable
fun SetupMenu() {
FavouriteMenu { menuItem ->
when (menuItem) {
MenuItem.IMPORT -> onImportFavourites()
MenuItem.EXPORT -> onExportFavourites()
}
}
}
Scaffold(
topBar = {
TopAppBarComposable(
title = stringResource(R.string.title_favourites_manager),
onNavigateUp = onNavigateUp,
actions = { if (favourites.itemCount != 0) SetupMenu() }
)
}
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.padding(vertical = dimensionResource(R.dimen.padding_medium))
) {
when (favourites.loadState.refresh) {
is LoadState.Loading -> ProgressComposable()
is LoadState.Error -> {
ErrorComposable(
modifier = Modifier.padding(paddingValues),
icon = painterResource(R.drawable.ic_disclaimer),
message = stringResource(R.string.error)
)
}
else -> {
if (favourites.itemCount == 0) {
ErrorComposable(
modifier = Modifier.padding(paddingValues),
icon = painterResource(R.drawable.ic_favorite_unchecked),
message = stringResource(R.string.details_no_favourites)
)
} else {
LazyColumn {
items(
count = favourites.itemCount,
key = favourites.itemKey { it.packageName }
) { index ->
favourites[index]?.let { favourite ->
FavouriteComposable(
favourite = favourite,
onClick = { onNavigateToAppDetails(favourite.packageName) },
onClear = { onRemoveFavourite(favourite.packageName) }
)
}
}
}
}
}
}
}
}
}
@Preview
@Composable
private fun FavouriteScreenPreview() {
ScreenContent()
}

View File

@@ -0,0 +1,67 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.ui.favourite.menu
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.aurora.store.R
/**
* Menu for the favourite screen
* @param modifier The modifier to be applied to the composable
* @param onMenuItemClicked Callback when a menu item has been clicked
* @see MenuItem
*/
@Composable
fun FavouriteMenu(
modifier: Modifier = Modifier,
isExpanded: Boolean = false,
onMenuItemClicked: (menuItem: MenuItem) -> Unit = {}
) {
var expanded by remember { mutableStateOf(isExpanded) }
fun onClick(menuItem: MenuItem) {
onMenuItemClicked(menuItem)
expanded = false
}
Box(modifier = modifier) {
IconButton(onClick = { expanded = true }) {
Icon(
painter = painterResource(R.drawable.ic_more_vert),
contentDescription = stringResource(R.string.menu)
)
}
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_import)) },
onClick = { onClick(MenuItem.IMPORT) }
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_export)) },
onClick = { onClick(MenuItem.EXPORT) }
)
}
}
}
@Preview(showBackground = true)
@Composable
private fun FavouriteMenuPreview() {
FavouriteMenu(isExpanded = true)
}

View File

@@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.ui.favourite.menu
/**
* Valid menu items for the purpose of handling clicks
*/
enum class MenuItem {
IMPORT,
EXPORT
}

View File

@@ -70,6 +70,7 @@ import com.aurora.store.compose.composables.SearchSuggestionComposable
import com.aurora.store.compose.composables.app.AppListComposable import com.aurora.store.compose.composables.app.AppListComposable
import com.aurora.store.compose.preview.AppPreviewProvider import com.aurora.store.compose.preview.AppPreviewProvider
import com.aurora.store.compose.preview.coilPreviewProvider import com.aurora.store.compose.preview.coilPreviewProvider
import com.aurora.store.compose.preview.emptyPagingItems
import com.aurora.store.compose.ui.details.AppDetailsScreen import com.aurora.store.compose.ui.details.AppDetailsScreen
import com.aurora.store.data.model.SearchFilter import com.aurora.store.data.model.SearchFilter
import com.aurora.store.viewmodel.search.SearchViewModel import com.aurora.store.viewmodel.search.SearchViewModel
@@ -98,7 +99,7 @@ fun SearchScreen(onNavigateUp: () -> Unit, viewModel: SearchViewModel = hiltView
@Composable @Composable
private fun ScreenContent( private fun ScreenContent(
suggestions: List<SearchSuggestEntry> = emptyList(), suggestions: List<SearchSuggestEntry> = emptyList(),
results: LazyPagingItems<App> = flowOf(PagingData.empty<App>()).collectAsLazyPagingItems(), results: LazyPagingItems<App> = emptyPagingItems(),
onNavigateUp: () -> Unit = {}, onNavigateUp: () -> Unit = {},
onFetchSuggestions: (String) -> Unit = {}, onFetchSuggestions: (String) -> Unit = {},
onSearch: (String) -> Unit = {}, onSearch: (String) -> Unit = {},

View File

@@ -1,5 +1,6 @@
package com.aurora.store.data.room.favourite package com.aurora.store.data.room.favourite
import androidx.paging.PagingSource
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
@@ -18,6 +19,9 @@ interface FavouriteDao {
@Query("SELECT * FROM favourite") @Query("SELECT * FROM favourite")
fun favourites(): Flow<List<Favourite>> fun favourites(): Flow<List<Favourite>>
@Query("SELECT * FROM favourite")
fun pagedFavourites(): PagingSource<Int, Favourite>
@Query("SELECT EXISTS(SELECT 1 FROM favourite WHERE packageName = :packageName)") @Query("SELECT EXISTS(SELECT 1 FROM favourite WHERE packageName = :packageName)")
suspend fun isFavourite(packageName: String): Boolean suspend fun isFavourite(packageName: String): Boolean

View File

@@ -1,65 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Aurora Store is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.epoxy.views
import android.content.Context
import android.util.AttributeSet
import coil3.load
import coil3.request.placeholder
import coil3.request.transformations
import coil3.transform.RoundedCornersTransformation
import com.airbnb.epoxy.CallbackProp
import com.airbnb.epoxy.ModelProp
import com.airbnb.epoxy.ModelView
import com.aurora.store.R
import com.aurora.store.data.room.favourite.Favourite
import com.aurora.store.databinding.ViewFavBinding
@ModelView(
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
baseModelClass = BaseModel::class
)
class FavouriteView @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : BaseView<ViewFavBinding>(context, attrs, defStyleAttr) {
@ModelProp
fun favourite(favourite: Favourite) {
binding.imgIcon.load(favourite.iconURL) {
placeholder(R.drawable.bg_placeholder)
transformations(RoundedCornersTransformation(25F))
}
binding.txtLine1.text = favourite.displayName
binding.txtLine2.text = favourite.packageName
}
@CallbackProp
fun onClick(onClickListener: OnClickListener?) {
binding.root.setOnClickListener(onClickListener)
}
@CallbackProp
fun onFavourite(onClickListener: OnClickListener?) {
binding.btnFavourite.setOnClickListener(onClickListener)
}
}

View File

@@ -1,124 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Aurora Store is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.ui.commons
import android.net.Uri
import android.os.Bundle
import android.view.View
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
import com.aurora.store.databinding.FragmentFavouriteBinding
import com.aurora.store.view.epoxy.views.FavouriteViewModel_
import com.aurora.store.view.epoxy.views.app.NoAppViewModel_
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
import com.aurora.store.viewmodel.all.FavouriteViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import java.util.Calendar
@AndroidEntryPoint
class FavouriteFragment : BaseFragment<FragmentFavouriteBinding>() {
private val viewModel: FavouriteViewModel by viewModels()
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(Constants.JSON_MIME_TYPE)) {
if (it != null) exportFavourites(it) else toast(R.string.toast_fav_export_failed)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
viewModel.favouritesList.collect {
updateController(it)
}
}
// Toolbar
binding.toolbar.apply {
setOnMenuItemClickListener {
when (it.itemId) {
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"
)
}
else -> {}
}
true
}
setNavigationOnClickListener { findNavController().navigateUp() }
}
}
private fun updateController(favourites: List<Favourite>?) {
binding.recycler.withModels {
setFilterDuplicates(true)
if (favourites == null) {
for (i in 1..10) {
add(
AppListViewShimmerModel_()
.id(i)
)
}
} else if (favourites.isEmpty()) {
add(
NoAppViewModel_()
.id("no_app")
.icon(R.drawable.ic_favorite_unchecked)
.message(R.string.details_no_favourites)
)
} else {
favourites.forEach {
add(
FavouriteViewModel_()
.id(it.packageName.hashCode())
.favourite(it)
.onClick { _ -> openDetailsFragment(it.packageName) }
.onFavourite { _ -> viewModel.removeFavourite(it.packageName) }
)
}
}
}
}
private fun importFavourites(uri: Uri) {
viewModel.importFavourites(requireContext(), uri)
binding.recycler.requestModelBuild()
toast(R.string.toast_fav_import_success)
}
private fun exportFavourites(uri: Uri) {
viewModel.exportFavourites(requireContext(), uri)
toast(R.string.toast_fav_export_success)
}
}

View File

@@ -433,10 +433,10 @@ class MoreDialogFragment : DialogFragment() {
icon = R.drawable.ic_blacklist, icon = R.drawable.ic_blacklist,
screen = Screen.Blacklist screen = Screen.Blacklist
), ),
ViewOption( ComposeOption(
title = R.string.title_favourites_manager, title = R.string.title_favourites_manager,
icon = R.drawable.ic_favorite_unchecked, icon = R.drawable.ic_favorite_unchecked,
destinationID = R.id.favouriteFragment screen = Screen.Favourite
), ),
ViewOption( ViewOption(
title = R.string.title_spoof_manager, title = R.string.title_spoof_manager,

View File

@@ -1,20 +1,7 @@
/* /*
* Aurora Store * SPDX-FileCopyrightText: 2024-2025 Rahul Kumar Patel <whyorean@gmail.com>
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com> * SPDX-FileCopyrightText: 2024-2025 The Calyx Institute
* * SPDX-License-Identifier: GPL-3.0-or-later
* Aurora Store is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Aurora Store is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/ */
package com.aurora.store.viewmodel.all package com.aurora.store.viewmodel.all
@@ -24,13 +11,21 @@ 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 androidx.paging.PagingData
import androidx.paging.cachedIn
import com.aurora.store.data.paging.GenericPagingSource.Companion.pager
import com.aurora.store.data.room.favourite.Favourite import com.aurora.store.data.room.favourite.Favourite
import com.aurora.store.data.room.favourite.FavouriteDao import com.aurora.store.data.room.favourite.FavouriteDao
import com.aurora.store.data.room.favourite.ImportExport import com.aurora.store.data.room.favourite.ImportExport
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import javax.inject.Inject import javax.inject.Inject
@@ -38,14 +33,19 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class FavouriteViewModel @Inject constructor( class FavouriteViewModel @Inject constructor(
private val favouriteDao: FavouriteDao, private val favouriteDao: FavouriteDao,
private val json: Json private val json: Json,
@ApplicationContext private val context: Context
) : ViewModel() { ) : ViewModel() {
private val TAG = FavouriteViewModel::class.java.simpleName private val TAG = FavouriteViewModel::class.java.simpleName
val favouritesList = favouriteDao.favourites() private val _favourites = MutableStateFlow<PagingData<Favourite>>(PagingData.empty())
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) val favourites = _favourites.asStateFlow()
fun importFavourites(context: Context, uri: Uri) { init {
getPagedFavourites()
}
fun importFavourites(uri: Uri) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
context.contentResolver.openInputStream(uri)?.use { context.contentResolver.openInputStream(uri)?.use {
@@ -54,7 +54,9 @@ class FavouriteViewModel @Inject constructor(
) )
favouriteDao.insertAll( favouriteDao.insertAll(
importExport.favourites.map { fav -> fav.copy(mode = Favourite.Mode.IMPORT) } importExport.favourites.map { fav ->
fav.copy(mode = Favourite.Mode.IMPORT)
}
) )
} }
} catch (exception: Exception) { } catch (exception: Exception) {
@@ -63,12 +65,12 @@ class FavouriteViewModel @Inject constructor(
} }
} }
fun exportFavourites(context: Context, uri: Uri) { fun exportFavourites(uri: Uri) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
context.contentResolver.openOutputStream(uri)?.use { context.contentResolver.openOutputStream(uri)?.use {
it.write( it.write(
json.encodeToString(ImportExport(favouritesList.value!!)) json.encodeToString(ImportExport(favouriteDao.favourites().first()))
.encodeToByteArray() .encodeToByteArray()
) )
} }
@@ -83,4 +85,12 @@ class FavouriteViewModel @Inject constructor(
favouriteDao.delete(packageName) favouriteDao.delete(packageName)
} }
} }
private fun getPagedFavourites() {
pager { favouriteDao.pagedFavourites() }.flow
.distinctUntilChanged()
.cachedIn(viewModelScope)
.onEach { _favourites.value = it }
.launchIn(viewModelScope)
}
} }

View File

@@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Aurora Store
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
~
~ Aurora Store is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 2 of the License, or
~ (at your option) any later version.
~
~ Aurora Store is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
~
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
app:menu="@menu/menu_import_export"
app:navigationIcon="@drawable/ic_arrow_back"
app:title="@string/title_favourites_manager" />
<com.airbnb.epoxy.EpoxyRecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/toolbar"
android:clipToPadding="true"
android:paddingBottom="@dimen/height_bottom_adj"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/view_app_list"
app:stackFromEnd="false" />
</RelativeLayout>

View File

@@ -1,76 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Aurora Store
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
~
~ Aurora Store is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 2 of the License, or
~ (at your option) any later version.
~
~ Aurora Store is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
~
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="@dimen/padding_small"
android:paddingTop="@dimen/padding_xsmall"
android:paddingEnd="@dimen/padding_small"
android:paddingBottom="@dimen/padding_xsmall">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/img_icon"
android:layout_width="@dimen/icon_size_medium"
android:layout_height="@dimen/icon_size_medium" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_line1"
style="@style/AuroraTextStyle.Line1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:layout_toStartOf="@id/btn_favourite"
android:layout_toEndOf="@id/img_icon"
tools:text="Line1" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_line2"
style="@style/AuroraTextStyle.Line2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txt_line1"
android:layout_alignStart="@id/txt_line1"
android:layout_alignEnd="@id/txt_line1"
tools:text="Line2" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_line3"
style="@style/AuroraTextStyle.Line3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txt_line2"
android:layout_alignStart="@id/txt_line1"
android:layout_alignEnd="@id/txt_line1"
tools:text="Line3" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_favourite"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerInParent="true"
android:contentDescription="@string/action_favourite"
android:padding="@dimen/padding_large"
app:icon="@drawable/ic_favorite_checked" />
</RelativeLayout>

View File

@@ -66,11 +66,6 @@
android:name="com.aurora.store.view.ui.spoof.SpoofFragment" android:name="com.aurora.store.view.ui.spoof.SpoofFragment"
android:label="@string/title_spoof_manager" android:label="@string/title_spoof_manager"
tools:layout="@layout/fragment_spoof" /> tools:layout="@layout/fragment_spoof" />
<fragment
android:id="@+id/favouriteFragment"
android:name="com.aurora.store.view.ui.commons.FavouriteFragment"
android:label="@string/title_favourites_manager"
tools:layout="@layout/fragment_generic_with_toolbar" />
<fragment <fragment
android:id="@+id/settingsFragment" android:id="@+id/settingsFragment"
android:name="com.aurora.store.view.ui.preferences.SettingsFragment" android:name="com.aurora.store.view.ui.preferences.SettingsFragment"