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:
@@ -0,0 +1,108 @@
|
||||
/// Nextcloud share types (subset the app uses).
|
||||
const int kShareTypeUser = 0;
|
||||
const int kShareTypeGroup = 1;
|
||||
const int kShareTypePublicLink = 3;
|
||||
const int kShareTypeEmail = 4;
|
||||
|
||||
/// A Talk conversation ("room") the file is linked into.
|
||||
const int kShareTypeRoom = 10;
|
||||
|
||||
/// A single share as returned by the OCS `files_sharing` API.
|
||||
///
|
||||
/// Parsed by hand rather than via code generation: OCS is inconsistent about
|
||||
/// types across versions (e.g. `id`/`share_type` may arrive as either strings
|
||||
/// or numbers) and omits optional fields entirely, so defensive parsing is
|
||||
/// safer than generated `as int` casts.
|
||||
class Share {
|
||||
final int id;
|
||||
final int shareType;
|
||||
final int permissions;
|
||||
|
||||
/// Server path of the shared item (e.g. `/Documents/x.pdf`).
|
||||
final String? path;
|
||||
|
||||
/// `'file'` or `'folder'`.
|
||||
final String? itemType;
|
||||
|
||||
/// Recipient id (user/group id); empty for public links.
|
||||
final String? shareWith;
|
||||
final String? shareWithDisplayname;
|
||||
|
||||
/// Public link URL (only set for [kShareTypePublicLink]).
|
||||
final String? url;
|
||||
|
||||
/// Raw expiration as `"YYYY-MM-DD HH:MM:SS"` (or null when none).
|
||||
final String? expiration;
|
||||
final String? label;
|
||||
|
||||
/// Redacted password marker: the server returns `null` when no password is
|
||||
/// set and a placeholder (`"redacted"`) when one is — never the real value.
|
||||
final String? password;
|
||||
|
||||
const Share({
|
||||
required this.id,
|
||||
required this.shareType,
|
||||
required this.permissions,
|
||||
this.path,
|
||||
this.itemType,
|
||||
this.shareWith,
|
||||
this.shareWithDisplayname,
|
||||
this.url,
|
||||
this.expiration,
|
||||
this.label,
|
||||
this.password,
|
||||
});
|
||||
|
||||
bool get isPublicLink => shareType == kShareTypePublicLink;
|
||||
bool get isGroup => shareType == kShareTypeGroup;
|
||||
bool get isEmail => shareType == kShareTypeEmail;
|
||||
bool get isRoom => shareType == kShareTypeRoom;
|
||||
bool get isFolder => itemType == 'folder';
|
||||
|
||||
/// Whether a (link) password is currently set. See [password].
|
||||
bool get hasPassword => password != null && password!.isNotEmpty;
|
||||
|
||||
/// Best display title for the share row.
|
||||
String get displayTitle {
|
||||
if (isPublicLink) return label?.isNotEmpty == true ? label! : 'Link';
|
||||
final name = shareWithDisplayname;
|
||||
if (name != null && name.isNotEmpty) return name;
|
||||
return shareWith ?? 'Unbekannt';
|
||||
}
|
||||
|
||||
/// Human label for the kind of share (for subtitles/headers).
|
||||
String get kindLabel {
|
||||
if (isPublicLink) return 'Öffentlicher Link';
|
||||
if (isGroup) return 'Gruppe';
|
||||
if (isRoom) return 'Talk-Chat';
|
||||
if (isEmail) return 'E-Mail';
|
||||
if (shareType == kShareTypeUser) return 'Person';
|
||||
return 'Freigabe';
|
||||
}
|
||||
|
||||
static int _asInt(Object? value, {int fallback = 0}) {
|
||||
if (value is int) return value;
|
||||
if (value is String) return int.tryParse(value) ?? fallback;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
static String? _asString(Object? value) {
|
||||
if (value == null) return null;
|
||||
final s = value.toString();
|
||||
return s.isEmpty ? null : s;
|
||||
}
|
||||
|
||||
factory Share.fromJson(Map<String, dynamic> json) => Share(
|
||||
id: _asInt(json['id']),
|
||||
shareType: _asInt(json['share_type']),
|
||||
permissions: _asInt(json['permissions']),
|
||||
path: _asString(json['path']),
|
||||
itemType: _asString(json['item_type']),
|
||||
shareWith: _asString(json['share_with']),
|
||||
shareWithDisplayname: _asString(json['share_with_displayname']),
|
||||
url: _asString(json['url']),
|
||||
expiration: _asString(json['expiration']),
|
||||
label: _asString(json['label']),
|
||||
password: _asString(json['password']),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/// Parameters for updating an existing share via OCS `PUT shares/{id}`.
|
||||
/// Only the fields that are explicitly set are sent — every field is optional
|
||||
/// and a null is omitted from the request (sending `permissions=null` would be
|
||||
/// rejected by the server).
|
||||
///
|
||||
/// Use an empty string for [expireDate]/[password] to explicitly clear the
|
||||
/// value server-side (Nextcloud treats `expireDate=` as "remove expiry").
|
||||
class ShareUpdateParams {
|
||||
final int? permissions;
|
||||
final String? password;
|
||||
final String? expireDate;
|
||||
|
||||
const ShareUpdateParams({this.permissions, this.password, this.expireDate});
|
||||
|
||||
Map<String, String> toQuery() => {
|
||||
if (permissions != null) 'permissions': permissions.toString(),
|
||||
'password': ?password,
|
||||
'expireDate': ?expireDate,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user