Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions changelog/unreleased/5022
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Enhancement: Permalink for files and folders on oCIS servers

The share screen has been made reachable for any single file or folder on Infinite Scale accounts,
regardless of capabilities and permissions, and a button to copy or send the item's permalink
has been added in its header.

https://github.com/owncloud/android/issues/4939
https://github.com/owncloud/android/pull/5022
2 changes: 2 additions & 0 deletions openspec/changes/permalink-sharing/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-19
78 changes: 78 additions & 0 deletions openspec/changes/permalink-sharing/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
## Context

See proposal.md — Why. Two independent pieces must change, both scoped to oCIS:

1. **`Share` reachability** lives in `FilterFileMenuOptionsUseCase` (owncloudApp), a use case shared by every entry point that builds the file context menu (`MainFileListViewModel`, `FileDetailsViewModel`, and the four preview ViewModels). Its current `SHARE` gate — `!onlyAvailableOfflineFiles && shareViaLinkOrWithUsersAllowed && resharingAllowed && hasResharePermission` — was designed for oC10. Because it is shared, any relaxation must be guarded so oC10 keeps the exact same gate.
2. **The permalink button** in the oCIS Share header exists in `members_activity.xml` as `permanentLinkButton`, but `ShareActivity.setupFileHeader()` hard-codes it hidden (`ShareActivity.kt:172`) and sets no listener. oC10 uses a different screen (`OcsShareFileFragment`) where the private link already works via `showOrHidePrivateLink()`.

Relevant existing facts the design relies on:
- `OCCapability.isSpacesAllowed()` (`spaces?.enabled == true`) is the established oCIS/oC10 discriminator across the app.
- `ShareActivity.onCreate()` fetches the stored `OCCapability` once via `capabilityViewModel.getStoredCapabilities()`, branches `setupOcisLayout()` vs `setupLegacyLayout()` on `isSpacesAllowed()`, and passes the capabilities down to `setupFileHeader()`, which runs only on the oCIS branch.
- `ShareActivity` already overrides `copyOrSendPrivateLink(file)` → `fileOperationsHelper.copyOrSendPrivateLink(file)`.
- oC10's rule (`OcsShareFileFragment.showOrHidePrivateLink()`): hide when `file.privateLink.isNullOrEmpty()` or `filesPrivateLinks == CapabilityBooleanType.FALSE`.
- `FilterFileMenuOptionsUseCase` already fetches `capability = capabilityRepository.getStoredCapabilities(params.accountName)`.

## Goals / Non-Goals

**Goals:**
- Make `Share` unconditionally reachable for a single, non-available-offline selection on oCIS, without changing the oC10 gate.
- Make the oCIS permalink button visible and functional under the same show/hide rule oC10 already uses.

**Non-Goals:**
- No change to oC10 (`FilterFileMenuOptionsUseCase` oC10 branch, `OcsShareFileFragment`, `share_file_layout.xml`).
- No change to `FilterFileMenuOptionsUseCase.Params`, its callers, or its Koin factory.
- No change to the section-level gating inside the Share screen.
- No data/domain/network layer changes.

## Decisions

### D1: Branch the `SHARE` gate by server type inside the use case
Reuse the already-fetched `capability`: `val isOcis = capability?.isSpacesAllowed() == true`. Then:

```kotlin
val shareReachable = if (isOcis) {
!onlyAvailableOfflineFiles && isSingleSelection(files)
} else {
!onlyAvailableOfflineFiles && shareViaLinkOrWithUsersAllowed &&
resharingAllowed && hasResharePermission
}
if (shareReachable) optionsToShow.add(FileMenuOption.SHARE)
```

