From e8a1e18750d01ccd039c4e7de57737e079161f22 Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Mon, 22 Sep 2025 14:46:38 +0800 Subject: [PATCH] compose: favourite: Initial migration Signed-off-by: Aayush Gupta --- .../store/compose/navigation/NavDisplay.kt | 10 ++ .../aurora/store/compose/navigation/Screen.kt | 3 + .../aurora/store/compose/preview/Paging.kt | 20 +++ .../store/compose/ui/details/ReviewScreen.kt | 3 +- .../compose/ui/downloads/DownloadsScreen.kt | 5 +- .../compose/ui/favourite/FavouriteScreen.kt | 169 ++++++++++++++++++ .../ui/favourite/menu/FavouriteMenu.kt | 67 +++++++ .../compose/ui/favourite/menu/MenuItem.kt | 14 ++ .../store/compose/ui/search/SearchScreen.kt | 3 +- .../store/data/room/favourite/FavouriteDao.kt | 4 + .../store/view/epoxy/views/FavouriteView.kt | 65 ------- .../view/ui/commons/FavouriteFragment.kt | 124 ------------- .../view/ui/commons/MoreDialogFragment.kt | 4 +- .../store/viewmodel/all/FavouriteViewModel.kt | 60 ++++--- .../main/res/layout/fragment_favourite.xml | 46 ----- app/src/main/res/layout/view_fav.xml | 76 -------- .../main/res/navigation/mobile_navigation.xml | 5 - 17 files changed, 330 insertions(+), 348 deletions(-) create mode 100644 app/src/main/java/com/aurora/store/compose/preview/Paging.kt create mode 100644 app/src/main/java/com/aurora/store/compose/ui/favourite/FavouriteScreen.kt create mode 100644 app/src/main/java/com/aurora/store/compose/ui/favourite/menu/FavouriteMenu.kt create mode 100644 app/src/main/java/com/aurora/store/compose/ui/favourite/menu/MenuItem.kt delete mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/FavouriteView.kt delete mode 100644 app/src/main/java/com/aurora/store/view/ui/commons/FavouriteFragment.kt delete mode 100644 app/src/main/res/layout/fragment_favourite.xml delete mode 100644 app/src/main/res/layout/view_fav.xml diff --git a/app/src/main/java/com/aurora/store/compose/navigation/NavDisplay.kt b/app/src/main/java/com/aurora/store/compose/navigation/NavDisplay.kt index 8b89d5bfb..5ce18437e 100644 --- a/app/src/main/java/com/aurora/store/compose/navigation/NavDisplay.kt +++ b/app/src/main/java/com/aurora/store/compose/navigation/NavDisplay.kt @@ -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.dev.DevProfileScreen import com.aurora.store.compose.ui.downloads.DownloadsScreen +import com.aurora.store.compose.ui.favourite.FavouriteScreen import com.aurora.store.compose.ui.search.SearchScreen /** @@ -114,6 +115,15 @@ fun NavDisplay(startDestination: NavKey) { entry { AboutScreen(onNavigateUp = { onNavigateUp() }) } + + entry { + FavouriteScreen( + onNavigateUp = { onNavigateUp() }, + onNavigateToAppDetails = { packageName -> + backstack.add(Screen.AppDetails(packageName)) + } + ) + } } ) } diff --git a/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt b/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt index 91a2ec41a..d204598c7 100644 --- a/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt +++ b/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt @@ -47,4 +47,7 @@ sealed class Screen : NavKey, Parcelable { @Serializable data object About : Screen() + + @Serializable + data object Favourite : Screen() } diff --git a/app/src/main/java/com/aurora/store/compose/preview/Paging.kt b/app/src/main/java/com/aurora/store/compose/preview/Paging.kt new file mode 100644 index 000000000..98c4b44f8 --- /dev/null +++ b/app/src/main/java/com/aurora/store/compose/preview/Paging.kt @@ -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 emptyPagingItems(): LazyPagingItems { + return flowOf(PagingData.empty()).collectAsLazyPagingItems() +} diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/ReviewScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/ReviewScreen.kt index 7beb24bf1..fdffd8082 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/ReviewScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/ReviewScreen.kt @@ -49,6 +49,7 @@ import com.aurora.store.compose.composables.TopAppBarComposable import com.aurora.store.compose.composables.details.ReviewComposable import com.aurora.store.compose.preview.ReviewPreviewProvider 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.ReviewViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -87,7 +88,7 @@ fun ReviewScreen( private fun ScreenContent( topAppBarTitle: String? = null, onNavigateUp: () -> Unit = {}, - reviews: LazyPagingItems, + reviews: LazyPagingItems = emptyPagingItems(), onFilter: (filter: Review.Filter) -> Unit = {}, windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo() ) { diff --git a/app/src/main/java/com/aurora/store/compose/ui/downloads/DownloadsScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/downloads/DownloadsScreen.kt index 2278e0e0f..bde67cfc6 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/downloads/DownloadsScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/downloads/DownloadsScreen.kt @@ -25,7 +25,6 @@ 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.PagingData import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems 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.ProgressComposable 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.MenuItem import com.aurora.store.data.room.download.Download import com.aurora.store.viewmodel.downloads.DownloadsViewModel -import kotlinx.coroutines.flow.flowOf @Composable fun DownloadsScreen( @@ -85,7 +84,7 @@ fun DownloadsScreen( @Composable private fun ScreenContent( - downloads: LazyPagingItems = flowOf(PagingData.empty()).collectAsLazyPagingItems(), + downloads: LazyPagingItems = emptyPagingItems(), onNavigateUp: () -> Unit = {}, onNavigateToAppDetails: (packageName: String) -> Unit = {}, onCancel: (packageName: String) -> Unit = {}, diff --git a/app/src/main/java/com/aurora/store/compose/ui/favourite/FavouriteScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/favourite/FavouriteScreen.kt new file mode 100644 index 000000000..658781c12 --- /dev/null +++ b/app/src/main/java/com/aurora/store/compose/ui/favourite/FavouriteScreen.kt @@ -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 = 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() +} diff --git a/app/src/main/java/com/aurora/store/compose/ui/favourite/menu/FavouriteMenu.kt b/app/src/main/java/com/aurora/store/compose/ui/favourite/menu/FavouriteMenu.kt new file mode 100644 index 000000000..3455368e0 --- /dev/null +++ b/app/src/main/java/com/aurora/store/compose/ui/favourite/menu/FavouriteMenu.kt @@ -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) +} diff --git a/app/src/main/java/com/aurora/store/compose/ui/favourite/menu/MenuItem.kt b/app/src/main/java/com/aurora/store/compose/ui/favourite/menu/MenuItem.kt new file mode 100644 index 000000000..1f6aebef2 --- /dev/null +++ b/app/src/main/java/com/aurora/store/compose/ui/favourite/menu/MenuItem.kt @@ -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 +} diff --git a/app/src/main/java/com/aurora/store/compose/ui/search/SearchScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/search/SearchScreen.kt index f45c72a91..1791aaf56 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/search/SearchScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/search/SearchScreen.kt @@ -70,6 +70,7 @@ import com.aurora.store.compose.composables.SearchSuggestionComposable import com.aurora.store.compose.composables.app.AppListComposable import com.aurora.store.compose.preview.AppPreviewProvider 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.data.model.SearchFilter import com.aurora.store.viewmodel.search.SearchViewModel @@ -98,7 +99,7 @@ fun SearchScreen(onNavigateUp: () -> Unit, viewModel: SearchViewModel = hiltView @Composable private fun ScreenContent( suggestions: List = emptyList(), - results: LazyPagingItems = flowOf(PagingData.empty()).collectAsLazyPagingItems(), + results: LazyPagingItems = emptyPagingItems(), onNavigateUp: () -> Unit = {}, onFetchSuggestions: (String) -> Unit = {}, onSearch: (String) -> Unit = {}, diff --git a/app/src/main/java/com/aurora/store/data/room/favourite/FavouriteDao.kt b/app/src/main/java/com/aurora/store/data/room/favourite/FavouriteDao.kt index 5a043eef8..7d62b1c08 100644 --- a/app/src/main/java/com/aurora/store/data/room/favourite/FavouriteDao.kt +++ b/app/src/main/java/com/aurora/store/data/room/favourite/FavouriteDao.kt @@ -1,5 +1,6 @@ package com.aurora.store.data.room.favourite +import androidx.paging.PagingSource import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy @@ -18,6 +19,9 @@ interface FavouriteDao { @Query("SELECT * FROM favourite") fun favourites(): Flow> + @Query("SELECT * FROM favourite") + fun pagedFavourites(): PagingSource + @Query("SELECT EXISTS(SELECT 1 FROM favourite WHERE packageName = :packageName)") suspend fun isFavourite(packageName: String): Boolean diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/FavouriteView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/FavouriteView.kt deleted file mode 100644 index 96fd8ef5d..000000000 --- a/app/src/main/java/com/aurora/store/view/epoxy/views/FavouriteView.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Aurora Store - * Copyright (C) 2021, Rahul Kumar Patel - * - * 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 . - * - */ - -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(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) - } -} diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/FavouriteFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/FavouriteFragment.kt deleted file mode 100644 index fee0f9da2..000000000 --- a/app/src/main/java/com/aurora/store/view/ui/commons/FavouriteFragment.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Aurora Store - * Copyright (C) 2021, Rahul Kumar Patel - * - * 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 . - * - */ - -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() { - 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?) { - 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) - } -} diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/MoreDialogFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/MoreDialogFragment.kt index a5b107ba0..0320806fb 100644 --- a/app/src/main/java/com/aurora/store/view/ui/commons/MoreDialogFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/commons/MoreDialogFragment.kt @@ -433,10 +433,10 @@ class MoreDialogFragment : DialogFragment() { icon = R.drawable.ic_blacklist, screen = Screen.Blacklist ), - ViewOption( + ComposeOption( title = R.string.title_favourites_manager, icon = R.drawable.ic_favorite_unchecked, - destinationID = R.id.favouriteFragment + screen = Screen.Favourite ), ViewOption( title = R.string.title_spoof_manager, diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/FavouriteViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/FavouriteViewModel.kt index ea628aae7..222caf359 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/all/FavouriteViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/all/FavouriteViewModel.kt @@ -1,20 +1,7 @@ /* - * Aurora Store - * Copyright (C) 2021, Rahul Kumar Patel - * - * 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 . - * + * SPDX-FileCopyrightText: 2024-2025 Rahul Kumar Patel + * SPDX-FileCopyrightText: 2024-2025 The Calyx Institute + * SPDX-License-Identifier: GPL-3.0-or-later */ package com.aurora.store.viewmodel.all @@ -24,13 +11,21 @@ import android.net.Uri import android.util.Log import androidx.lifecycle.ViewModel 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.FavouriteDao import com.aurora.store.data.room.favourite.ImportExport import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.MutableStateFlow +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.serialization.json.Json import javax.inject.Inject @@ -38,14 +33,19 @@ import javax.inject.Inject @HiltViewModel class FavouriteViewModel @Inject constructor( private val favouriteDao: FavouriteDao, - private val json: Json + private val json: Json, + @ApplicationContext private val context: Context ) : ViewModel() { private val TAG = FavouriteViewModel::class.java.simpleName - val favouritesList = favouriteDao.favourites() - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) + private val _favourites = MutableStateFlow>(PagingData.empty()) + val favourites = _favourites.asStateFlow() - fun importFavourites(context: Context, uri: Uri) { + init { + getPagedFavourites() + } + + fun importFavourites(uri: Uri) { viewModelScope.launch(Dispatchers.IO) { try { context.contentResolver.openInputStream(uri)?.use { @@ -54,7 +54,9 @@ class FavouriteViewModel @Inject constructor( ) 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) { @@ -63,12 +65,12 @@ class FavouriteViewModel @Inject constructor( } } - fun exportFavourites(context: Context, uri: Uri) { + fun exportFavourites(uri: Uri) { viewModelScope.launch(Dispatchers.IO) { try { context.contentResolver.openOutputStream(uri)?.use { it.write( - json.encodeToString(ImportExport(favouritesList.value!!)) + json.encodeToString(ImportExport(favouriteDao.favourites().first())) .encodeToByteArray() ) } @@ -83,4 +85,12 @@ class FavouriteViewModel @Inject constructor( favouriteDao.delete(packageName) } } + + private fun getPagedFavourites() { + pager { favouriteDao.pagedFavourites() }.flow + .distinctUntilChanged() + .cachedIn(viewModelScope) + .onEach { _favourites.value = it } + .launchIn(viewModelScope) + } } diff --git a/app/src/main/res/layout/fragment_favourite.xml b/app/src/main/res/layout/fragment_favourite.xml deleted file mode 100644 index 0bd7c51f5..000000000 --- a/app/src/main/res/layout/fragment_favourite.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/layout/view_fav.xml b/app/src/main/res/layout/view_fav.xml deleted file mode 100644 index 1296c1ddc..000000000 --- a/app/src/main/res/layout/view_fav.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 2e118ffe2..8e0082697 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -66,11 +66,6 @@ android:name="com.aurora.store.view.ui.spoof.SpoofFragment" android:label="@string/title_spoof_manager" tools:layout="@layout/fragment_spoof" /> -