diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 0987750f..30715383 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -61,6 +61,10 @@
+
+
+
+
diff --git a/app/src/main/java/org/permanent/permanent/network/NetworkClient.kt b/app/src/main/java/org/permanent/permanent/network/NetworkClient.kt
index ee4d22f4..0ea43985 100644
--- a/app/src/main/java/org/permanent/permanent/network/NetworkClient.kt
+++ b/app/src/main/java/org/permanent/permanent/network/NetworkClient.kt
@@ -19,6 +19,7 @@ import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.ResponseBody
import okhttp3.logging.HttpLoggingInterceptor
+import org.json.JSONObject
import org.permanent.permanent.BuildConfig
import org.permanent.permanent.Constants
import org.permanent.permanent.PermanentApplication
@@ -41,6 +42,8 @@ import org.permanent.permanent.network.models.LegacyContact
import org.permanent.permanent.network.models.LocnVO
import org.permanent.permanent.network.models.ProfileItemsRequestContainer
import org.permanent.permanent.network.models.ResponseVO
+import org.permanent.permanent.network.models.ShareLinkVO
+import org.permanent.permanent.network.models.ShareLinkVOResponse
import org.permanent.permanent.network.models.Shareby_urlVO
import org.permanent.permanent.network.models.SimpleRequestContainer
import org.permanent.permanent.network.models.StorageGift
@@ -784,6 +787,30 @@ class NetworkClient(private var okHttpClient: OkHttpClient?, context: Context) {
fun disableTwoFactor(twoFAVO: TwoFAVO): Call = stelaAccountService.disableTwoFactor(twoFAVO)
+ fun getShareLink(shareTokens: List?,shareLinkIds: List?): Call = stelaAccountService.getShareLink(shareTokens,shareLinkIds)
+
+ fun generateShareLink(shareLink: ShareLinkVO): Call = stelaAccountService.generateShareLink(shareLink)
+
+ fun updateShareLink(shareLink: ShareLinkVO): Call {
+ val json = JSONObject()
+
+ shareLink.permissionsLevel?.let { json.put("permissionsLevel", it) }
+ shareLink.accessRestrictions?.let { json.put("accessRestrictions", it) }
+ if (shareLink.expirationTimestamp == null) {
+ json.put("expirationTimestamp", JSONObject.NULL)
+ } else {
+ json.put("expirationTimestamp", shareLink.expirationTimestamp)
+ }
+ val body = json.toString()
+ .toRequestBody("application/json; charset=utf-8".toMediaType())
+
+ return stelaAccountService.updateShareLink(shareLink.id, body)
+ }
+
+ fun deleteShareLink(shareLinkId: String): Call {
+ return stelaAccountService.deleteShareLink(shareLinkId)
+ }
+
fun getPaymentIntent(
accountId: Int,
accountEmail: String?,
diff --git a/app/src/main/java/org/permanent/permanent/network/StelaAccountService.kt b/app/src/main/java/org/permanent/permanent/network/StelaAccountService.kt
index f097718f..cbad21c7 100644
--- a/app/src/main/java/org/permanent/permanent/network/StelaAccountService.kt
+++ b/app/src/main/java/org/permanent/permanent/network/StelaAccountService.kt
@@ -1,17 +1,38 @@
package org.permanent.permanent.network
+import okhttp3.RequestBody
import okhttp3.ResponseBody
import org.permanent.permanent.models.Tags
import org.permanent.permanent.network.models.ResponseVO
+import org.permanent.permanent.network.models.ShareLinkVO
+import org.permanent.permanent.network.models.ShareLinkVOResponse
import org.permanent.permanent.network.models.TwoFAVO
import retrofit2.Call
import retrofit2.http.Body
+import retrofit2.http.DELETE
import retrofit2.http.GET
+import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.PUT
+import retrofit2.http.Path
+import retrofit2.http.Query
interface StelaAccountService {
+ @GET("api/v2/share-links")
+ fun getShareLink(@Query("shareTokens[]") shareTokens: List?,
+ @Query("shareLinkIds[]") shareLinkIds: List?
+ ): Call
+
+ @POST("api/v2/share-links")
+ fun generateShareLink(@Body shareLink: ShareLinkVO): Call
+
+ @PATCH("api/v2/share-links/{shareLinkId}")
+ fun updateShareLink(@Path("shareLinkId") shareLinkId: String?, @Body body: RequestBody): Call
+
+ @DELETE("api/v2/share-links/{shareLinkId}")
+ fun deleteShareLink(@Path("shareLinkId") shareLinkId: String?): Call
+
@PUT("api/v2/account/tags")
fun addRemoveTags(@Body tags: Tags): Call
diff --git a/app/src/main/java/org/permanent/permanent/network/models/Datum.kt b/app/src/main/java/org/permanent/permanent/network/models/Datum.kt
index 9a9aeaa6..24ca2ce7 100644
--- a/app/src/main/java/org/permanent/permanent/network/models/Datum.kt
+++ b/app/src/main/java/org/permanent/permanent/network/models/Datum.kt
@@ -20,4 +20,6 @@ class Datum {
var TagVO: TagVO? = null
var TagLinkVO: TagLinkVO? = null
var PromoVO: PromoVO? = null
+ var ShareLinkVO: ShareLinkVO? = null
+
}
\ No newline at end of file
diff --git a/app/src/main/java/org/permanent/permanent/network/models/ResponseVO.kt b/app/src/main/java/org/permanent/permanent/network/models/ResponseVO.kt
index 514bb54c..b891a953 100644
--- a/app/src/main/java/org/permanent/permanent/network/models/ResponseVO.kt
+++ b/app/src/main/java/org/permanent/permanent/network/models/ResponseVO.kt
@@ -85,4 +85,8 @@ class ResponseVO {
fun getPromoVO(): PromoVO? {
return getData()?.get(0)?.PromoVO
}
+
+ fun getShareLinkVO(): ShareLinkVO? {
+ return getData()?.get(0)?.ShareLinkVO
+ }
}
diff --git a/app/src/main/java/org/permanent/permanent/network/models/ShareLinkVO.kt b/app/src/main/java/org/permanent/permanent/network/models/ShareLinkVO.kt
new file mode 100644
index 00000000..85cc4cf9
--- /dev/null
+++ b/app/src/main/java/org/permanent/permanent/network/models/ShareLinkVO.kt
@@ -0,0 +1,24 @@
+package org.permanent.permanent.network.models
+
+
+data class ShareLinkVOResponse(
+ val items: List
+)
+
+data class ShareLinkVO (
+ var id: String? = null,
+ var itemId: String? = null,
+ var itemType: String? = null,
+ var permissionsLevel: String? = null,
+ var accessRestrictions: String? = null,
+
+ var token: String? = null,
+
+ var maxUses: Int? = null, // can be 0 for unlimited uses
+
+ var usesExpended: Int? = null,
+ var expirationTimestamp: String? = null, // can be null for no expiration, or this format: "2026-11-17 13:56:31"
+
+ var createdAt: String? = null,
+ var updatedAt: String? = null,
+)
\ No newline at end of file
diff --git a/app/src/main/java/org/permanent/permanent/repositories/StelaAccountRepository.kt b/app/src/main/java/org/permanent/permanent/repositories/StelaAccountRepository.kt
index f215025c..5bde8339 100644
--- a/app/src/main/java/org/permanent/permanent/repositories/StelaAccountRepository.kt
+++ b/app/src/main/java/org/permanent/permanent/repositories/StelaAccountRepository.kt
@@ -3,6 +3,7 @@ package org.permanent.permanent.repositories
import org.permanent.permanent.models.Tags
import org.permanent.permanent.network.IResponseListener
import org.permanent.permanent.network.ITwoFAListener
+import org.permanent.permanent.network.models.ShareLinkVO
import org.permanent.permanent.network.models.TwoFAVO
interface StelaAccountRepository {
@@ -18,4 +19,12 @@ interface StelaAccountRepository {
fun sendDisableCode(twoFAVO: TwoFAVO, listener: IResponseListener)
fun disableTwoFactor(twoFAVO: TwoFAVO, listener: IResponseListener)
+
+ fun getShareLink(shareTokens: List?,shareLinkIds: List?, listener: IResponseListener)
+
+ fun generateShareLink(shareLinkVO: ShareLinkVO, listener: IResponseListener)
+
+ fun updateShareLink(shareLinkVO: ShareLinkVO, listener: IResponseListener)
+
+ fun deleteShareLink(shareLinkId: String, listener: IResponseListener)
}
\ No newline at end of file
diff --git a/app/src/main/java/org/permanent/permanent/repositories/StelaAccountRepositoryImpl.kt b/app/src/main/java/org/permanent/permanent/repositories/StelaAccountRepositoryImpl.kt
index 17306987..72a9bb18 100644
--- a/app/src/main/java/org/permanent/permanent/repositories/StelaAccountRepositoryImpl.kt
+++ b/app/src/main/java/org/permanent/permanent/repositories/StelaAccountRepositoryImpl.kt
@@ -10,6 +10,8 @@ import org.permanent.permanent.network.ITwoFAListener
import org.permanent.permanent.network.NetworkClient
import org.permanent.permanent.network.models.ErrorResponse
import org.permanent.permanent.network.models.ResponseVO
+import org.permanent.permanent.network.models.ShareLinkVO
+import org.permanent.permanent.network.models.ShareLinkVOResponse
import org.permanent.permanent.network.models.TwoFAVO
import retrofit2.Call
import retrofit2.Callback
@@ -192,4 +194,97 @@ class StelaAccountRepositoryImpl(context: Context) : StelaAccountRepository {
}
})
}
+
+ override fun getShareLink(shareTokens: List?,shareLinkIds: List?, listener: IResponseListener) {
+ NetworkClient.instance().getShareLink(shareTokens, shareLinkIds).enqueue( object : Callback {
+
+ override fun onResponse(call: Call, response: Response) {
+ if (response.isSuccessful) {
+ val responseVO = response.body()
+ if (responseVO != null) {
+ listener.onSuccess("")
+ } else {
+ listener.onFailed(appContext.getString(R.string.generic_error))
+ }
+ } else {
+ listener.onFailed("No Link detected")
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ listener.onFailed(t.message)
+ }
+ })
+ }
+
+ override fun generateShareLink(shareLinkVO: ShareLinkVO, listener: IResponseListener) {
+ NetworkClient.instance().generateShareLink(shareLinkVO).enqueue( object : Callback {
+
+ override fun onResponse(call: Call, response: Response) {
+ if (response.isSuccessful) {
+ val responseVO = response.body()
+ if (responseVO != null) {
+ listener.onSuccess("")
+ } else {
+ listener.onFailed(appContext.getString(R.string.generic_error))
+ }
+ } else {
+ listener.onFailed("No Link detected")
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ listener.onFailed(t.message)
+ }
+ })
+ }
+
+ override fun updateShareLink(shareLinkVO: ShareLinkVO, listener: IResponseListener) {
+ NetworkClient.instance().updateShareLink(shareLinkVO)
+ .enqueue(object : Callback {
+
+ override fun onResponse(call: Call, response: Response) {
+ if (response.isSuccessful) {
+ val responseVO = response.body()
+ if (responseVO != null) {
+ listener.onSuccess("")
+ } else {
+ listener.onFailed(appContext.getString(R.string.generic_error))
+ }
+ } else {
+ try {
+ listener.onFailed(response.errorBody().toString())
+ } catch (e: Exception) {
+ listener.onFailed(e.message)
+ }
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ listener.onFailed(t.message)
+ }
+ })
+ }
+
+ override fun deleteShareLink(shareLinkId: String, listener: IResponseListener) {
+ NetworkClient.instance().deleteShareLink(shareLinkId)
+ .enqueue(object : Callback {
+
+ override fun onResponse(call: Call, response: Response) {
+ if (response.isSuccessful) {
+ listener.onSuccess("")
+ } else {
+ try {
+ listener.onFailed(response.errorBody().toString())
+ } catch (e: Exception) {
+ listener.onFailed(e.message)
+ }
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ listener.onFailed(t.message)
+ }
+ })
+ }
}
\ No newline at end of file
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/Extensions.kt b/app/src/main/java/org/permanent/permanent/ui/Extensions.kt
index 6714dc86..9d38a783 100644
--- a/app/src/main/java/org/permanent/permanent/ui/Extensions.kt
+++ b/app/src/main/java/org/permanent/permanent/ui/Extensions.kt
@@ -20,7 +20,9 @@ import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
import java.text.SimpleDateFormat
+import java.time.LocalDate
import java.time.LocalDateTime
+import java.time.LocalTime
import java.time.format.DateTimeFormatter
import java.util.Date
import java.util.Locale
@@ -117,6 +119,21 @@ fun bytesToCustomHumanReadableString(bytes: Long, showDecimal: Boolean): String
}.toString()
}
+private val BACKEND_DATE_TIME_FORMATTER: DateTimeFormatter =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
+
+/**
+ * Convert a LocalDate to a backend datetime string.
+ *
+ * @param useEndOfDay if true, result will be at 23:59:59 of the date (default = true).
+ * if false, result will be at 00:00:00 of the date.
+ */
+fun LocalDate.toBackendDateTimeString(useEndOfDay: Boolean = true): String? {
+ val time = if (useEndOfDay) LocalTime.of(23, 59, 59) else LocalTime.MIDNIGHT
+ val dt = LocalDateTime.of(this, time)
+ return dt.format(BACKEND_DATE_TIME_FORMATTER)
+}
+
fun String?.toDisplayDate(): String {
if (this.isNullOrBlank()) return ""
return try {
diff --git a/app/src/main/java/org/permanent/permanent/ui/composeComponents/AnimatedSnackbar.kt b/app/src/main/java/org/permanent/permanent/ui/composeComponents/AnimatedSnackbar.kt
index 9fe6c665..9011d3a3 100644
--- a/app/src/main/java/org/permanent/permanent/ui/composeComponents/AnimatedSnackbar.kt
+++ b/app/src/main/java/org/permanent/permanent/ui/composeComponents/AnimatedSnackbar.kt
@@ -70,7 +70,7 @@ fun AnimatedSnackbar(
.fillMaxWidth()
.border(1.dp, colorResource(id = if (isForError) R.color.errorLight else R.color.success200), RoundedCornerShape(12.dp))
.background(
- color = colorResource(id = if (isForError) R.color.errorSuperLight else R.color.successLight),
+ color = colorResource(id = if (isForError) R.color.errorSuperLight else R.color.success50),
shape = RoundedCornerShape(size = 12.dp)
), contentAlignment = Alignment.Center
) {
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/composeComponents/LinkSettingsMenuItem.kt b/app/src/main/java/org/permanent/permanent/ui/composeComponents/LinkSettingsMenuItem.kt
new file mode 100644
index 00000000..d1543e5b
--- /dev/null
+++ b/app/src/main/java/org/permanent/permanent/ui/composeComponents/LinkSettingsMenuItem.kt
@@ -0,0 +1,121 @@
+package org.permanent.permanent.ui.composeComponents
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+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.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import org.permanent.permanent.R
+
+@Composable
+fun LinkSettingsMenuItem(
+ isTablet: Boolean = false,
+ iconResource: Painter,
+ title: String,
+ subtitle: String,
+ isSelected: Boolean = false,
+ onClick: () -> Unit
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { onClick() }
+ .background(if (isSelected) colorResource(id = R.color.blue25).copy(alpha = 0.5f) else Color.Transparent)
+ .padding(24.dp)
+ .height(IntrinsicSize.Min),
+ verticalAlignment = Alignment.Top,
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+
+ ) {
+ Box(
+ modifier = Modifier
+ .size(32.dp)
+ .background(if (isSelected) Color.White else colorResource(R.color.success50), RoundedCornerShape(4.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ painter = iconResource,
+ contentDescription = "",
+ tint = Color.Unspecified
+ )
+ }
+
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .weight(1.0f, fill = false),
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ horizontalAlignment = Alignment.Start,
+ ) {
+ Text(
+ text = title,
+ fontSize = if (isTablet) 18.sp else 14.sp,
+ lineHeight = 24.sp,
+ color = colorResource(R.color.blue900),
+ fontFamily = FontFamily(Font(R.font.usual_medium))
+ )
+
+ Text(
+ text = subtitle,
+ fontSize = if (isTablet) 18.sp else 12.sp,
+ lineHeight = if (isTablet) 32.sp else 16.sp,
+ color = colorResource(R.color.blue900),
+ fontFamily = FontFamily(Font(R.font.usual_regular))
+ )
+ }
+
+ if (isSelected) {
+ Box(
+ modifier = Modifier
+ .size(32.dp)
+ .background(Color.White, CircleShape),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_check_circle_green),
+ contentDescription = "Checklist Icon",
+ tint = Color.Unspecified
+ )
+ }
+ } else {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_circle_blue),
+ contentDescription = "",
+ tint = Color.Unspecified
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+fun LinkSettingsComposablePreview() {
+ LinkSettingsMenuItem(
+ iconResource = painterResource(id = R.drawable.ic_plus_primary),
+ title = "Add storage!",
+ subtitle = "Increase your space easily by adding more storage.",
+ isSelected = true,
+ onClick = { })
+}
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 7d6c1ae0..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,7 @@ 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
import org.permanent.permanent.ui.shares.URL_TOKEN_KEY
@@ -82,7 +82,6 @@ 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 sortOptionsFragment: SortOptionsFragment? = null
private var selectionMenuFragment: SelectionMenuFragment? = null
private var bottomSheetFragment: ChecklistBottomSheetFragment? = null
@@ -329,9 +328,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/recordMenu/RecordMenuFragment.kt b/app/src/main/java/org/permanent/permanent/ui/recordMenu/RecordMenuFragment.kt
index b542ed21..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
@@ -37,7 +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.ShareManagementFragment
+import org.permanent.permanent.ui.shareManagement.ShareLinkFragment
+import org.permanent.permanent.ui.shareManagement.shareLink.ShareLinkScreen
import org.permanent.permanent.viewmodels.RecordMenuItem
import org.permanent.permanent.viewmodels.RecordMenuViewModel
@@ -49,7 +50,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 +217,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/settings/compose/twoStepVerification/TwoStepEnabledScreen.kt b/app/src/main/java/org/permanent/permanent/ui/settings/compose/twoStepVerification/TwoStepEnabledScreen.kt
index 3a3fc27d..941afbbb 100644
--- a/app/src/main/java/org/permanent/permanent/ui/settings/compose/twoStepVerification/TwoStepEnabledScreen.kt
+++ b/app/src/main/java/org/permanent/permanent/ui/settings/compose/twoStepVerification/TwoStepEnabledScreen.kt
@@ -235,7 +235,7 @@ private fun TabletBody(
HorizontalDivider(
modifier = Modifier.fillMaxWidth(),
thickness = 1.dp,
- color = colorResource(id = R.color.dividerBlue)
+ color = colorResource(id = R.color.blue100)
)
Spacer(modifier = Modifier.height(24.dp))
@@ -410,7 +410,7 @@ private fun PhoneBody(
HorizontalDivider(
modifier = Modifier.fillMaxWidth(),
thickness = 1.dp,
- color = colorResource(id = R.color.dividerBlue)
+ color = colorResource(id = R.color.blue100)
)
Spacer(modifier = Modifier.height(24.dp))
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..9f2c9fcf
--- /dev/null
+++ b/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareLinkFragment.kt
@@ -0,0 +1,86 @@
+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.unit.dp
+import androidx.core.os.bundleOf
+import androidx.lifecycle.ViewModelProvider
+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.compose.ShareManagementContainer
+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),
+ ) {
+ ShareManagementContainer(
+ 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
+ isHideable = false
+ try {
+ this.isDraggable = false
+ } catch (ignored: Throwable) {
+ }
+ }
+ // transparent background so Compose Surface can draw rounded corners
+ (requireView().parent as? View)?.setBackgroundColor(Color.TRANSPARENT)
+ }
+
+ override fun connectViewModelEvents() {}
+
+ override fun 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/ShareManagementFragment.kt b/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareManagementFragment.kt
deleted file mode 100644
index 2855521f..00000000
--- a/app/src/main/java/org/permanent/permanent/ui/shareManagement/ShareManagementFragment.kt
+++ /dev/null
@@ -1,266 +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.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()
- }
-
- 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/compose/AccessRolesPage.kt b/app/src/main/java/org/permanent/permanent/ui/shareManagement/compose/AccessRolesPage.kt
new file mode 100644
index 00000000..ef599a3c
--- /dev/null
+++ b/app/src/main/java/org/permanent/permanent/ui/shareManagement/compose/AccessRolesPage.kt
@@ -0,0 +1,102 @@
+package org.permanent.permanent.ui.shareManagement.compose
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+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.unit.dp
+import org.permanent.permanent.R
+import org.permanent.permanent.models.AccessRole
+import org.permanent.permanent.ui.composeComponents.LinkSettingsMenuItem
+import org.permanent.permanent.viewmodels.ShareManagementViewModel
+
+@Composable
+fun AccessRolesPage(
+ viewModel: ShareManagementViewModel,
+ onClose: () -> Unit,
+) {
+ val selectedAccessRole by viewModel.selectedAccessRole.collectAsState()
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.White)
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Top
+ ) {
+
+ NavigationHeader(
+ title = stringResource(R.string.select_access_role),
+ onBackBtnClick = { viewModel.onBackBtnClick(SharePage.ACCESS_ROLES) },
+ onCloseClick = onClose
+ )
+
+ val isOwnerSelected = selectedAccessRole == AccessRole.OWNER
+ LinkSettingsMenuItem(
+ iconResource = painterResource(id = R.drawable.ic_owner_green),
+ title = AccessRole.OWNER.toTitleCase(),
+ subtitle = stringResource(R.string.owner_description),
+ isSelected = isOwnerSelected
+ ) { viewModel.onAccessRoleClick(AccessRole.OWNER) }
+
+ HorizontalDivider(
+ thickness = 1.dp, color = colorResource(R.color.blue25)
+ )
+
+ val isCuratorSelected = selectedAccessRole == AccessRole.CURATOR
+ LinkSettingsMenuItem(
+ iconResource = painterResource(id = R.drawable.ic_curator_green),
+ title = AccessRole.CURATOR.toTitleCase(),
+ subtitle = stringResource(R.string.curator_description),
+ isSelected = isCuratorSelected
+ ) { viewModel.onAccessRoleClick(AccessRole.CURATOR) }
+
+ HorizontalDivider(
+ thickness = 1.dp, color = colorResource(R.color.blue25)
+ )
+
+ val isEditorSelected = selectedAccessRole == AccessRole.EDITOR
+ LinkSettingsMenuItem(
+ iconResource = painterResource(id = R.drawable.ic_editor_green),
+ title = AccessRole.EDITOR.toTitleCase(),
+ subtitle = stringResource(R.string.editor_description),
+ isSelected = isEditorSelected
+ ) { viewModel.onAccessRoleClick(AccessRole.EDITOR) }
+
+ HorizontalDivider(
+ thickness = 1.dp, color = colorResource(R.color.blue25)
+ )
+
+ val isContributorSelected = selectedAccessRole == AccessRole.CONTRIBUTOR
+ LinkSettingsMenuItem(
+ iconResource = painterResource(id = R.drawable.ic_contributor_green),
+ title = AccessRole.CONTRIBUTOR.toTitleCase(),
+ subtitle = stringResource(R.string.contributor_description),
+ isSelected = isContributorSelected
+ ) { viewModel.onAccessRoleClick(AccessRole.CONTRIBUTOR) }
+
+ HorizontalDivider(
+ thickness = 1.dp, color = colorResource(R.color.blue25)
+ )
+
+ val isViewerSelected = selectedAccessRole == AccessRole.VIEWER
+ LinkSettingsMenuItem(
+ iconResource = painterResource(id = R.drawable.ic_viewer_green),
+ title = AccessRole.VIEWER.toTitleCase(),
+ subtitle = stringResource(R.string.viewer_description),
+ isSelected = isViewerSelected
+ ) { viewModel.onAccessRoleClick(AccessRole.VIEWER) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/permanent/permanent/ui/shareManagement/compose/GeneralAccessPage.kt b/app/src/main/java/org/permanent/permanent/ui/shareManagement/compose/GeneralAccessPage.kt
new file mode 100644
index 00000000..dbae7bbf
--- /dev/null
+++ b/app/src/main/java/org/permanent/permanent/ui/shareManagement/compose/GeneralAccessPage.kt
@@ -0,0 +1,79 @@
+package org.permanent.permanent.ui.shareManagement.compose
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+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.unit.dp
+import org.permanent.permanent.R
+import org.permanent.permanent.ui.composeComponents.LinkSettingsMenuItem
+import org.permanent.permanent.viewmodels.ShareManagementViewModel
+
+@Composable
+fun GeneralAccessPage(
+ viewModel: ShareManagementViewModel,
+ onClose: () -> Unit,
+) {
+ val selectedAccessType by viewModel.selectedGeneralAccessType.collectAsState()
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.White)
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Top
+ ) {
+
+ NavigationHeader(
+ title = stringResource(R.string.general_access),
+ onBackBtnClick = { viewModel.onBackBtnClick(SharePage.GENERAL_ACCESS) },
+ onCloseClick = onClose
+ )
+
+ val isAnyoneSelected = selectedAccessType == AccessType.ANYONE_CAN_VIEW
+ LinkSettingsMenuItem(
+ iconResource = painterResource(id = R.drawable.ic_globe_green),
+ title = stringResource(R.string.anyone_can_view),
+ subtitle = stringResource(R.string.anyone_can_view_description),
+ isSelected = isAnyoneSelected
+ ) { viewModel.onGeneralAccessItemClick(AccessType.ANYONE_CAN_VIEW) }
+
+ HorizontalDivider(
+ thickness = 1.dp, color = colorResource(R.color.blue25)
+ )
+
+ val isRestrictedSelected = selectedAccessType == AccessType.RESTRICTED
+ LinkSettingsMenuItem(
+ iconResource = painterResource(id = R.drawable.ic_lock_green),
+ title = stringResource(R.string.restricted),
+ subtitle = stringResource(R.string.restricted_description),
+ isSelected = isRestrictedSelected
+ ) { viewModel.onGeneralAccessItemClick(AccessType.RESTRICTED) }
+
+ HorizontalDivider(
+ thickness = 1.dp, color = colorResource(R.color.blue25)
+ )
+ }
+ }
+}
+
+enum class AccessType(val value: Int) {
+ ANYONE_CAN_VIEW(0), RESTRICTED(1);
+
+ val backendValue: String
+ get() = when (this) {
+ ANYONE_CAN_VIEW -> "none"
+ RESTRICTED -> "approval"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/permanent/permanent/ui/shareManagement/compose/LinkSettingsPage.kt b/app/src/main/java/org/permanent/permanent/ui/shareManagement/compose/LinkSettingsPage.kt
new file mode 100644
index 00000000..e85a01f6
--- /dev/null
+++ b/app/src/main/java/org/permanent/permanent/ui/shareManagement/compose/LinkSettingsPage.kt
@@ -0,0 +1,673 @@
+package org.permanent.permanent.ui.shareManagement.compose
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+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.lazy.LazyColumn
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+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.graphics.painter.Painter
+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.font.FontWeight
+import androidx.compose.ui.text.intl.Locale
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.text.toUpperCase
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import org.permanent.permanent.R
+import org.permanent.permanent.models.AccessRole
+import org.permanent.permanent.ui.composeComponents.ButtonColor
+import org.permanent.permanent.ui.composeComponents.CenteredTextAndIconButton
+import org.permanent.permanent.ui.composeComponents.ConfirmationBottomSheet
+import org.permanent.permanent.viewmodels.ShareManagementViewModel
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
+
+@Composable
+fun LinkSettingsPage(
+ viewModel: ShareManagementViewModel,
+ onClose: () -> Unit,
+) {
+ val shareLink by viewModel.shareLink.collectAsState()
+ val selectedGeneralAccessType by viewModel.selectedGeneralAccessType.collectAsState()
+ val selectedAccessRole by viewModel.selectedAccessRole.collectAsState()
+ val selectedLinkDuration by viewModel.selectedLinkDuration.collectAsState()
+ var showRevokeConfirmation by remember { mutableStateOf(false) }
+ val durationOptions = remember { LinkDuration.entries }
+
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ topBar = {
+ NavigationHeader(
+ title = stringResource(R.string.link_settings),
+ onBackBtnClick = { viewModel.onBackBtnClick(SharePage.LINK_SETTINGS) },
+ onCloseClick = onClose
+ )
+ },
+ bottomBar = {
+ Column {
+ // Top gradient fade
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(16.dp)
+ .background(
+ Brush.verticalGradient(
+ colors = listOf(
+ Color.Transparent,
+ Color.White
+ )
+ )
+ )
+ )
+
+ // Buttons container
+ Surface(color = Color.White, tonalElevation = 4.dp) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(start = 24.dp, end = 24.dp, bottom = 24.dp, top = 16.dp),
+ horizontalArrangement = Arrangement.spacedBy(24.dp)
+ ) {
+ Box(modifier = Modifier.weight(1f)) {
+ CenteredTextAndIconButton(
+ buttonColor = ButtonColor.LIGHT_BLUE,
+ text = stringResource(id = R.string.button_cancel),
+ icon = null,
+ onButtonClick = { viewModel.onBackBtnClick(SharePage.LINK_SETTINGS) })
+ }
+
+ Box(modifier = Modifier.weight(1f)) {
+ CenteredTextAndIconButton(
+ buttonColor = ButtonColor.DARK,
+ text = stringResource(id = R.string.done),
+ icon = null,
+ onButtonClick = { viewModel.onDoneBtnClick() })
+ }
+ }
+ }
+ }
+ },
+ containerColor = Color.White
+ ) { innerPadding ->
+ // only this area scrolls; topBar and bottomBar remain fixed
+ LazyColumn(
+ contentPadding = innerPadding,
+ modifier = Modifier
+ .fillMaxSize()
+ .background(colorResource(R.color.blue25))
+ ) {
+ item {
+ LinkRow(
+ shareLink = viewModel.cleanUrlRegex(shareLink),
+ onCopyClick = { viewModel.copyLinkToClipboard() }
+ )
+ }
+
+ item {
+ Column(
+ modifier = Modifier
+ .fillParentMaxHeight()
+ .fillMaxWidth()
+ .background(Color.White)
+ .padding(24.dp)
+ ) {
+ // General access header + row
+ Text(
+ text = stringResource(R.string.general_access).toUpperCase(Locale.current),
+ style = TextStyle(
+ fontSize = 10.sp,
+ lineHeight = 8.sp,
+ fontFamily = FontFamily(Font(R.font.usual_regular)),
+ color = colorResource(R.color.colorPrimary),
+ letterSpacing = 1.6.sp,
+ )
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { viewModel.onGeneralAccessClick() },
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Box(
+ modifier = Modifier
+ .size(32.dp)
+ .background(
+ colorResource(R.color.success50),
+ RoundedCornerShape(4.dp)
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ painter = painterResource(
+ id = when (selectedGeneralAccessType) {
+ AccessType.ANYONE_CAN_VIEW -> R.drawable.ic_globe_green
+ AccessType.RESTRICTED -> R.drawable.ic_lock_green
+ }
+ ), contentDescription = "", tint = Color.Unspecified
+ )
+ }
+
+ Spacer(modifier = Modifier.width(16.dp))
+
+ Text(
+ text = when (selectedGeneralAccessType) {
+ AccessType.ANYONE_CAN_VIEW -> stringResource(R.string.anyone_can_view)
+ AccessType.RESTRICTED -> stringResource(R.string.restricted)
+ }, modifier = Modifier.weight(1f), style = TextStyle(
+ fontSize = 14.sp,
+ lineHeight = 24.sp,
+ fontFamily = FontFamily(Font(R.font.usual_medium)),
+ color = colorResource(R.color.colorPrimary),
+ )
+ )
+
+ Spacer(modifier = Modifier.width(16.dp))
+
+ Icon(
+ painter = painterResource(id = R.drawable.ic_arrow_select_light_blue),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.size(12.dp)
+ )
+
+ Spacer(modifier = Modifier.width(12.dp))
+ }
+
+ // default access role only when restricted
+ if (selectedGeneralAccessType == AccessType.RESTRICTED) {
+ Spacer(modifier = Modifier.height(24.dp))
+
+ Text(
+ text = stringResource(R.string.default_access_role).toUpperCase(Locale.current),
+ style = TextStyle(
+ fontSize = 10.sp,
+ lineHeight = 8.sp,
+ fontFamily = FontFamily(Font(R.font.usual_regular)),
+ color = colorResource(R.color.colorPrimary),
+ letterSpacing = 1.6.sp,
+ )
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { viewModel.onDefaultAccessRoleClick() },
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Box(
+ modifier = Modifier
+ .size(32.dp)
+ .background(
+ colorResource(R.color.success50),
+ RoundedCornerShape(4.dp)
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ painter = painterResource(
+ id = when (selectedAccessRole) {
+ AccessRole.VIEWER -> R.drawable.ic_viewer_green
+ AccessRole.CONTRIBUTOR -> R.drawable.ic_contributor_green
+ AccessRole.EDITOR -> R.drawable.ic_editor_green
+ AccessRole.CURATOR -> R.drawable.ic_curator_green
+ AccessRole.OWNER -> R.drawable.ic_owner_green
+ AccessRole.MANAGER -> TODO()
+ }
+ ), contentDescription = "", tint = Color.Unspecified
+ )
+ }
+
+ Spacer(modifier = Modifier.width(16.dp))
+
+ Text(
+ text = when (selectedAccessRole) {
+ AccessRole.VIEWER -> AccessRole.VIEWER.toTitleCase()
+ AccessRole.CONTRIBUTOR -> AccessRole.CONTRIBUTOR.toTitleCase()
+ AccessRole.EDITOR -> AccessRole.EDITOR.toTitleCase()
+ AccessRole.CURATOR -> AccessRole.CURATOR.toTitleCase()
+ AccessRole.OWNER -> AccessRole.OWNER.toTitleCase()
+ AccessRole.MANAGER -> AccessRole.MANAGER.toTitleCase()
+ }, modifier = Modifier.weight(1f), style = TextStyle(
+ fontSize = 14.sp,
+ lineHeight = 24.sp,
+ fontFamily = FontFamily(Font(R.font.usual_medium)),
+ color = colorResource(R.color.colorPrimary),
+ )
+ )
+
+ Spacer(modifier = Modifier.width(16.dp))
+
+ Icon(
+ painter = painterResource(id = R.drawable.ic_arrow_select_light_blue),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.size(12.dp)
+ )
+
+ Spacer(modifier = Modifier.width(12.dp))
+ }
+ }
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ // Link expiration
+ Text(
+ text = stringResource(R.string.link_expiration).toUpperCase(Locale.current),
+ style = TextStyle(
+ fontSize = 10.sp,
+ lineHeight = 8.sp,
+ fontFamily = FontFamily(Font(R.font.usual_regular)),
+ color = colorResource(R.color.colorPrimary),
+ letterSpacing = 1.6.sp,
+ )
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ val option1 = durationOptions[0] // ONE_DAY
+ val option2 = durationOptions[1] // ONE_MONTH
+
+ DurationOption(
+ icon = painterResource(id = option1.iconRes),
+ label = stringResource(option1.labelRes),
+ selected = selectedLinkDuration == option1,
+ onClick = { viewModel.onLinkDurationSelected(option1) },
+ modifier = Modifier.weight(1f)
+ )
+
+ DurationOption(
+ icon = painterResource(id = option2.iconRes),
+ label = stringResource(option2.labelRes),
+ selected = selectedLinkDuration == option2,
+ onClick = { viewModel.onLinkDurationSelected(option2) },
+ modifier = Modifier.weight(1f),
+ )
+ }
+
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ val option3 = durationOptions[2] // ONE_YEAR
+ val option4 = durationOptions[3] // NEVER
+
+ DurationOption(
+ icon = painterResource(id = option3.iconRes),
+ label = stringResource(option3.labelRes),
+ selected = selectedLinkDuration == option3,
+ onClick = { viewModel.onLinkDurationSelected(option3) },
+ modifier = Modifier.weight(1f),
+ )
+
+ DurationOption(
+ icon = painterResource(id = option4.iconRes),
+ label = stringResource(option4.labelRes),
+ selected = selectedLinkDuration == option4,
+ onClick = { viewModel.onLinkDurationSelected(option4) },
+ modifier = Modifier.weight(1f),
+ )
+ }
+ }
+
+ Spacer(Modifier.height(16.dp))
+
+ InfoBanner(duration = selectedLinkDuration)
+
+ Spacer(Modifier.height(24.dp))
+
+ HorizontalDivider(thickness = 1.dp, color = colorResource(R.color.blue50))
+
+ Spacer(Modifier.height(16.dp))
+
+ RevokeLink(onClick = { showRevokeConfirmation = true })
+ }
+ }
+ }
+ }
+
+ if (showRevokeConfirmation) {
+ ConfirmationBottomSheet(
+ message = stringResource(R.string.confirm_revoke_link_message),
+ boldText = stringResource(R.string.revoke_this_share_link),
+ confirmationButtonText = stringResource(id = R.string.revoke_link),
+ onConfirm = {
+ showRevokeConfirmation = false
+ viewModel.revokeLink()
+ },
+ onDismiss = {
+ showRevokeConfirmation = false
+ })
+ }
+}
+
+@Composable
+fun NavigationHeader(
+ title: String, onBackBtnClick: () -> Unit, onCloseClick: (() -> Unit)? = null
+) {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(colorResource(R.color.white))
+ .padding(12.dp), contentAlignment = Alignment.Center
+ ) {
+ IconButton(
+ onClick = onBackBtnClick, modifier = Modifier.align(Alignment.CenterStart)
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_arrow_back_blue),
+ contentDescription = "Back",
+ tint = Color.Unspecified,
+ )
+ }
+
+ 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.blue200),
+ )
+ }
+ }
+ }
+
+ HorizontalDivider(
+ thickness = 1.dp, color = colorResource(R.color.blue100)
+ )
+ }
+}
+
+@Composable
+fun LinkRow(
+ shareLink: String, onCopyClick: () -> Unit
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(colorResource(R.color.blue25))
+ .padding(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_link_gradient),
+ 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
+ )
+
+ Row(
+ modifier = Modifier
+ .clickable { onCopyClick() }
+ .padding(start = 8.dp, end = 12.dp, top = 8.dp, bottom = 8.dp),
+ verticalAlignment = Alignment.CenterVertically) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_copy_blue),
+ contentDescription = null,
+ tint = Color.Unspecified
+ )
+
+ Spacer(modifier = Modifier.width(8.dp))
+
+ Text(
+ text = stringResource(R.string.copy), style = TextStyle(
+ fontSize = 14.sp,
+ lineHeight = 24.sp,
+ fontFamily = FontFamily(Font(R.font.usual_medium)),
+ color = colorResource(R.color.blue900)
+ )
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun DurationOption(
+ icon: Painter,
+ label: String,
+ selected: Boolean,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val backgroundColor = if (selected) colorResource(R.color.blue25) else Color.Transparent
+ val gradientBrush = Brush.horizontalGradient(
+ colors = listOf(
+ colorResource(R.color.barneyPurple), colorResource(R.color.colorAccent)
+ )
+ )
+ val borderModifier = if (selected) {
+ Modifier.border(
+ width = 1.dp, brush = gradientBrush, shape = RoundedCornerShape(12.dp)
+ )
+ } else {
+ Modifier.border(
+ width = 1.dp, color = colorResource(R.color.blue100), shape = RoundedCornerShape(12.dp)
+ )
+ }
+
+ Surface(
+ modifier = modifier
+ .height(88.dp)
+ .clip(RoundedCornerShape(12.dp))
+ .clickable(onClick = onClick)
+ .then(borderModifier),
+ color = backgroundColor,
+ tonalElevation = if (selected) 2.dp else 0.dp
+ ) {
+ Box(
+ modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Icon(
+ painter = icon,
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.size(24.dp)
+ )
+
+ Text(
+ text = label, style = TextStyle(
+ fontSize = 14.sp,
+ lineHeight = 24.sp,
+ fontFamily = FontFamily(Font(R.font.usual_regular)),
+ fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Normal,
+ color = colorResource(if (selected) R.color.blue900 else R.color.blue600)
+ )
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun InfoBanner(
+ duration: LinkDuration, today: LocalDate = LocalDate.now()
+) {
+ val dateFormatter = remember { DateTimeFormatter.ofPattern("MMMM d, yyyy") }
+ val infoText = when (duration) {
+ LinkDuration.NEVER -> stringResource(R.string.link_never_expires)
+ else -> {
+ val date = duration.expirationDate(today)!!
+ stringResource(R.string.link_expires_on, date.format(dateFormatter))
+ }
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(12.dp))
+ .background(colorResource(R.color.warning50))
+ .border(1.dp, colorResource(R.color.warning100), RoundedCornerShape(12.dp))
+ .padding(8.dp), verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_info_orange),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ )
+
+ Spacer(Modifier.width(8.dp))
+
+ Text(
+ text = infoText, style = TextStyle(
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ fontFamily = FontFamily(Font(R.font.usual_regular)),
+ color = colorResource(R.color.warning800)
+ )
+ )
+ }
+}
+
+@Composable
+private fun RevokeLink(onClick: () -> Unit) {
+ TextButton(
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(12.dp),
+ contentPadding = PaddingValues(0.dp),
+ onClick = onClick
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_revoke_link),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ )
+
+ Spacer(Modifier.width(16.dp))
+
+ Text(
+ text = stringResource(R.string.revoke_link), style = TextStyle(
+ fontSize = 14.sp,
+ lineHeight = 24.sp,
+ fontFamily = FontFamily(Font(R.font.usual_regular)),
+ color = colorResource(R.color.error500)
+ )
+ )
+ }
+ }
+}
+
+enum class LinkDuration(
+ @StringRes val labelRes: Int,
+ @DrawableRes val iconRes: Int,
+) {
+ ONE_DAY(
+ labelRes = R.string.one_day, iconRes = R.drawable.ic_one_day_blue
+ ),
+ ONE_MONTH(
+ labelRes = R.string.one_month, iconRes = R.drawable.ic_one_month_blue
+ ),
+ ONE_YEAR(
+ labelRes = R.string.one_year, iconRes = R.drawable.ic_one_year_blue
+ ),
+ NEVER(
+ labelRes = R.string.never, iconRes = R.drawable.ic_never_blue
+ );
+
+ fun expirationDate(from: LocalDate): LocalDate? = when (this) {
+ ONE_DAY -> from.plusDays(1)
+ ONE_MONTH -> from.plusMonths(1)
+ ONE_YEAR -> from.plusYears(1)
+ NEVER -> null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/permanent/permanent/ui/shareManagement/compose/ShareManagementContainer.kt b/app/src/main/java/org/permanent/permanent/ui/shareManagement/compose/ShareManagementContainer.kt
new file mode 100644
index 00000000..450b47cf
--- /dev/null
+++ b/app/src/main/java/org/permanent/permanent/ui/shareManagement/compose/ShareManagementContainer.kt
@@ -0,0 +1,100 @@
+package org.permanent.permanent.ui.shareManagement.compose
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import org.permanent.permanent.R
+import org.permanent.permanent.ui.composeComponents.AnimatedSnackbar
+import org.permanent.permanent.ui.composeComponents.CircularProgressIndicator
+import org.permanent.permanent.ui.composeComponents.SnackbarType
+import org.permanent.permanent.ui.shareManagement.shareLink.ShareLinkScreen
+import org.permanent.permanent.viewmodels.ShareManagementViewModel
+
+@Composable
+fun ShareManagementContainer(
+ viewModel: ShareManagementViewModel,
+ onClose: () -> Unit,
+) {
+ val isBusyState by viewModel.isBusyState.collectAsState()
+ val snackbarMessage by viewModel.snackbarMessage.collectAsState()
+ val snackbarType by viewModel.snackbarType.collectAsState()
+ val pagerState = rememberPagerState(
+ initialPage = SharePage.SHARE_ITEM.value, pageCount = { SharePage.entries.size })
+ val navigateToPage by viewModel.navigateToPage.collectAsState()
+
+ LaunchedEffect(navigateToPage) {
+ navigateToPage?.let { page ->
+ pagerState.animateScrollToPage(page.value)
+ viewModel.clearPageNavigation()
+ }
+ }
+
+ Box(
+ modifier = Modifier
+ .fillMaxHeight(0.95f)
+ .background(colorResource(id = R.color.blue900))
+ ) {
+ Row(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ HorizontalPager(
+ state = pagerState, beyondViewportPageCount = 3, userScrollEnabled = false
+ ) { page ->
+ when (page) {
+ SharePage.SHARE_ITEM.value -> {
+ ShareLinkScreen(
+ viewModel = viewModel, onClose = { onClose() })
+ }
+
+ SharePage.LINK_SETTINGS.value -> {
+ LinkSettingsPage(
+ viewModel = viewModel, onClose = { onClose() })
+ }
+
+ SharePage.GENERAL_ACCESS.value -> {
+ GeneralAccessPage(
+ viewModel = viewModel, onClose = { onClose() })
+ }
+
+ SharePage.ACCESS_ROLES.value -> {
+ AccessRolesPage(
+ viewModel = viewModel, onClose = { onClose() })
+ }
+ }
+ }
+ }
+
+ AnimatedSnackbar(
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .padding(32.dp),
+ isForError = snackbarType == SnackbarType.ERROR,
+ message = snackbarMessage,
+ buttonText = stringResource(id = R.string.ok),
+ onButtonClick = {
+ viewModel.clearSnackbar()
+ })
+ }
+
+ if (isBusyState) {
+ CircularProgressIndicator(modifier = Modifier.fillMaxHeight(0.95f))
+ }
+}
+
+enum class SharePage(val value: Int) {
+ SHARE_ITEM(0), LINK_SETTINGS(1), GENERAL_ACCESS(2), ACCESS_ROLES(3)
+}
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..061ed4a5
--- /dev/null
+++ b/app/src/main/java/org/permanent/permanent/ui/shareManagement/shareLink/ShareLinkScreen.kt
@@ -0,0 +1,307 @@
+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.HorizontalDivider
+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()
+ .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 = {
+ viewModel.onLinkSettingsBtnClick()
+ }, 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
+) {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ 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.blue200),
+ )
+ }
+ }
+ }
+
+ HorizontalDivider(
+ thickness = 1.dp, color = colorResource(R.color.blue50)
+ )
+ }
+}
+
+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..efdb726c 100644
--- a/app/src/main/java/org/permanent/permanent/viewmodels/ShareManagementViewModel.kt
+++ b/app/src/main/java/org/permanent/permanent/viewmodels/ShareManagementViewModel.kt
@@ -2,31 +2,48 @@ 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.util.Log
import android.view.KeyEvent
import android.view.View
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.R
import org.permanent.permanent.models.AccessRole
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
import org.permanent.permanent.network.ShareRequestType
+import org.permanent.permanent.network.models.ShareLinkVO
import org.permanent.permanent.network.models.Shareby_urlVO
import org.permanent.permanent.repositories.EventsRepositoryImpl
import org.permanent.permanent.repositories.IEventsRepository
import org.permanent.permanent.repositories.IShareRepository
import org.permanent.permanent.repositories.ShareRepositoryImpl
+import org.permanent.permanent.repositories.StelaAccountRepository
+import org.permanent.permanent.repositories.StelaAccountRepositoryImpl
import org.permanent.permanent.ui.PREFS_NAME
import org.permanent.permanent.ui.PreferencesHelper
+import org.permanent.permanent.ui.bytesToHumanReadableString
+import org.permanent.permanent.ui.composeComponents.SnackbarType
import org.permanent.permanent.ui.shareManagement.ShareListener
+import org.permanent.permanent.ui.shareManagement.compose.AccessType
+import org.permanent.permanent.ui.shareManagement.compose.LinkDuration
+import org.permanent.permanent.ui.shareManagement.compose.SharePage
+import org.permanent.permanent.ui.toBackendDateTimeString
+import org.permanent.permanent.ui.toDisplayDate
+import java.time.LocalDate
class ShareManagementViewModel(application: Application) : ObservableAndroidViewModel(application),
@@ -37,9 +54,35 @@ 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 val _selectedGeneralAccessType = MutableStateFlow(AccessType.ANYONE_CAN_VIEW)
+ val selectedGeneralAccessType: StateFlow = _selectedGeneralAccessType
+ private val _selectedAccessRole = MutableStateFlow(AccessRole.VIEWER)
+ val selectedAccessRole: StateFlow = _selectedAccessRole
+ private val _selectedLinkDuration = MutableStateFlow(LinkDuration.NEVER)
+ val selectedLinkDuration: StateFlow = _selectedLinkDuration
+ private val _isBusyState = MutableStateFlow(false)
+ val isBusyState: StateFlow = _isBusyState
+ private val _snackbarMessage = MutableStateFlow("")
+ val snackbarMessage: StateFlow = _snackbarMessage
+ private val _snackbarType = MutableStateFlow(SnackbarType.NONE)
+ val snackbarType: StateFlow = _snackbarType
private var shareByUrlVO: Shareby_urlVO? = null
- private val shareLink = MutableLiveData("")
+ private val _navigateToPage = MutableStateFlow(null)
+ val navigateToPage: StateFlow = _navigateToPage
private var shares = mutableListOf()
private var pendingShares = mutableListOf()
private val sharesSize = MutableLiveData(0)
@@ -51,22 +94,26 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView
private val expirationDate = MutableLiveData()
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()
- private val onRevokeLinkRequest = SingleLiveEvent()
private val showAccessRolesForLink = SingleLiveEvent()
private val showAccessRolesForShare = SingleLiveEvent()
private val onShareApproved = SingleLiveEvent()
private val onShareDenied = SingleLiveEvent()
private var shareRepository: IShareRepository = ShareRepositoryImpl(appContext)
private var eventsRepository: IEventsRepository = EventsRepositoryImpl(application)
+ private var stelaAccountRepository: StelaAccountRepository =
+ StelaAccountRepositoryImpl(application)
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 +138,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,59 +148,177 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView
}
private fun checkForExistingLink(record: Record) {
- if (isBusy.value != null && isBusy.value!!) {
+ if (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!!) {
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
- onShowLinkSettingsBtnClick()
+ _shareLink.value = it
+ _isLinkSharedState.value = _shareLink.value != ""
+ onLinkSettingsBtnClick()
}
}
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()
}
- fun onShowLinkSettingsBtnClick() {
- areLinkSettingsVisible.value = true
+ fun onLinkSettingsBtnClick() {
+ _navigateToPage.value = SharePage.LINK_SETTINGS
}
- fun onHideLinkSettingsBtnClick() {
- areLinkSettingsVisible.value = false
+ fun onBackBtnClick(fromPage: SharePage) {
+ when (fromPage) {
+ SharePage.LINK_SETTINGS -> {
+ _navigateToPage.value = SharePage.SHARE_ITEM
+ }
+
+ SharePage.GENERAL_ACCESS, SharePage.ACCESS_ROLES -> {
+ _navigateToPage.value = SharePage.LINK_SETTINGS
+ }
+
+ else -> {
+ Log.d("ShareManagementViewModel", "onBackBtnClick: ${_navigateToPage.value}")
+ }
+ }
+ }
+
+ fun onGeneralAccessClick() {
+ _navigateToPage.value = SharePage.GENERAL_ACCESS
+ }
+
+ fun onGeneralAccessItemClick(type: AccessType) {
+ _selectedGeneralAccessType.value = type
+ _navigateToPage.value = SharePage.LINK_SETTINGS
+ }
+
+ fun onDefaultAccessRoleClick() {
+ _navigateToPage.value = SharePage.ACCESS_ROLES
+ }
+
+ fun onAccessRoleClick(role: AccessRole) {
+ _selectedAccessRole.value = role
+ _navigateToPage.value = SharePage.LINK_SETTINGS
+ }
+
+ fun onLinkDurationSelected(duration: LinkDuration) {
+ _selectedLinkDuration.value = duration
+ }
+
+ fun revokeLink() {
+ if (_isBusyState.value) {
+ return
+ }
+ _isBusyState.value = true
+ val shareId: String = shareByUrlVO?.shareby_urlId.toString()
+ stelaAccountRepository.deleteShareLink(shareId, object : IResponseListener {
+
+ override fun onSuccess(message: String?) {
+ _isBusyState.value = false
+ _isLinkSharedState.value = false
+ _navigateToPage.value = SharePage.SHARE_ITEM
+ _snackbarMessage.value = appContext.getString(R.string.link_revoked)
+ _snackbarType.value = SnackbarType.SUCCESS
+ }
+
+ override fun onFailed(error: String?) {
+ _isBusyState.value = false
+ error?.let {
+ _snackbarMessage.value = it
+ _snackbarType.value = SnackbarType.ERROR
+ }
+ }
+ })
+ }
+
+ fun onDoneBtnClick() {
+ if (_isBusyState.value) {
+ return
+ }
+ val shareId: String = shareByUrlVO?.shareby_urlId.toString()
+ val accessRestrictions = _selectedGeneralAccessType.value.backendValue
+ val permissionLevel =
+ if (_selectedGeneralAccessType.value == AccessType.ANYONE_CAN_VIEW) AccessRole.VIEWER.name.lowercase() else
+ if (_selectedAccessRole.value == AccessRole.CURATOR) AccessRole.MANAGER.name.lowercase() else
+ _selectedAccessRole.value.name.lowercase()
+ val expirationTimestamp =
+ _selectedLinkDuration.value.expirationDate(LocalDate.now())?.toBackendDateTimeString()
+
+ _isBusyState.value = true
+ val shareLinkVO = ShareLinkVO(
+ id = shareId,
+ permissionsLevel = permissionLevel,
+ accessRestrictions = accessRestrictions,
+ expirationTimestamp = expirationTimestamp
+ )
+ stelaAccountRepository.updateShareLink(shareLinkVO, object : IResponseListener {
+
+ override fun onSuccess(message: String?) {
+ _isBusyState.value = false
+ _navigateToPage.value = SharePage.SHARE_ITEM
+ _snackbarMessage.value = appContext.getString(R.string.link_settings_updated)
+ _snackbarType.value = SnackbarType.SUCCESS
+ }
+
+ override fun onFailed(error: String?) {
+ _isBusyState.value = false
+ error?.let {
+ _snackbarMessage.value = it
+ _snackbarType.value = SnackbarType.ERROR
+ }
+ }
+ })
}
fun onDefaultAccessRoleBtnClick() {
@@ -191,29 +357,26 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView
saveChanges()
}
- fun onRevokeLinkBtnClick() {
- onRevokeLinkRequest.call()
- }
-
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 +384,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 +393,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 +414,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 +434,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 +456,7 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView
}
override fun onFailed(error: String?) {
- isBusy.value = false
+ _isCreatingLinkState.value = false
showSnackbar.value = error
}
})
@@ -318,24 +481,24 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView
defaultAccessRole.value = accessRole
}
+ fun clearPageNavigation() {
+ _navigateToPage.value = null
+ }
+
+ fun clearSnackbar() {
+ _snackbarMessage.value = ""
+ }
+
fun getShares(): List = shares
fun getPendingShares(): List = pendingShares
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
fun getAutoApprove(): MutableLiveData = autoApprove
@@ -358,8 +521,6 @@ class ShareManagementViewModel(application: Application) : ObservableAndroidView
fun getSharedWithLabelTxt(): MutableLiveData = sharedWithLabelTxt
- fun getOnRevokeLinkRequest(): LiveData = onRevokeLinkRequest
-
fun getShowAccessRolesForShare(): LiveData = showAccessRolesForShare
fun getOnShareApproved(): MutableLiveData = onShareApproved
diff --git a/app/src/main/res/drawable/ic_arrow_back_blue.xml b/app/src/main/res/drawable/ic_arrow_back_blue.xml
new file mode 100644
index 00000000..4546796a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_back_blue.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_circle_blue.xml b/app/src/main/res/drawable/ic_circle_blue.xml
new file mode 100644
index 00000000..d4c7287f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_circle_blue.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_contributor_green.xml b/app/src/main/res/drawable/ic_contributor_green.xml
new file mode 100644
index 00000000..88e4bb7f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_contributor_green.xml
@@ -0,0 +1,9 @@
+
+
+
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_curator_green.xml b/app/src/main/res/drawable/ic_curator_green.xml
new file mode 100644
index 00000000..fb0f4c9a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_curator_green.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_editor_green.xml b/app/src/main/res/drawable/ic_editor_green.xml
new file mode 100644
index 00000000..35651d99
--- /dev/null
+++ b/app/src/main/res/drawable/ic_editor_green.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_globe_green.xml b/app/src/main/res/drawable/ic_globe_green.xml
new file mode 100644
index 00000000..a67777ea
--- /dev/null
+++ b/app/src/main/res/drawable/ic_globe_green.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_info_orange.xml b/app/src/main/res/drawable/ic_info_orange.xml
new file mode 100644
index 00000000..35058355
--- /dev/null
+++ b/app/src/main/res/drawable/ic_info_orange.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_link_gradient.xml b/app/src/main/res/drawable/ic_link_gradient.xml
new file mode 100644
index 00000000..a9c07b6a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_link_gradient.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
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_lock_green.xml b/app/src/main/res/drawable/ic_lock_green.xml
new file mode 100644
index 00000000..4ceef841
--- /dev/null
+++ b/app/src/main/res/drawable/ic_lock_green.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_never_blue.xml b/app/src/main/res/drawable/ic_never_blue.xml
new file mode 100644
index 00000000..4a45ef23
--- /dev/null
+++ b/app/src/main/res/drawable/ic_never_blue.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_one_day_blue.xml b/app/src/main/res/drawable/ic_one_day_blue.xml
new file mode 100644
index 00000000..b6647beb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_one_day_blue.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_one_month_blue.xml b/app/src/main/res/drawable/ic_one_month_blue.xml
new file mode 100644
index 00000000..bc486623
--- /dev/null
+++ b/app/src/main/res/drawable/ic_one_month_blue.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_one_year_blue.xml b/app/src/main/res/drawable/ic_one_year_blue.xml
new file mode 100644
index 00000000..9bb31353
--- /dev/null
+++ b/app/src/main/res/drawable/ic_one_year_blue.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_owner_green.xml b/app/src/main/res/drawable/ic_owner_green.xml
new file mode 100644
index 00000000..00ce91f4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_owner_green.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_revoke_link.xml b/app/src/main/res/drawable/ic_revoke_link.xml
new file mode 100644
index 00000000..7e11a8b0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_revoke_link.xml
@@ -0,0 +1,9 @@
+
+
+
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/drawable/ic_viewer_green.xml b/app/src/main/res/drawable/ic_viewer_green.xml
new file mode 100644
index 00000000..5e27b7f6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_viewer_green.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/fragment_access_roles.xml b/app/src/main/res/layout/fragment_access_roles.xml
index 39945600..1b33aa52 100644
--- a/app/src/main/res/layout/fragment_access_roles.xml
+++ b/app/src/main/res/layout/fragment_access_roles.xml
@@ -67,7 +67,7 @@
android:ellipsize="end"
android:fontFamily="@font/open_sans_bold"
android:maxLines="1"
- android:text="@string/access_roles_link_settings_title"
+ android:text="@string/link_settings"
android:textColor="@color/white"
android:textSize="16sp"
android:visibility="@{viewModel.shareByUrlVO != null ? View.VISIBLE : View.GONE}"
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 04372557..00000000
--- a/app/src/main/res/layout/fragment_share_management.xml
+++ /dev/null
@@ -1,700 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/navigation/file_navigation_graph.xml b/app/src/main/res/navigation/file_navigation_graph.xml
index 116be0b2..6b362d26 100644
--- a/app/src/main/res/navigation/file_navigation_graph.xml
+++ b/app/src/main/res/navigation/file_navigation_graph.xml
@@ -14,11 +14,6 @@
app:destination="@id/fileMetadataFragment"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" />
-
-
-
-
-
-
#B8BBC9
#0f163b
#1c2451
- #D0D1DB
#FEF3F2
#FEE4E2
#FFFBFA
@@ -34,9 +33,11 @@
#FF9933
#ff8000
#F17400
- #F79009
+ #FFFAEB
#FEF0C7
#FEDF89
+ #F79009
+ #93370D
#D1FADF
#027A48
#34C759
@@ -45,9 +46,9 @@
#82B15F
#CAE5C9
#F6FEF9
+ #ECFDF3
#A6F4C5
#12B76A
- #ECFDF3
#32D583
#039855
#FFFFFF
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ad28fe2c..b053acc3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -360,7 +360,7 @@
Pending requests
Access Roles
- Link Settings
+ Link Settings
What\'s this
Viewer
Contributor
@@ -825,7 +825,36 @@
Delete
Leave share
Confirm
+ Copy
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.
+ Revoke link
+ Are you sure you want to revoke this share link?
+ revoke this share link
+ Anyone can view
+ Anyone with the link can view and download.
+ Restricted
+ The user must have an account and be logged in to view.
+ General access
+ Default access role
+ Link expiration
+ One day
+ One month
+ One year
+ Never
+ The link will never expire.
+ The link will expire on %1$s.
+ Select access role
+ Has ultimate control: manages roles, settings, and full archive access, including deletion.
+ Trusted member with full control: edit, organize, delete, publish, share, and manage archive membership.
+ Can upload and edit metadata but cannot delete materials, publish them, or share anything.
+ Can upload new materials but cannot edit metadata, delete items, publish content, or share.
+ Can view all materials but cannot edit, delete, upload, publish, or share anything.
+ Link settings has been updated.
+ Link has been revoked.