- **Why in the use case** rather than at the call sites: the gate and all its inputs already live here; the six callers pass identical `Params` and would otherwise each need the oCIS branch duplicated.
- **Why keep the oC10 branch verbatim**: the change must be behavior-preserving for oC10. Consequently `resharingAllowed`, `hasResharePermission`, `anyFileSharedWithMe`, `shareViaLinkOrWithUsersAllowed`, the `capability` lookup, the `capabilityRepository` dependency, and the `shareViaLinkAllowed`/`shareWithUsersAllowed` `Params` fields are all retained (still read by the oC10 branch), so detekt's zero-tolerance "unused" rules stay satisfied and no caller/Koin edits are needed.
- **Alternative rejected**: removing the oC10-only inputs and reducing the gate to `!onlyAvailableOfflineFiles && isSingleSelection(files)` for everyone. Rejected because it changes oC10 behavior (an item "shared with me" with resharing disabled would newly show `Share`), which is explicitly out of scope.

### D2: Implement the oCIS permalink button in `ShareActivity.setupFileHeader()`
Replace the hard-coded `binding.permanentLinkButton.isVisible = false` with the oC10 rule inside a single `binding.permanentLinkButton.apply { … }` block, wiring the click to the existing override and a long press to the info toast:

```kotlin
val privateLinkDisabled = capabilities?.filesPrivateLinks == CapabilityBooleanType.FALSE
binding.permanentLinkButton.apply {
if (file.privateLink.isNullOrEmpty() || privateLinkDisabled) {
isVisible = false
} else {
isVisible = true
setOnClickListener { copyOrSendPrivateLink(file) }
setOnLongClickListener {
Toast.makeText(this@ShareActivity, R.string.private_link_info, Toast.LENGTH_LONG).show()
true
}
}
}
```

- **Why here**: `setupFileHeader()` already runs only on the oCIS layout branch, so no extra oCIS guard is required; the button and the `copyOrSendPrivateLink` plumbing already exist.
- **Why mirror oC10's rule** instead of inventing one: keeps permalink behavior consistent across both screens — hidden when no link or `filesPrivateLinks == FALSE`, click copies/sends the private link, and long press shows the `R.string.private_link_info` toast, exactly as `OcsShareFileFragment.showOrHidePrivateLink()` does.
- **Capability access**: a thin read-only `getStoredCapabilities()` getter (backed by `GetStoredCapabilitiesUseCase`) was added to `CapabilityViewModel`, replacing the previous `isOcisServer()` boolean accessor. `onCreate()` reads the stored `OCCapability` once through it, branches on `isSpacesAllowed()`, and passes the capabilities into `setupFileHeader(binding, capabilities)`; `filesPrivateLinks` is read from that parameter rather than re-querying Room or the view model inside the method.
- **Reference**: `SpaceMembersActivity.kt:74` wires the same button via `setOnClickListener`.

## Risks / Trade-offs

- [oC10 regression from the shared use case] → Guard the relaxation behind `isOcis` so the oC10 branch stays byte-for-byte unchanged; validated by the manual oC10 regression QA cases in `tasks.md` (item shared-with-me, resharing false, no `R` → `Share` absent).
- [`capability` is null when stored capabilities are missing] → `capability?.isSpacesAllowed() == true` evaluates to `false`, so the account falls back to the oC10 branch — the pre-change behavior, which is the safe default.
- [Detekt flags a now-unused symbol] → None expected, because the oC10 branch keeps reading every retained symbol; verify with `./gradlew detekt` during apply.

## Open Questions

- ~~Which existing `CapabilityViewModel` accessor returns the stored `OCCapability` (or specifically `filesPrivateLinks`) for the account, or whether a small read-only getter must be added.~~ **Resolved:** no existing accessor returned the full `OCCapability`, so a read-only `getStoredCapabilities()` getter was added to `CapabilityViewModel`, backed by `GetStoredCapabilitiesUseCase`; `onCreate()` calls it once and passes the result into `setupFileHeader()`. The former `isOcisServer()` boolean accessor was removed, as `getStoredCapabilities()?.isSpacesAllowed()` now covers both the layout branch and the permalink rule.
29 changes: 29 additions & 0 deletions openspec/changes/permalink-sharing/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Why

