From 781f58bc7285fa732bc14e3b020009ffdee0ccf9 Mon Sep 17 00:00:00 2001 From: Radu Sabau <> Date: Mon, 10 Nov 2025 10:13:08 +0200 Subject: [PATCH 1/6] [VSP-1602] Share Link UI --- .../permanent/ui/AccessRolesFragment.kt | 10 +- .../CircularProgressIndicator.kt | 5 +- .../permanent/ui/myFiles/MyFilesFragment.kt | 9 +- .../ui/myFiles/RecordOptionsFragment.kt | 306 ++++++++++++++++++ .../ui/recordMenu/RecordMenuFragment.kt | 9 +- .../ui/recordMenu/compose/RecordMenuScreen.kt | 10 +- .../ui/shareManagement/ShareLinkFragment.kt | 96 ++++++ .../ShareManagementFragment.kt | 5 +- .../shareLink/ShareLinkScreen.kt | 299 +++++++++++++++++ .../viewmodels/ShareManagementViewModel.kt | 116 ++++--- app/src/main/res/drawable/ic_copy_blue.xml | 9 + app/src/main/res/drawable/ic_lock_closed.xml | 12 + .../main/res/drawable/ic_settings_blue.xml | 9 + app/src/main/res/drawable/ic_share_link.xml | 9 + .../main/res/layout/fragment_share_link.xml | 14 + .../res/layout/fragment_share_management.xml | 11 - app/src/main/res/values/strings.xml | 6 + 17 files changed, 863 insertions(+), 72 deletions(-) create mode 100644 app/src/main/java/org/permanent/permanent/ui/myFiles/RecordOptionsFragment.kt create mode 100644 app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareLinkFragment.kt create mode 100644 app/src/main/java/org/permanent/permanent/ui/shareManagement/shareLink/ShareLinkScreen.kt create mode 100644 app/src/main/res/drawable/ic_copy_blue.xml create mode 100644 app/src/main/res/drawable/ic_lock_closed.xml create mode 100644 app/src/main/res/drawable/ic_settings_blue.xml create mode 100644 app/src/main/res/drawable/ic_share_link.xml create mode 100644 app/src/main/res/layout/fragment_share_link.xml diff --git a/app/src/main/java/org/permanent/permanent/ui/AccessRolesFragment.kt b/app/src/main/java/org/permanent/permanent/ui/AccessRolesFragment.kt index 1aa61b8d..0b76657e 100644 --- a/app/src/main/java/org/permanent/permanent/ui/AccessRolesFragment.kt +++ b/app/src/main/java/org/permanent/permanent/ui/AccessRolesFragment.kt @@ -24,10 +24,10 @@ import org.permanent.permanent.databinding.FragmentAccessRolesBinding import org.permanent.permanent.models.AccessRole import org.permanent.permanent.models.Share import org.permanent.permanent.network.models.Shareby_urlVO -import org.permanent.permanent.ui.shareManagement.ShareManagementFragment import org.permanent.permanent.viewmodels.AccessRolesViewModel import org.permanent.permanent.viewmodels.SingleLiveEvent import androidx.core.net.toUri +import org.permanent.permanent.ui.shareManagement.ShareLinkFragment class AccessRolesFragment : PermanentBottomSheetFragment() { private lateinit var binding: FragmentAccessRolesBinding @@ -38,7 +38,7 @@ class AccessRolesFragment : PermanentBottomSheetFragment() { shareByUrlVo: Shareby_urlVO, ) { val bundle = Bundle() - bundle.putParcelable(ShareManagementFragment.SHARE_BY_URL_VO_KEY, shareByUrlVo) + bundle.putParcelable(ShareLinkFragment.SHARE_BY_URL_VO_KEY, shareByUrlVo) this.arguments = bundle } @@ -46,7 +46,7 @@ class AccessRolesFragment : PermanentBottomSheetFragment() { share: Share, ) { val bundle = Bundle() - bundle.putParcelable(ShareManagementFragment.PARCELABLE_SHARE_KEY, share) + bundle.putParcelable(ShareLinkFragment.PARCELABLE_SHARE_KEY, share) this.arguments = bundle } @@ -60,8 +60,8 @@ class AccessRolesFragment : PermanentBottomSheetFragment() { binding.executePendingBindings() binding.lifecycleOwner = this binding.viewModel = viewModel - viewModel.setShareLink(arguments?.getParcelable(ShareManagementFragment.SHARE_BY_URL_VO_KEY)) - viewModel.setShare(arguments?.getParcelable(ShareManagementFragment.PARCELABLE_SHARE_KEY)) + viewModel.setShareLink(arguments?.getParcelable(ShareLinkFragment.SHARE_BY_URL_VO_KEY)) + viewModel.setShare(arguments?.getParcelable(ShareLinkFragment.PARCELABLE_SHARE_KEY)) initCurrentAccessRole() return binding.root diff --git a/app/src/main/java/org/permanent/permanent/ui/composeComponents/CircularProgressIndicator.kt b/app/src/main/java/org/permanent/permanent/ui/composeComponents/CircularProgressIndicator.kt index 8299b3c8..dfaa77b3 100644 --- a/app/src/main/java/org/permanent/permanent/ui/composeComponents/CircularProgressIndicator.kt +++ b/app/src/main/java/org/permanent/permanent/ui/composeComponents/CircularProgressIndicator.kt @@ -21,10 +21,9 @@ import androidx.compose.ui.res.painterResource import org.permanent.permanent.R @Composable -fun CircularProgressIndicator(overlayColor: OverlayColor = OverlayColor.DARK) { +fun CircularProgressIndicator(overlayColor: OverlayColor = OverlayColor.DARK, modifier: Modifier = Modifier.fillMaxSize()) { Box( - modifier = Modifier - .fillMaxSize() + modifier = modifier .background(if (overlayColor == OverlayColor.DARK) Color.Black.copy(alpha = 0.5f) else Color.White.copy(alpha = 0.5f)) .clickable(enabled = false) {}, contentAlignment = Alignment.Center ) { diff --git a/app/src/main/java/org/permanent/permanent/ui/myFiles/MyFilesFragment.kt b/app/src/main/java/org/permanent/permanent/ui/myFiles/MyFilesFragment.kt index 7d6c1ae0..f21d01d1 100644 --- a/app/src/main/java/org/permanent/permanent/ui/myFiles/MyFilesFragment.kt +++ b/app/src/main/java/org/permanent/permanent/ui/myFiles/MyFilesFragment.kt @@ -54,6 +54,7 @@ import org.permanent.permanent.ui.recordMenu.RecordMenuFragment import org.permanent.permanent.ui.recordMenu.RecordUiModel import org.permanent.permanent.ui.recordMenu.SelectionMenuFragment import org.permanent.permanent.ui.shareManagement.ShareManagementFragment +import org.permanent.permanent.ui.shareManagement.ShareLinkFragment import org.permanent.permanent.ui.shares.PreviewState import org.permanent.permanent.ui.shares.SHOW_SCREEN_SIMPLIFIED_KEY import org.permanent.permanent.ui.shares.URL_TOKEN_KEY @@ -82,7 +83,7 @@ class MyFilesFragment : PermanentBaseFragment() { private var addOptionsFragment: AddOptionsFragment? = null private var recordMenuFragment: RecordMenuFragment? = null private var saveToPermanentFragment: SaveToPermanentFragment? = null - private var shareManagementFragment: ShareManagementFragment? = null + private var shareManagementFragment: ShareLinkFragment? = null private var sortOptionsFragment: SortOptionsFragment? = null private var selectionMenuFragment: SelectionMenuFragment? = null private var bottomSheetFragment: ChecklistBottomSheetFragment? = null @@ -329,9 +330,9 @@ class MyFilesFragment : PermanentBaseFragment() { } private fun showShareManagementFragment(record: Record?) { - shareManagementFragment = ShareManagementFragment() - shareManagementFragment?.setBundleArguments(record, null) - shareManagementFragment?.show(parentFragmentManager, shareManagementFragment?.tag) + val shareManagementScreen = ShareLinkFragment() + shareManagementScreen.setBundleArguments(record, null) + shareManagementScreen.show(parentFragmentManager, shareManagementScreen.tag) } private val shrinkIslandRequestObserver = Observer { diff --git a/app/src/main/java/org/permanent/permanent/ui/myFiles/RecordOptionsFragment.kt b/app/src/main/java/org/permanent/permanent/ui/myFiles/RecordOptionsFragment.kt new file mode 100644 index 00000000..06c6e1f6 --- /dev/null +++ b/app/src/main/java/org/permanent/permanent/ui/myFiles/RecordOptionsFragment.kt @@ -0,0 +1,306 @@ +package org.permanent.permanent.ui.myFiles + +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Typeface +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.snackbar.Snackbar +import org.permanent.permanent.DevicePermissionsHelper +import org.permanent.permanent.R +import org.permanent.permanent.REQUEST_CODE_WRITE_STORAGE_PERMISSION +import org.permanent.permanent.databinding.DialogTitleTextTwoButtonsBinding +import org.permanent.permanent.databinding.FragmentRecordOptionsBinding +import org.permanent.permanent.models.AccountEventAction +import org.permanent.permanent.models.Record +import org.permanent.permanent.models.Share +import org.permanent.permanent.ui.MenuSharesAdapter +import org.permanent.permanent.ui.PermanentBottomSheetFragment +import org.permanent.permanent.ui.Workspace +import org.permanent.permanent.ui.shareManagement.ShareLinkFragment +import org.permanent.permanent.viewmodels.RecordOptionsViewModel + +const val SHOWN_IN_WHICH_WORKSPACE = "shown_in_which_workspace_key" +const val IS_SHOWN_IN_SHARED_WITH_ME = "is_shown_in_shared_with_me_key" +const val IS_SHOWN_IN_ROOT_FOLDER = "is_shown_in_root_folder_key" + +class RecordOptionsFragment : PermanentBottomSheetFragment() { + private lateinit var binding: FragmentRecordOptionsBinding + private lateinit var viewModel: RecordOptionsViewModel + private lateinit var sharesRecyclerView: RecyclerView + private lateinit var sharesAdapter: MenuSharesAdapter + private lateinit var record: Record + private var downloadingAlert: AlertDialog? = null + private val onFileDownloadRequest = MutableLiveData() + private val onRecordDeleteRequest = MutableLiveData() + private val onRecordLeaveShareRequest = MutableLiveData() + private val onRecordRenameRequest = MutableLiveData() + private val onRecordRelocateRequest = MutableLiveData>() + private var shareManagementFragment: ShareLinkFragment? = null + + fun setBundleArguments( + record: Record, + workspace: Workspace, + isShownInSharedWithMe: Boolean = false, + isShownInRootFolder: Boolean = false + ) { + val bundle = Bundle() + bundle.putParcelable(PARCELABLE_RECORD_KEY, record) + bundle.putParcelable(SHOWN_IN_WHICH_WORKSPACE, workspace) + bundle.putBoolean(IS_SHOWN_IN_SHARED_WITH_ME, isShownInSharedWithMe) + bundle.putBoolean(IS_SHOWN_IN_ROOT_FOLDER, isShownInRootFolder) + this.arguments = bundle + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + viewModel = ViewModelProvider(this)[RecordOptionsViewModel::class.java] + binding = FragmentRecordOptionsBinding.inflate(inflater, container, false) + binding.executePendingBindings() + binding.lifecycleOwner = this + binding.viewModel = viewModel + arguments?.getParcelable(PARCELABLE_RECORD_KEY)?.let { + record = it + val shownInWorkspace = + arguments?.getParcelable(SHOWN_IN_WHICH_WORKSPACE) + val isShownInSharedWithMe = + arguments?.getBoolean(IS_SHOWN_IN_SHARED_WITH_ME) + val isShownInRootFolder = + arguments?.getBoolean(IS_SHOWN_IN_ROOT_FOLDER) ?: false + + if (shownInWorkspace != null && isShownInSharedWithMe != null) { + viewModel.initWith(it, shownInWorkspace, isShownInSharedWithMe, isShownInRootFolder) + } + } + + initSharesRecyclerView(binding.rvShares) + + return binding.root + } + + private fun initSharesRecyclerView(rvShares: RecyclerView) { + sharesRecyclerView = rvShares + sharesAdapter = MenuSharesAdapter() + sharesRecyclerView.apply { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + adapter = sharesAdapter + } + } + + private val onSharesRetrieved = Observer> { + sharesAdapter.set(it) + } + + private val onRequestWritePermission = Observer { + DevicePermissionsHelper().requestWriteStoragePermission(this) + } + + private val onFileDownloadRequestObserver = Observer { + dismiss() + onFileDownloadRequest.value = record + } + + private val onDeleteObserver = Observer { + dismiss() + onRecordDeleteRequest.value = record + } + + private val onLeaveShareObserver = Observer { + dismiss() + onRecordLeaveShareRequest.value = record + } + + private val onRenameObserver = Observer { + dismiss() + onRecordRenameRequest.value = record + } + + private val onManageSharingObserver = Observer { + shareManagementFragment = ShareLinkFragment() + shareManagementFragment?.setBundleArguments(record, viewModel.getShareByUrlVO()) + shareManagementFragment?.show(parentFragmentManager, shareManagementFragment?.tag) + dismiss() + } + + private val onShareToAnotherAppObserver = Observer { contentType -> + viewModel.getUriForSharing()?.let { + shareFile(it, contentType) + dismiss() + } ?: run { + downloadingAlert = context?.let { + AlertDialog.Builder(it) + .setTitle(getString(R.string.downloading_file_in_progress)) + .setNegativeButton( + getString(R.string.button_cancel) + ) { _, _ -> viewModel.cancelDownload() } + .create() + } + downloadingAlert?.show() + viewModel.downloadFileForSharing(this) + } + + viewModel.sendEvent(AccountEventAction.OPEN_SHARE_MODAL) + } + + private val onFileDownloadedForSharing = Observer { contentType -> + downloadingAlert?.cancel() + viewModel.getUriForSharing()?.let { shareFile(it, contentType) } + dismiss() + } + + private val showSnackbarSuccess = Observer { message -> + downloadingAlert?.cancel() + dialog?.window?.decorView?.let { + val snackBar = Snackbar.make(it, message, Snackbar.LENGTH_LONG) + val view: View = snackBar.view + context?.let { + view.setBackgroundColor(ContextCompat.getColor(it, R.color.deepGreen)) + snackBar.setTextColor(ContextCompat.getColor(it, R.color.paleGreen)) + } + val snackbarTextTextView = view.findViewById(R.id.snackbar_text) as TextView + snackbarTextTextView.setTypeface(snackbarTextTextView.typeface, Typeface.BOLD) + snackBar.show() + } + } + + private val showSnackbar = Observer { message -> + downloadingAlert?.cancel() + dialog?.window?.decorView?.let { + Snackbar.make(it, message, Snackbar.LENGTH_LONG).show() + } + } + + private val onRelocateRequestObserver = Observer { + dismiss() + onRecordRelocateRequest.value = Pair(record, it) + } + + private val onShareLinkObserver = Observer { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, it) + type = "text/plain" + } + + val shareIntent = Intent.createChooser(sendIntent, null) + startActivity(shareIntent) + } + + private val onPublishRequestObserver = Observer { + val dialogBinding: DialogTitleTextTwoButtonsBinding = DataBindingUtil.inflate( + LayoutInflater.from(context), R.layout.dialog_title_text_two_buttons, null, false + ) + val alert = android.app.AlertDialog.Builder(context).setView(dialogBinding.root).create() + + dialogBinding.tvTitle.text = + getString(R.string.dialog_record_publish_confirmation_title, record.displayName) + dialogBinding.tvText.text = getString(R.string.dialog_record_publish_confirmation_text) + dialogBinding.btnPositive.text = getString(R.string.button_publish) + dialogBinding.btnPositive.setOnClickListener { + viewModel.publishRecord() + alert.dismiss() + } + dialogBinding.btnNegative.setOnClickListener { + alert.dismiss() + } + alert.show() + } + + @Deprecated("Deprecated in Java") + override fun onRequestPermissionsResult( + requestCode: Int, permissions: Array, + grantResults: IntArray + ) { + when (requestCode) { + REQUEST_CODE_WRITE_STORAGE_PERMISSION -> + if (grantResults.isNotEmpty() + && grantResults[0] == PackageManager.PERMISSION_GRANTED + ) { + viewModel.onWritePermissionGranted() + } else { + Toast.makeText( + context, R.string.download_no_permissions_error, Toast.LENGTH_LONG + ).show() + } + } + } + + private fun shareFile(sharingUri: Uri, mimeType: String) { + val intent = Intent(Intent.ACTION_SEND) + intent.putExtra(Intent.EXTRA_STREAM, sharingUri) + intent.type = mimeType + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + startActivity(Intent.createChooser(intent, "")) + } + + fun getOnFileDownloadRequest(): MutableLiveData = onFileDownloadRequest + + fun getOnRecordDeleteRequest(): MutableLiveData = onRecordDeleteRequest + + fun getOnRecordLeaveShareRequest(): MutableLiveData = onRecordLeaveShareRequest + + fun getOnRecordRenameRequest(): MutableLiveData = onRecordRenameRequest + + fun getOnRecordRelocateRequest(): MutableLiveData> = + onRecordRelocateRequest + + override fun connectViewModelEvents() { + viewModel.getOnSharesRetrieved().observe(this, onSharesRetrieved) + viewModel.getOnRequestWritePermission().observe(this, onRequestWritePermission) + viewModel.getOnFileDownloadRequest().observe(this, onFileDownloadRequestObserver) + viewModel.getOnDeleteRequest().observe(this, onDeleteObserver) + viewModel.getOnLeaveShareRequest().observe(this, onLeaveShareObserver) + viewModel.getOnRenameRequest().observe(this, onRenameObserver) + viewModel.getOnManageSharingRequest().observe(this, onManageSharingObserver) + viewModel.getOnShareToAnotherAppRequest().observe(this, onShareToAnotherAppObserver) + viewModel.getOnRelocateRequest().observe(this, onRelocateRequestObserver) + viewModel.getOnShareLinkRequest().observe(this, onShareLinkObserver) + viewModel.getOnPublishRequest().observe(this, onPublishRequestObserver) + viewModel.getOnFileDownloadedForSharing().observe(this, onFileDownloadedForSharing) + viewModel.getShowSnackbar().observe(this, showSnackbar) + viewModel.getShowSnackbarSuccess().observe(this, showSnackbarSuccess) + } + + override fun disconnectViewModelEvents() { + viewModel.getOnSharesRetrieved().removeObserver(onSharesRetrieved) + viewModel.getOnRequestWritePermission().removeObserver(onRequestWritePermission) + viewModel.getOnFileDownloadRequest().removeObserver(onFileDownloadRequestObserver) + viewModel.getOnDeleteRequest().removeObserver(onDeleteObserver) + viewModel.getOnRenameRequest().removeObserver(onRenameObserver) + viewModel.getOnManageSharingRequest().removeObserver(onManageSharingObserver) + viewModel.getOnShareToAnotherAppRequest().removeObserver(onShareToAnotherAppObserver) + viewModel.getOnRelocateRequest().removeObserver(onRelocateRequestObserver) + viewModel.getOnShareLinkRequest().removeObserver(onShareLinkObserver) + viewModel.getOnPublishRequest().removeObserver(onPublishRequestObserver) + viewModel.getOnFileDownloadedForSharing().removeObserver(onFileDownloadedForSharing) + viewModel.getShowSnackbar().observe(this, showSnackbar) + viewModel.getShowSnackbarSuccess().observe(this, showSnackbarSuccess) + } + + override fun onResume() { + super.onResume() + connectViewModelEvents() + } + + override fun onPause() { + super.onPause() + disconnectViewModelEvents() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/permanent/permanent/ui/recordMenu/RecordMenuFragment.kt b/app/src/main/java/org/permanent/permanent/ui/recordMenu/RecordMenuFragment.kt index b542ed21..31d9c709 100644 --- a/app/src/main/java/org/permanent/permanent/ui/recordMenu/RecordMenuFragment.kt +++ b/app/src/main/java/org/permanent/permanent/ui/recordMenu/RecordMenuFragment.kt @@ -37,6 +37,8 @@ import org.permanent.permanent.ui.Workspace import org.permanent.permanent.ui.myFiles.ModificationType import org.permanent.permanent.ui.myFiles.PARCELABLE_RECORD_KEY import org.permanent.permanent.ui.recordMenu.compose.RecordMenuScreen +import org.permanent.permanent.ui.shareManagement.ShareLinkFragment +import org.permanent.permanent.ui.shareManagement.shareLink.ShareLinkScreen import org.permanent.permanent.ui.shareManagement.ShareManagementFragment import org.permanent.permanent.viewmodels.RecordMenuItem import org.permanent.permanent.viewmodels.RecordMenuViewModel @@ -49,7 +51,6 @@ class RecordMenuFragment : PermanentBottomSheetFragment() { private lateinit var record: Record private val onFileDownloadRequest = MutableLiveData() private val onRecordLeaveShareRequest = MutableLiveData() - private var shareManagementFragment: ShareManagementFragment? = null private val onRecordPublishRequest = MutableLiveData() private val onRecordRenameRequest = MutableLiveData() private val onRecordRelocateRequest = MutableLiveData>() @@ -217,9 +218,9 @@ class RecordMenuFragment : PermanentBottomSheetFragment() { } private fun showShareManagementFragment() { - shareManagementFragment = ShareManagementFragment() - shareManagementFragment?.setBundleArguments(record, viewModel.getShareByUrlVO()) - shareManagementFragment?.show(parentFragmentManager, shareManagementFragment?.tag) + val shareManagementScreen = ShareLinkFragment() + shareManagementScreen.setBundleArguments(record, viewModel.getShareByUrlVO()) + shareManagementScreen.show(parentFragmentManager, shareManagementScreen.tag) } private fun showConfirmationDialogForPublish() { diff --git a/app/src/main/java/org/permanent/permanent/ui/recordMenu/compose/RecordMenuScreen.kt b/app/src/main/java/org/permanent/permanent/ui/recordMenu/compose/RecordMenuScreen.kt index 9c028766..95a1655a 100644 --- a/app/src/main/java/org/permanent/permanent/ui/recordMenu/compose/RecordMenuScreen.kt +++ b/app/src/main/java/org/permanent/permanent/ui/recordMenu/compose/RecordMenuScreen.kt @@ -153,7 +153,7 @@ fun RecordMenuHeader( recordName: String, recordSize: String, recordDate: String, - onCloseClick: () -> Unit + onCloseClick: (() -> Unit)? = null ) { Row( modifier = Modifier @@ -224,12 +224,14 @@ fun RecordMenuHeader( Spacer(modifier = Modifier.width(16.dp)) // Close Icon - IconButton(onClick = onCloseClick) { - Icon( + if (onCloseClick != null) { + IconButton(onClick = onCloseClick) { + Icon( painter = painterResource(id = R.drawable.ic_close_light_blue), contentDescription = "Close", tint = colorResource(R.color.blue400), - ) + ) + } } } } \ No newline at end of file diff --git a/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareLinkFragment.kt b/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareLinkFragment.kt new file mode 100644 index 00000000..5455141a --- /dev/null +++ b/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareLinkFragment.kt @@ -0,0 +1,96 @@ +package org.permanent.permanent.ui.shareManagement + +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.unit.dp +import androidx.core.os.bundleOf +import androidx.lifecycle.ViewModelProvider +import com.google.android.material.R +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import org.permanent.permanent.models.Record +import org.permanent.permanent.network.models.Shareby_urlVO +import org.permanent.permanent.ui.PermanentBottomSheetFragment +import org.permanent.permanent.ui.myFiles.PARCELABLE_RECORD_KEY +import org.permanent.permanent.ui.shareManagement.shareLink.ShareLinkScreen +import org.permanent.permanent.viewmodels.ShareManagementViewModel + +class ShareLinkFragment : PermanentBottomSheetFragment() { + + private var record: Record? = null + + private lateinit var viewModel: ShareManagementViewModel + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + viewModel = ViewModelProvider(this)[ShareManagementViewModel::class.java] + record = arguments?.getParcelable(PARCELABLE_RECORD_KEY) + record?.let { + viewModel.setRecord(it) + } + viewModel.setShareLink(arguments?.getParcelable(SHARE_BY_URL_VO_KEY)) + + return ComposeView(requireContext()).apply { + setContent { + MaterialTheme { + Surface( + shape = RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp), + ) { + ShareLinkScreen( + viewModel = viewModel, + onClose = { dismiss() } + ) + } + } + } + } + } + + fun setBundleArguments(record: Record?, shareByUrlVO: Shareby_urlVO?) { + val bundle = bundleOf(PARCELABLE_RECORD_KEY to record, SHARE_BY_URL_VO_KEY to shareByUrlVO) + this.arguments = bundle + } + + override fun onStart() { + super.onStart() + (dialog as? BottomSheetDialog)?.behavior?.apply { + state = BottomSheetBehavior.STATE_EXPANDED + skipCollapsed = true + } + // Optional: transparent background so your Compose Surface can draw rounded corners + (requireView().parent as? View)?.setBackgroundColor(Color.TRANSPARENT) + } + + override fun connectViewModelEvents() { +// viewModel.getShowSnackbar().observe(this, showSnackbar) +// viewModel.getOnShareToAnotherAppRequest().observe(this, onShareToAnotherAppObserver) +// viewModel.getOnFileDownloadedForSharing().observe(this, onFileDownloadedForSharing) +// viewModel.getOnRequestWritePermission().observe(this, onRequestWritePermission) +// viewModel.getOnFileDownloadRequest().observe(this, onFileDownloadRequestObserver) + } + + override fun disconnectViewModelEvents() { +// viewModel.getShowSnackbar().observe(this, showSnackbar) +// viewModel.getOnShareToAnotherAppRequest().removeObserver(onShareToAnotherAppObserver) +// viewModel.getOnFileDownloadedForSharing().removeObserver(onFileDownloadedForSharing) +// viewModel.getOnRequestWritePermission().removeObserver(onRequestWritePermission) +// viewModel.getOnFileDownloadRequest().removeObserver(onFileDownloadRequestObserver) + } + + + companion object { + const val PARCELABLE_SHARE_KEY = "parcelable_share_key" + const val SHARE_BY_URL_VO_KEY = "share_by_url_vo_key" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareManagementFragment.kt b/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareManagementFragment.kt index 2855521f..4a037fc6 100644 --- a/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareManagementFragment.kt +++ b/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareManagementFragment.kt @@ -35,6 +35,7 @@ import org.permanent.permanent.ui.AccessRolesFragment import org.permanent.permanent.ui.PermanentBottomSheetFragment import org.permanent.permanent.ui.fileView.FileActivity import org.permanent.permanent.ui.myFiles.PARCELABLE_RECORD_KEY +import org.permanent.permanent.ui.shareManagement.ShareLinkFragment.Companion.SHARE_BY_URL_VO_KEY import org.permanent.permanent.viewmodels.ShareManagementViewModel import java.util.Calendar @@ -259,8 +260,4 @@ class ShareManagementFragment : PermanentBottomSheetFragment() { disconnectViewModelEvents() } - companion object { - const val PARCELABLE_SHARE_KEY = "parcelable_share_key" - const val SHARE_BY_URL_VO_KEY = "share_by_url_vo_key" - } } \ No newline at end of file diff --git a/app/src/main/java/org/permanent/permanent/ui/shareManagement/shareLink/ShareLinkScreen.kt b/app/src/main/java/org/permanent/permanent/ui/shareManagement/shareLink/ShareLinkScreen.kt new file mode 100644 index 00000000..d37c466d --- /dev/null +++ b/app/src/main/java/org/permanent/permanent/ui/shareManagement/shareLink/ShareLinkScreen.kt @@ -0,0 +1,299 @@ +package org.permanent.permanent.ui.shareManagement.shareLink + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.permanent.permanent.R +import org.permanent.permanent.ui.composeComponents.CircularProgressIndicator +import org.permanent.permanent.ui.composeComponents.OverlayColor +import org.permanent.permanent.ui.recordMenu.compose.RecordMenuHeader +import org.permanent.permanent.viewmodels.ShareManagementViewModel + +@Composable +fun ShareLinkScreen( + viewModel: ShareManagementViewModel, + onClose: () -> Unit, +) { + + val recordThumbURL by viewModel.recordThumb.collectAsState() + val recordName by viewModel.recordName.collectAsState() + val recordSize by viewModel.recordSize.collectAsState() + val recordDate by viewModel.recordDate.collectAsState() + val shareLink by viewModel.shareLink.collectAsState() + val creatingLink by viewModel.isCreatingLinkState.collectAsState() + val isLinkSharedState by viewModel.isLinkSharedState.collectAsState() + + Box( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(0.9f) + .background(colorResource(R.color.blue25)) + .wrapContentHeight(Alignment.Top) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + ) { + SheetHeader( + title = "Share Item", onCloseClick = onClose + ) + + RecordMenuHeader( + recordThumbURL = recordThumbURL, + recordName = recordName, + recordSize = recordSize, + recordDate = recordDate + ) + + if (creatingLink) { + CreatingLinkRow() + } else { + if (isLinkSharedState) { + SharedLinkRow(shareLink = viewModel.cleanUrlRegex(shareLink), onSettingClick = {}, onCopyClick = { + viewModel.copyLinkToClipboard() + }) + } else { + CreateLinkRow(onClick = { + viewModel.onCreateLinkBtnClick() + }) + } + } + } + } +} + +@Composable +fun CreatingLinkRow() { + Row( + modifier = Modifier + .fillMaxWidth() + .background(colorResource(R.color.blue25)) + .padding(start = 36.dp, top = 24.dp, bottom = 24.dp, end = 24.dp), + verticalAlignment = Alignment.CenterVertically + ) { + + CircularProgressIndicator( + overlayColor = OverlayColor.LIGHT, modifier = Modifier + .height(16.dp) + .width(16.dp) + ) + + Spacer(modifier = Modifier.width(28.dp)) + + Text( + text = stringResource(R.string.creating_link), style = TextStyle( + fontSize = 14.sp, + lineHeight = 24.sp, + fontFamily = FontFamily(Font(R.font.usual_regular)), + brush = Brush.linearGradient( + colors = listOf( + colorResource(R.color.barneyPurple), colorResource(R.color.colorAccent) + ), start = Offset(0f, 0f), end = Offset(300f, 300f) + ), + ), maxLines = 1, overflow = TextOverflow.Ellipsis + ) + } +} + +@Composable +fun SharedLinkRow( + shareLink: String, onSettingClick: () -> Unit, onCopyClick: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .background(colorResource(R.color.blue25)) + .padding(start = 24.dp, top = 6.dp, bottom = 6.dp, end = 24.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.CenterVertically) + .border( + width = 1.dp, + color = colorResource(R.color.blue50), + shape = RoundedCornerShape(10.dp) + ) + .clip(RoundedCornerShape(10.dp)) + .background(colorResource(R.color.white)) + ) { + Row( + modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(id = R.drawable.ic_lock_closed), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier + .size(44.dp) + .clip(RoundedCornerShape(6.dp)) + .padding(8.dp) + ) + + Spacer(modifier = Modifier.width(8.dp)) + + Text( + text = shareLink, modifier = Modifier.weight(1f), style = TextStyle( + fontSize = 14.sp, + lineHeight = 24.sp, + fontFamily = FontFamily(Font(R.font.usual_regular)), + textAlign = TextAlign.Left, + brush = Brush.linearGradient( + colors = listOf( + colorResource(R.color.barneyPurple), + colorResource(R.color.colorAccent) + ), start = Offset(0f, 0f), end = Offset(300f, 300f) + ), + ), maxLines = 1, overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = Modifier.width(8.dp)) + + IconButton(onClick = onSettingClick, modifier = Modifier.size(24.dp)) { + Icon( + painter = painterResource(id = R.drawable.ic_settings_blue), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.size(24.dp) + ) + } + Spacer(modifier = Modifier.width(16.dp)) + + IconButton(onClick = onCopyClick, modifier = Modifier.size(24.dp)) { + Icon( + painter = painterResource(id = R.drawable.ic_copy_blue), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.size(24.dp) + ) + } + Spacer(modifier = Modifier.width(12.dp)) + } + } + } +} + +@Composable +fun CreateLinkRow(onClick: () -> Unit) { + Row( + modifier = Modifier + .fillMaxWidth() + .background(colorResource(R.color.blue25)) + .clickable { onClick() } + .padding(start = 24.dp, top = 12.dp, bottom = 12.dp, end = 12.dp), + verticalAlignment = Alignment.CenterVertically) { + + Icon( + painter = painterResource(id = R.drawable.ic_share_link), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier + .size(40.dp) + .clip(RoundedCornerShape(6.dp)) + ) + + Spacer(modifier = Modifier.width(16.dp)) + + Column( + modifier = Modifier + .weight(1f) + .height(56.dp) + .padding(top = 4.dp, end = 24.dp) + .align(Alignment.CenterVertically) + ) { + Text( + text = stringResource(R.string.create_link_to_sare), style = TextStyle( + fontSize = 14.sp, + lineHeight = 24.sp, + fontFamily = FontFamily(Font(R.font.usual_medium)), + color = colorResource(R.color.blue900), + ), maxLines = 1, overflow = TextOverflow.Ellipsis + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = stringResource(R.string.create_link_description), style = TextStyle( + fontSize = 12.sp, + lineHeight = 16.sp, + fontFamily = FontFamily(Font(R.font.usual_regular)), + color = colorResource(R.color.blue400), + ), maxLines = 2, overflow = TextOverflow.Ellipsis + ) + } + } +} + +@Composable +fun SheetHeader( + title: String, onCloseClick: (() -> Unit)? = null +) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(colorResource(R.color.white)) + .padding(start = 24.dp, top = 12.dp, bottom = 12.dp, end = 12.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = title, style = TextStyle( + fontSize = 16.sp, + lineHeight = 24.sp, + fontFamily = FontFamily(Font(R.font.usual_medium)), + color = colorResource(R.color.blue900), + textAlign = TextAlign.Center + ), maxLines = 1, overflow = TextOverflow.Ellipsis + ) + + if (onCloseClick != null) { + IconButton( + onClick = onCloseClick, modifier = Modifier.align(Alignment.CenterEnd) + ) { + Icon( + painter = painterResource(id = R.drawable.ic_close_light_blue), + contentDescription = "Close", + tint = colorResource(R.color.blue400), + ) + } + } + } +} + +enum class LinkState { + SHARED, LOADING, NOTSHARED +} diff --git a/app/src/main/java/org/permanent/permanent/viewmodels/ShareManagementViewModel.kt b/app/src/main/java/org/permanent/permanent/viewmodels/ShareManagementViewModel.kt index 184191e2..bb8ae44a 100644 --- a/app/src/main/java/org/permanent/permanent/viewmodels/ShareManagementViewModel.kt +++ b/app/src/main/java/org/permanent/permanent/viewmodels/ShareManagementViewModel.kt @@ -2,6 +2,8 @@ package org.permanent.permanent.viewmodels import android.app.Application import android.app.DatePickerDialog +import android.content.ClipData +import android.content.ClipboardManager import android.content.Context import android.text.Editable import android.view.KeyEvent @@ -10,11 +12,16 @@ import android.view.inputmethod.EditorInfo import android.widget.DatePicker import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import okhttp3.internal.trimSubstring +import org.permanent.permanent.BuildConfig import org.permanent.permanent.R import org.permanent.permanent.models.AccessRole +import org.permanent.permanent.models.Archive import org.permanent.permanent.models.EventAction import org.permanent.permanent.models.Record +import org.permanent.permanent.models.RecordType import org.permanent.permanent.models.Share import org.permanent.permanent.models.Status import org.permanent.permanent.network.IResponseListener @@ -26,7 +33,9 @@ import org.permanent.permanent.repositories.IShareRepository import org.permanent.permanent.repositories.ShareRepositoryImpl import org.permanent.permanent.ui.PREFS_NAME import org.permanent.permanent.ui.PreferencesHelper +import org.permanent.permanent.ui.bytesToHumanReadableString import org.permanent.permanent.ui.shareManagement.ShareListener +import org.permanent.permanent.ui.toDisplayDate class ShareManagementViewModel(application: Application) : ObservableAndroidViewModel(application), @@ -37,9 +46,23 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView application.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) ) private lateinit var record: Record - private val recordName = MutableLiveData() + private val _recordThumb = MutableStateFlow("") + val recordThumb: StateFlow = _recordThumb + private val _recordSize = MutableStateFlow("") + val recordSize: StateFlow = _recordSize + private val _recordDate = MutableStateFlow("") + val recordDate: StateFlow = _recordDate + private val _recordName = MutableStateFlow("") + val recordName: StateFlow = _recordName + private val _shareLink = MutableStateFlow("") + val shareLink: StateFlow = _shareLink + private val _isCreatingLinkState = MutableStateFlow(false) + val isCreatingLinkState: StateFlow = _isCreatingLinkState + + private val _isLinkSharedState = MutableStateFlow(false) + val isLinkSharedState: StateFlow = _isLinkSharedState + private var shareByUrlVO: Shareby_urlVO? = null - private val shareLink = MutableLiveData("") private var shares = mutableListOf() private var pendingShares = mutableListOf() private val sharesSize = MutableLiveData(0) @@ -52,7 +75,6 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView private val showDatePicker = SingleLiveEvent() private val sharedWithLabelTxt = MutableLiveData() private val areLinkSettingsVisible = MutableLiveData(false) - private val isBusy = MutableLiveData(false) private val showSnackbar = MutableLiveData() private val showSnackbarSuccess = MutableLiveData() private val onShareLinkRequest = SingleLiveEvent() @@ -66,7 +88,12 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView fun setRecord(record: Record) { this.record = record - recordName.value = record.displayName + + _recordName.value = record.displayName ?: "" + _recordSize.value = if (record.size != -1L) bytesToHumanReadableString(record.size) else "" + _recordDate.value = record.displayDate.toDisplayDate() + _recordThumb.value = if (record.type == RecordType.FILE) record.thumbURL200 ?: "" else "" + initShares(record.shares) sharedWithLabelTxt.value = appContext.getString(R.string.record_options_shared_with, shares.size) @@ -91,7 +118,8 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView private fun init(shareByUrlVO: Shareby_urlVO) { this.shareByUrlVO = shareByUrlVO - this.shareLink.value = shareByUrlVO.shareUrl ?: "" + _shareLink.value = shareByUrlVO.shareUrl ?: "" + _isLinkSharedState.value = _shareLink.value != "" sharePreview.value = shareByUrlVO.previewToggle == 1 autoApprove.value = shareByUrlVO.autoApproveToggle == 1 maxUses.value = (shareByUrlVO.maxUses ?: 0).toString() @@ -100,49 +128,68 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView } private fun checkForExistingLink(record: Record) { - if (isBusy.value != null && isBusy.value!!) { + if (isCreatingLinkState.value != null && isCreatingLinkState.value!!) { return } - isBusy.value = true + _isCreatingLinkState.value = true shareRepository.requestShareLink(record, ShareRequestType.GET, object : IShareRepository.IShareByUrlListener { override fun onSuccess(shareByUrlVO: Shareby_urlVO?) { - isBusy.value = false + _isCreatingLinkState.value = false + _isLinkSharedState.value = true shareByUrlVO?.let { init(it) } } override fun onFailed(error: String?) { - isBusy.value = false + _isCreatingLinkState.value = false showSnackbar.value = error } }) } fun onCreateLinkBtnClick() { - if (isBusy.value != null && isBusy.value!!) { + if (_isCreatingLinkState.value != null && _isCreatingLinkState.value!!) { return } - isBusy.value = true + _isCreatingLinkState.value = true shareRepository.requestShareLink(record, ShareRequestType.GENERATE, object : IShareRepository.IShareByUrlListener { override fun onSuccess(shareByUrlVO: Shareby_urlVO?) { - isBusy.value = false + _isCreatingLinkState.value = false + _isLinkSharedState.value = true this@ShareManagementViewModel.shareByUrlVO = shareByUrlVO shareByUrlVO?.shareUrl?.let { - shareLink.value = it + _shareLink.value = it + _isLinkSharedState.value = _shareLink.value != "" onShowLinkSettingsBtnClick() } } override fun onFailed(error: String?) { - isBusy.value = false + _isCreatingLinkState.value = false + _isLinkSharedState.value = false showSnackbar.value = error } }) } + fun copyLinkToClipboard() { + + val clipboard = appContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip: ClipData = ClipData.newPlainText( + appContext.getString(R.string.share_management_share_link), _shareLink.value + ) + clipboard.setPrimaryClip(clip) + //showMessage.value = appContext.getString(R.string.share_management_link_copied) + } + + fun cleanUrlRegex(url: String): String { + return url.replace(Regex("^https?://(www\\.)?"), "") + } + + fun onShareLinkBtnClick() { onShareLinkRequest.value = shareLink.value.toString() } @@ -196,24 +243,25 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView } fun deleteShareLink() { - if (isBusy.value != null && isBusy.value!!) { + if (_isCreatingLinkState.value != null && _isCreatingLinkState.value!!) { return } shareByUrlVO?.let { - isBusy.value = true + _isCreatingLinkState.value = true shareRepository.modifyShareLink( it, ShareRequestType.DELETE, object : IResponseListener { override fun onSuccess(message: String?) { - isBusy.value = false + _isCreatingLinkState.value = false this@ShareManagementViewModel.shareByUrlVO = null - shareLink.value = "" + _shareLink.value = "" + _isLinkSharedState.value = false } override fun onFailed(error: String?) { - isBusy.value = false + _isCreatingLinkState.value = false showSnackbar.value = error } }) @@ -221,7 +269,7 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView } private fun saveChanges() { - if (isBusy.value != null && isBusy.value!!) { + if (_isCreatingLinkState.value != null && _isCreatingLinkState.value!!) { return } shareByUrlVO?.let { @@ -230,16 +278,16 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView it.maxUses = if (maxUses.value.isNullOrBlank()) 0 else maxUses.value!!.toInt() it.expiresDT = expirationDate.value - isBusy.value = true + _isCreatingLinkState.value = true shareRepository.modifyShareLink(it, ShareRequestType.UPDATE, object : IResponseListener { override fun onSuccess(message: String?) { - isBusy.value = false + _isCreatingLinkState.value = false showSnackbar.value = message } override fun onFailed(error: String?) { - isBusy.value = false + _isCreatingLinkState.value = false showSnackbar.value = error } }) @@ -251,14 +299,14 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView } override fun onApproveClick(share: Share) { - if (isBusy.value != null && isBusy.value!!) { + if (_isCreatingLinkState.value != null && _isCreatingLinkState.value!!) { return } - isBusy.value = true + _isCreatingLinkState.value = true shareRepository.updateShare(share, object : IResponseListener { override fun onSuccess(message: String?) { - isBusy.value = false + _isCreatingLinkState.value = false share.status.value = Status.OK // This hides the Approve and Deny buttons pendingShares.remove(share) shares.add(share) @@ -271,21 +319,21 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView } override fun onFailed(error: String?) { - isBusy.value = false + _isCreatingLinkState.value = false showSnackbar.value = error } }) } override fun onDenyClick(share: Share) { - if (isBusy.value != null && isBusy.value!!) { + if (_isCreatingLinkState.value != null && _isCreatingLinkState.value!!) { return } - isBusy.value = true + _isCreatingLinkState.value = true shareRepository.deleteShare(share, object : IResponseListener { override fun onSuccess(message: String?) { - isBusy.value = false + _isCreatingLinkState.value = false pendingShares.remove(share) pendingSharesSize.value = pendingShares.size showSnackbarSuccess.value = message @@ -293,7 +341,7 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView } override fun onFailed(error: String?) { - isBusy.value = false + _isCreatingLinkState.value = false showSnackbar.value = error } }) @@ -324,16 +372,10 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView fun getRecord(): Record = record - fun getRecordName(): MutableLiveData = recordName - - fun getShareLink(): MutableLiveData = shareLink - fun getSharesSize(): MutableLiveData = sharesSize fun getPendingSharesSize(): MutableLiveData = pendingSharesSize - fun getIsBusy(): MutableLiveData = isBusy - fun getAreLinkSettingsVisible(): MutableLiveData = areLinkSettingsVisible fun getSharePreview(): MutableLiveData = sharePreview diff --git a/app/src/main/res/drawable/ic_copy_blue.xml b/app/src/main/res/drawable/ic_copy_blue.xml new file mode 100644 index 00000000..7e9da8ab --- /dev/null +++ b/app/src/main/res/drawable/ic_copy_blue.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock_closed.xml b/app/src/main/res/drawable/ic_lock_closed.xml new file mode 100644 index 00000000..35a645fa --- /dev/null +++ b/app/src/main/res/drawable/ic_lock_closed.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_settings_blue.xml b/app/src/main/res/drawable/ic_settings_blue.xml new file mode 100644 index 00000000..d5fd67f1 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_blue.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_share_link.xml b/app/src/main/res/drawable/ic_share_link.xml new file mode 100644 index 00000000..4d1af9ee --- /dev/null +++ b/app/src/main/res/drawable/ic_share_link.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_share_link.xml b/app/src/main/res/layout/fragment_share_link.xml new file mode 100644 index 00000000..414e3c8e --- /dev/null +++ b/app/src/main/res/layout/fragment_share_link.xml @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_share_management.xml b/app/src/main/res/layout/fragment_share_management.xml index 04372557..7de85f94 100644 --- a/app/src/main/res/layout/fragment_share_management.xml +++ b/app/src/main/res/layout/fragment_share_management.xml @@ -683,17 +683,6 @@ app:layout_constraintTop_toBottomOf="@id/btnShareLink" /> - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ad28fe2c..4256965b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -828,4 +828,10 @@ Are you sure you want to delete "%1$s"? the selected items Are you sure you want to give up your access to the "%1$s" item? + + Create link to share + Creating link... + Generate a link to send via text or email to invite people to share this content. + + Hello blank fragment From a27453eaac8d4b96a4cf5cd1c04910b7b4f9331b Mon Sep 17 00:00:00 2001 From: Radu Sabau <> Date: Mon, 10 Nov 2025 13:52:10 +0200 Subject: [PATCH 2/6] [VSP-1602] Cleanup old ShareManagement Implementation --- .../permanent/ui/fileView/FileActivity.kt | 11 +- .../ui/fileView/FileViewOptionsFragment.kt | 5 +- .../permanent/ui/myFiles/MyFilesFragment.kt | 2 - .../ui/myFiles/RecordOptionsFragment.kt | 306 -------- .../ui/recordMenu/RecordMenuFragment.kt | 1 - .../ui/shareManagement/ShareLinkFragment.kt | 19 +- .../ShareManagementFragment.kt | 263 ------- .../viewmodels/ShareManagementViewModel.kt | 8 +- .../res/layout/fragment_share_management.xml | 689 ------------------ .../res/navigation/file_navigation_graph.xml | 12 - .../res/navigation/main_navigation_graph.xml | 6 - 11 files changed, 15 insertions(+), 1307 deletions(-) delete mode 100644 app/src/main/java/org/permanent/permanent/ui/myFiles/RecordOptionsFragment.kt delete mode 100644 app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareManagementFragment.kt delete mode 100644 app/src/main/res/layout/fragment_share_management.xml diff --git a/app/src/main/java/org/permanent/permanent/ui/fileView/FileActivity.kt b/app/src/main/java/org/permanent/permanent/ui/fileView/FileActivity.kt index 370d667c..2ffd0e24 100644 --- a/app/src/main/java/org/permanent/permanent/ui/fileView/FileActivity.kt +++ b/app/src/main/java/org/permanent/permanent/ui/fileView/FileActivity.kt @@ -70,11 +70,12 @@ class FileActivity : PermanentBaseActivity() { this@FileActivity.finish() true } - R.id.shareLinkFragment -> { - navController.navigateUp(appBarConfig) || super.onSupportNavigateUp() - setToolbarAndStatusBarColor(R.color.black) - true - } + //TODO Replace with new Share Link Settings +// R.id.shareLinkFragment -> { +// navController.navigateUp(appBarConfig) || super.onSupportNavigateUp() +// setToolbarAndStatusBarColor(R.color.black) +// true +// } else -> navController.navigateUp(appBarConfig) || super.onSupportNavigateUp() } } diff --git a/app/src/main/java/org/permanent/permanent/ui/fileView/FileViewOptionsFragment.kt b/app/src/main/java/org/permanent/permanent/ui/fileView/FileViewOptionsFragment.kt index 3b720a31..68256c7d 100644 --- a/app/src/main/java/org/permanent/permanent/ui/fileView/FileViewOptionsFragment.kt +++ b/app/src/main/java/org/permanent/permanent/ui/fileView/FileViewOptionsFragment.kt @@ -52,8 +52,9 @@ class FileViewOptionsFragment : PermanentBottomSheetFragment() { private val onShareViaPermanentObserver = Observer { val bundle = bundleOf(PARCELABLE_RECORD_KEY to record) - requireParentFragment().requireParentFragment().findNavController() - .navigate(R.id.action_filesContainerFragment_to_shareLinkFragment, bundle) + //TODO Replace with new Share Link Settings + // requireParentFragment().requireParentFragment().findNavController() + // .navigate(R.id.action_filesContainerFragment_to_shareLinkFragment, bundle) dismiss() } diff --git a/app/src/main/java/org/permanent/permanent/ui/myFiles/MyFilesFragment.kt b/app/src/main/java/org/permanent/permanent/ui/myFiles/MyFilesFragment.kt index f21d01d1..d6a66d0a 100644 --- a/app/src/main/java/org/permanent/permanent/ui/myFiles/MyFilesFragment.kt +++ b/app/src/main/java/org/permanent/permanent/ui/myFiles/MyFilesFragment.kt @@ -53,7 +53,6 @@ import org.permanent.permanent.ui.public.PublicFragment import org.permanent.permanent.ui.recordMenu.RecordMenuFragment import org.permanent.permanent.ui.recordMenu.RecordUiModel import org.permanent.permanent.ui.recordMenu.SelectionMenuFragment -import org.permanent.permanent.ui.shareManagement.ShareManagementFragment import org.permanent.permanent.ui.shareManagement.ShareLinkFragment import org.permanent.permanent.ui.shares.PreviewState import org.permanent.permanent.ui.shares.SHOW_SCREEN_SIMPLIFIED_KEY @@ -83,7 +82,6 @@ class MyFilesFragment : PermanentBaseFragment() { private var addOptionsFragment: AddOptionsFragment? = null private var recordMenuFragment: RecordMenuFragment? = null private var saveToPermanentFragment: SaveToPermanentFragment? = null - private var shareManagementFragment: ShareLinkFragment? = null private var sortOptionsFragment: SortOptionsFragment? = null private var selectionMenuFragment: SelectionMenuFragment? = null private var bottomSheetFragment: ChecklistBottomSheetFragment? = null diff --git a/app/src/main/java/org/permanent/permanent/ui/myFiles/RecordOptionsFragment.kt b/app/src/main/java/org/permanent/permanent/ui/myFiles/RecordOptionsFragment.kt deleted file mode 100644 index 06c6e1f6..00000000 --- a/app/src/main/java/org/permanent/permanent/ui/myFiles/RecordOptionsFragment.kt +++ /dev/null @@ -1,306 +0,0 @@ -package org.permanent.permanent.ui.myFiles - -import android.content.Intent -import android.content.pm.PackageManager -import android.graphics.Typeface -import android.net.Uri -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import android.widget.Toast -import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProvider -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.snackbar.Snackbar -import org.permanent.permanent.DevicePermissionsHelper -import org.permanent.permanent.R -import org.permanent.permanent.REQUEST_CODE_WRITE_STORAGE_PERMISSION -import org.permanent.permanent.databinding.DialogTitleTextTwoButtonsBinding -import org.permanent.permanent.databinding.FragmentRecordOptionsBinding -import org.permanent.permanent.models.AccountEventAction -import org.permanent.permanent.models.Record -import org.permanent.permanent.models.Share -import org.permanent.permanent.ui.MenuSharesAdapter -import org.permanent.permanent.ui.PermanentBottomSheetFragment -import org.permanent.permanent.ui.Workspace -import org.permanent.permanent.ui.shareManagement.ShareLinkFragment -import org.permanent.permanent.viewmodels.RecordOptionsViewModel - -const val SHOWN_IN_WHICH_WORKSPACE = "shown_in_which_workspace_key" -const val IS_SHOWN_IN_SHARED_WITH_ME = "is_shown_in_shared_with_me_key" -const val IS_SHOWN_IN_ROOT_FOLDER = "is_shown_in_root_folder_key" - -class RecordOptionsFragment : PermanentBottomSheetFragment() { - private lateinit var binding: FragmentRecordOptionsBinding - private lateinit var viewModel: RecordOptionsViewModel - private lateinit var sharesRecyclerView: RecyclerView - private lateinit var sharesAdapter: MenuSharesAdapter - private lateinit var record: Record - private var downloadingAlert: AlertDialog? = null - private val onFileDownloadRequest = MutableLiveData() - private val onRecordDeleteRequest = MutableLiveData() - private val onRecordLeaveShareRequest = MutableLiveData() - private val onRecordRenameRequest = MutableLiveData() - private val onRecordRelocateRequest = MutableLiveData>() - private var shareManagementFragment: ShareLinkFragment? = null - - fun setBundleArguments( - record: Record, - workspace: Workspace, - isShownInSharedWithMe: Boolean = false, - isShownInRootFolder: Boolean = false - ) { - val bundle = Bundle() - bundle.putParcelable(PARCELABLE_RECORD_KEY, record) - bundle.putParcelable(SHOWN_IN_WHICH_WORKSPACE, workspace) - bundle.putBoolean(IS_SHOWN_IN_SHARED_WITH_ME, isShownInSharedWithMe) - bundle.putBoolean(IS_SHOWN_IN_ROOT_FOLDER, isShownInRootFolder) - this.arguments = bundle - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - viewModel = ViewModelProvider(this)[RecordOptionsViewModel::class.java] - binding = FragmentRecordOptionsBinding.inflate(inflater, container, false) - binding.executePendingBindings() - binding.lifecycleOwner = this - binding.viewModel = viewModel - arguments?.getParcelable(PARCELABLE_RECORD_KEY)?.let { - record = it - val shownInWorkspace = - arguments?.getParcelable(SHOWN_IN_WHICH_WORKSPACE) - val isShownInSharedWithMe = - arguments?.getBoolean(IS_SHOWN_IN_SHARED_WITH_ME) - val isShownInRootFolder = - arguments?.getBoolean(IS_SHOWN_IN_ROOT_FOLDER) ?: false - - if (shownInWorkspace != null && isShownInSharedWithMe != null) { - viewModel.initWith(it, shownInWorkspace, isShownInSharedWithMe, isShownInRootFolder) - } - } - - initSharesRecyclerView(binding.rvShares) - - return binding.root - } - - private fun initSharesRecyclerView(rvShares: RecyclerView) { - sharesRecyclerView = rvShares - sharesAdapter = MenuSharesAdapter() - sharesRecyclerView.apply { - setHasFixedSize(true) - layoutManager = LinearLayoutManager(context) - adapter = sharesAdapter - } - } - - private val onSharesRetrieved = Observer> { - sharesAdapter.set(it) - } - - private val onRequestWritePermission = Observer { - DevicePermissionsHelper().requestWriteStoragePermission(this) - } - - private val onFileDownloadRequestObserver = Observer { - dismiss() - onFileDownloadRequest.value = record - } - - private val onDeleteObserver = Observer { - dismiss() - onRecordDeleteRequest.value = record - } - - private val onLeaveShareObserver = Observer { - dismiss() - onRecordLeaveShareRequest.value = record - } - - private val onRenameObserver = Observer { - dismiss() - onRecordRenameRequest.value = record - } - - private val onManageSharingObserver = Observer { - shareManagementFragment = ShareLinkFragment() - shareManagementFragment?.setBundleArguments(record, viewModel.getShareByUrlVO()) - shareManagementFragment?.show(parentFragmentManager, shareManagementFragment?.tag) - dismiss() - } - - private val onShareToAnotherAppObserver = Observer { contentType -> - viewModel.getUriForSharing()?.let { - shareFile(it, contentType) - dismiss() - } ?: run { - downloadingAlert = context?.let { - AlertDialog.Builder(it) - .setTitle(getString(R.string.downloading_file_in_progress)) - .setNegativeButton( - getString(R.string.button_cancel) - ) { _, _ -> viewModel.cancelDownload() } - .create() - } - downloadingAlert?.show() - viewModel.downloadFileForSharing(this) - } - - viewModel.sendEvent(AccountEventAction.OPEN_SHARE_MODAL) - } - - private val onFileDownloadedForSharing = Observer { contentType -> - downloadingAlert?.cancel() - viewModel.getUriForSharing()?.let { shareFile(it, contentType) } - dismiss() - } - - private val showSnackbarSuccess = Observer { message -> - downloadingAlert?.cancel() - dialog?.window?.decorView?.let { - val snackBar = Snackbar.make(it, message, Snackbar.LENGTH_LONG) - val view: View = snackBar.view - context?.let { - view.setBackgroundColor(ContextCompat.getColor(it, R.color.deepGreen)) - snackBar.setTextColor(ContextCompat.getColor(it, R.color.paleGreen)) - } - val snackbarTextTextView = view.findViewById(R.id.snackbar_text) as TextView - snackbarTextTextView.setTypeface(snackbarTextTextView.typeface, Typeface.BOLD) - snackBar.show() - } - } - - private val showSnackbar = Observer { message -> - downloadingAlert?.cancel() - dialog?.window?.decorView?.let { - Snackbar.make(it, message, Snackbar.LENGTH_LONG).show() - } - } - - private val onRelocateRequestObserver = Observer { - dismiss() - onRecordRelocateRequest.value = Pair(record, it) - } - - private val onShareLinkObserver = Observer { - val sendIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_TEXT, it) - type = "text/plain" - } - - val shareIntent = Intent.createChooser(sendIntent, null) - startActivity(shareIntent) - } - - private val onPublishRequestObserver = Observer { - val dialogBinding: DialogTitleTextTwoButtonsBinding = DataBindingUtil.inflate( - LayoutInflater.from(context), R.layout.dialog_title_text_two_buttons, null, false - ) - val alert = android.app.AlertDialog.Builder(context).setView(dialogBinding.root).create() - - dialogBinding.tvTitle.text = - getString(R.string.dialog_record_publish_confirmation_title, record.displayName) - dialogBinding.tvText.text = getString(R.string.dialog_record_publish_confirmation_text) - dialogBinding.btnPositive.text = getString(R.string.button_publish) - dialogBinding.btnPositive.setOnClickListener { - viewModel.publishRecord() - alert.dismiss() - } - dialogBinding.btnNegative.setOnClickListener { - alert.dismiss() - } - alert.show() - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, permissions: Array, - grantResults: IntArray - ) { - when (requestCode) { - REQUEST_CODE_WRITE_STORAGE_PERMISSION -> - if (grantResults.isNotEmpty() - && grantResults[0] == PackageManager.PERMISSION_GRANTED - ) { - viewModel.onWritePermissionGranted() - } else { - Toast.makeText( - context, R.string.download_no_permissions_error, Toast.LENGTH_LONG - ).show() - } - } - } - - private fun shareFile(sharingUri: Uri, mimeType: String) { - val intent = Intent(Intent.ACTION_SEND) - intent.putExtra(Intent.EXTRA_STREAM, sharingUri) - intent.type = mimeType - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - startActivity(Intent.createChooser(intent, "")) - } - - fun getOnFileDownloadRequest(): MutableLiveData = onFileDownloadRequest - - fun getOnRecordDeleteRequest(): MutableLiveData = onRecordDeleteRequest - - fun getOnRecordLeaveShareRequest(): MutableLiveData = onRecordLeaveShareRequest - - fun getOnRecordRenameRequest(): MutableLiveData = onRecordRenameRequest - - fun getOnRecordRelocateRequest(): MutableLiveData> = - onRecordRelocateRequest - - override fun connectViewModelEvents() { - viewModel.getOnSharesRetrieved().observe(this, onSharesRetrieved) - viewModel.getOnRequestWritePermission().observe(this, onRequestWritePermission) - viewModel.getOnFileDownloadRequest().observe(this, onFileDownloadRequestObserver) - viewModel.getOnDeleteRequest().observe(this, onDeleteObserver) - viewModel.getOnLeaveShareRequest().observe(this, onLeaveShareObserver) - viewModel.getOnRenameRequest().observe(this, onRenameObserver) - viewModel.getOnManageSharingRequest().observe(this, onManageSharingObserver) - viewModel.getOnShareToAnotherAppRequest().observe(this, onShareToAnotherAppObserver) - viewModel.getOnRelocateRequest().observe(this, onRelocateRequestObserver) - viewModel.getOnShareLinkRequest().observe(this, onShareLinkObserver) - viewModel.getOnPublishRequest().observe(this, onPublishRequestObserver) - viewModel.getOnFileDownloadedForSharing().observe(this, onFileDownloadedForSharing) - viewModel.getShowSnackbar().observe(this, showSnackbar) - viewModel.getShowSnackbarSuccess().observe(this, showSnackbarSuccess) - } - - override fun disconnectViewModelEvents() { - viewModel.getOnSharesRetrieved().removeObserver(onSharesRetrieved) - viewModel.getOnRequestWritePermission().removeObserver(onRequestWritePermission) - viewModel.getOnFileDownloadRequest().removeObserver(onFileDownloadRequestObserver) - viewModel.getOnDeleteRequest().removeObserver(onDeleteObserver) - viewModel.getOnRenameRequest().removeObserver(onRenameObserver) - viewModel.getOnManageSharingRequest().removeObserver(onManageSharingObserver) - viewModel.getOnShareToAnotherAppRequest().removeObserver(onShareToAnotherAppObserver) - viewModel.getOnRelocateRequest().removeObserver(onRelocateRequestObserver) - viewModel.getOnShareLinkRequest().removeObserver(onShareLinkObserver) - viewModel.getOnPublishRequest().removeObserver(onPublishRequestObserver) - viewModel.getOnFileDownloadedForSharing().removeObserver(onFileDownloadedForSharing) - viewModel.getShowSnackbar().observe(this, showSnackbar) - viewModel.getShowSnackbarSuccess().observe(this, showSnackbarSuccess) - } - - override fun onResume() { - super.onResume() - connectViewModelEvents() - } - - override fun onPause() { - super.onPause() - disconnectViewModelEvents() - } -} \ No newline at end of file diff --git a/app/src/main/java/org/permanent/permanent/ui/recordMenu/RecordMenuFragment.kt b/app/src/main/java/org/permanent/permanent/ui/recordMenu/RecordMenuFragment.kt index 31d9c709..8cf066fe 100644 --- a/app/src/main/java/org/permanent/permanent/ui/recordMenu/RecordMenuFragment.kt +++ b/app/src/main/java/org/permanent/permanent/ui/recordMenu/RecordMenuFragment.kt @@ -39,7 +39,6 @@ import org.permanent.permanent.ui.myFiles.PARCELABLE_RECORD_KEY import org.permanent.permanent.ui.recordMenu.compose.RecordMenuScreen import org.permanent.permanent.ui.shareManagement.ShareLinkFragment import org.permanent.permanent.ui.shareManagement.shareLink.ShareLinkScreen -import org.permanent.permanent.ui.shareManagement.ShareManagementFragment import org.permanent.permanent.viewmodels.RecordMenuItem import org.permanent.permanent.viewmodels.RecordMenuViewModel diff --git a/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareLinkFragment.kt b/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareLinkFragment.kt index 5455141a..6cf278fc 100644 --- a/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareLinkFragment.kt +++ b/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareLinkFragment.kt @@ -9,11 +9,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf import androidx.lifecycle.ViewModelProvider -import com.google.android.material.R import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import org.permanent.permanent.models.Record @@ -72,22 +70,9 @@ class ShareLinkFragment : PermanentBottomSheetFragment() { (requireView().parent as? View)?.setBackgroundColor(Color.TRANSPARENT) } - override fun connectViewModelEvents() { -// viewModel.getShowSnackbar().observe(this, showSnackbar) -// viewModel.getOnShareToAnotherAppRequest().observe(this, onShareToAnotherAppObserver) -// viewModel.getOnFileDownloadedForSharing().observe(this, onFileDownloadedForSharing) -// viewModel.getOnRequestWritePermission().observe(this, onRequestWritePermission) -// viewModel.getOnFileDownloadRequest().observe(this, onFileDownloadRequestObserver) - } - - override fun disconnectViewModelEvents() { -// viewModel.getShowSnackbar().observe(this, showSnackbar) -// viewModel.getOnShareToAnotherAppRequest().removeObserver(onShareToAnotherAppObserver) -// viewModel.getOnFileDownloadedForSharing().removeObserver(onFileDownloadedForSharing) -// viewModel.getOnRequestWritePermission().removeObserver(onRequestWritePermission) -// viewModel.getOnFileDownloadRequest().removeObserver(onFileDownloadRequestObserver) - } + override fun connectViewModelEvents() {} + override fun disconnectViewModelEvents() {} companion object { const val PARCELABLE_SHARE_KEY = "parcelable_share_key" diff --git a/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareManagementFragment.kt b/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareManagementFragment.kt deleted file mode 100644 index 4a037fc6..00000000 --- a/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareManagementFragment.kt +++ /dev/null @@ -1,263 +0,0 @@ -package org.permanent.permanent.ui.shareManagement - -import android.app.DatePickerDialog -import android.app.Dialog -import android.content.DialogInterface -import android.content.Intent -import android.graphics.Typeface -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.TextView -import androidx.activity.OnBackPressedCallback -import androidx.core.content.ContextCompat -import androidx.core.os.bundleOf -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialog -import com.google.android.material.snackbar.Snackbar -import org.permanent.permanent.R -import org.permanent.permanent.databinding.DialogDeleteBinding -import org.permanent.permanent.databinding.FragmentShareManagementBinding -import org.permanent.permanent.models.AccessRole -import org.permanent.permanent.models.AccountEventAction -import org.permanent.permanent.models.Record -import org.permanent.permanent.models.Share -import org.permanent.permanent.network.models.Shareby_urlVO -import org.permanent.permanent.ui.AccessRolesFragment -import org.permanent.permanent.ui.PermanentBottomSheetFragment -import org.permanent.permanent.ui.fileView.FileActivity -import org.permanent.permanent.ui.myFiles.PARCELABLE_RECORD_KEY -import org.permanent.permanent.ui.shareManagement.ShareLinkFragment.Companion.SHARE_BY_URL_VO_KEY -import org.permanent.permanent.viewmodels.ShareManagementViewModel -import java.util.Calendar - -class ShareManagementFragment : PermanentBottomSheetFragment() { - - private lateinit var viewModel: ShareManagementViewModel - private lateinit var binding: FragmentShareManagementBinding - private lateinit var pendingSharesRecyclerView: RecyclerView - private lateinit var pendingSharesAdapter: SharesAdapter - private lateinit var sharesRecyclerView: RecyclerView - private lateinit var sharesAdapter: SharesAdapter - private var record: Record? = null - private var shareToEdit: Share? = null - private var accessRolesFragment: AccessRolesFragment? = null - - fun setBundleArguments(record: Record?, shareByUrlVO: Shareby_urlVO?) { - val bundle = bundleOf(PARCELABLE_RECORD_KEY to record, SHARE_BY_URL_VO_KEY to shareByUrlVO) - this.arguments = bundle - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - viewModel = ViewModelProvider(this)[ShareManagementViewModel::class.java] - binding = FragmentShareManagementBinding.inflate(inflater, container, false) - binding.executePendingBindings() - binding.lifecycleOwner = this - binding.viewModel = viewModel - record = arguments?.getParcelable(PARCELABLE_RECORD_KEY) - record?.let { - viewModel.setRecord(it) - initPendingSharesRecyclerView(binding.rvPendingShares) - initSharesRecyclerView(binding.rvShares) - } - viewModel.setShareLink(arguments?.getParcelable(SHARE_BY_URL_VO_KEY)) - if (activity is FileActivity) { - (activity as FileActivity).setToolbarAndStatusBarColor(R.color.colorPrimary) - } - return binding.root - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val bottomSheetDialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog - bottomSheetDialog.setOnShowListener { dialog: DialogInterface -> - val dialogc = dialog as BottomSheetDialog - val bottomSheet = - dialogc.findViewById(com.google.android.material.R.id.design_bottom_sheet) - BottomSheetBehavior.from(bottomSheet as FrameLayout) - .setState(BottomSheetBehavior.STATE_EXPANDED) - } - return bottomSheetDialog - } - - private fun initPendingSharesRecyclerView(rvPendingShares: RecyclerView) { - pendingSharesRecyclerView = rvPendingShares - pendingSharesAdapter = SharesAdapter(viewModel.getPendingShares(), viewModel) - pendingSharesRecyclerView.apply { - setHasFixedSize(true) - layoutManager = LinearLayoutManager(context) - adapter = pendingSharesAdapter - } - } - - private fun initSharesRecyclerView(rvShares: RecyclerView) { - sharesRecyclerView = rvShares - sharesAdapter = SharesAdapter(viewModel.getShares(), viewModel) - sharesRecyclerView.apply { - setHasFixedSize(true) - layoutManager = LinearLayoutManager(context) - adapter = sharesAdapter - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - // Device's back press - requireActivity().onBackPressedDispatcher - .addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - if (activity is FileActivity) { - (activity as FileActivity).setToolbarAndStatusBarColor(R.color.black) - } - findNavController().popBackStack(R.id.shareLinkFragment, true) - } - }) - } - - private val showSnackbarSuccess = Observer { message -> - val snackBar = Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG) - val view: View = snackBar.view - context?.let { - view.setBackgroundColor(ContextCompat.getColor(it, R.color.deepGreen)) - snackBar.setTextColor(ContextCompat.getColor(it, R.color.paleGreen)) - } - val snackbarTextTextView = view.findViewById(R.id.snackbar_text) as TextView - snackbarTextTextView.setTypeface(snackbarTextTextView.typeface, Typeface.BOLD) - snackBar.show() - } - - private val showSnackbar = Observer { message -> - Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show() - } - - private val onShareLinkObserver = Observer { - val sendIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_TEXT, it) - type = "text/plain" - } - - val shareIntent = Intent.createChooser(sendIntent, null) - startActivity(shareIntent) - viewModel.sendEvent(AccountEventAction.COPY_SHARE_LINK) - } - - private val onRevokeLinkRequest = Observer { - val dialogBinding: DialogDeleteBinding = DataBindingUtil.inflate( - LayoutInflater.from(context), R.layout.dialog_delete, null, false - ) - val alert = android.app.AlertDialog.Builder(context).setView(dialogBinding.root).create() - - dialogBinding.tvTitle.text = getString(R.string.share_management_revoke_title) - dialogBinding.btnDelete.text = getString(R.string.share_management_revoke_button) - dialogBinding.btnDelete.setOnClickListener { - viewModel.deleteShareLink() - alert.dismiss() - } - dialogBinding.btnCancel.setOnClickListener { - alert.dismiss() - } - alert.show() - } - - private val onShareApproved = Observer { - pendingSharesAdapter.remove(it) - sharesAdapter.add(it) - } - - private val onShareRemoved = Observer { - viewModel.onShareRemoved(it) - sharesAdapter.remove(it) - record?.shares?.remove(it) - } - - private val showAccessRolesForShareObserver = Observer { share -> - shareToEdit = share - accessRolesFragment = AccessRolesFragment() - accessRolesFragment?.setBundleArguments(share) - accessRolesFragment?.getOnAccessRoleUpdated() - ?.observe(this, onAccessRoleForShareUpdatedObserver) - accessRolesFragment?.show(parentFragmentManager, accessRolesFragment?.tag) - } - - private val showAccessRolesForLinkObserver = Observer { - accessRolesFragment = AccessRolesFragment() - accessRolesFragment?.setBundleArguments(it) - accessRolesFragment?.getOnAccessRoleUpdated() - ?.observe(this, onAccessRoleForLinkUpdatedObserver) - accessRolesFragment?.show(parentFragmentManager, accessRolesFragment?.tag) - } - - private val onAccessRoleForLinkUpdatedObserver = Observer { - viewModel.onAccessRoleUpdated(it) - } - - private val onAccessRoleForShareUpdatedObserver = Observer { - if (it == null) { - shareToEdit?.let { share -> onShareRemoved.onChanged(share) } - } else { - shareToEdit?.accessRole = it - shareToEdit?.let { share -> sharesAdapter.update(share) } - } - } - - private val onShowDatePicker = Observer { - context?.let { context -> - val c = Calendar.getInstance() - val year = c.get(Calendar.YEAR) - val month = c.get(Calendar.MONTH) - val day = c.get(Calendar.DAY_OF_MONTH) - DatePickerDialog(context, viewModel, year, month, day).show() - } - } - - override fun connectViewModelEvents() { - viewModel.getShowSnackbar().observe(this, showSnackbar) - viewModel.getShowSnackbarSuccess().observe(this, showSnackbarSuccess) - viewModel.getOnShareLinkRequest().observe(this, onShareLinkObserver) - viewModel.getShowAccessRolesForShare().observe(this, showAccessRolesForShareObserver) - viewModel.getOnRevokeLinkRequest().observe(this, onRevokeLinkRequest) - viewModel.getOnShareApproved().observe(this, onShareApproved) - viewModel.getOnShareDenied().observe(this, onShareRemoved) - viewModel.getShowAccessRolesForLink().observe(this, showAccessRolesForLinkObserver) - viewModel.getShowDatePicker().observe(this, onShowDatePicker) - } - - override fun disconnectViewModelEvents() { - viewModel.getShowSnackbar().removeObserver(showSnackbar) - viewModel.getShowSnackbarSuccess().removeObserver(showSnackbarSuccess) - viewModel.getOnShareLinkRequest().removeObserver(onShareLinkObserver) - viewModel.getShowAccessRolesForShare().removeObserver(showAccessRolesForShareObserver) - viewModel.getOnRevokeLinkRequest().removeObserver(onRevokeLinkRequest) - viewModel.getOnShareApproved().removeObserver(onShareApproved) - viewModel.getOnShareDenied().removeObserver(onShareRemoved) - viewModel.getShowAccessRolesForLink().removeObserver(showAccessRolesForLinkObserver) - viewModel.getShowDatePicker().removeObserver(onShowDatePicker) - accessRolesFragment?.getOnAccessRoleUpdated() - ?.removeObserver(onAccessRoleForLinkUpdatedObserver) - accessRolesFragment?.getOnAccessRoleUpdated() - ?.removeObserver(onAccessRoleForShareUpdatedObserver) - } - - override fun onResume() { - super.onResume() - connectViewModelEvents() - } - - override fun onPause() { - super.onPause() - disconnectViewModelEvents() - } - -} \ No newline at end of file diff --git a/app/src/main/java/org/permanent/permanent/viewmodels/ShareManagementViewModel.kt b/app/src/main/java/org/permanent/permanent/viewmodels/ShareManagementViewModel.kt index bb8ae44a..743c6c17 100644 --- a/app/src/main/java/org/permanent/permanent/viewmodels/ShareManagementViewModel.kt +++ b/app/src/main/java/org/permanent/permanent/viewmodels/ShareManagementViewModel.kt @@ -58,11 +58,11 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView val shareLink: StateFlow = _shareLink private val _isCreatingLinkState = MutableStateFlow(false) val isCreatingLinkState: StateFlow = _isCreatingLinkState - private val _isLinkSharedState = MutableStateFlow(false) val isLinkSharedState: StateFlow = _isLinkSharedState - private var shareByUrlVO: Shareby_urlVO? = null + + private var shares = mutableListOf() private var pendingShares = mutableListOf() private val sharesSize = MutableLiveData(0) @@ -128,7 +128,7 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView } private fun checkForExistingLink(record: Record) { - if (isCreatingLinkState.value != null && isCreatingLinkState.value!!) { + if (isCreatingLinkState.value!!) { return } @@ -149,7 +149,7 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView } fun onCreateLinkBtnClick() { - if (_isCreatingLinkState.value != null && _isCreatingLinkState.value!!) { + if (_isCreatingLinkState.value!!) { return } diff --git a/app/src/main/res/layout/fragment_share_management.xml b/app/src/main/res/layout/fragment_share_management.xml deleted file mode 100644 index 7de85f94..00000000 --- a/app/src/main/res/layout/fragment_share_management.xml +++ /dev/null @@ -1,689 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -