diff --git a/app/src/main/kotlin/com/looker/droidify/ui/appList/AppListAdapter.kt b/app/src/main/kotlin/com/looker/droidify/ui/appList/AppListAdapter.kt index f50ecda65..4448fb82d 100644 --- a/app/src/main/kotlin/com/looker/droidify/ui/appList/AppListAdapter.kt +++ b/app/src/main/kotlin/com/looker/droidify/ui/appList/AppListAdapter.kt @@ -2,7 +2,10 @@ package com.looker.droidify.ui.appList import android.annotation.SuppressLint import android.content.Context +import android.view.ContextMenu import android.view.Gravity +import android.view.Menu +import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.FrameLayout @@ -10,7 +13,6 @@ import android.widget.TextView import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import coil.load -import com.google.android.material.R as MaterialR import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.progressindicator.CircularProgressIndicator import com.looker.core.common.extension.authentication @@ -27,10 +29,13 @@ import com.looker.droidify.database.Database import com.looker.droidify.utility.extension.ImageUtils.icon import com.looker.droidify.utility.extension.resources.TypefaceExtra import com.looker.droidify.widget.CursorRecyclerAdapter +import com.google.android.material.R as MaterialR class AppListAdapter( private val source: AppListFragment.Source, - private val onClick: (ProductItem) -> Unit + private val favouriteApps: Set, + private val onClick: (ProductItem) -> Unit, + private val onContextAction: (ProductItem, MenuItem) -> Boolean ) : CursorRecyclerAdapter() { enum class ViewType { PRODUCT, LOADING, EMPTY } @@ -122,6 +127,44 @@ class AppListAdapter( return when (viewType) { ViewType.PRODUCT -> ProductViewHolder(parent.inflate(R.layout.product_item)).apply { itemView.setOnClickListener { onClick(getProductItem(absoluteAdapterPosition)) } + + itemView.setOnCreateContextMenuListener( + fun( + menu: ContextMenu?, + _: View?, + _: ContextMenu.ContextMenuInfo? + ) { + menu?.setHeaderTitle("Pick an Action"); + + val installItem = menu?.add( + Menu.NONE, + Menu.NONE, + Menu.NONE, + if (getProductItem(absoluteAdapterPosition).installedVersion.nullIfEmpty() != null) + com.looker.core.common.R.string.uninstall + else + com.looker.core.common.R.string.install + ); + + val favItem = menu?.add( + Menu.NONE, + Menu.NONE, + Menu.NONE, + if (favouriteApps.contains(getProductItem(absoluteAdapterPosition).packageName)) + com.looker.core.common.R.string.remove_from_favourites + else + com.looker.core.common.R.string.add_to_favourites + ); + + val clickListener = MenuItem.OnMenuItemClickListener { + onContextAction( + getProductItem(absoluteAdapterPosition), it + ) + } + + installItem?.setOnMenuItemClickListener(clickListener) + favItem?.setOnMenuItemClickListener(clickListener) + }) } ViewType.LOADING -> LoadingViewHolder(parent.context) diff --git a/app/src/main/kotlin/com/looker/droidify/ui/appList/AppListFragment.kt b/app/src/main/kotlin/com/looker/droidify/ui/appList/AppListFragment.kt index 4065efced..4836fd161 100644 --- a/app/src/main/kotlin/com/looker/droidify/ui/appList/AppListFragment.kt +++ b/app/src/main/kotlin/com/looker/droidify/ui/appList/AppListFragment.kt @@ -6,6 +6,7 @@ import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -14,8 +15,6 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.looker.core.common.R as CommonR -import com.looker.core.common.R.string as stringRes import com.looker.core.common.extension.dp import com.looker.core.common.extension.isFirstItemVisible import com.looker.core.common.extension.systemBarsMargin @@ -26,6 +25,8 @@ import com.looker.droidify.databinding.RecyclerViewWithFabBinding import com.looker.droidify.utility.extension.screenActivity import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch +import com.looker.core.common.R as CommonR +import com.looker.core.common.R.string as stringRes @AndroidEntryPoint class AppListFragment() : Fragment(), CursorOwner.Callback { @@ -82,9 +83,50 @@ class AppListFragment() : Fragment(), CursorOwner.Callback { isMotionEventSplittingEnabled = false setHasFixedSize(true) recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30) - recyclerViewAdapter = AppListAdapter(source) { - screenActivity.navigateProduct(it.packageName) + + var favouriteApps: Set = emptySet() + + lifecycleScope.launch { + favouriteApps = viewModel.settingsRepository.getInitial().favouriteApps } + + recyclerViewAdapter = AppListAdapter( + source, + favouriteApps, + { screenActivity.navigateProduct(it.packageName) }, + fun(productItem, menuItem): Boolean { + + when (menuItem.title) { + + getString(com.looker.core.common.R.string.install) -> { + Toast.makeText( + requireContext(), + com.looker.core.common.R.string.installing, + Toast.LENGTH_SHORT + ).show() + viewModel.install(requireContext(), productItem) + } + + getString(com.looker.core.common.R.string.uninstall) -> { + Toast.makeText( + requireContext(), + com.looker.core.common.R.string.installing, + Toast.LENGTH_SHORT + ).show() + viewModel.uninstallPackage(productItem) + } + + else -> { + lifecycleScope.launch { + viewModel.settingsRepository.toggleFavourites( + productItem.packageName + ) + } + } + } + return true + } + ) adapter = recyclerViewAdapter systemBarsPadding() } diff --git a/app/src/main/kotlin/com/looker/droidify/ui/appList/AppListViewModel.kt b/app/src/main/kotlin/com/looker/droidify/ui/appList/AppListViewModel.kt index 9fd17c79a..a3e398ef0 100644 --- a/app/src/main/kotlin/com/looker/droidify/ui/appList/AppListViewModel.kt +++ b/app/src/main/kotlin/com/looker/droidify/ui/appList/AppListViewModel.kt @@ -1,8 +1,10 @@ package com.looker.droidify.ui.appList +import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.looker.core.common.extension.asStateFlow +import com.looker.core.common.toPackageName import com.looker.core.datastore.SettingsRepository import com.looker.core.datastore.get import com.looker.core.datastore.model.SortOrder @@ -11,18 +13,23 @@ import com.looker.core.domain.ProductItem.Section.All import com.looker.droidify.database.CursorOwner import com.looker.droidify.database.Database import com.looker.droidify.service.Connection +import com.looker.droidify.service.DownloadService import com.looker.droidify.service.SyncService +import com.looker.droidify.utility.extension.startUpdate +import com.looker.installer.InstallManager +import com.looker.installer.model.installFrom import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import javax.inject.Inject @HiltViewModel class AppListViewModel @Inject constructor( - settingsRepository: SettingsRepository + val settingsRepository: SettingsRepository, + val installManager: InstallManager ) : ViewModel() { val reposStream = Database.RepositoryAdapter @@ -37,6 +44,21 @@ class AppListViewModel val sortOrderFlow = settingsRepository.get { sortOrder } .asStateFlow(SortOrder.UPDATED) + private val downloadConnection = Connection( + serviceClass = DownloadService::class.java, + onBind = { _, binder -> + viewModelScope.launch { + + val state = binder.downloadState.value + + if (state.currentItem is DownloadService.State.Success) { + installManager + .install(state.currentItem.packageName installFrom state.currentItem.release.cacheFileName) + } + } + } + ) + private val sections = MutableStateFlow(All) val searchQuery = MutableStateFlow("") @@ -88,4 +110,28 @@ class AppListViewModel } } } + + fun install(context: Context, productItem: ProductItem) { + viewModelScope.launch(Dispatchers.Main) { + val installedItem = Database.InstalledAdapter.get(productItem.packageName, null) + + val products = Database.ProductAdapter.get(productItem.packageName, null) + .filter { it.repositoryId == productItem.repositoryId } + .map { product -> + reposStream.value.find { + it.id == product.repositoryId + }!!.let { product to it } + } + + downloadConnection.bind(context) + + downloadConnection.startUpdate(productItem.packageName, installedItem, products) + } + } + + fun uninstallPackage(productItem: ProductItem) { + viewModelScope.launch(Dispatchers.Main) { + installManager.uninstall(productItem.packageName.toPackageName()) + } + } } diff --git a/core/common/src/main/res/values/strings.xml b/core/common/src/main/res/values/strings.xml index 393c4785a..32c5e1ecf 100644 --- a/core/common/src/main/res/values/strings.xml +++ b/core/common/src/main/res/values/strings.xml @@ -59,6 +59,8 @@ Export Repositories Export all repositories to file Enable the repository + Add to Favourites + Remove from Favourites Favourites Invalid file format. Fingerprint