Add function to handle sharing actions

This commit is contained in:
AmanRajSinghMourya
2025-08-27 13:46:58 +05:30
parent 6da615b7dc
commit a1d9fb5969
3 changed files with 250 additions and 4 deletions

View File

@@ -1,3 +1,4 @@
import "dart:async";
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
@@ -15,6 +16,7 @@ import 'package:locker/services/collections/models/collection_file_item.dart';
import 'package:locker/services/collections/models/collection_magic.dart';
import 'package:locker/services/collections/models/diff.dart';
import "package:locker/services/collections/models/public_url.dart";
import "package:locker/services/collections/models/user.dart";
import 'package:locker/services/configuration.dart';
import "package:locker/services/files/sync/metadata_updater_service.dart";
import 'package:locker/services/files/sync/models/file.dart';
@@ -33,7 +35,11 @@ class CollectionApiClient {
final _enteDio = Network.instance.enteDio;
final _config = Configuration.instance;
Future<void> init() async {}
late CollectionDB _db;
Future<void> init() async {
_db = CollectionDB.instance;
}
Future<List<Collection>> getCollections(int sinceTime) async {
try {
@@ -414,7 +420,7 @@ class CollectionApiClient {
},
);
collection.publicURLs.add(PublicURL.fromMap(response.data["result"]));
await CollectionDB.instance.updateCollections([collection]);
await _db.updateCollections([collection]);
CollectionService.instance.updateCollectionCache(collection);
Bus.instance.fire(CollectionsUpdatedEvent());
} catch (e, s) {
@@ -429,7 +435,7 @@ class CollectionApiClient {
"/collections/share-url/" + collection.id.toString(),
);
collection.publicURLs.clear();
await CollectionDB.instance.updateCollections(List.from([collection]));
await _db.updateCollections(List.from([collection]));
CollectionService.instance.updateCollectionCache(collection);
Bus.instance.fire(CollectionsUpdatedEvent());
} on DioException catch (e) {
@@ -451,7 +457,7 @@ class CollectionApiClient {
// remove existing url information
collection.publicURLs.clear();
collection.publicURLs.add(PublicURL.fromMap(response.data["result"]));
await CollectionDB.instance.updateCollections(List.from([collection]));
await _db.updateCollections(List.from([collection]));
CollectionService.instance.updateCollectionCache(collection);
Bus.instance.fire(CollectionsUpdatedEvent());
} on DioException catch (e) {
@@ -464,6 +470,45 @@ class CollectionApiClient {
rethrow;
}
}
Future<List<User>> share(
int collectionID,
String email,
String publicKey,
CollectionParticipantRole role,
) async {
final collectionKey =
CollectionService.instance.getCollectionKey(collectionID);
final encryptedKey = CryptoUtil.sealSync(
collectionKey,
CryptoUtil.base642bin(publicKey),
);
try {
final response = await _enteDio.post(
"/collections/share",
data: {
"collectionID": collectionID,
"email": email,
"encryptedKey": CryptoUtil.bin2base64(encryptedKey),
"role": role.toStringVal(),
},
);
final sharees = <User>[];
for (final user in response.data["sharees"]) {
sharees.add(User.fromMap(user));
}
final collection = CollectionService.instance.getFromCache(collectionID);
final updatedCollection = collection!.copyWith(sharees: sharees);
CollectionService.instance.updateCollectionCache(updatedCollection);
unawaited(_db.updateCollections([updatedCollection]));
return sharees;
} on DioException catch (e) {
if (e.response?.statusCode == 402) {
throw SharingNotPermittedForFreeAccountsError();
}
rethrow;
}
}
}
class CreateRequest {

View File

@@ -12,6 +12,7 @@ import "package:locker/services/collections/collections_db.dart";
import 'package:locker/services/collections/models/collection.dart';
import "package:locker/services/collections/models/collection_items.dart";
import "package:locker/services/collections/models/public_url.dart";
import "package:locker/services/collections/models/user.dart";
import 'package:locker/services/configuration.dart';
import 'package:locker/services/files/sync/models/file.dart';
import 'package:locker/services/trash/models/trash_item_request.dart';
@@ -385,6 +386,71 @@ class CollectionService {
}
}
// getActiveCollections returns list of collections which are not deleted yet
List<Collection> getActiveCollections() {
return _collectionIDToCollections.values
.toList()
.where((element) => !element.isDeleted)
.toList();
}
/// Returns Contacts(Users) that are relevant to the account owner.
/// Note: "User" refers to the account owner in the points below.
/// This includes:
/// - Collaborators and viewers of collections owned by user
/// - Owners of collections shared to user.
/// - All collaborators of collections in which user is a collaborator or
/// a viewer.
/// - All family members of user.
/// - All contacts linked to a person.
List<User> getRelevantContacts() {
final List<User> relevantUsers = [];
final existingEmails = <String>{};
final int ownerID = Configuration.instance.getUserID()!;
final String ownerEmail = Configuration.instance.getEmail()!;
existingEmails.add(ownerEmail);
for (final c in getActiveCollections()) {
// Add collaborators and viewers of collections owned by user
if (c.owner.id == ownerID) {
for (final User u in c.sharees) {
if (u.id != null && u.email.isNotEmpty) {
if (!existingEmails.contains(u.email)) {
relevantUsers.add(u);
existingEmails.add(u.email);
}
}
}
} else if (c.owner.id != null && c.owner.email.isNotEmpty) {
// Add owners of collections shared with user
if (!existingEmails.contains(c.owner.email)) {
relevantUsers.add(c.owner);
existingEmails.add(c.owner.email);
}
// Add collaborators of collections shared with user where user is a
// viewer or a collaborator
for (final User u in c.sharees) {
if (u.id != null &&
u.email.isNotEmpty &&
u.email == ownerEmail &&
(u.isCollaborator || u.isViewer)) {
for (final User u in c.sharees) {
if (u.id != null && u.email.isNotEmpty && u.isCollaborator) {
if (!existingEmails.contains(u.email)) {
relevantUsers.add(u);
existingEmails.add(u.email);
}
}
}
break;
}
}
}
}
return relevantUsers;
}
String getPublicUrl(Collection c) {
final PublicURL url = c.publicURLs.firstOrNull!;
final Uri publicUrl = Uri.parse(url.url);

View File

@@ -1,13 +1,22 @@
import "dart:async";
import "package:ente_accounts/services/user_service.dart";
import "package:ente_ui/components/action_sheet_widget.dart";
import 'package:ente_ui/components/buttons/button_widget.dart';
import 'package:ente_ui/components/buttons/models/button_type.dart';
import "package:ente_ui/components/dialog_widget.dart";
import "package:ente_ui/components/progress_dialog.dart";
import 'package:ente_ui/utils/dialog_util.dart';
import "package:ente_utils/email_util.dart";
import "package:ente_utils/share_utils.dart";
import 'package:flutter/material.dart';
import "package:locker/core/errors.dart";
import 'package:locker/l10n/l10n.dart';
import "package:locker/services/collections/collections_api_client.dart";
import 'package:locker/services/collections/collections_service.dart';
import 'package:locker/services/collections/models/collection.dart';
import "package:locker/services/configuration.dart";
import "package:locker/ui/components/user_dialogs.dart";
import 'package:locker/utils/snack_bar_utils.dart';
import 'package:logging/logging.dart';
@@ -274,4 +283,130 @@ class CollectionActions {
barrierDismissible: true,
);
}
Future<bool> doesEmailHaveAccount(
BuildContext context,
String email, {
bool showProgress = false,
}) async {
ProgressDialog? dialog;
String? publicKey;
if (showProgress) {
dialog = createProgressDialog(
context,
context.l10n.sharing,
isDismissible: true,
);
await dialog.show();
}
try {
publicKey = await UserService.instance.getPublicKey(email);
} catch (e) {
await dialog?.hide();
_logger.severe("Failed to get public key", e);
await showGenericErrorDialog(context: context, error: e);
return false;
}
// getPublicKey can return null when no user is associated with given
// email id
if (publicKey == null || publicKey == '') {
// todo: neeraj replace this as per the design where a new screen
// is used for error. Do this change along with handling of network errors
await showInviteDialog(context, email);
return false;
} else {
return true;
}
}
// addEmailToCollection returns true if add operation was successful
Future<bool> addEmailToCollection(
BuildContext context,
Collection collection,
String email,
CollectionParticipantRole role, {
bool showProgress = false,
}) async {
if (!isValidEmail(email)) {
await showErrorDialog(
context,
context.l10n.invalidEmailAddress,
context.l10n.enterValidEmail,
);
return false;
} else if (email.trim() == Configuration.instance.getEmail()) {
await showErrorDialog(
context,
context.l10n.oops,
context.l10n.youCannotShareWithYourself,
);
return false;
}
ProgressDialog? dialog;
String? publicKey;
if (showProgress) {
dialog = createProgressDialog(
context,
context.l10n.sharing,
isDismissible: true,
);
await dialog.show();
}
try {
publicKey = await UserService.instance.getPublicKey(email);
} catch (e) {
await dialog?.hide();
_logger.severe("Failed to get public key", e);
await showGenericErrorDialog(context: context, error: e);
return false;
}
// getPublicKey can return null when no user is associated with given
// email id
if (publicKey == null || publicKey == '') {
// todo: neeraj replace this as per the design where a new screen
// is used for error. Do this change along with handling of network errors
await showDialogWidget(
context: context,
title: context.l10n.inviteToEnte,
icon: Icons.info_outline,
body: context.l10n.emailNoEnteAccount(email),
isDismissible: true,
buttons: [
ButtonWidget(
buttonType: ButtonType.neutral,
icon: Icons.adaptive.share,
labelText: context.l10n.sendInvite,
isInAlert: true,
onTap: () async {
unawaited(
shareText(
context.l10n.shareTextRecommendUsingEnte,
),
);
},
),
],
);
return false;
} else {
try {
final newSharees = await CollectionApiClient.instance
.share(collection.id, email, publicKey, role);
await dialog?.hide();
collection.updateSharees(newSharees);
return true;
} catch (e) {
await dialog?.hide();
if (e is SharingNotPermittedForFreeAccountsError) {
await _showUnSupportedAlert(context);
} else {
_logger.severe("failed to share collection", e);
await showGenericErrorDialog(context: context, error: e);
}
return false;
}
}
}
}