implemented a comprehensive Nextcloud file sharing system with support for user, group, and public link shares with gating based on server-side permissions; added sharing management interfaces including a share sheet; updated the file list with visual badges for incoming shares and improved OCS API response handling.

This commit is contained in:
2026-06-02 21:42:08 +02:00
parent b6d06dd3b4
commit baa26a6e79
33 changed files with 2453 additions and 29 deletions
@@ -0,0 +1,131 @@
/// Subset of Nextcloud's `files_sharing` capabilities block that the mobile
/// sharing UI gates on. Nextcloud reports these per authenticated user, so a
/// group that an admin excluded from creating public links sees
/// `public.enabled == false` here — exactly how the web UI hides those buttons.
///
/// The block is deeply nested and varies between server versions, so this is
/// parsed by hand from the raw OCS map with safe fallbacks rather than via
/// code generation. Missing fields default to the most restrictive value so a
/// newer/older server never accidentally unlocks a capability.
class NextcloudSharingCapabilities {
/// `files_sharing.api_enabled` — master switch. When false the user may not
/// create any share (user, group or link).
final bool apiEnabled;
/// `files_sharing.public.enabled` — public link shares allowed.
final bool publicEnabled;
/// `files_sharing.public.multiple_links` — more than one link per file.
final bool publicMultipleLinks;
/// `files_sharing.public.upload` — public upload / file-drop folders.
final bool publicUploadEnabled;
/// `files_sharing.public.password.enforced` — a password is mandatory on
/// public links, so the create flow must collect one upfront.
final bool publicPasswordEnforced;
/// `files_sharing.public.expire_date.enabled`.
final bool publicExpireEnabled;
/// `files_sharing.public.expire_date.days` — default/maximum lifetime.
final int? publicExpireDays;
/// `files_sharing.public.expire_date.enforced` — expiry cannot be removed.
final bool publicExpireEnforced;
/// `files_sharing.group.enabled` (falls back to the older `group_sharing`).
final bool groupEnabled;
/// `files_sharing.resharing` — recipients may reshare.
final bool resharing;
// --- password_policy (a sibling capability of files_sharing) ---
// These let the link-password UI state the rules up front instead of only
// surfacing them after the server rejects a weak password. The
// "non-common password" (breach) check can only be enforced server-side.
/// `password_policy.minLength`.
final int? passwordMinLength;
/// `password_policy.enforceUpperLowerCase`.
final bool passwordEnforceUpperLower;
/// `password_policy.enforceNumericCharacters`.
final bool passwordEnforceNumeric;
/// `password_policy.enforceSpecialCharacters`.
final bool passwordEnforceSpecial;
const NextcloudSharingCapabilities({
this.apiEnabled = false,
this.publicEnabled = false,
this.publicMultipleLinks = false,
this.publicUploadEnabled = false,
this.publicPasswordEnforced = false,
this.publicExpireEnabled = false,
this.publicExpireDays,
this.publicExpireEnforced = false,
this.groupEnabled = false,
this.resharing = false,
this.passwordMinLength,
this.passwordEnforceUpperLower = false,
this.passwordEnforceNumeric = false,
this.passwordEnforceSpecial = false,
});
/// Parses the `files_sharing` sub-map of an OCS `cloud/capabilities`
/// response, plus the optional sibling `password_policy` map. Tolerates
/// missing intermediate maps and type drift.
factory NextcloudSharingCapabilities.fromFilesSharing(
Map<String, dynamic> filesSharing, {
Map<String, dynamic>? passwordPolicy,
}) {
Map<String, dynamic>? sub(Map<String, dynamic>? m, String key) {
final value = m?[key];
return value is Map<String, dynamic> ? value : null;
}
bool boolAt(Map<String, dynamic>? m, String key) => m?[key] == true;
int? intAt(Map<String, dynamic>? m, String key) {
final v = m?[key];
if (v is int) return v;
if (v is String) return int.tryParse(v);
return null;
}
final public = sub(filesSharing, 'public');
final password = sub(public, 'password');
final expire = sub(public, 'expire_date');
final group = sub(filesSharing, 'group');
return NextcloudSharingCapabilities(
apiEnabled: boolAt(filesSharing, 'api_enabled'),
publicEnabled: boolAt(public, 'enabled'),
publicMultipleLinks: boolAt(public, 'multiple_links'),
publicUploadEnabled: boolAt(public, 'upload'),
publicPasswordEnforced: boolAt(password, 'enforced'),
publicExpireEnabled: boolAt(expire, 'enabled'),
publicExpireDays: intAt(expire, 'days'),
publicExpireEnforced: boolAt(expire, 'enforced'),
// Newer servers nest it under `group.enabled`; older ones expose a flat
// `group_sharing` boolean.
groupEnabled:
boolAt(group, 'enabled') || boolAt(filesSharing, 'group_sharing'),
resharing: boolAt(filesSharing, 'resharing'),
passwordMinLength: intAt(passwordPolicy, 'minLength'),
passwordEnforceUpperLower: boolAt(
passwordPolicy,
'enforceUpperLowerCase',
),
passwordEnforceNumeric: boolAt(
passwordPolicy,
'enforceNumericCharacters',
),
passwordEnforceSpecial: boolAt(
passwordPolicy,
'enforceSpecialCharacters',
),
);
}
}