mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-04-23 19:32:16 +00:00
Remove: Using AppDetailAdapter in AppSheet
This commit is contained in:
parent
31d988ed2e
commit
1920820f1d
@ -1,6 +1,9 @@
|
|||||||
package com.looker.droidify.ui.fragments
|
package com.looker.droidify.ui.fragments
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -8,32 +11,44 @@ import android.provider.Settings
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import com.looker.droidify.R
|
||||||
|
import com.looker.droidify.content.Preferences
|
||||||
import com.looker.droidify.content.ProductPreferences
|
import com.looker.droidify.content.ProductPreferences
|
||||||
import com.looker.droidify.database.entity.Installed
|
|
||||||
import com.looker.droidify.database.entity.Product
|
|
||||||
import com.looker.droidify.database.entity.Release
|
import com.looker.droidify.database.entity.Release
|
||||||
import com.looker.droidify.database.entity.Repository
|
import com.looker.droidify.entity.Cancelable
|
||||||
import com.looker.droidify.databinding.SheetAppXBinding
|
import com.looker.droidify.entity.Connecting
|
||||||
import com.looker.droidify.entity.Action
|
import com.looker.droidify.entity.Details
|
||||||
|
import com.looker.droidify.entity.Install
|
||||||
|
import com.looker.droidify.entity.Launch
|
||||||
|
import com.looker.droidify.entity.PackageState
|
||||||
|
import com.looker.droidify.entity.Pending
|
||||||
import com.looker.droidify.entity.ProductPreference
|
import com.looker.droidify.entity.ProductPreference
|
||||||
import com.looker.droidify.entity.Screenshot
|
import com.looker.droidify.entity.Screenshot
|
||||||
|
import com.looker.droidify.entity.Share
|
||||||
|
import com.looker.droidify.entity.Uninstall
|
||||||
|
import com.looker.droidify.entity.Update
|
||||||
import com.looker.droidify.installer.AppInstaller
|
import com.looker.droidify.installer.AppInstaller
|
||||||
import com.looker.droidify.screen.MessageDialog
|
import com.looker.droidify.screen.MessageDialog
|
||||||
import com.looker.droidify.screen.ScreenshotsFragment
|
import com.looker.droidify.screen.ScreenshotsFragment
|
||||||
import com.looker.droidify.service.Connection
|
import com.looker.droidify.service.Connection
|
||||||
import com.looker.droidify.service.DownloadService
|
import com.looker.droidify.service.DownloadService
|
||||||
import com.looker.droidify.ui.activities.MainActivityX
|
import com.looker.droidify.ui.activities.MainActivityX
|
||||||
import com.looker.droidify.ui.adapters.AppDetailAdapter
|
import com.looker.droidify.ui.compose.theme.AppTheme
|
||||||
|
import com.looker.droidify.ui.compose.utils.Callbacks
|
||||||
import com.looker.droidify.ui.viewmodels.AppViewModelX
|
import com.looker.droidify.ui.viewmodels.AppViewModelX
|
||||||
import com.looker.droidify.utility.Utils.rootInstallerEnabled
|
import com.looker.droidify.utility.Utils.rootInstallerEnabled
|
||||||
import com.looker.droidify.utility.Utils.startUpdate
|
import com.looker.droidify.utility.Utils.startUpdate
|
||||||
import com.looker.droidify.utility.extension.android.Android
|
import com.looker.droidify.utility.extension.android.Android
|
||||||
import com.looker.droidify.utility.findSuggestedProduct
|
import com.looker.droidify.utility.findSuggestedProduct
|
||||||
|
import com.looker.droidify.utility.isDarkTheme
|
||||||
import com.looker.droidify.utility.onLaunchClick
|
import com.looker.droidify.utility.onLaunchClick
|
||||||
import io.reactivex.rxjava3.disposables.Disposable
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
@ -43,10 +58,9 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
// TODO clean up and replace dropped functions from AppDetailFragment
|
// TODO clean up and replace dropped functions from AppDetailFragment
|
||||||
class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Callbacks {
|
class AppSheetX() : FullscreenBottomSheetDialogFragment(), Callbacks {
|
||||||
companion object {
|
companion object {
|
||||||
private const val EXTRA_PACKAGE_NAME = "packageName"
|
private const val EXTRA_PACKAGE_NAME = "packageName"
|
||||||
private const val STATE_ADAPTER = "adapter"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(packageName: String) : this() {
|
constructor(packageName: String) : this() {
|
||||||
@ -55,7 +69,6 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var binding: SheetAppXBinding
|
|
||||||
val viewModel: AppViewModelX by viewModels {
|
val viewModel: AppViewModelX by viewModels {
|
||||||
AppViewModelX.Factory(mainActivityX.db, packageName)
|
AppViewModelX.Factory(mainActivityX.db, packageName)
|
||||||
}
|
}
|
||||||
@ -65,13 +78,7 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
|
|||||||
val packageName: String
|
val packageName: String
|
||||||
get() = requireArguments().getString(EXTRA_PACKAGE_NAME)!!
|
get() = requireArguments().getString(EXTRA_PACKAGE_NAME)!!
|
||||||
|
|
||||||
private var actions = Pair(emptySet<Action>(), null as Action?)
|
|
||||||
private var productRepos = emptyList<Pair<Product, Repository>>()
|
|
||||||
private var products = emptyList<Product>()
|
|
||||||
private var installed: Installed? = null
|
|
||||||
private var downloading = false
|
private var downloading = false
|
||||||
|
|
||||||
private var productDisposable: Disposable? = null
|
|
||||||
private val downloadConnection = Connection(DownloadService::class.java, onBind = { _, binder ->
|
private val downloadConnection = Connection(DownloadService::class.java, onBind = { _, binder ->
|
||||||
binder.stateSubject
|
binder.stateSubject
|
||||||
.filter { it.packageName == packageName }
|
.filter { it.packageName == packageName }
|
||||||
@ -86,83 +93,53 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
|
|||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
binding = SheetAppXBinding.inflate(layoutInflater)
|
super.onCreate(savedInstanceState)
|
||||||
binding.lifecycleOwner = this
|
|
||||||
setupAdapters()
|
setupAdapters()
|
||||||
return binding.root
|
return ComposeView(requireContext()).apply {
|
||||||
}
|
setContent {
|
||||||
|
AppTheme(
|
||||||
private fun setupAdapters() {
|
darkTheme = when (Preferences[Preferences.Key.Theme]) {
|
||||||
downloadConnection.bind(requireContext())
|
is Preferences.Theme.System -> isSystemInDarkTheme()
|
||||||
|
is Preferences.Theme.AmoledSystem -> isSystemInDarkTheme()
|
||||||
binding.recyclerView.apply {
|
else -> isDarkTheme
|
||||||
id = android.R.id.list
|
}
|
||||||
this.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
) {
|
||||||
this.adapter = AppDetailAdapter(this@AppSheetX)
|
AppSheet()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun setupLayout() {
|
|
||||||
// TODO simplify observing and updating
|
|
||||||
viewModel.installedItem.observe(viewLifecycleOwner) {
|
|
||||||
installed = it
|
|
||||||
updateSheet()
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.repositories.observe(viewLifecycleOwner) {
|
|
||||||
if (it.isNotEmpty() && products.isNotEmpty()) updateSheet()
|
|
||||||
}
|
|
||||||
viewModel.products.observe(viewLifecycleOwner) {
|
|
||||||
products = it.filterNotNull()
|
|
||||||
viewModel.repositories.value?.let { repos ->
|
|
||||||
if (repos.isNotEmpty() && products.isNotEmpty()) updateSheet()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateSheet() {
|
private fun setupAdapters() {
|
||||||
products.mapNotNull { product ->
|
downloadConnection.bind(requireContext())
|
||||||
viewModel.repositories.value
|
}
|
||||||
?.firstOrNull { it.id == product.repositoryId }
|
|
||||||
?.let { Pair(product, it) }
|
|
||||||
}.apply {
|
|
||||||
productRepos = this
|
|
||||||
|
|
||||||
val adapter = binding.recyclerView.adapter as AppDetailAdapter
|
override fun setupLayout() {
|
||||||
adapter.setProducts(
|
viewModel._productRepos.observe(this) {
|
||||||
binding.recyclerView.context,
|
|
||||||
packageName,
|
|
||||||
this,
|
|
||||||
installed
|
|
||||||
)
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
updateButtons()
|
updateButtons()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateSheet() {
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
|
||||||
productDisposable?.dispose()
|
|
||||||
productDisposable = null
|
|
||||||
downloadConnection.unbind(requireContext())
|
downloadConnection.unbind(requireContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
|
||||||
super.onSaveInstanceState(outState)
|
|
||||||
|
|
||||||
val adapterState = (binding.recyclerView.adapter as? AppDetailAdapter)?.saveState()
|
|
||||||
adapterState?.let { outState.putParcelable(STATE_ADAPTER, it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun updateButtons() {
|
private suspend fun updateButtons() {
|
||||||
updateButtons(ProductPreferences[packageName])
|
updateButtons(ProductPreferences[packageName])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO rename to updateActions
|
||||||
private suspend fun updateButtons(preference: ProductPreference) =
|
private suspend fun updateButtons(preference: ProductPreference) =
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
val installed = installed
|
val installed = viewModel.installedItem.value
|
||||||
|
val productRepos = viewModel.productRepos
|
||||||
val product = findSuggestedProduct(productRepos, installed) { it.first }?.first
|
val product = findSuggestedProduct(productRepos, installed) { it.first }?.first
|
||||||
val compatible = product != null && product.selectedReleases.firstOrNull()
|
val compatible = product != null && product.selectedReleases.firstOrNull()
|
||||||
.let { it != null && it.incompatibilities.isEmpty() }
|
.let { it != null && it.incompatibilities.isEmpty() }
|
||||||
@ -175,80 +152,76 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
|
|||||||
product != null && installed != null && installed.launcherActivities.isNotEmpty()
|
product != null && installed != null && installed.launcherActivities.isNotEmpty()
|
||||||
val canShare = product != null && productRepos[0].second.name == "F-Droid"
|
val canShare = product != null && productRepos[0].second.name == "F-Droid"
|
||||||
|
|
||||||
val actions = mutableSetOf<Action>()
|
val actions = mutableSetOf<PackageState>()
|
||||||
launch {
|
launch {
|
||||||
if (canInstall) {
|
if (canInstall) {
|
||||||
actions += Action.INSTALL
|
actions += Install
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
launch {
|
launch {
|
||||||
if (canUpdate) {
|
if (canUpdate) {
|
||||||
actions += Action.UPDATE
|
actions += Update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
launch {
|
launch {
|
||||||
if (canLaunch) {
|
if (canLaunch) {
|
||||||
actions += Action.LAUNCH
|
actions += Launch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
launch {
|
launch {
|
||||||
if (installed != null) {
|
if (installed != null) {
|
||||||
actions += Action.DETAILS
|
actions += Details
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
launch {
|
launch {
|
||||||
if (canUninstall) {
|
if (canUninstall) {
|
||||||
actions += Action.UNINSTALL
|
actions += Uninstall
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
launch {
|
launch {
|
||||||
if (canShare) {
|
if (canShare) {
|
||||||
actions += Action.SHARE
|
actions += Share
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val primaryAction = when {
|
val primaryAction = when {
|
||||||
canUpdate -> Action.UPDATE
|
canUpdate -> Update
|
||||||
canLaunch -> Action.LAUNCH
|
canLaunch -> Launch
|
||||||
canInstall -> Action.INSTALL
|
canInstall -> Install
|
||||||
canShare -> Action.SHARE
|
canShare -> Share
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
val secondaryAction = when {
|
val secondaryAction = when {
|
||||||
primaryAction != Action.SHARE && canShare -> Action.SHARE
|
primaryAction != Share && canShare -> Share
|
||||||
primaryAction != Action.LAUNCH && canLaunch -> Action.LAUNCH
|
primaryAction != Launch && canLaunch -> Launch
|
||||||
installed != null && canUninstall -> Action.UNINSTALL
|
installed != null && canUninstall -> Uninstall
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
launch(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
val adapterAction =
|
viewModel.actions.value = actions
|
||||||
if (downloading) Action.CANCEL else primaryAction
|
if (!downloading) {
|
||||||
val adapterSecondaryAction =
|
viewModel.state.value = primaryAction
|
||||||
if (downloading) null else secondaryAction
|
viewModel.secondaryAction.value = secondaryAction
|
||||||
(binding.recyclerView.adapter as? AppDetailAdapter)
|
} else {
|
||||||
?.setAction(adapterAction)
|
viewModel.secondaryAction.value = null
|
||||||
(binding.recyclerView.adapter as? AppDetailAdapter)
|
}
|
||||||
?.setSecondaryAction(adapterSecondaryAction)
|
|
||||||
}
|
}
|
||||||
launch { this@AppSheetX.actions = Pair(actions, primaryAction) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateDownloadState(state: DownloadService.State?) {
|
private suspend fun updateDownloadState(state: DownloadService.State?) {
|
||||||
val status = when (state) {
|
val status = when (state) {
|
||||||
is DownloadService.State.Pending -> AppDetailAdapter.Status.Pending
|
is DownloadService.State.Pending -> Pending
|
||||||
is DownloadService.State.Connecting -> AppDetailAdapter.Status.Connecting
|
is DownloadService.State.Connecting -> Connecting
|
||||||
is DownloadService.State.Downloading -> AppDetailAdapter.Status.Downloading(
|
is DownloadService.State.Downloading -> com.looker.droidify.entity.Downloading(
|
||||||
state.read,
|
state.read,
|
||||||
state.total
|
state.total
|
||||||
)
|
)
|
||||||
is DownloadService.State.Success, is DownloadService.State.Error, is DownloadService.State.Cancel, null -> null
|
else -> null
|
||||||
}
|
}
|
||||||
val downloading = status != null
|
val downloading = status is Cancelable
|
||||||
if (this.downloading != downloading) {
|
this.downloading = downloading
|
||||||
this.downloading = downloading
|
updateButtons()
|
||||||
updateButtons()
|
viewModel.state.value = status
|
||||||
}
|
|
||||||
(binding.recyclerView.adapter as? AppDetailAdapter)?.setStatus(status)
|
|
||||||
if (state is DownloadService.State.Success && isResumed && !rootInstallerEnabled) {
|
if (state is DownloadService.State.Success && isResumed && !rootInstallerEnabled) {
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
AppInstaller.getInstance(context)?.defaultInstaller?.install(state.release.cacheFileName)
|
AppInstaller.getInstance(context)?.defaultInstaller?.install(state.release.cacheFileName)
|
||||||
@ -256,12 +229,13 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActionClick(action: Action) {
|
override fun onActionClick(action: PackageState?) {
|
||||||
|
val productRepos = viewModel.productRepos
|
||||||
when (action) {
|
when (action) {
|
||||||
Action.INSTALL,
|
Install,
|
||||||
Action.UPDATE,
|
Update,
|
||||||
-> {
|
-> {
|
||||||
val installedItem = installed
|
val installedItem = viewModel.installedItem.value
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
startUpdate(
|
startUpdate(
|
||||||
packageName,
|
packageName,
|
||||||
@ -272,31 +246,37 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
|
|||||||
}
|
}
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
Action.LAUNCH -> {
|
Launch -> {
|
||||||
installed?.let { requireContext().onLaunchClick(it, childFragmentManager) }
|
viewModel.installedItem.value?.let {
|
||||||
|
requireContext().onLaunchClick(
|
||||||
|
it,
|
||||||
|
childFragmentManager
|
||||||
|
)
|
||||||
|
}
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
Action.DETAILS -> {
|
Details -> {
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||||
.setData(Uri.parse("package:$packageName"))
|
.setData(Uri.parse("package:$packageName"))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Action.UNINSTALL -> {
|
Uninstall -> {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
AppInstaller.getInstance(context)?.defaultInstaller?.uninstall(packageName)
|
AppInstaller.getInstance(context)?.defaultInstaller?.uninstall(packageName)
|
||||||
}
|
}
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
Action.CANCEL -> {
|
is Cancelable -> {
|
||||||
val binder = downloadConnection.binder
|
val binder = downloadConnection.binder
|
||||||
if (downloading && binder != null) {
|
if (downloading && binder != null) {
|
||||||
binder.cancel(packageName)
|
binder.cancel(packageName)
|
||||||
} else Unit
|
} else Unit
|
||||||
}
|
}
|
||||||
Action.SHARE -> {
|
Share -> {
|
||||||
shareIntent(packageName, productRepos[0].first.label)
|
shareIntent(packageName, productRepos[0].first.label)
|
||||||
}
|
}
|
||||||
|
else -> Unit
|
||||||
}::class
|
}::class
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,8 +305,9 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO fix in compose implementation
|
||||||
override fun onScreenshotClick(screenshot: Screenshot) {
|
override fun onScreenshotClick(screenshot: Screenshot) {
|
||||||
val pair = productRepos.asSequence()
|
val pair = viewModel.productRepos.asSequence()
|
||||||
.map { it ->
|
.map { it ->
|
||||||
Pair(
|
Pair(
|
||||||
it.second,
|
it.second,
|
||||||
@ -345,7 +326,7 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onReleaseClick(release: Release) {
|
override fun onReleaseClick(release: Release) {
|
||||||
val installedItem = installed
|
val installedItem = viewModel.installedItem.value
|
||||||
when {
|
when {
|
||||||
release.incompatibilities.isNotEmpty() -> {
|
release.incompatibilities.isNotEmpty() -> {
|
||||||
MessageDialog(
|
MessageDialog(
|
||||||
@ -365,7 +346,7 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val productRepository =
|
val productRepository =
|
||||||
productRepos.asSequence()
|
viewModel.productRepos.asSequence()
|
||||||
.filter { it -> it.first.releases.any { it === release } }
|
.filter { it -> it.first.releases.any { it === release } }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
if (productRepository != null) {
|
if (productRepository != null) {
|
||||||
@ -392,4 +373,17 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun copyLinkToClipboard(view: View, link: String) {
|
||||||
|
val clipboardManager =
|
||||||
|
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, link))
|
||||||
|
Snackbar.make(view, R.string.link_copied_to_clipboard, Snackbar.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AppSheet() {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user