From 8b97b95633526daf1a3c05e14c5c65441d7b19f2 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 16 Apr 2026 14:56:04 +0200 Subject: [PATCH 01/18] feat(unified-share): ui Signed-off-by: alperozturk96 # Conflicts: # app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java --- .../ui/fragment/share/UnifiedShareView.kt | 459 ++++++++++++++++++ .../layout/file_details_sharing_fragment.xml | 7 + 2 files changed, 466 insertions(+) create mode 100644 app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt new file mode 100644 index 000000000000..12c2993e62f3 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt @@ -0,0 +1,459 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.ui.fragment.share + +import android.content.Context +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.Button +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.FilledTonalIconButton +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItem +import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.unit.dp +import com.owncloud.android.utils.theme.ViewThemeUtils + +enum class UnifiedShareCategory { + Invited, Anyone +} + +enum class UnifiedShareType { + InternalUser, InternalGroup, InternalLink, ExternalLink, ExternalFederated, ExternalMail; + + fun icon(): String { + return when (this) { + InternalUser -> "👤" + InternalGroup -> "👥" + InternalLink -> "🔗" + ExternalLink -> "🌍" + ExternalFederated -> "☁️" + ExternalMail -> "📧" + } + } +} + +data class UnifiedShareDownloadLimit( + val limit: Int, + val downloadCount: Int +) + +sealed class UnifiedSharePermission { + // file drop only for folder + data object FileDrop : UnifiedSharePermission() + + data object CanView : UnifiedSharePermission() + data object CanEdit : UnifiedSharePermission() + + // create only for folder + data class Custom(val read: Boolean, val edit: Boolean, val delete: Boolean, val create: Boolean) : + UnifiedSharePermission() + + fun getText(): String { + return when(this) { + FileDrop -> "FileDrop" + CanView -> "CanView" + CanEdit -> "CanEdit" + is Custom -> "Custom permissions" + } + } +} + +data class UnifiedShares( + val id: Int, + val password: String, + val note: String, + val limit: UnifiedShareDownloadLimit, + val expirationDate: Int, + val permission: UnifiedSharePermission, + val label: String, + val sharedTo: String, + val type: UnifiedShareType, + val category: UnifiedShareCategory, +) + +// TODO: MOVE TO THE ANDROID: COMMON +// TODO: MAKE LAZY COLUMN +// TODO: EXPOSE ACTIONS, IMPLEMENT VIEWMODEL, REPOSITORY TO FETCH ACTUAL SHARE, INJECT NECESSARY PARAMETERS + +@Composable +fun UnifiedShareView() { + var showAddShare by remember { mutableStateOf(false) } + + val mockUnifiedShares = listOf( + UnifiedShares( + id = 1, + password = "", + note = "Design review – please check latest changes", + limit = UnifiedShareDownloadLimit( + limit = 100, + downloadCount = 12 + ), + expirationDate = 0, + permission = UnifiedSharePermission.CanView, + label = "Alice Johnson", + sharedTo = "alice@company.com", + type = UnifiedShareType.InternalUser, + category = UnifiedShareCategory.Invited + ), + + UnifiedShares( + id = 2, + password = "", + note = "", + limit = UnifiedShareDownloadLimit( + limit = 0, + downloadCount = 0 + ), + expirationDate = 0, + permission = UnifiedSharePermission.CanEdit, + label = "Marketing Team", + sharedTo = "marketing", + type = UnifiedShareType.InternalGroup, + category = UnifiedShareCategory.Invited + ), + + UnifiedShares( + id = 3, + password = "1234", + note = "Public link for client review", + limit = UnifiedShareDownloadLimit( + limit = 50, + downloadCount = 5 + ), + expirationDate = 1710000000, + permission = UnifiedSharePermission.Custom( + read = true, + edit = false, + delete = false, + create = false + ), + label = "Public Link", + sharedTo = "https://nextcloud.com/s/abc123", + type = UnifiedShareType.InternalLink, + category = UnifiedShareCategory.Anyone + ), + + UnifiedShares( + id = 4, + password = "", + note = "External partner access", + limit = UnifiedShareDownloadLimit( + limit = 20, + downloadCount = 2 + ), + expirationDate = 0, + permission = UnifiedSharePermission.CanView, + label = "John External", + sharedTo = "john@external.com", + type = UnifiedShareType.ExternalMail, + category = UnifiedShareCategory.Anyone + ), + + UnifiedShares( + id = 5, + password = "", + note = "Federated sharing with partner instance", + limit = UnifiedShareDownloadLimit( + limit = 0, + downloadCount = 0 + ), + expirationDate = 0, + permission = UnifiedSharePermission.FileDrop, + label = "Partner Cloud", + sharedTo = "partner@nextcloud.org", + type = UnifiedShareType.ExternalFederated, + category = UnifiedShareCategory.Anyone + ) + ) + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(2.dp) + ) { + mockUnifiedShares.forEachIndexed { index, share -> + val type = when (index) { + 0 -> { + UnifiedSharesListItemType.Top + } + + mockUnifiedShares.lastIndex -> { + UnifiedSharesListItemType.Bottom + } + + else -> { + UnifiedSharesListItemType.Mid + } + } + + UnifiedSharesListItem(share, type) + } + + FloatingActionButton( + onClick = { showAddShare = true }, + modifier = Modifier + .align(Alignment.End) + .padding(top = 16.dp) + ) { + Icon(Icons.Default.Add, contentDescription = "Add") + } + + if (showAddShare) { + AddShareBottomSheet("Abc.txt",onDismiss = { showAddShare = false }) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun AddShareBottomSheet(filename: String, onDismiss: () -> Unit) { + val sheetState = rememberModalBottomSheetState() + + var category by remember { mutableStateOf(UnifiedShareCategory.Invited) } + var permission by remember { mutableStateOf(UnifiedSharePermission.CanView) } + var categoryDropDownExpanded by remember { mutableStateOf(false) } + var permissionDropDownExpanded by remember { mutableStateOf(false) } + val availablePermissions = remember { + listOf( + UnifiedSharePermission.CanView, + UnifiedSharePermission.CanEdit, + UnifiedSharePermission.FileDrop + ) + } + var searchQuery by remember { mutableStateOf("") } + var note by remember { mutableStateOf("") } + + ModalBottomSheet( + onDismissRequest = onDismiss, + sheetState = sheetState, + containerColor = MaterialTheme.colorScheme.surface, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(bottom = 48.dp) // Extra padding for bottom navigation bars + ) { + Text( + text = "Share $filename", + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onSurface + ) + + Spacer(modifier = Modifier.height(24.dp)) + + ExposedDropdownMenuBox( + expanded = categoryDropDownExpanded, + onExpandedChange = { categoryDropDownExpanded = !categoryDropDownExpanded }, + modifier = Modifier.fillMaxWidth() + ) { + OutlinedTextField( + value = category.name, + onValueChange = {}, + readOnly = true, + label = { Text("Share type") }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = categoryDropDownExpanded) }, + colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), + modifier = Modifier + .menuAnchor() + .fillMaxWidth() + ) + + ExposedDropdownMenu( + expanded = categoryDropDownExpanded, + onDismissRequest = { categoryDropDownExpanded = false } + ) { + UnifiedShareCategory.entries.forEach { selectionOption -> + DropdownMenuItem( + text = { Text(selectionOption.name) }, + onClick = { + category = selectionOption + categoryDropDownExpanded = false + }, + contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding + ) + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + if (category == UnifiedShareCategory.Invited) { + OutlinedTextField( + value = searchQuery, + onValueChange = { searchQuery = it }, + modifier = Modifier.fillMaxWidth(), + label = { Text("Add people") }, + placeholder = { Text("Name, team, email or federated cloud ID") }, + singleLine = true, + shape = RoundedCornerShape(8.dp) + ) + + Spacer(modifier = Modifier.height(16.dp)) + + ExposedDropdownMenuBox( + expanded = permissionDropDownExpanded, + onExpandedChange = { permissionDropDownExpanded = !permissionDropDownExpanded }, + modifier = Modifier.fillMaxWidth() + ) { + OutlinedTextField( + value = permission.getText(), + onValueChange = {}, + readOnly = true, + label = { Text("Participants") }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = permissionDropDownExpanded) }, + colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), + modifier = Modifier.menuAnchor().fillMaxWidth() + ) + + ExposedDropdownMenu( + expanded = permissionDropDownExpanded, + onDismissRequest = { permissionDropDownExpanded = false } + ) { + availablePermissions.forEach { selectionOption -> + DropdownMenuItem( + text = { Text(selectionOption.getText()) }, + onClick = { + // permission = selectionOption + permissionDropDownExpanded = false + } + ) + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + OutlinedTextField( + value = note, + onValueChange = { note = it }, + modifier = Modifier.fillMaxWidth(), + placeholder = { Text("Note to recipients") }, + singleLine = true, + shape = RoundedCornerShape(8.dp) + ) + } else { + Text( + text = "Creating a public link will allow anyone with the link to access this file.", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(vertical = 8.dp) + ) + + Button( + onClick = { /* TODO: Create Public Link Logic */ }, + modifier = Modifier.fillMaxWidth().padding(top = 8.dp) + ) { + Text("Create public link") + } + } + + Row(modifier = Modifier.fillMaxWidth()) { + FilledTonalIconButton(onClick = { + + }) { + Text("Copy link") + } + + Spacer(modifier = Modifier.width(16.dp)) + + FilledTonalIconButton(onClick = { + + }) { + Text("Send") + } + } + } + } +} +enum class UnifiedSharesListItemType { + Top, Mid, Bottom; + + @Composable + fun getShape(): RoundedCornerShape { + return when (this) { + Top -> RoundedCornerShape(12.dp, 12.dp, 4.dp, 4.dp) + Mid -> RoundedCornerShape(4.dp, 4.dp, 4.dp, 4.dp) + Bottom -> RoundedCornerShape(4.dp, 4.dp, 12.dp, 12.dp) + } + } +} + +@Composable +private fun UnifiedSharesListItem(share: UnifiedShares, type: UnifiedSharesListItemType) { + ListItem( + modifier = Modifier + .fillMaxWidth() + .clip(type.getShape()) + .clickable( + onClick = { } + ) + .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)), + headlineContent = { + Text( + text = share.label, + style = MaterialTheme.typography.titleSmall + ) + }, + supportingContent = { + Text( + text = share.sharedTo, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + }, + colors = ListItemDefaults.colors( + containerColor = Color.Transparent + ) + ) +} + +fun ComposeView.setupUnifiedShare(viewThemeUtils: ViewThemeUtils, context: Context) { + setContent { + MaterialTheme( + colorScheme = viewThemeUtils.getColorScheme(context), + content = { + UnifiedShareView() + } + ) + } +} diff --git a/app/src/main/res/layout/file_details_sharing_fragment.xml b/app/src/main/res/layout/file_details_sharing_fragment.xml index 14a056da8f5c..a03e8e26ba39 100644 --- a/app/src/main/res/layout/file_details_sharing_fragment.xml +++ b/app/src/main/res/layout/file_details_sharing_fragment.xml @@ -231,10 +231,17 @@ android:text="@string/show_all" /> + + + From ae9871e8a6499683729abd12cff81a500c93fa3e Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 16 Apr 2026 15:15:26 +0200 Subject: [PATCH 02/18] feat(unified-share): ui Signed-off-by: alperozturk96 --- .../ui/fragment/share/UnifiedShareView.kt | 422 +++++++++++++----- 1 file changed, 301 insertions(+), 121 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt index 12c2993e62f3..9ac4b0fc71df 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt @@ -18,7 +18,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.Button @@ -27,15 +29,14 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.FilledTonalButton -import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -88,9 +89,9 @@ sealed class UnifiedSharePermission { fun getText(): String { return when(this) { - FileDrop -> "FileDrop" - CanView -> "CanView" - CanEdit -> "CanEdit" + FileDrop -> "File drop" + CanView -> "Can view" + CanEdit -> "Can edit" is Custom -> "Custom permissions" } } @@ -246,21 +247,27 @@ fun UnifiedShareView() { @OptIn(ExperimentalMaterial3Api::class) @Composable private fun AddShareBottomSheet(filename: String, onDismiss: () -> Unit) { - val sheetState = rememberModalBottomSheetState() + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val scrollState = rememberScrollState() var category by remember { mutableStateOf(UnifiedShareCategory.Invited) } - var permission by remember { mutableStateOf(UnifiedSharePermission.CanView) } - var categoryDropDownExpanded by remember { mutableStateOf(false) } - var permissionDropDownExpanded by remember { mutableStateOf(false) } + var permission by remember { mutableStateOf(UnifiedSharePermission.CanView) } + var searchQuery by remember { mutableStateOf("") } + var note by remember { mutableStateOf("") } + + var viewFiles by remember { mutableStateOf(false) } + var editFiles by remember { mutableStateOf(false) } + var createFiles by remember { mutableStateOf(false) } + var deleteFiles by remember { mutableStateOf(false) } + val availablePermissions = remember { listOf( UnifiedSharePermission.CanView, UnifiedSharePermission.CanEdit, - UnifiedSharePermission.FileDrop + UnifiedSharePermission.FileDrop, + UnifiedSharePermission.Custom(false, false, false, false) ) } - var searchQuery by remember { mutableStateOf("") } - var note by remember { mutableStateOf("") } ModalBottomSheet( onDismissRequest = onDismiss, @@ -271,140 +278,313 @@ private fun AddShareBottomSheet(filename: String, onDismiss: () -> Unit) { modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp) - .padding(bottom = 48.dp) // Extra padding for bottom navigation bars + .padding(bottom = 32.dp) + .verticalScroll(scrollState), + verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Text( - text = "Share $filename", - style = MaterialTheme.typography.headlineSmall, - color = MaterialTheme.colorScheme.onSurface + ShareBottomSheetHeader(filename) + + ShareCategoryDropdown( + selectedCategory = category, + onCategoryChange = { category = it } ) - Spacer(modifier = Modifier.height(24.dp)) + if (category == UnifiedShareCategory.Invited) { + InvitedShareContent( + searchQuery = searchQuery, + onSearchChange = { searchQuery = it }, + permission = permission, + availablePermissions = availablePermissions, + onPermissionChange = { permission = it }, + ) + + InvitedInlineSettings() - ExposedDropdownMenuBox( - expanded = categoryDropDownExpanded, - onExpandedChange = { categoryDropDownExpanded = !categoryDropDownExpanded }, - modifier = Modifier.fillMaxWidth() - ) { - OutlinedTextField( - value = category.name, - onValueChange = {}, - readOnly = true, - label = { Text("Share type") }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = categoryDropDownExpanded) }, - colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), - modifier = Modifier - .menuAnchor() - .fillMaxWidth() + NoteToRecipients(note = note, onNoteChange = { note = it }) + } else { + AnyoneShareContent( + permission = permission, + availablePermissions = availablePermissions, + onPermissionChange = { permission = it }, ) - ExposedDropdownMenu( - expanded = categoryDropDownExpanded, - onDismissRequest = { categoryDropDownExpanded = false } - ) { - UnifiedShareCategory.entries.forEach { selectionOption -> - DropdownMenuItem( - text = { Text(selectionOption.name) }, - onClick = { - category = selectionOption - categoryDropDownExpanded = false - }, - contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding - ) - } + if (permission is UnifiedSharePermission.Custom) { + SettingsSwitchRow("View files", viewFiles) { viewFiles = it } + SettingsSwitchRow("Edit files", editFiles) { editFiles = it } + SettingsSwitchRow("Create files", createFiles) { createFiles = it } + SettingsSwitchRow("Delete files", deleteFiles) { deleteFiles = it } } + + AnyoneInlineSettings() + + NoteToRecipients(note = note, onNoteChange = { note = it }) } - Spacer(modifier = Modifier.height(16.dp)) + ShareActionButtons( + category = category, + isSendEnabled = searchQuery.isNotBlank(), + onCopyClick = { /* TODO */ }, + onSendClick = { /* TODO */ } + ) + } + } +} - if (category == UnifiedShareCategory.Invited) { - OutlinedTextField( - value = searchQuery, - onValueChange = { searchQuery = it }, - modifier = Modifier.fillMaxWidth(), - label = { Text("Add people") }, - placeholder = { Text("Name, team, email or federated cloud ID") }, - singleLine = true, - shape = RoundedCornerShape(8.dp) - ) +@Composable +private fun ShareBottomSheetHeader(filename: String) { + Text( + text = "Share $filename", + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(bottom = 8.dp) + ) +} - Spacer(modifier = Modifier.height(16.dp)) - - ExposedDropdownMenuBox( - expanded = permissionDropDownExpanded, - onExpandedChange = { permissionDropDownExpanded = !permissionDropDownExpanded }, - modifier = Modifier.fillMaxWidth() - ) { - OutlinedTextField( - value = permission.getText(), - onValueChange = {}, - readOnly = true, - label = { Text("Participants") }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = permissionDropDownExpanded) }, - colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), - modifier = Modifier.menuAnchor().fillMaxWidth() - ) - - ExposedDropdownMenu( - expanded = permissionDropDownExpanded, - onDismissRequest = { permissionDropDownExpanded = false } - ) { - availablePermissions.forEach { selectionOption -> - DropdownMenuItem( - text = { Text(selectionOption.getText()) }, - onClick = { - // permission = selectionOption - permissionDropDownExpanded = false - } - ) - } +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun ShareCategoryDropdown( + selectedCategory: UnifiedShareCategory, + onCategoryChange: (UnifiedShareCategory) -> Unit +) { + var expanded by remember { mutableStateOf(false) } + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = !expanded }, + modifier = Modifier.fillMaxWidth() + ) { + OutlinedTextField( + value = selectedCategory.name, + onValueChange = {}, + readOnly = true, + label = { Text("Share type") }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, + colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), + modifier = Modifier + .menuAnchor() + .fillMaxWidth() + ) + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + UnifiedShareCategory.entries.forEach { option -> + DropdownMenuItem( + text = { Text(option.name) }, + onClick = { + onCategoryChange(option) + expanded = false } - } + ) + } + } + } +} - Spacer(modifier = Modifier.height(16.dp)) +@Composable +private fun InvitedShareContent( + searchQuery: String, + onSearchChange: (String) -> Unit, + permission: UnifiedSharePermission, + availablePermissions: List, + onPermissionChange: (UnifiedSharePermission) -> Unit, + +) { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + OutlinedTextField( + value = searchQuery, + onValueChange = onSearchChange, + modifier = Modifier.fillMaxWidth(), + label = { Text("Add people") }, + placeholder = { Text("Name, team, email or federated ID") }, + singleLine = true, + shape = RoundedCornerShape(8.dp) + ) - OutlinedTextField( - value = note, - onValueChange = { note = it }, - modifier = Modifier.fillMaxWidth(), - placeholder = { Text("Note to recipients") }, - singleLine = true, - shape = RoundedCornerShape(8.dp) - ) - } else { - Text( - text = "Creating a public link will allow anyone with the link to access this file.", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(vertical = 8.dp) - ) + PermissionDropdown( + label = "Participants", + selectedPermission = permission, + availablePermissions = availablePermissions, + onPermissionChange = onPermissionChange + ) + } +} - Button( - onClick = { /* TODO: Create Public Link Logic */ }, - modifier = Modifier.fillMaxWidth().padding(top = 8.dp) - ) { - Text("Create public link") - } +@Composable +private fun NoteToRecipients( + note: String, + onNoteChange: (String) -> Unit +) { + OutlinedTextField( + value = note, + onValueChange = onNoteChange, + modifier = Modifier.fillMaxWidth(), + placeholder = { Text("Note to recipients") }, + shape = RoundedCornerShape(8.dp) + ) +} + +@Composable +private fun AnyoneShareContent( + permission: UnifiedSharePermission, + availablePermissions: List, + onPermissionChange: (UnifiedSharePermission) -> Unit, +) { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + PermissionDropdown( + label = "Anyone with the link", + selectedPermission = permission, + availablePermissions = availablePermissions, + onPermissionChange = onPermissionChange + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun PermissionDropdown( + label: String, + selectedPermission: UnifiedSharePermission, + availablePermissions: List, + onPermissionChange: (UnifiedSharePermission) -> Unit +) { + var expanded by remember { mutableStateOf(false) } + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = !expanded }, + modifier = Modifier.fillMaxWidth() + ) { + OutlinedTextField( + value = selectedPermission.getText(), + onValueChange = {}, + readOnly = true, + label = { Text(label) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, + colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), + modifier = Modifier + .menuAnchor() + .fillMaxWidth() + ) + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + availablePermissions.forEach { option -> + DropdownMenuItem( + text = { Text(option.getText()) }, + onClick = { + onPermissionChange(option) + expanded = false + } + ) } + } + } +} - Row(modifier = Modifier.fillMaxWidth()) { - FilledTonalIconButton(onClick = { - }) { - Text("Copy link") - } +@Composable +private fun InvitedInlineSettings() { + var shareWithOthers by remember { mutableStateOf(false) } + var editFile by remember { mutableStateOf(false) } + var hasExpiration by remember { mutableStateOf(false) } + var hideDownload by remember { mutableStateOf(false) } + + Column { + SettingsSwitchRow("Share with others", shareWithOthers) { shareWithOthers = it } + SettingsSwitchRow("Edit file", editFile) { editFile = it } + SettingsSwitchRow("Expiration date", hasExpiration) { hasExpiration = it } + SettingsSwitchRow("Hide download and sync options", hideDownload) { hideDownload = it } + } +} - Spacer(modifier = Modifier.width(16.dp)) +@Composable +private fun AnyoneInlineSettings() { + var hasPassword by remember { mutableStateOf(false) } + var hasExpiration by remember { mutableStateOf(false) } + var limitDownloads by remember { mutableStateOf(false) } + + var hideDownloads by remember { mutableStateOf(false) } + var videoVerification by remember { mutableStateOf(false) } + var showFilesInGridView by remember { mutableStateOf(false) } + + Column { + OutlinedTextField( + value = "", + onValueChange = {}, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp), + label = { Text("Label") }, + placeholder = { Text("Optional name for this link") }, + singleLine = true + ) - FilledTonalIconButton(onClick = { + SettingsSwitchRow("Expiration date", hasExpiration) { hasExpiration = it } + SettingsSwitchRow("Password", hasPassword) { hasPassword = it } + SettingsSwitchRow("Limit downloads", limitDownloads) { limitDownloads = it } - }) { - Text("Send") - } + SettingsSwitchRow("Hide downloads", hideDownloads) { hideDownloads = it } + SettingsSwitchRow("Video verification", videoVerification) { videoVerification = it } + SettingsSwitchRow("Show files in grid view", showFilesInGridView) { showFilesInGridView = it } + + } +} + +@Composable +private fun SettingsSwitchRow(label: String, checked: Boolean, onCheckedChange: (Boolean) -> Unit) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(text = label, style = MaterialTheme.typography.bodyLarge) + Switch(checked = checked, onCheckedChange = onCheckedChange) + } +} + +// --- ACTION BUTTONS --- + +@Composable +private fun ShareActionButtons( + category: UnifiedShareCategory, + isSendEnabled: Boolean, + onCopyClick: () -> Unit, + onSendClick: () -> Unit +) { + Row(modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp)) { + if (category == UnifiedShareCategory.Invited) { + FilledTonalButton( + onClick = onCopyClick, + modifier = Modifier.weight(1f) + ) { + Text("Copy link") + } + Spacer(modifier = Modifier.width(16.dp)) + Button( + onClick = onSendClick, + modifier = Modifier.weight(1f), + enabled = isSendEnabled // Disabled if search query is empty + ) { + Text("Send") + } + } else { + // For "Anyone" (Public link), usually just one big action to create/copy + Button( + onClick = onCopyClick, + modifier = Modifier.fillMaxWidth() + ) { + Text("Create public link") } } } } + enum class UnifiedSharesListItemType { Top, Mid, Bottom; From 15103913003a171432ff69a5d7ea84bc3799e5f5 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 17 Apr 2026 10:06:24 +0200 Subject: [PATCH 03/18] add todos Signed-off-by: alperozturk96 --- .../android/ui/fragment/share/UnifiedShareView.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt index 9ac4b0fc71df..01859705454b 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt @@ -244,6 +244,13 @@ fun UnifiedShareView() { } } +// TODO: Instead of showing all options in the bottom sheet collect extra sharing options inside the expadable/collable sub-menu + +// TODO: Use conntected button group for invited and anyone type + +// TODO: Use like inner tags whenever user add a new people to the search and it should look like User 1, Group 1 etc. + + @OptIn(ExperimentalMaterial3Api::class) @Composable private fun AddShareBottomSheet(filename: String, onDismiss: () -> Unit) { @@ -598,6 +605,13 @@ enum class UnifiedSharesListItemType { } } +// TODO: - Show avatar, email group or user in the leading content as a one rounded ICON +// TODO: - Replace supporting content with share permission but without editing option +// TODO: - Add right arrow icon end of the list item to access share detail and change share settings +// TODO: - Add context menu to access copy link, delete, and other actions u have with previous 3 dot menu + +// NOTE: To just create a public link anyone tab + just send DOES SAME THING + @Composable private fun UnifiedSharesListItem(share: UnifiedShares, type: UnifiedSharesListItemType) { ListItem( From 2234739a0bc50debb91cf0cca4f832659b05ed69 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 17 Apr 2026 10:27:21 +0200 Subject: [PATCH 04/18] add todos Signed-off-by: alperozturk96 --- .../owncloud/android/ui/fragment/share/UnifiedShareView.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt index 01859705454b..870ec507fde8 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt @@ -607,8 +607,9 @@ enum class UnifiedSharesListItemType { // TODO: - Show avatar, email group or user in the leading content as a one rounded ICON // TODO: - Replace supporting content with share permission but without editing option -// TODO: - Add right arrow icon end of the list item to access share detail and change share settings -// TODO: - Add context menu to access copy link, delete, and other actions u have with previous 3 dot menu +// TODO: - Add more icon end of the list item to access options we have before, delete, send email ... +// TODO: - Add context menu just does same thing like more icon +// TODO: - When user taps the list item it should show share detail bottom sheet // NOTE: To just create a public link anyone tab + just send DOES SAME THING From f6147308750196576e15a5886ae2e52036368606 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 17 Apr 2026 10:29:05 +0200 Subject: [PATCH 05/18] add todos Signed-off-by: alperozturk96 --- .../com/owncloud/android/ui/fragment/share/UnifiedShareView.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt index 870ec507fde8..0dd36f154728 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt @@ -250,6 +250,7 @@ fun UnifiedShareView() { // TODO: Use like inner tags whenever user add a new people to the search and it should look like User 1, Group 1 etc. +// TODO: Replace FAB with person icon @OptIn(ExperimentalMaterial3Api::class) @Composable From 1a0b71b959e204bd2eaafea55c5e263538e8d9a5 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 17 Apr 2026 10:47:41 +0200 Subject: [PATCH 06/18] wip Signed-off-by: alperozturk96 # Conflicts: # gradle/libs.versions.toml --- app/build.gradle.kts | 1 + .../ui/fragment/share/UnifiedShareView.kt | 117 +++++++++++------- 2 files changed, 75 insertions(+), 43 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4764a83ea074..220f8f8b2b0d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -352,6 +352,7 @@ dependencies { implementation(libs.compose.activity) implementation(libs.compose.ui.tooling.preview) implementation(libs.foundation) + implementation(libs.material3) debugImplementation(libs.compose.ui.tooling) // endregion diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt index 0dd36f154728..11d346569ae7 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt @@ -8,6 +8,7 @@ package com.owncloud.android.ui.fragment.share import android.content.Context +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -23,6 +24,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.material.icons.filled.Person import androidx.compose.material3.Button import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api @@ -36,6 +40,9 @@ import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.SegmentedButton +import androidx.compose.material3.SegmentedButtonDefaults +import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState @@ -235,7 +242,7 @@ fun UnifiedShareView() { .align(Alignment.End) .padding(top = 16.dp) ) { - Icon(Icons.Default.Add, contentDescription = "Add") + Icon(Icons.Default.Person, contentDescription = "Add") } if (showAddShare) { @@ -244,14 +251,8 @@ fun UnifiedShareView() { } } -// TODO: Instead of showing all options in the bottom sheet collect extra sharing options inside the expadable/collable sub-menu - -// TODO: Use conntected button group for invited and anyone type - // TODO: Use like inner tags whenever user add a new people to the search and it should look like User 1, Group 1 etc. -// TODO: Replace FAB with person icon - @OptIn(ExperimentalMaterial3Api::class) @Composable private fun AddShareBottomSheet(filename: String, onDismiss: () -> Unit) { @@ -263,6 +264,10 @@ private fun AddShareBottomSheet(filename: String, onDismiss: () -> Unit) { var searchQuery by remember { mutableStateOf("") } var note by remember { mutableStateOf("") } + // Toggle states for collapse/expand + var showInvitedSettings by remember { mutableStateOf(false) } + var showAnyoneSettings by remember { mutableStateOf(false) } + var viewFiles by remember { mutableStateOf(false) } var editFiles by remember { mutableStateOf(false) } var createFiles by remember { mutableStateOf(false) } @@ -292,7 +297,7 @@ private fun AddShareBottomSheet(filename: String, onDismiss: () -> Unit) { ) { ShareBottomSheetHeader(filename) - ShareCategoryDropdown( + ShareCategoryButtonGroup( selectedCategory = category, onCategoryChange = { category = it } ) @@ -306,9 +311,12 @@ private fun AddShareBottomSheet(filename: String, onDismiss: () -> Unit) { onPermissionChange = { permission = it }, ) - InvitedInlineSettings() - - NoteToRecipients(note = note, onNoteChange = { note = it }) + CollapsibleSettingsSection( + isExpanded = showInvitedSettings, + onToggle = { showInvitedSettings = !showInvitedSettings } + ) { + InvitedInlineSettings() + } } else { AnyoneShareContent( permission = permission, @@ -323,11 +331,17 @@ private fun AddShareBottomSheet(filename: String, onDismiss: () -> Unit) { SettingsSwitchRow("Delete files", deleteFiles) { deleteFiles = it } } - AnyoneInlineSettings() - - NoteToRecipients(note = note, onNoteChange = { note = it }) + CollapsibleSettingsSection( + isExpanded = showAnyoneSettings, + onToggle = { showAnyoneSettings = !showAnyoneSettings } + ) { + AnyoneInlineSettings() + } } + NoteToRecipients(note = note, onNoteChange = { note = it }) + + ShareActionButtons( category = category, isSendEnabled = searchQuery.isNotBlank(), @@ -338,6 +352,41 @@ private fun AddShareBottomSheet(filename: String, onDismiss: () -> Unit) { } } +@Composable +private fun CollapsibleSettingsSection( + isExpanded: Boolean, + onToggle: () -> Unit, + content: @Composable () -> Unit +) { + Column(modifier = Modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onToggle() } + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = "Settings", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary + ) + Icon( + imageVector = if (isExpanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } + + AnimatedVisibility(visible = isExpanded) { + Column { + content() + } + } + } +} + @Composable private fun ShareBottomSheetHeader(filename: String) { Text( @@ -350,40 +399,23 @@ private fun ShareBottomSheetHeader(filename: String) { @OptIn(ExperimentalMaterial3Api::class) @Composable -private fun ShareCategoryDropdown( +private fun ShareCategoryButtonGroup( selectedCategory: UnifiedShareCategory, onCategoryChange: (UnifiedShareCategory) -> Unit ) { - var expanded by remember { mutableStateOf(false) } - - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { expanded = !expanded }, + SingleChoiceSegmentedButtonRow( modifier = Modifier.fillMaxWidth() ) { - OutlinedTextField( - value = selectedCategory.name, - onValueChange = {}, - readOnly = true, - label = { Text("Share type") }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, - colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), - modifier = Modifier - .menuAnchor() - .fillMaxWidth() - ) - ExposedDropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - UnifiedShareCategory.entries.forEach { option -> - DropdownMenuItem( - text = { Text(option.name) }, - onClick = { - onCategoryChange(option) - expanded = false - } + UnifiedShareCategory.entries.forEachIndexed { index, option -> + SegmentedButton( + selected = selectedCategory == option, + onClick = { onCategoryChange(option) }, + shape = SegmentedButtonDefaults.itemShape( + index = index, + count = UnifiedShareCategory.entries.size ) + ) { + Text(option.name) } } } @@ -491,7 +523,6 @@ private fun PermissionDropdown( } } - @Composable private fun InvitedInlineSettings() { var shareWithOthers by remember { mutableStateOf(false) } From 40af75c8c92051d3f4c20f4b059d6151585c443a Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 17 Apr 2026 10:48:52 +0200 Subject: [PATCH 07/18] wip Signed-off-by: alperozturk96 --- .../android/ui/fragment/share/UnifiedShareView.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt index 11d346569ae7..a394cd7eba7b 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt @@ -26,6 +26,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Person import androidx.compose.material3.Button import androidx.compose.material3.DropdownMenuItem @@ -638,13 +639,11 @@ enum class UnifiedSharesListItemType { } // TODO: - Show avatar, email group or user in the leading content as a one rounded ICON -// TODO: - Replace supporting content with share permission but without editing option // TODO: - Add more icon end of the list item to access options we have before, delete, send email ... // TODO: - Add context menu just does same thing like more icon // TODO: - When user taps the list item it should show share detail bottom sheet // NOTE: To just create a public link anyone tab + just send DOES SAME THING - @Composable private fun UnifiedSharesListItem(share: UnifiedShares, type: UnifiedSharesListItemType) { ListItem( @@ -663,11 +662,14 @@ private fun UnifiedSharesListItem(share: UnifiedShares, type: UnifiedSharesListI }, supportingContent = { Text( - text = share.sharedTo, + text = share.permission.getText(), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) }, + trailingContent = { + Icon(Icons.Default.MoreVert, contentDescription = "More") + }, colors = ListItemDefaults.colors( containerColor = Color.Transparent ) From 6ac8816a99f69e4ab5fb5c8495b72898465a265c Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 17 Apr 2026 10:54:06 +0200 Subject: [PATCH 08/18] wip Signed-off-by: alperozturk96 --- .../ui/fragment/share/UnifiedShareView.kt | 78 ++++++++++++++++--- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt index a394cd7eba7b..9a6584ef4210 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt @@ -11,15 +11,19 @@ import android.content.Context import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement +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.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.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons @@ -29,13 +33,16 @@ import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Person import androidx.compose.material3.Button +import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme @@ -56,7 +63,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.unit.dp import com.owncloud.android.utils.theme.ViewThemeUtils @@ -586,8 +595,6 @@ private fun SettingsSwitchRow(label: String, checked: Boolean, onCheckedChange: } } -// --- ACTION BUTTONS --- - @Composable private fun ShareActionButtons( category: UnifiedShareCategory, @@ -638,22 +645,36 @@ enum class UnifiedSharesListItemType { } } -// TODO: - Show avatar, email group or user in the leading content as a one rounded ICON -// TODO: - Add more icon end of the list item to access options we have before, delete, send email ... -// TODO: - Add context menu just does same thing like more icon -// TODO: - When user taps the list item it should show share detail bottom sheet - // NOTE: To just create a public link anyone tab + just send DOES SAME THING @Composable private fun UnifiedSharesListItem(share: UnifiedShares, type: UnifiedSharesListItemType) { + var showContextMenu by remember { mutableStateOf(false) } + var showDetailSheet by remember { mutableStateOf(false) } + val haptics = LocalHapticFeedback.current + ListItem( modifier = Modifier .fillMaxWidth() .clip(type.getShape()) - .clickable( - onClick = { } + .combinedClickable( + onClick = { showDetailSheet = true }, + onLongClick = { + haptics.performHapticFeedback(HapticFeedbackType.LongPress) + showContextMenu = true + }, ) .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)), + leadingContent = { + Box( + modifier = Modifier + .size(40.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primaryContainer), + contentAlignment = Alignment.Center + ) { + Text(text = share.type.icon()) + } + }, headlineContent = { Text( text = share.label, @@ -668,12 +689,49 @@ private fun UnifiedSharesListItem(share: UnifiedShares, type: UnifiedSharesListI ) }, trailingContent = { - Icon(Icons.Default.MoreVert, contentDescription = "More") + Box { + IconButton(onClick = { showContextMenu = true }) { + Icon(Icons.Default.MoreVert, contentDescription = "More options") + } + + DropdownMenu( + expanded = showContextMenu, + onDismissRequest = { showContextMenu = false } + ) { + DropdownMenuItem( + text = { Text("Edit") }, + onClick = { + showContextMenu = false + showDetailSheet = true + } + ) + + DropdownMenuItem( + text = { Text("Send email") }, + onClick = { showContextMenu = false } + ) + + HorizontalDivider() + + DropdownMenuItem( + text = { Text("Delete", color = MaterialTheme.colorScheme.error) }, + onClick = { showContextMenu = false } + ) + } + } }, colors = ListItemDefaults.colors( containerColor = Color.Transparent ) ) + + // TODO: USE EXISTING SHARE DETAILS + if (showDetailSheet) { + AddShareBottomSheet( + filename = share.label, + onDismiss = { showDetailSheet = false } + ) + } } fun ComposeView.setupUnifiedShare(viewThemeUtils: ViewThemeUtils, context: Context) { From c4ca0b6813bef1e506e4ccbd1f0a7a1a351397b2 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 17 Apr 2026 10:54:22 +0200 Subject: [PATCH 09/18] wip Signed-off-by: alperozturk96 --- .../com/owncloud/android/ui/fragment/share/UnifiedShareView.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt index 9a6584ef4210..0b14bea5f943 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt @@ -27,7 +27,6 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.icons.filled.MoreVert From f4e8f5ca4ee47d58cee3b655ae6a9c3edd0ebbda Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 5 May 2026 13:38:07 +0200 Subject: [PATCH 10/18] wip Signed-off-by: alperozturk96 --- .../ui/activity/FileDisplayActivity.kt | 22 +++++++++++++++++ .../fragment/FileDetailSharingFragment.java | 24 +++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 6cf56f6c862f..160ab78b0393 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -301,6 +301,28 @@ class FileDisplayActivity : setupDrawer(menuItemId) } + @Suppress("DEPRECATION") + private fun logOcsCredentials() { + + lifecycleScope.launch(Dispatchers.IO) { + val user = accountManager.user + val serverUrl = user.server.uri.toString() + val accountName = user.accountName + + try { + val client = clientFactory.create(user) + val username = client.userIdPlain + val authToken = client.credentials.authToken + Log_OC.d(TAG, "OCS credentials — serverUrl=$serverUrl") + Log_OC.d(TAG, "OCS credentials — accountName=$accountName username=$username authToken=$authToken") + + } catch (e: CreationException) { + Log_OC.e(TAG, "OCS credentials — serverUrl=$serverUrl accountName=$accountName (client creation failed)", e) + } + } + + } + /** * Determines which navigation drawer item should be selected. * diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 424f54041076..05bc87e26239 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -69,6 +69,7 @@ import com.owncloud.android.ui.dialog.SharePasswordDialogFragment; import com.owncloud.android.ui.fragment.share.RemoteShareRepository; import com.owncloud.android.ui.fragment.share.ShareRepository; +import com.owncloud.android.ui.fragment.share.UnifiedShareViewKt; import com.owncloud.android.ui.fragment.util.FileDetailSharingFragmentHelper; import com.owncloud.android.ui.helpers.FileOperationsHelper; import com.owncloud.android.utils.ClipboardUtil; @@ -208,11 +209,30 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat binding.pickContactEmailBtn.setOnClickListener(v -> checkContactPermission()); // start loading process - fetchSharees(); - + // TODO: REPLACE FAKE CONDITION + if (user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_34) || 2 < 4) { + showUnifiedShare(); + } else { + fetchSharees(); + } setupView(); } + private void showUnifiedShare() { + if (binding == null) { + return; + } + + binding.shareContainer.setVisibility(View.GONE); + binding.unifiedShare.setVisibility(View.VISIBLE); + + final LinearLayout shimmerLayout = binding.shimmerLayout.getRoot(); + shimmerLayout.clearAnimation(); + shimmerLayout.setVisibility(View.GONE); + + UnifiedShareViewKt.setupUnifiedShare(binding.unifiedShare, viewThemeUtils, requireContext()); + } + private void fetchSharees() { final var activity = fileActivity; if (activity == null) { From 828a7365df99fc6d2e999c0c158be6bff4a896c2 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 5 May 2026 16:00:59 +0200 Subject: [PATCH 11/18] wip Signed-off-by: alperozturk96 --- .../extensions/OwnCloudClientExtensions.kt | 4 + .../ui/activity/FileDisplayActivity.kt | 2 + .../fragment/FileDetailSharingFragment.java | 20 +- .../ui/fragment/share/UnifiedShareView.kt | 745 ------------------ gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 24 + 6 files changed, 49 insertions(+), 748 deletions(-) delete mode 100644 app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OwnCloudClientExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OwnCloudClientExtensions.kt index 06e4ff1a9b11..8c10d4aecac2 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/OwnCloudClientExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/OwnCloudClientExtensions.kt @@ -9,6 +9,7 @@ package com.nextcloud.utils.extensions import android.content.Context +import com.nextcloud.android.common.ui.network.api.ApiCredentials import com.nextcloud.common.NextcloudClient import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClientFactory @@ -27,3 +28,6 @@ fun OwnCloudClient.getPreviewEndpoint(remoteId: String, x: Int, y: Int): String remoteId + "&x=" + (x / 2) + "&y=" + (y / 2) + "&a=1&mode=cover&forceIcon=0" + +fun OwnCloudClient.toApiCredentials(baseURL: String): ApiCredentials = + ApiCredentials(baseURL, userIdPlain, credentials.authToken) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 160ab78b0393..2b77b8bab338 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -299,6 +299,7 @@ class FileDisplayActivity : startMetadataSyncForRoot() handleBackPress() setupDrawer(menuItemId) + logOcsCredentials() } @Suppress("DEPRECATION") @@ -316,6 +317,7 @@ class FileDisplayActivity : Log_OC.d(TAG, "OCS credentials — serverUrl=$serverUrl") Log_OC.d(TAG, "OCS credentials — accountName=$accountName username=$username authToken=$authToken") + } catch (e: CreationException) { Log_OC.e(TAG, "OCS credentials — serverUrl=$serverUrl accountName=$accountName (client creation failed)", e) } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 05bc87e26239..a224e80ba1a1 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -34,6 +34,7 @@ import android.view.animation.AnimationUtils; import android.widget.LinearLayout; +import com.nextcloud.android.common.ui.share.ShareViewKt; import com.nextcloud.android.common.ui.theme.utils.ColorRole; import com.nextcloud.client.account.User; import com.nextcloud.client.account.UserAccountManager; @@ -43,6 +44,7 @@ import com.nextcloud.client.utils.IntentUtil; import com.nextcloud.utils.extensions.BundleExtensionsKt; import com.nextcloud.utils.extensions.OCShareExtensionsKt; +import com.nextcloud.utils.extensions.OwnCloudClientExtensionsKt; import com.nextcloud.utils.extensions.ViewExtensionsKt; import com.nextcloud.utils.mdm.MDMConfig; import com.owncloud.android.R; @@ -69,7 +71,6 @@ import com.owncloud.android.ui.dialog.SharePasswordDialogFragment; import com.owncloud.android.ui.fragment.share.RemoteShareRepository; import com.owncloud.android.ui.fragment.share.ShareRepository; -import com.owncloud.android.ui.fragment.share.UnifiedShareViewKt; import com.owncloud.android.ui.fragment.util.FileDetailSharingFragmentHelper; import com.owncloud.android.ui.helpers.FileOperationsHelper; import com.owncloud.android.utils.ClipboardUtil; @@ -230,7 +231,22 @@ private void showUnifiedShare() { shimmerLayout.clearAnimation(); shimmerLayout.setVisibility(View.GONE); - UnifiedShareViewKt.setupUnifiedShare(binding.unifiedShare, viewThemeUtils, requireContext()); + new Thread(() -> {{ + try { + final var baseURL = user.getServer().getUri().toString(); + final var client = clientFactory.create(user); + final var apiCredentials = OwnCloudClientExtensionsKt.toApiCredentials(client, baseURL); + final var activity = getActivity(); + if (activity != null) { + activity.runOnUiThread(() -> ShareViewKt.setupUnifiedShare(binding.unifiedShare, + viewThemeUtils.files.getColorScheme(requireContext()), + apiCredentials)); + + } + } catch (ClientFactory.CreationException e) { + Log_OC.e(TAG, "client creation failed"); + } + }}).start(); } private void fetchSharees() { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt deleted file mode 100644 index 0b14bea5f943..000000000000 --- a/app/src/main/java/com/owncloud/android/ui/fragment/share/UnifiedShareView.kt +++ /dev/null @@ -1,745 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2026 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -package com.owncloud.android.ui.fragment.share - -import android.content.Context -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Arrangement -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.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.rememberScrollState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.KeyboardArrowDown -import androidx.compose.material.icons.filled.KeyboardArrowUp -import androidx.compose.material.icons.filled.MoreVert -import androidx.compose.material.icons.filled.Person -import androidx.compose.material3.Button -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults -import androidx.compose.material3.FilledTonalButton -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.ListItem -import androidx.compose.material3.ListItemDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.SegmentedButton -import androidx.compose.material3.SegmentedButtonDefaults -import androidx.compose.material3.SingleChoiceSegmentedButtonRow -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.material3.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.hapticfeedback.HapticFeedbackType -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.LocalHapticFeedback -import androidx.compose.ui.unit.dp -import com.owncloud.android.utils.theme.ViewThemeUtils - -enum class UnifiedShareCategory { - Invited, Anyone -} - -enum class UnifiedShareType { - InternalUser, InternalGroup, InternalLink, ExternalLink, ExternalFederated, ExternalMail; - - fun icon(): String { - return when (this) { - InternalUser -> "👤" - InternalGroup -> "👥" - InternalLink -> "🔗" - ExternalLink -> "🌍" - ExternalFederated -> "☁️" - ExternalMail -> "📧" - } - } -} - -data class UnifiedShareDownloadLimit( - val limit: Int, - val downloadCount: Int -) - -sealed class UnifiedSharePermission { - // file drop only for folder - data object FileDrop : UnifiedSharePermission() - - data object CanView : UnifiedSharePermission() - data object CanEdit : UnifiedSharePermission() - - // create only for folder - data class Custom(val read: Boolean, val edit: Boolean, val delete: Boolean, val create: Boolean) : - UnifiedSharePermission() - - fun getText(): String { - return when(this) { - FileDrop -> "File drop" - CanView -> "Can view" - CanEdit -> "Can edit" - is Custom -> "Custom permissions" - } - } -} - -data class UnifiedShares( - val id: Int, - val password: String, - val note: String, - val limit: UnifiedShareDownloadLimit, - val expirationDate: Int, - val permission: UnifiedSharePermission, - val label: String, - val sharedTo: String, - val type: UnifiedShareType, - val category: UnifiedShareCategory, -) - -// TODO: MOVE TO THE ANDROID: COMMON -// TODO: MAKE LAZY COLUMN -// TODO: EXPOSE ACTIONS, IMPLEMENT VIEWMODEL, REPOSITORY TO FETCH ACTUAL SHARE, INJECT NECESSARY PARAMETERS - -@Composable -fun UnifiedShareView() { - var showAddShare by remember { mutableStateOf(false) } - - val mockUnifiedShares = listOf( - UnifiedShares( - id = 1, - password = "", - note = "Design review – please check latest changes", - limit = UnifiedShareDownloadLimit( - limit = 100, - downloadCount = 12 - ), - expirationDate = 0, - permission = UnifiedSharePermission.CanView, - label = "Alice Johnson", - sharedTo = "alice@company.com", - type = UnifiedShareType.InternalUser, - category = UnifiedShareCategory.Invited - ), - - UnifiedShares( - id = 2, - password = "", - note = "", - limit = UnifiedShareDownloadLimit( - limit = 0, - downloadCount = 0 - ), - expirationDate = 0, - permission = UnifiedSharePermission.CanEdit, - label = "Marketing Team", - sharedTo = "marketing", - type = UnifiedShareType.InternalGroup, - category = UnifiedShareCategory.Invited - ), - - UnifiedShares( - id = 3, - password = "1234", - note = "Public link for client review", - limit = UnifiedShareDownloadLimit( - limit = 50, - downloadCount = 5 - ), - expirationDate = 1710000000, - permission = UnifiedSharePermission.Custom( - read = true, - edit = false, - delete = false, - create = false - ), - label = "Public Link", - sharedTo = "https://nextcloud.com/s/abc123", - type = UnifiedShareType.InternalLink, - category = UnifiedShareCategory.Anyone - ), - - UnifiedShares( - id = 4, - password = "", - note = "External partner access", - limit = UnifiedShareDownloadLimit( - limit = 20, - downloadCount = 2 - ), - expirationDate = 0, - permission = UnifiedSharePermission.CanView, - label = "John External", - sharedTo = "john@external.com", - type = UnifiedShareType.ExternalMail, - category = UnifiedShareCategory.Anyone - ), - - UnifiedShares( - id = 5, - password = "", - note = "Federated sharing with partner instance", - limit = UnifiedShareDownloadLimit( - limit = 0, - downloadCount = 0 - ), - expirationDate = 0, - permission = UnifiedSharePermission.FileDrop, - label = "Partner Cloud", - sharedTo = "partner@nextcloud.org", - type = UnifiedShareType.ExternalFederated, - category = UnifiedShareCategory.Anyone - ) - ) - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(2.dp) - ) { - mockUnifiedShares.forEachIndexed { index, share -> - val type = when (index) { - 0 -> { - UnifiedSharesListItemType.Top - } - - mockUnifiedShares.lastIndex -> { - UnifiedSharesListItemType.Bottom - } - - else -> { - UnifiedSharesListItemType.Mid - } - } - - UnifiedSharesListItem(share, type) - } - - FloatingActionButton( - onClick = { showAddShare = true }, - modifier = Modifier - .align(Alignment.End) - .padding(top = 16.dp) - ) { - Icon(Icons.Default.Person, contentDescription = "Add") - } - - if (showAddShare) { - AddShareBottomSheet("Abc.txt",onDismiss = { showAddShare = false }) - } - } -} - -// TODO: Use like inner tags whenever user add a new people to the search and it should look like User 1, Group 1 etc. - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun AddShareBottomSheet(filename: String, onDismiss: () -> Unit) { - val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) - val scrollState = rememberScrollState() - - var category by remember { mutableStateOf(UnifiedShareCategory.Invited) } - var permission by remember { mutableStateOf(UnifiedSharePermission.CanView) } - var searchQuery by remember { mutableStateOf("") } - var note by remember { mutableStateOf("") } - - // Toggle states for collapse/expand - var showInvitedSettings by remember { mutableStateOf(false) } - var showAnyoneSettings by remember { mutableStateOf(false) } - - var viewFiles by remember { mutableStateOf(false) } - var editFiles by remember { mutableStateOf(false) } - var createFiles by remember { mutableStateOf(false) } - var deleteFiles by remember { mutableStateOf(false) } - - val availablePermissions = remember { - listOf( - UnifiedSharePermission.CanView, - UnifiedSharePermission.CanEdit, - UnifiedSharePermission.FileDrop, - UnifiedSharePermission.Custom(false, false, false, false) - ) - } - - ModalBottomSheet( - onDismissRequest = onDismiss, - sheetState = sheetState, - containerColor = MaterialTheme.colorScheme.surface, - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .padding(bottom = 32.dp) - .verticalScroll(scrollState), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - ShareBottomSheetHeader(filename) - - ShareCategoryButtonGroup( - selectedCategory = category, - onCategoryChange = { category = it } - ) - - if (category == UnifiedShareCategory.Invited) { - InvitedShareContent( - searchQuery = searchQuery, - onSearchChange = { searchQuery = it }, - permission = permission, - availablePermissions = availablePermissions, - onPermissionChange = { permission = it }, - ) - - CollapsibleSettingsSection( - isExpanded = showInvitedSettings, - onToggle = { showInvitedSettings = !showInvitedSettings } - ) { - InvitedInlineSettings() - } - } else { - AnyoneShareContent( - permission = permission, - availablePermissions = availablePermissions, - onPermissionChange = { permission = it }, - ) - - if (permission is UnifiedSharePermission.Custom) { - SettingsSwitchRow("View files", viewFiles) { viewFiles = it } - SettingsSwitchRow("Edit files", editFiles) { editFiles = it } - SettingsSwitchRow("Create files", createFiles) { createFiles = it } - SettingsSwitchRow("Delete files", deleteFiles) { deleteFiles = it } - } - - CollapsibleSettingsSection( - isExpanded = showAnyoneSettings, - onToggle = { showAnyoneSettings = !showAnyoneSettings } - ) { - AnyoneInlineSettings() - } - } - - NoteToRecipients(note = note, onNoteChange = { note = it }) - - - ShareActionButtons( - category = category, - isSendEnabled = searchQuery.isNotBlank(), - onCopyClick = { /* TODO */ }, - onSendClick = { /* TODO */ } - ) - } - } -} - -@Composable -private fun CollapsibleSettingsSection( - isExpanded: Boolean, - onToggle: () -> Unit, - content: @Composable () -> Unit -) { - Column(modifier = Modifier.fillMaxWidth()) { - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { onToggle() } - .padding(vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = "Settings", - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.primary - ) - Icon( - imageVector = if (isExpanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary - ) - } - - AnimatedVisibility(visible = isExpanded) { - Column { - content() - } - } - } -} - -@Composable -private fun ShareBottomSheetHeader(filename: String) { - Text( - text = "Share $filename", - style = MaterialTheme.typography.headlineSmall, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(bottom = 8.dp) - ) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun ShareCategoryButtonGroup( - selectedCategory: UnifiedShareCategory, - onCategoryChange: (UnifiedShareCategory) -> Unit -) { - SingleChoiceSegmentedButtonRow( - modifier = Modifier.fillMaxWidth() - ) { - UnifiedShareCategory.entries.forEachIndexed { index, option -> - SegmentedButton( - selected = selectedCategory == option, - onClick = { onCategoryChange(option) }, - shape = SegmentedButtonDefaults.itemShape( - index = index, - count = UnifiedShareCategory.entries.size - ) - ) { - Text(option.name) - } - } - } -} - -@Composable -private fun InvitedShareContent( - searchQuery: String, - onSearchChange: (String) -> Unit, - permission: UnifiedSharePermission, - availablePermissions: List, - onPermissionChange: (UnifiedSharePermission) -> Unit, - -) { - Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { - OutlinedTextField( - value = searchQuery, - onValueChange = onSearchChange, - modifier = Modifier.fillMaxWidth(), - label = { Text("Add people") }, - placeholder = { Text("Name, team, email or federated ID") }, - singleLine = true, - shape = RoundedCornerShape(8.dp) - ) - - PermissionDropdown( - label = "Participants", - selectedPermission = permission, - availablePermissions = availablePermissions, - onPermissionChange = onPermissionChange - ) - } -} - -@Composable -private fun NoteToRecipients( - note: String, - onNoteChange: (String) -> Unit -) { - OutlinedTextField( - value = note, - onValueChange = onNoteChange, - modifier = Modifier.fillMaxWidth(), - placeholder = { Text("Note to recipients") }, - shape = RoundedCornerShape(8.dp) - ) -} - -@Composable -private fun AnyoneShareContent( - permission: UnifiedSharePermission, - availablePermissions: List, - onPermissionChange: (UnifiedSharePermission) -> Unit, -) { - Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { - PermissionDropdown( - label = "Anyone with the link", - selectedPermission = permission, - availablePermissions = availablePermissions, - onPermissionChange = onPermissionChange - ) - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun PermissionDropdown( - label: String, - selectedPermission: UnifiedSharePermission, - availablePermissions: List, - onPermissionChange: (UnifiedSharePermission) -> Unit -) { - var expanded by remember { mutableStateOf(false) } - - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { expanded = !expanded }, - modifier = Modifier.fillMaxWidth() - ) { - OutlinedTextField( - value = selectedPermission.getText(), - onValueChange = {}, - readOnly = true, - label = { Text(label) }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, - colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), - modifier = Modifier - .menuAnchor() - .fillMaxWidth() - ) - ExposedDropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - availablePermissions.forEach { option -> - DropdownMenuItem( - text = { Text(option.getText()) }, - onClick = { - onPermissionChange(option) - expanded = false - } - ) - } - } - } -} - -@Composable -private fun InvitedInlineSettings() { - var shareWithOthers by remember { mutableStateOf(false) } - var editFile by remember { mutableStateOf(false) } - var hasExpiration by remember { mutableStateOf(false) } - var hideDownload by remember { mutableStateOf(false) } - - Column { - SettingsSwitchRow("Share with others", shareWithOthers) { shareWithOthers = it } - SettingsSwitchRow("Edit file", editFile) { editFile = it } - SettingsSwitchRow("Expiration date", hasExpiration) { hasExpiration = it } - SettingsSwitchRow("Hide download and sync options", hideDownload) { hideDownload = it } - } -} - -@Composable -private fun AnyoneInlineSettings() { - var hasPassword by remember { mutableStateOf(false) } - var hasExpiration by remember { mutableStateOf(false) } - var limitDownloads by remember { mutableStateOf(false) } - - var hideDownloads by remember { mutableStateOf(false) } - var videoVerification by remember { mutableStateOf(false) } - var showFilesInGridView by remember { mutableStateOf(false) } - - Column { - OutlinedTextField( - value = "", - onValueChange = {}, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 8.dp), - label = { Text("Label") }, - placeholder = { Text("Optional name for this link") }, - singleLine = true - ) - - SettingsSwitchRow("Expiration date", hasExpiration) { hasExpiration = it } - SettingsSwitchRow("Password", hasPassword) { hasPassword = it } - SettingsSwitchRow("Limit downloads", limitDownloads) { limitDownloads = it } - - SettingsSwitchRow("Hide downloads", hideDownloads) { hideDownloads = it } - SettingsSwitchRow("Video verification", videoVerification) { videoVerification = it } - SettingsSwitchRow("Show files in grid view", showFilesInGridView) { showFilesInGridView = it } - - } -} - -@Composable -private fun SettingsSwitchRow(label: String, checked: Boolean, onCheckedChange: (Boolean) -> Unit) { - Row( - modifier = Modifier - .fillMaxWidth() - .height(48.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text(text = label, style = MaterialTheme.typography.bodyLarge) - Switch(checked = checked, onCheckedChange = onCheckedChange) - } -} - -@Composable -private fun ShareActionButtons( - category: UnifiedShareCategory, - isSendEnabled: Boolean, - onCopyClick: () -> Unit, - onSendClick: () -> Unit -) { - Row(modifier = Modifier - .fillMaxWidth() - .padding(top = 16.dp)) { - if (category == UnifiedShareCategory.Invited) { - FilledTonalButton( - onClick = onCopyClick, - modifier = Modifier.weight(1f) - ) { - Text("Copy link") - } - Spacer(modifier = Modifier.width(16.dp)) - Button( - onClick = onSendClick, - modifier = Modifier.weight(1f), - enabled = isSendEnabled // Disabled if search query is empty - ) { - Text("Send") - } - } else { - // For "Anyone" (Public link), usually just one big action to create/copy - Button( - onClick = onCopyClick, - modifier = Modifier.fillMaxWidth() - ) { - Text("Create public link") - } - } - } -} - -enum class UnifiedSharesListItemType { - Top, Mid, Bottom; - - @Composable - fun getShape(): RoundedCornerShape { - return when (this) { - Top -> RoundedCornerShape(12.dp, 12.dp, 4.dp, 4.dp) - Mid -> RoundedCornerShape(4.dp, 4.dp, 4.dp, 4.dp) - Bottom -> RoundedCornerShape(4.dp, 4.dp, 12.dp, 12.dp) - } - } -} - -// NOTE: To just create a public link anyone tab + just send DOES SAME THING -@Composable -private fun UnifiedSharesListItem(share: UnifiedShares, type: UnifiedSharesListItemType) { - var showContextMenu by remember { mutableStateOf(false) } - var showDetailSheet by remember { mutableStateOf(false) } - val haptics = LocalHapticFeedback.current - - ListItem( - modifier = Modifier - .fillMaxWidth() - .clip(type.getShape()) - .combinedClickable( - onClick = { showDetailSheet = true }, - onLongClick = { - haptics.performHapticFeedback(HapticFeedbackType.LongPress) - showContextMenu = true - }, - ) - .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)), - leadingContent = { - Box( - modifier = Modifier - .size(40.dp) - .clip(CircleShape) - .background(MaterialTheme.colorScheme.primaryContainer), - contentAlignment = Alignment.Center - ) { - Text(text = share.type.icon()) - } - }, - headlineContent = { - Text( - text = share.label, - style = MaterialTheme.typography.titleSmall - ) - }, - supportingContent = { - Text( - text = share.permission.getText(), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - }, - trailingContent = { - Box { - IconButton(onClick = { showContextMenu = true }) { - Icon(Icons.Default.MoreVert, contentDescription = "More options") - } - - DropdownMenu( - expanded = showContextMenu, - onDismissRequest = { showContextMenu = false } - ) { - DropdownMenuItem( - text = { Text("Edit") }, - onClick = { - showContextMenu = false - showDetailSheet = true - } - ) - - DropdownMenuItem( - text = { Text("Send email") }, - onClick = { showContextMenu = false } - ) - - HorizontalDivider() - - DropdownMenuItem( - text = { Text("Delete", color = MaterialTheme.colorScheme.error) }, - onClick = { showContextMenu = false } - ) - } - } - }, - colors = ListItemDefaults.colors( - containerColor = Color.Transparent - ) - ) - - // TODO: USE EXISTING SHARE DETAILS - if (showDetailSheet) { - AddShareBottomSheet( - filename = share.label, - onDismiss = { showDetailSheet = false } - ) - } -} - -fun ComposeView.setupUnifiedShare(viewThemeUtils: ViewThemeUtils, context: Context) { - setContent { - MaterialTheme( - colorScheme = viewThemeUtils.getColorScheme(context), - content = { - UnifiedShareView() - } - ) - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5af872bd7ca3..dae234abb3ef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later [versions] -androidCommonLibraryVersion = "0.33.2" +androidCommonLibraryVersion = "e45466a08a" androidGifDrawableVersion = "1.2.31" androidImageCropperVersion = "4.7.0" androidLibraryVersion ="1f476c0ab14fb280172be2aefd70db80e50bb17b" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 9b38429f9e72..922a46d2a555 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -22414,6 +22414,14 @@ + + + + + + + + @@ -22658,6 +22666,14 @@ + + + + + + + + @@ -22898,6 +22914,14 @@ + + + + + + + + From 1a4c81c8c7f798eb3ce77079e6fcb4aff8c1e3cf Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 6 May 2026 14:23:35 +0200 Subject: [PATCH 12/18] wip Signed-off-by: alperozturk96 --- .../extensions/OwnCloudClientExtensions.kt | 9 ++- .../fragment/FileDetailSharingFragment.java | 10 +-- .../layout/file_details_sharing_fragment.xml | 65 +++++++++---------- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 24 +++++++ 5 files changed, 69 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OwnCloudClientExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OwnCloudClientExtensions.kt index 8c10d4aecac2..b5006ee48632 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/OwnCloudClientExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/OwnCloudClientExtensions.kt @@ -9,7 +9,7 @@ package com.nextcloud.utils.extensions import android.content.Context -import com.nextcloud.android.common.ui.network.api.ApiCredentials +import com.nextcloud.android.common.ui.network.auth.ServerCredentials import com.nextcloud.common.NextcloudClient import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClientFactory @@ -29,5 +29,8 @@ fun OwnCloudClient.getPreviewEndpoint(remoteId: String, x: Int, y: Int): String "&x=" + (x / 2) + "&y=" + (y / 2) + "&a=1&mode=cover&forceIcon=0" -fun OwnCloudClient.toApiCredentials(baseURL: String): ApiCredentials = - ApiCredentials(baseURL, userIdPlain, credentials.authToken) +/** + * Used in Android Common + */ +fun OwnCloudClient.toServerCredentials(baseURL: String): ServerCredentials = + ServerCredentials(baseURL, userIdPlain, credentials.authToken) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index a224e80ba1a1..bb5ac1d1b902 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -235,12 +235,14 @@ private void showUnifiedShare() { try { final var baseURL = user.getServer().getUri().toString(); final var client = clientFactory.create(user); - final var apiCredentials = OwnCloudClientExtensionsKt.toApiCredentials(client, baseURL); + final var serverCredentials = OwnCloudClientExtensionsKt.toServerCredentials(client, baseURL); final var activity = getActivity(); if (activity != null) { - activity.runOnUiThread(() -> ShareViewKt.setupUnifiedShare(binding.unifiedShare, - viewThemeUtils.files.getColorScheme(requireContext()), - apiCredentials)); + activity.runOnUiThread(() -> ShareViewKt.setupUnifiedShare( + binding.unifiedShare, + viewThemeUtils.files.getColorScheme(activity), + serverCredentials) + ); } } catch (ClientFactory.CreationException e) { diff --git a/app/src/main/res/layout/file_details_sharing_fragment.xml b/app/src/main/res/layout/file_details_sharing_fragment.xml index a03e8e26ba39..54daff819b9f 100644 --- a/app/src/main/res/layout/file_details_sharing_fragment.xml +++ b/app/src/main/res/layout/file_details_sharing_fragment.xml @@ -5,25 +5,25 @@ ~ SPDX-FileCopyrightText: 2018 Andy Scherzinger ~ SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only --> - + android:layout_below="@id/appbar"> - + android:paddingTop="@dimen/standard_eight_padding"> + tools:visibility="visible"> + app:iconGravity="textStart" /> + android:textAppearance="?android:attr/textAppearanceMedium" + android:visibility="gone" + tools:visibility="visible" /> @@ -209,9 +209,9 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/standard_margin" android:layout_marginEnd="@dimen/standard_margin" + android:text="@string/create_link" app:icon="@drawable/file_link" - app:iconGravity="textStart" - android:text="@string/create_link" /> + app:iconGravity="textStart" /> - - - + - + + - + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dae234abb3ef..2d051976a264 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later [versions] -androidCommonLibraryVersion = "e45466a08a" +androidCommonLibraryVersion = "38fffa4a03" androidGifDrawableVersion = "1.2.31" androidImageCropperVersion = "4.7.0" androidLibraryVersion ="1f476c0ab14fb280172be2aefd70db80e50bb17b" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 922a46d2a555..6ecaeab909ea 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -22350,6 +22350,14 @@ + + + + + + + + @@ -22602,6 +22610,14 @@ + + + + + + + + @@ -22850,6 +22866,14 @@ + + + + + + + + From 20cf1fb7f98279fb50bc444c514dde566412f0e0 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 28 May 2026 15:48:50 +0300 Subject: [PATCH 13/18] wip Signed-off-by: alperozturk96 --- .../android/ui/fragment/FileDetailSharingFragment.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index bb5ac1d1b902..37dfb640732b 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -240,8 +240,9 @@ private void showUnifiedShare() { if (activity != null) { activity.runOnUiThread(() -> ShareViewKt.setupUnifiedShare( binding.unifiedShare, - viewThemeUtils.files.getColorScheme(activity), - serverCredentials) + String.valueOf(file.getFileId()), + serverCredentials, + viewThemeUtils.files.getColorScheme(activity)) ); } From 6f4e1988fe8160692c8d7d5e261c876c8eb65fc3 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 28 May 2026 15:56:06 +0300 Subject: [PATCH 14/18] wip Signed-off-by: alperozturk96 --- .../owncloud/android/ui/fragment/FileDetailSharingFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 37dfb640732b..b372eb913751 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -240,7 +240,7 @@ private void showUnifiedShare() { if (activity != null) { activity.runOnUiThread(() -> ShareViewKt.setupUnifiedShare( binding.unifiedShare, - String.valueOf(file.getFileId()), + String.valueOf(file.getRemoteId()), serverCredentials, viewThemeUtils.files.getColorScheme(activity)) ); From 7fbe36eb1a17d45300b0d809c7a5abb920860ac8 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 29 May 2026 14:45:57 +0300 Subject: [PATCH 15/18] wip Signed-off-by: alperozturk96 --- .../android/ui/fragment/FileDetailSharingFragment.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index b372eb913751..31ec6936a907 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -237,10 +237,13 @@ private void showUnifiedShare() { final var client = clientFactory.create(user); final var serverCredentials = OwnCloudClientExtensionsKt.toServerCredentials(client, baseURL); final var activity = getActivity(); - if (activity != null) { + final var sharingCapabilities = fileDataStorageManager.getCapability(user).getSharingJson(); + + if (activity != null && sharingCapabilities != null) { activity.runOnUiThread(() -> ShareViewKt.setupUnifiedShare( binding.unifiedShare, - String.valueOf(file.getRemoteId()), + sharingCapabilities, + String.valueOf(file.getRemoteId()), serverCredentials, viewThemeUtils.files.getColorScheme(activity)) ); From 3333b687ffc7e692e4478393c85d069f546f4f6a Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 29 May 2026 15:27:32 +0300 Subject: [PATCH 16/18] wip Signed-off-by: alperozturk96 --- .../101.json | 1313 +++++++++++++++++ .../client/database/NextcloudDatabase.kt | 3 +- .../database/entity/CapabilityEntity.kt | 5 +- .../datamodel/FileDataStorageManager.java | 5 + .../com/owncloud/android/db/ProviderMeta.java | 3 +- .../fragment/FileDetailSharingFragment.java | 2 +- 6 files changed, 1327 insertions(+), 4 deletions(-) create mode 100644 app/schemas/com.nextcloud.client.database.NextcloudDatabase/101.json diff --git a/app/schemas/com.nextcloud.client.database.NextcloudDatabase/101.json b/app/schemas/com.nextcloud.client.database.NextcloudDatabase/101.json new file mode 100644 index 000000000000..19a6f82cb315 --- /dev/null +++ b/app/schemas/com.nextcloud.client.database.NextcloudDatabase/101.json @@ -0,0 +1,1313 @@ +{ + "formatVersion": 1, + "database": { + "version": 101, + "identityHash": "3bdbf71fa42d512b25ee335bfc766faf", + "entities": [ + { + "tableName": "arbitrary_data", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `cloud_id` TEXT, `key` TEXT, `value` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "cloudId", + "columnName": "cloud_id", + "affinity": "TEXT" + }, + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT" + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "capabilities", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `assistant` INTEGER, `account` TEXT, `version_mayor` INTEGER, `version_minor` INTEGER, `version_micro` INTEGER, `version_string` TEXT, `version_edition` TEXT, `extended_support` INTEGER, `core_pollinterval` INTEGER, `sharing_api_enabled` INTEGER, `sharing_public_enabled` INTEGER, `sharing_public_password_enforced` INTEGER, `sharing_public_expire_date_enabled` INTEGER, `sharing_public_expire_date_days` INTEGER, `sharing_public_expire_date_enforced` INTEGER, `sharing_public_send_mail` INTEGER, `sharing_public_upload` INTEGER, `sharing_user_send_mail` INTEGER, `sharing_resharing` INTEGER, `sharing_federation_outgoing` INTEGER, `sharing_federation_incoming` INTEGER, `files_bigfilechunking` INTEGER, `files_undelete` INTEGER, `files_versioning` INTEGER, `external_links` INTEGER, `server_name` TEXT, `server_color` TEXT, `server_text_color` TEXT, `server_element_color` TEXT, `server_slogan` TEXT, `server_logo` TEXT, `background_url` TEXT, `end_to_end_encryption` INTEGER, `end_to_end_encryption_keys_exist` INTEGER, `end_to_end_encryption_api_version` TEXT, `activity` INTEGER, `background_default` INTEGER, `background_plain` INTEGER, `richdocument` INTEGER, `richdocument_mimetype_list` TEXT, `richdocument_direct_editing` INTEGER, `richdocument_direct_templates` INTEGER, `richdocument_optional_mimetype_list` TEXT, `sharing_public_ask_for_optional_password` INTEGER, `richdocument_product_name` TEXT, `direct_editing_etag` TEXT, `user_status` INTEGER, `user_status_supports_emoji` INTEGER, `etag` TEXT, `files_locking_version` TEXT, `groupfolders` INTEGER, `drop_account` INTEGER, `security_guard` INTEGER, `forbidden_filename_characters` TEXT, `forbidden_filenames` TEXT, `forbidden_filename_extensions` TEXT, `forbidden_filename_basenames` TEXT, `files_download_limit` INTEGER, `files_download_limit_default` INTEGER, `recommendation` INTEGER, `notes_folder_path` TEXT, `default_permissions` INTEGER, `user_status_supports_busy` INTEGER, `windows_compatible_filenames` INTEGER, `has_valid_subscription` INTEGER, `client_integration_json` TEXT, `sharing_json` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "assistant", + "columnName": "assistant", + "affinity": "INTEGER" + }, + { + "fieldPath": "accountName", + "columnName": "account", + "affinity": "TEXT" + }, + { + "fieldPath": "versionMajor", + "columnName": "version_mayor", + "affinity": "INTEGER" + }, + { + "fieldPath": "versionMinor", + "columnName": "version_minor", + "affinity": "INTEGER" + }, + { + "fieldPath": "versionMicro", + "columnName": "version_micro", + "affinity": "INTEGER" + }, + { + "fieldPath": "versionString", + "columnName": "version_string", + "affinity": "TEXT" + }, + { + "fieldPath": "versionEditor", + "columnName": "version_edition", + "affinity": "TEXT" + }, + { + "fieldPath": "extendedSupport", + "columnName": "extended_support", + "affinity": "INTEGER" + }, + { + "fieldPath": "corePollinterval", + "columnName": "core_pollinterval", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingApiEnabled", + "columnName": "sharing_api_enabled", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicEnabled", + "columnName": "sharing_public_enabled", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicPasswordEnforced", + "columnName": "sharing_public_password_enforced", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicExpireDateEnabled", + "columnName": "sharing_public_expire_date_enabled", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicExpireDateDays", + "columnName": "sharing_public_expire_date_days", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicExpireDateEnforced", + "columnName": "sharing_public_expire_date_enforced", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicSendMail", + "columnName": "sharing_public_send_mail", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicUpload", + "columnName": "sharing_public_upload", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingUserSendMail", + "columnName": "sharing_user_send_mail", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingResharing", + "columnName": "sharing_resharing", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingFederationOutgoing", + "columnName": "sharing_federation_outgoing", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingFederationIncoming", + "columnName": "sharing_federation_incoming", + "affinity": "INTEGER" + }, + { + "fieldPath": "filesBigfilechunking", + "columnName": "files_bigfilechunking", + "affinity": "INTEGER" + }, + { + "fieldPath": "filesUndelete", + "columnName": "files_undelete", + "affinity": "INTEGER" + }, + { + "fieldPath": "filesVersioning", + "columnName": "files_versioning", + "affinity": "INTEGER" + }, + { + "fieldPath": "externalLinks", + "columnName": "external_links", + "affinity": "INTEGER" + }, + { + "fieldPath": "serverName", + "columnName": "server_name", + "affinity": "TEXT" + }, + { + "fieldPath": "serverColor", + "columnName": "server_color", + "affinity": "TEXT" + }, + { + "fieldPath": "serverTextColor", + "columnName": "server_text_color", + "affinity": "TEXT" + }, + { + "fieldPath": "serverElementColor", + "columnName": "server_element_color", + "affinity": "TEXT" + }, + { + "fieldPath": "serverSlogan", + "columnName": "server_slogan", + "affinity": "TEXT" + }, + { + "fieldPath": "serverLogo", + "columnName": "server_logo", + "affinity": "TEXT" + }, + { + "fieldPath": "serverBackgroundUrl", + "columnName": "background_url", + "affinity": "TEXT" + }, + { + "fieldPath": "endToEndEncryption", + "columnName": "end_to_end_encryption", + "affinity": "INTEGER" + }, + { + "fieldPath": "endToEndEncryptionKeysExist", + "columnName": "end_to_end_encryption_keys_exist", + "affinity": "INTEGER" + }, + { + "fieldPath": "endToEndEncryptionApiVersion", + "columnName": "end_to_end_encryption_api_version", + "affinity": "TEXT" + }, + { + "fieldPath": "activity", + "columnName": "activity", + "affinity": "INTEGER" + }, + { + "fieldPath": "serverBackgroundDefault", + "columnName": "background_default", + "affinity": "INTEGER" + }, + { + "fieldPath": "serverBackgroundPlain", + "columnName": "background_plain", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocument", + "columnName": "richdocument", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocumentMimetypeList", + "columnName": "richdocument_mimetype_list", + "affinity": "TEXT" + }, + { + "fieldPath": "richdocumentDirectEditing", + "columnName": "richdocument_direct_editing", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocumentTemplates", + "columnName": "richdocument_direct_templates", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocumentOptionalMimetypeList", + "columnName": "richdocument_optional_mimetype_list", + "affinity": "TEXT" + }, + { + "fieldPath": "sharingPublicAskForOptionalPassword", + "columnName": "sharing_public_ask_for_optional_password", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocumentProductName", + "columnName": "richdocument_product_name", + "affinity": "TEXT" + }, + { + "fieldPath": "directEditingEtag", + "columnName": "direct_editing_etag", + "affinity": "TEXT" + }, + { + "fieldPath": "userStatus", + "columnName": "user_status", + "affinity": "INTEGER" + }, + { + "fieldPath": "userStatusSupportsEmoji", + "columnName": "user_status_supports_emoji", + "affinity": "INTEGER" + }, + { + "fieldPath": "etag", + "columnName": "etag", + "affinity": "TEXT" + }, + { + "fieldPath": "filesLockingVersion", + "columnName": "files_locking_version", + "affinity": "TEXT" + }, + { + "fieldPath": "groupfolders", + "columnName": "groupfolders", + "affinity": "INTEGER" + }, + { + "fieldPath": "dropAccount", + "columnName": "drop_account", + "affinity": "INTEGER" + }, + { + "fieldPath": "securityGuard", + "columnName": "security_guard", + "affinity": "INTEGER" + }, + { + "fieldPath": "forbiddenFileNameCharacters", + "columnName": "forbidden_filename_characters", + "affinity": "TEXT" + }, + { + "fieldPath": "forbiddenFileNames", + "columnName": "forbidden_filenames", + "affinity": "TEXT" + }, + { + "fieldPath": "forbiddenFileNameExtensions", + "columnName": "forbidden_filename_extensions", + "affinity": "TEXT" + }, + { + "fieldPath": "forbiddenFilenameBaseNames", + "columnName": "forbidden_filename_basenames", + "affinity": "TEXT" + }, + { + "fieldPath": "filesDownloadLimit", + "columnName": "files_download_limit", + "affinity": "INTEGER" + }, + { + "fieldPath": "filesDownloadLimitDefault", + "columnName": "files_download_limit_default", + "affinity": "INTEGER" + }, + { + "fieldPath": "recommendation", + "columnName": "recommendation", + "affinity": "INTEGER" + }, + { + "fieldPath": "notesFolderPath", + "columnName": "notes_folder_path", + "affinity": "TEXT" + }, + { + "fieldPath": "defaultPermissions", + "columnName": "default_permissions", + "affinity": "INTEGER" + }, + { + "fieldPath": "userStatusSupportsBusy", + "columnName": "user_status_supports_busy", + "affinity": "INTEGER" + }, + { + "fieldPath": "isWCFEnabled", + "columnName": "windows_compatible_filenames", + "affinity": "INTEGER" + }, + { + "fieldPath": "hasValidSubscription", + "columnName": "has_valid_subscription", + "affinity": "INTEGER" + }, + { + "fieldPath": "clientIntegrationJson", + "columnName": "client_integration_json", + "affinity": "TEXT" + }, + { + "fieldPath": "sharingJson", + "columnName": "sharing_json", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "external_links", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `icon_url` TEXT, `language` TEXT, `type` INTEGER, `name` TEXT, `url` TEXT, `redirect` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT" + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT" + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT" + }, + { + "fieldPath": "redirect", + "columnName": "redirect", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "filelist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `filename` TEXT, `encrypted_filename` TEXT, `path` TEXT, `path_decrypted` TEXT, `parent` INTEGER, `created` INTEGER, `modified` INTEGER, `content_type` TEXT, `content_length` INTEGER, `media_path` TEXT, `file_owner` TEXT, `last_sync_date` INTEGER, `last_sync_date_for_data` INTEGER, `modified_at_last_sync_for_data` INTEGER, `etag` TEXT, `etag_on_server` TEXT, `share_by_link` INTEGER, `permissions` TEXT, `remote_id` TEXT, `local_id` INTEGER NOT NULL DEFAULT -1, `update_thumbnail` INTEGER, `is_downloading` INTEGER, `favorite` INTEGER, `hidden` INTEGER, `is_encrypted` INTEGER, `etag_in_conflict` TEXT, `shared_via_users` INTEGER, `mount_type` INTEGER, `has_preview` INTEGER, `unread_comments_count` INTEGER, `owner_id` TEXT, `owner_display_name` TEXT, `note` TEXT, `sharees` TEXT, `rich_workspace` TEXT, `metadata_size` TEXT, `metadata_live_photo` TEXT, `locked` INTEGER, `lock_type` INTEGER, `lock_owner` TEXT, `lock_owner_display_name` TEXT, `lock_owner_editor` TEXT, `lock_timestamp` INTEGER, `lock_timeout` INTEGER, `lock_token` TEXT, `tags` TEXT, `metadata_gps` TEXT, `e2e_counter` INTEGER, `internal_two_way_sync_timestamp` INTEGER, `internal_two_way_sync_result` TEXT, `uploaded` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "name", + "columnName": "filename", + "affinity": "TEXT" + }, + { + "fieldPath": "encryptedName", + "columnName": "encrypted_filename", + "affinity": "TEXT" + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT" + }, + { + "fieldPath": "pathDecrypted", + "columnName": "path_decrypted", + "affinity": "TEXT" + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "INTEGER" + }, + { + "fieldPath": "creation", + "columnName": "created", + "affinity": "INTEGER" + }, + { + "fieldPath": "modified", + "columnName": "modified", + "affinity": "INTEGER" + }, + { + "fieldPath": "contentType", + "columnName": "content_type", + "affinity": "TEXT" + }, + { + "fieldPath": "contentLength", + "columnName": "content_length", + "affinity": "INTEGER" + }, + { + "fieldPath": "storagePath", + "columnName": "media_path", + "affinity": "TEXT" + }, + { + "fieldPath": "accountOwner", + "columnName": "file_owner", + "affinity": "TEXT" + }, + { + "fieldPath": "lastSyncDate", + "columnName": "last_sync_date", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastSyncDateForData", + "columnName": "last_sync_date_for_data", + "affinity": "INTEGER" + }, + { + "fieldPath": "modifiedAtLastSyncForData", + "columnName": "modified_at_last_sync_for_data", + "affinity": "INTEGER" + }, + { + "fieldPath": "etag", + "columnName": "etag", + "affinity": "TEXT" + }, + { + "fieldPath": "etagOnServer", + "columnName": "etag_on_server", + "affinity": "TEXT" + }, + { + "fieldPath": "sharedViaLink", + "columnName": "share_by_link", + "affinity": "INTEGER" + }, + { + "fieldPath": "permissions", + "columnName": "permissions", + "affinity": "TEXT" + }, + { + "fieldPath": "remoteId", + "columnName": "remote_id", + "affinity": "TEXT" + }, + { + "fieldPath": "localId", + "columnName": "local_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "updateThumbnail", + "columnName": "update_thumbnail", + "affinity": "INTEGER" + }, + { + "fieldPath": "isDownloading", + "columnName": "is_downloading", + "affinity": "INTEGER" + }, + { + "fieldPath": "favorite", + "columnName": "favorite", + "affinity": "INTEGER" + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER" + }, + { + "fieldPath": "isEncrypted", + "columnName": "is_encrypted", + "affinity": "INTEGER" + }, + { + "fieldPath": "etagInConflict", + "columnName": "etag_in_conflict", + "affinity": "TEXT" + }, + { + "fieldPath": "sharedWithSharee", + "columnName": "shared_via_users", + "affinity": "INTEGER" + }, + { + "fieldPath": "mountType", + "columnName": "mount_type", + "affinity": "INTEGER" + }, + { + "fieldPath": "hasPreview", + "columnName": "has_preview", + "affinity": "INTEGER" + }, + { + "fieldPath": "unreadCommentsCount", + "columnName": "unread_comments_count", + "affinity": "INTEGER" + }, + { + "fieldPath": "ownerId", + "columnName": "owner_id", + "affinity": "TEXT" + }, + { + "fieldPath": "ownerDisplayName", + "columnName": "owner_display_name", + "affinity": "TEXT" + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT" + }, + { + "fieldPath": "sharees", + "columnName": "sharees", + "affinity": "TEXT" + }, + { + "fieldPath": "richWorkspace", + "columnName": "rich_workspace", + "affinity": "TEXT" + }, + { + "fieldPath": "metadataSize", + "columnName": "metadata_size", + "affinity": "TEXT" + }, + { + "fieldPath": "metadataLivePhoto", + "columnName": "metadata_live_photo", + "affinity": "TEXT" + }, + { + "fieldPath": "locked", + "columnName": "locked", + "affinity": "INTEGER" + }, + { + "fieldPath": "lockType", + "columnName": "lock_type", + "affinity": "INTEGER" + }, + { + "fieldPath": "lockOwner", + "columnName": "lock_owner", + "affinity": "TEXT" + }, + { + "fieldPath": "lockOwnerDisplayName", + "columnName": "lock_owner_display_name", + "affinity": "TEXT" + }, + { + "fieldPath": "lockOwnerEditor", + "columnName": "lock_owner_editor", + "affinity": "TEXT" + }, + { + "fieldPath": "lockTimestamp", + "columnName": "lock_timestamp", + "affinity": "INTEGER" + }, + { + "fieldPath": "lockTimeout", + "columnName": "lock_timeout", + "affinity": "INTEGER" + }, + { + "fieldPath": "lockToken", + "columnName": "lock_token", + "affinity": "TEXT" + }, + { + "fieldPath": "tags", + "columnName": "tags", + "affinity": "TEXT" + }, + { + "fieldPath": "metadataGPS", + "columnName": "metadata_gps", + "affinity": "TEXT" + }, + { + "fieldPath": "e2eCounter", + "columnName": "e2e_counter", + "affinity": "INTEGER" + }, + { + "fieldPath": "internalTwoWaySync", + "columnName": "internal_two_way_sync_timestamp", + "affinity": "INTEGER" + }, + { + "fieldPath": "internalTwoWaySyncResult", + "columnName": "internal_two_way_sync_result", + "affinity": "TEXT" + }, + { + "fieldPath": "uploaded", + "columnName": "uploaded", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "filesystem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `remote_path` TEXT, `is_folder` INTEGER, `found_at` INTEGER, `upload_triggered` INTEGER, `syncedfolder_id` TEXT, `crc32` TEXT, `modified_at` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "localPath", + "columnName": "local_path", + "affinity": "TEXT" + }, + { + "fieldPath": "remotePath", + "columnName": "remote_path", + "affinity": "TEXT" + }, + { + "fieldPath": "fileIsFolder", + "columnName": "is_folder", + "affinity": "INTEGER" + }, + { + "fieldPath": "fileFoundRecently", + "columnName": "found_at", + "affinity": "INTEGER" + }, + { + "fieldPath": "fileSentForUpload", + "columnName": "upload_triggered", + "affinity": "INTEGER" + }, + { + "fieldPath": "syncedFolderId", + "columnName": "syncedfolder_id", + "affinity": "TEXT" + }, + { + "fieldPath": "crc32", + "columnName": "crc32", + "affinity": "TEXT" + }, + { + "fieldPath": "fileModified", + "columnName": "modified_at", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "ocshares", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `file_source` INTEGER, `item_source` INTEGER, `share_type` INTEGER, `shate_with` TEXT, `path` TEXT, `permissions` INTEGER, `shared_date` INTEGER, `expiration_date` INTEGER, `token` TEXT, `shared_with_display_name` TEXT, `is_directory` INTEGER, `user_id` TEXT, `id_remote_shared` INTEGER, `owner_share` TEXT, `is_password_protected` INTEGER, `note` TEXT, `hide_download` INTEGER, `share_link` TEXT, `share_label` TEXT, `download_limit_limit` INTEGER, `download_limit_count` INTEGER, `attributes` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "fileSource", + "columnName": "file_source", + "affinity": "INTEGER" + }, + { + "fieldPath": "itemSource", + "columnName": "item_source", + "affinity": "INTEGER" + }, + { + "fieldPath": "shareType", + "columnName": "share_type", + "affinity": "INTEGER" + }, + { + "fieldPath": "shareWith", + "columnName": "shate_with", + "affinity": "TEXT" + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT" + }, + { + "fieldPath": "permissions", + "columnName": "permissions", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharedDate", + "columnName": "shared_date", + "affinity": "INTEGER" + }, + { + "fieldPath": "expirationDate", + "columnName": "expiration_date", + "affinity": "INTEGER" + }, + { + "fieldPath": "token", + "columnName": "token", + "affinity": "TEXT" + }, + { + "fieldPath": "shareWithDisplayName", + "columnName": "shared_with_display_name", + "affinity": "TEXT" + }, + { + "fieldPath": "isDirectory", + "columnName": "is_directory", + "affinity": "INTEGER" + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT" + }, + { + "fieldPath": "idRemoteShared", + "columnName": "id_remote_shared", + "affinity": "INTEGER" + }, + { + "fieldPath": "accountOwner", + "columnName": "owner_share", + "affinity": "TEXT" + }, + { + "fieldPath": "isPasswordProtected", + "columnName": "is_password_protected", + "affinity": "INTEGER" + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT" + }, + { + "fieldPath": "hideDownload", + "columnName": "hide_download", + "affinity": "INTEGER" + }, + { + "fieldPath": "shareLink", + "columnName": "share_link", + "affinity": "TEXT" + }, + { + "fieldPath": "shareLabel", + "columnName": "share_label", + "affinity": "TEXT" + }, + { + "fieldPath": "downloadLimitLimit", + "columnName": "download_limit_limit", + "affinity": "INTEGER" + }, + { + "fieldPath": "downloadLimitCount", + "columnName": "download_limit_count", + "affinity": "INTEGER" + }, + { + "fieldPath": "attributes", + "columnName": "attributes", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "synced_folders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `remote_path` TEXT, `wifi_only` INTEGER, `charging_only` INTEGER, `existing` INTEGER, `enabled` INTEGER, `enabled_timestamp_ms` INTEGER, `subfolder_by_date` INTEGER, `account` TEXT, `upload_option` INTEGER, `name_collision_policy` INTEGER, `type` INTEGER, `hidden` INTEGER, `sub_folder_rule` INTEGER, `exclude_hidden` INTEGER, `last_scan_timestamp_ms` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "localPath", + "columnName": "local_path", + "affinity": "TEXT" + }, + { + "fieldPath": "remotePath", + "columnName": "remote_path", + "affinity": "TEXT" + }, + { + "fieldPath": "wifiOnly", + "columnName": "wifi_only", + "affinity": "INTEGER" + }, + { + "fieldPath": "chargingOnly", + "columnName": "charging_only", + "affinity": "INTEGER" + }, + { + "fieldPath": "existing", + "columnName": "existing", + "affinity": "INTEGER" + }, + { + "fieldPath": "enabled", + "columnName": "enabled", + "affinity": "INTEGER" + }, + { + "fieldPath": "enabledTimestampMs", + "columnName": "enabled_timestamp_ms", + "affinity": "INTEGER" + }, + { + "fieldPath": "subfolderByDate", + "columnName": "subfolder_by_date", + "affinity": "INTEGER" + }, + { + "fieldPath": "account", + "columnName": "account", + "affinity": "TEXT" + }, + { + "fieldPath": "uploadAction", + "columnName": "upload_option", + "affinity": "INTEGER" + }, + { + "fieldPath": "nameCollisionPolicy", + "columnName": "name_collision_policy", + "affinity": "INTEGER" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER" + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER" + }, + { + "fieldPath": "subFolderRule", + "columnName": "sub_folder_rule", + "affinity": "INTEGER" + }, + { + "fieldPath": "excludeHidden", + "columnName": "exclude_hidden", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastScanTimestampMs", + "columnName": "last_scan_timestamp_ms", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "list_of_uploads", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `remote_path` TEXT, `account_name` TEXT, `file_size` INTEGER, `status` INTEGER, `local_behaviour` INTEGER, `upload_time` INTEGER, `name_collision_policy` INTEGER, `is_create_remote_folder` INTEGER, `upload_end_timestamp` INTEGER, `upload_end_timestamp_long` INTEGER, `last_result` INTEGER, `is_while_charging_only` INTEGER, `is_wifi_only` INTEGER, `created_by` INTEGER, `folder_unlock_token` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "localPath", + "columnName": "local_path", + "affinity": "TEXT" + }, + { + "fieldPath": "remotePath", + "columnName": "remote_path", + "affinity": "TEXT" + }, + { + "fieldPath": "accountName", + "columnName": "account_name", + "affinity": "TEXT" + }, + { + "fieldPath": "fileSize", + "columnName": "file_size", + "affinity": "INTEGER" + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER" + }, + { + "fieldPath": "localBehaviour", + "columnName": "local_behaviour", + "affinity": "INTEGER" + }, + { + "fieldPath": "uploadTime", + "columnName": "upload_time", + "affinity": "INTEGER" + }, + { + "fieldPath": "nameCollisionPolicy", + "columnName": "name_collision_policy", + "affinity": "INTEGER" + }, + { + "fieldPath": "isCreateRemoteFolder", + "columnName": "is_create_remote_folder", + "affinity": "INTEGER" + }, + { + "fieldPath": "uploadEndTimestamp", + "columnName": "upload_end_timestamp", + "affinity": "INTEGER" + }, + { + "fieldPath": "uploadEndTimestampLong", + "columnName": "upload_end_timestamp_long", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastResult", + "columnName": "last_result", + "affinity": "INTEGER" + }, + { + "fieldPath": "isWhileChargingOnly", + "columnName": "is_while_charging_only", + "affinity": "INTEGER" + }, + { + "fieldPath": "isWifiOnly", + "columnName": "is_wifi_only", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdBy", + "columnName": "created_by", + "affinity": "INTEGER" + }, + { + "fieldPath": "folderUnlockToken", + "columnName": "folder_unlock_token", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "virtual", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `type` TEXT, `ocfile_id` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT" + }, + { + "fieldPath": "ocFileId", + "columnName": "ocfile_id", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "offline_operations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `offline_operations_parent_oc_file_id` INTEGER, `offline_operations_path` TEXT, `offline_operations_type` TEXT, `offline_operations_file_name` TEXT, `offline_operations_created_at` INTEGER, `offline_operations_modified_at` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "parentOCFileId", + "columnName": "offline_operations_parent_oc_file_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "path", + "columnName": "offline_operations_path", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "offline_operations_type", + "affinity": "TEXT" + }, + { + "fieldPath": "filename", + "columnName": "offline_operations_file_name", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "offline_operations_created_at", + "affinity": "INTEGER" + }, + { + "fieldPath": "modifiedAt", + "columnName": "offline_operations_modified_at", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "recommended_files", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `directory` TEXT NOT NULL, `extension` TEXT NOT NULL, `mime_type` TEXT NOT NULL, `has_preview` INTEGER NOT NULL, `reason` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `account_name` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directory", + "columnName": "directory", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "extension", + "columnName": "extension", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mimeType", + "columnName": "mime_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPreview", + "columnName": "has_preview", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reason", + "columnName": "reason", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountName", + "columnName": "account_name", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "assistant", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountName` TEXT, `type` TEXT, `status` TEXT, `userId` TEXT, `appId` TEXT, `input` TEXT, `output` TEXT, `completionExpectedAt` INTEGER, `progress` INTEGER, `lastUpdated` INTEGER, `scheduledAt` INTEGER, `endedAt` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountName", + "columnName": "accountName", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT" + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT" + }, + { + "fieldPath": "userId", + "columnName": "userId", + "affinity": "TEXT" + }, + { + "fieldPath": "appId", + "columnName": "appId", + "affinity": "TEXT" + }, + { + "fieldPath": "input", + "columnName": "input", + "affinity": "TEXT" + }, + { + "fieldPath": "output", + "columnName": "output", + "affinity": "TEXT" + }, + { + "fieldPath": "completionExpectedAt", + "columnName": "completionExpectedAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "progress", + "columnName": "progress", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER" + }, + { + "fieldPath": "scheduledAt", + "columnName": "scheduledAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "endedAt", + "columnName": "endedAt", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3bdbf71fa42d512b25ee335bfc766faf')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt index 39fa5c7b33ae..a61600bd7f7e 100644 --- a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt +++ b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt @@ -97,8 +97,9 @@ import com.owncloud.android.db.ProviderMeta AutoMigration(from = 95, to = 96), AutoMigration(from = 96, to = 97, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), // manual migration used for 97 to 98 - AutoMigration(from = 98, to = 99) + AutoMigration(from = 98, to = 99), // manual migration used for 99 to 100 + AutoMigration(from = 100, to = 101, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), ], exportSchema = true ) diff --git a/app/src/main/java/com/nextcloud/client/database/entity/CapabilityEntity.kt b/app/src/main/java/com/nextcloud/client/database/entity/CapabilityEntity.kt index eb225bab7972..d0bd51e8e99c 100644 --- a/app/src/main/java/com/nextcloud/client/database/entity/CapabilityEntity.kt +++ b/app/src/main/java/com/nextcloud/client/database/entity/CapabilityEntity.kt @@ -151,7 +151,9 @@ data class CapabilityEntity( @ColumnInfo(name = ProviderTableMeta.CAPABILITIES_HAS_VALID_SUBSCRIPTION) val hasValidSubscription: Int?, @ColumnInfo(name = ProviderTableMeta.CAPABILITIES_CLIENT_INTEGRATION_JSON) - val clientIntegrationJson: String? + val clientIntegrationJson: String?, + @ColumnInfo(name = ProviderTableMeta.CAPABILITIES_SHARING_JSON) + val sharingJson: String? ) @Suppress("LongMethod", "ReturnCount") @@ -232,6 +234,7 @@ fun CapabilityEntity?.toOCCapability(): OCCapability { capability.defaultPermissions = this.defaultPermissions ?: 0 capability.hasValidSubscription = intToBoolean(this.hasValidSubscription) capability.clientIntegrationJson = this.clientIntegrationJson + capability.sharingJson = this.sharingJson return capability } diff --git a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index a82ad391bb5d..6d3cfe8ba9d0 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -2414,6 +2414,8 @@ private ContentValues createContentValues(String accountName, OCCapability capab contentValues.put(ProviderTableMeta.CAPABILITIES_CLIENT_INTEGRATION_JSON, capability.getClientIntegrationJson()); + contentValues.put(ProviderTableMeta.CAPABILITIES_SHARING_JSON, capability.getSharingJson()); + return contentValues; } @@ -2601,6 +2603,9 @@ private OCCapability createCapabilityInstance(Cursor cursor) { capability.setHasValidSubscription(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_HAS_VALID_SUBSCRIPTION)); capability.setClientIntegrationJson(getString(cursor, ProviderTableMeta.CAPABILITIES_CLIENT_INTEGRATION_JSON)); + + capability.setSharingJson(getString(cursor, ProviderTableMeta.CAPABILITIES_SHARING_JSON)); + } return capability; diff --git a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java index e12b0b44ec54..eed95b4dc8d7 100644 --- a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java +++ b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java @@ -23,7 +23,7 @@ */ public class ProviderMeta { public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 100; + public static final int DB_VERSION = 101; private ProviderMeta() { // No instance @@ -293,6 +293,7 @@ static public class ProviderTableMeta implements BaseColumns { public static final String CAPABILITIES_DEFAULT_PERMISSIONS = "default_permissions"; public static final String CAPABILITIES_HAS_VALID_SUBSCRIPTION = "has_valid_subscription"; public static final String CAPABILITIES_CLIENT_INTEGRATION_JSON = "client_integration_json"; + public static final String CAPABILITIES_SHARING_JSON = "sharing_json"; //Columns of Uploads table public static final String UPLOADS_LOCAL_PATH = "local_path"; diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 31ec6936a907..d5ef5376ed83 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -242,8 +242,8 @@ private void showUnifiedShare() { if (activity != null && sharingCapabilities != null) { activity.runOnUiThread(() -> ShareViewKt.setupUnifiedShare( binding.unifiedShare, - sharingCapabilities, String.valueOf(file.getRemoteId()), + sharingCapabilities, serverCredentials, viewThemeUtils.files.getColorScheme(activity)) ); From 5fec71c362476e78ced17ae9d4ea3d7b246d9670 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 1 Jun 2026 09:00:01 +0300 Subject: [PATCH 17/18] wip Signed-off-by: alperozturk96 --- .../java/com/nextcloud/client/database/NextcloudDatabase.kt | 2 +- .../android/ui/fragment/FileDetailSharingFragment.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt index a61600bd7f7e..abad90195739 100644 --- a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt +++ b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt @@ -99,7 +99,7 @@ import com.owncloud.android.db.ProviderMeta // manual migration used for 97 to 98 AutoMigration(from = 98, to = 99), // manual migration used for 99 to 100 - AutoMigration(from = 100, to = 101, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), + AutoMigration(from = 100, to = 101, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class) ], exportSchema = true ) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index d5ef5376ed83..201f9b7fb47c 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -34,7 +34,7 @@ import android.view.animation.AnimationUtils; import android.widget.LinearLayout; -import com.nextcloud.android.common.ui.share.ShareViewKt; +import com.nextcloud.android.common.ui.share.ShareScreenKt; import com.nextcloud.android.common.ui.theme.utils.ColorRole; import com.nextcloud.client.account.User; import com.nextcloud.client.account.UserAccountManager; @@ -240,7 +240,7 @@ private void showUnifiedShare() { final var sharingCapabilities = fileDataStorageManager.getCapability(user).getSharingJson(); if (activity != null && sharingCapabilities != null) { - activity.runOnUiThread(() -> ShareViewKt.setupUnifiedShare( + activity.runOnUiThread(() -> ShareScreenKt.initShareScreen( binding.unifiedShare, String.valueOf(file.getRemoteId()), sharingCapabilities, From 1381f9dd195b20b5894fa5b89a6908239017d122 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 9 Jun 2026 11:22:50 +0200 Subject: [PATCH 18/18] wip Signed-off-by: alperozturk96 --- app/build.gradle.kts | 1 - gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 12 ++++++------ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 220f8f8b2b0d..4764a83ea074 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -352,7 +352,6 @@ dependencies { implementation(libs.compose.activity) implementation(libs.compose.ui.tooling.preview) implementation(libs.foundation) - implementation(libs.material3) debugImplementation(libs.compose.ui.tooling) // endregion diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2d051976a264..5f8ff34c7b8a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -114,7 +114,7 @@ photoview = { module = "com.github.Baseflow:PhotoView", version.ref = "photoview material = { module = "com.google.android.material:material", version.ref = "materialVersion" } android-gif-drawable = { module = "pl.droidsonroids.gif:android-gif-drawable", version.ref = "androidGifDrawableVersion" } android-image-cropper = { module = "com.vanniktech:android-image-cropper", version.ref = "androidImageCropperVersion" } -androidsvg = { module = "com.caverock:androidsvg", version.ref = "androidsvgVersion" } +androidsvg = { module = "com.caverock:androidsvg-aar", version.ref = "androidsvgVersion" } coil = { module = "io.coil-kt:coil", version.ref = "coilVersion" } constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayoutVersion" } emoji-google = { module = "com.vanniktech:emoji-google", version.ref = "emojiGoogleVersion" } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 6ecaeab909ea..123cab1a53df 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -525,7 +525,7 @@ - + @@ -19958,12 +19958,12 @@ - - - + + + - - + +