On oCIS the permalink can become unreachable. The `Share` screen is the only entry point to it, and the `FileMenuOption.SHARE` gate in `FilterFileMenuOptionsUseCase` — designed for oC10 — hides `Share` whenever the `resharing` capability is `false` (always the case on oCIS) or the item carries no `R` permission (empty `oc:permissions` in custom spaces). Even when `Share` is reachable, the oCIS header's `permanent_link_button` is hard-coded hidden and wired to nothing, so the permalink is never shown. oC10 is unaffected and must stay exactly as it is.

## What Changes

- On oCIS accounts (identified by the `spaces` capability, `OCCapability.isSpacesAllowed()`), the `Share` menu option becomes reachable for any single selected item, independent of the `resharing`/`sharing` capabilities, the `R` permission, and the `share_via_link_feature`/`share_with_users_feature` setup flags. It stays hidden in the available-offline shortcut and for multiselection.
- On oC10 the `Share` gate is left unchanged (no observable difference).
- On oCIS, the `permanent_link_button` in the Share header (`members_activity.xml`, driven by `ShareActivity.setupFileHeader()`) is made visible and wired to `copyOrSendPrivateLink(file)` on click and to an informational toast (`R.string.private_link_info`) on long press, shown only when the item has a private link and the `filesPrivateLinks` capability is not `FALSE` — mirroring oC10's `showOrHidePrivateLink()` and the `SpaceMembersActivity` reference.
- The capability/feature-flag gating of the "share with users" and "share by link" sections *inside* the Share screen is untouched on both server types.

## Capabilities

### New Capabilities
- `files-sharing/permalink`: Reachability of the `Share` screen on oCIS and the display/behavior of the permalink (private link) button in the oCIS Share header, including the show/hide rule and the copy/send action.

### Modified Capabilities
<!-- None: no existing capability spec covers Share-menu reachability or the permalink button. -->

## Impact

- **owncloudApp** (presentation):
- `usecases/files/FilterFileMenuOptionsUseCase.kt` — split the `SHARE` gate by server type; the oCIS branch reduces to `!onlyAvailableOfflineFiles && isSingleSelection(files)`, the oC10 branch is unchanged. All existing inputs (`capabilityRepository`, `resharingAllowed`, `hasResharePermission`, the `shareViaLinkAllowed`/`shareWithUsersAllowed` `Params`) are retained because the oC10 branch still needs them — no caller or Koin changes.
- `presentation/sharing/ShareActivity.kt` — `setupFileHeader()` shows and wires `permanentLinkButton`.
- No unit tests are added: the change touches only the presentation layer (owncloudApp), where unit tests are not established per project convention. The oCIS reachability and permalink behavior are covered by the manual QA cases in `tasks.md`.
- **owncloudDomain / owncloudData / owncloudComLibrary**: no changes. `OCFile.privateLink` is already populated from the `oc:privatelink` PROPFIND property and `OCCapability.isSpacesAllowed()` already exists.
- **Release notes**: user-facing entry summarising the oCIS Share/permalink change.
- **Changelog**: new `Enhancement` entry under `changelog/unreleased/`.
- No new dependencies.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
## Purpose

Defines when the `Share` screen is reachable on oCIS and how the permalink (private link) button in the oCIS Share header is shown and acted upon, so that a user can always reach and copy an item's permalink while oC10 behavior stays unchanged.

## ADDED Requirements

### Requirement: Share screen reachable for a single item on oCIS

On an oCIS account, the `Share` menu option SHALL be offered for a single selected item regardless of the `resharing`/`sharing` capability values, the item's `R` (reshare) permission, and the `share_via_link_feature`/`share_with_users_feature` setup flags. Reachability of the `Share` screen SHALL NOT depend on any of those values on oCIS.

An account is treated as oCIS when its stored capabilities report the `spaces` capability as enabled; otherwise the account is treated as oC10.

#### Scenario: Single item in an oCIS personal space with resharing disabled
- **WHEN** an oCIS account has a single item selected in its personal space and the `resharing` capability is `false`
- **THEN** the `Share` menu option is offered

#### Scenario: Single item in an oCIS custom space with no reshare permission
- **WHEN** an oCIS account has a single item selected in a custom space, the item's permissions carry no `R` flag, and the `resharing` capability is `false`
- **THEN** the `Share` menu option is offered

#### Scenario: oCIS with both share feature flags disabled
- **WHEN** an oCIS account has a single item selected and both `share_via_link_feature` and `share_with_users_feature` are `false`
- **THEN** the `Share` menu option is offered

### Requirement: Share screen stays restricted in available-offline and multiselection on oCIS

On an oCIS account, the `Share` menu option SHALL NOT be offered in the available-offline shortcut, and SHALL NOT be offered when more than one item is selected.

#### Scenario: Available-offline shortcut on oCIS
- **WHEN** an oCIS account is in the available-offline shortcut
- **THEN** the `Share` menu option is not offered

#### Scenario: Multiselection on oCIS
- **WHEN** an oCIS account has more than one item selected
- **THEN** the `Share` menu option is not offered

### Requirement: oC10 Share reachability unchanged

On an oC10 account, the conditions under which the `Share` menu option is offered SHALL remain exactly as they were before this change: it is offered only when the selection is not the available-offline shortcut, at least one of the share-via-link / share-with-users features is enabled, resharing is allowed for the selection, and the single selected item carries the reshare permission.

#### Scenario: oC10 gate unchanged for an item shared with the user
- **WHEN** an oC10 account has a single item that is shared with the user, the `resharing` capability is `false`, and the item carries no reshare permission
- **THEN** the `Share` menu option is not offered, exactly as before this change

#### Scenario: oC10 gate unchanged when conditions are met
- **WHEN** an oC10 account has a single selected item that satisfies the pre-existing share-feature, resharing, and reshare-permission conditions outside the available-offline shortcut
- **THEN** the `Share` menu option is offered, exactly as before this change

### Requirement: Permalink button shown and functional in the oCIS Share header

On an oCIS account, the Share header SHALL display the permalink (private link) button when the selected item has a non-empty private link and the `filesPrivateLinks` capability is not `FALSE`. When the button is displayed and the user activates it, the system SHALL copy or send that item's private link, reusing the existing private-link copy/send action. When the user long-presses the button, the system SHALL show an informational message explaining what a private link is.

#### Scenario: Item with a private link and private links not disabled
- **WHEN** the oCIS Share header is shown for an item whose private link is non-empty and the `filesPrivateLinks` capability is not `FALSE`
- **THEN** the permalink button is visible
- **AND** activating it copies or sends that item's private link

#### Scenario: Long-pressing the permalink button
- **WHEN** the oCIS Share header is shown for an item whose private link is non-empty and the user long-presses the permalink button
- **THEN** an informational message explaining the private link is shown

#### Scenario: Private links capability disabled
- **WHEN** the oCIS Share header is shown and the `filesPrivateLinks` capability is `FALSE`
- **THEN** the permalink button is hidden

#### Scenario: Item without a private link
- **WHEN** the oCIS Share header is shown for an item whose private link is null or empty
- **THEN** the permalink button is hidden

### Requirement: Share screen section gating unchanged

The visibility of the "share with users" and "share by link" sections inside the Share screen SHALL continue to be governed by their existing capabilities and build-time feature flags, on both oCIS and oC10. This change SHALL NOT alter that section-level gating.

#### Scenario: Sections still governed by their own rules
- **WHEN** the Share screen is opened on either server type
- **THEN** the "share with users" and "share by link" sections are shown or hidden according to their existing capabilities and feature flags, unchanged by this change
Loading
Loading