[mob] Mark collection owner, sharee and publicUrls as non-nullable field

This commit is contained in:
Neeraj Gupta
2025-02-13 15:31:29 +05:30
parent 5b168021f4
commit 3ff8d04d7b
21 changed files with 100 additions and 119 deletions

View File

@@ -251,7 +251,7 @@ class CollectionsDB {
Map<String, dynamic> _getRowForCollection(Collection collection) {
final row = <String, dynamic>{};
row[columnID] = collection.id;
row[columnOwner] = collection.owner!.toJson();
row[columnOwner] = collection.owner.toJson();
row[columnEncryptedKey] = collection.encryptedKey;
row[columnKeyDecryptionNonce] = collection.keyDecryptionNonce;
row[columnName] = collection.name;
@@ -262,9 +262,9 @@ class CollectionsDB {
row[columnPathDecryptionNonce] = collection.attributes.pathDecryptionNonce;
row[columnVersion] = collection.attributes.version;
row[columnSharees] =
json.encode(collection.sharees?.map((x) => x?.toMap()).toList());
json.encode(collection.sharees.map((x) => x.toMap()).toList());
row[columnPublicURLs] =
json.encode(collection.publicURLs?.map((x) => x?.toMap()).toList());
json.encode(collection.publicURLs.map((x) => x.toMap()).toList());
row[columnUpdationTime] = collection.updationTime;
if (collection.isDeleted) {
row[columnIsDeleted] = _sqlBoolTrue;

View File

@@ -322,22 +322,20 @@ class _AddContactPage extends State<AddContactPage> {
final int ownerID = Configuration.instance.getUserID()!;
existingEmails.add(Configuration.instance.getEmail()!);
for (final c in CollectionsService.instance.getActiveCollections()) {
if (c.owner?.id == ownerID) {
for (final User? u in c.sharees ?? []) {
if (u != null &&
u.id != null &&
if (c.owner.id == ownerID) {
for (final User u in c.sharees) {
if (u.id != null &&
u.email.isNotEmpty &&
!existingEmails.contains(u.email)) {
existingEmails.add(u.email);
suggestedUsers.add(u);
}
}
} else if (c.owner != null &&
c.owner!.id != null &&
c.owner!.email.isNotEmpty &&
!existingEmails.contains(c.owner!.email)) {
existingEmails.add(c.owner!.email);
suggestedUsers.add(c.owner!);
} else if (c.owner.id != null &&
c.owner.email.isNotEmpty &&
!existingEmails.contains(c.owner.email)) {
existingEmails.add(c.owner.email);
suggestedUsers.add(c.owner);
}
}
final cachedUserDetails = UserService.instance.getCachedUserDetails();

View File

@@ -8,7 +8,7 @@ import "package:photos/models/metadata/common_keys.dart";
class Collection {
final int id;
final User? owner;
final User owner;
final String encryptedKey;
final String? keyDecryptionNonce;
@Deprecated("Use collectionName instead")
@@ -20,8 +20,8 @@ class Collection {
final String? nameDecryptionNonce;
final CollectionType type;
final CollectionAttributes attributes;
final List<User?>? sharees;
final List<PublicURL?>? publicURLs;
final List<User> sharees;
final List<PublicURL> publicURLs;
final int updationTime;
final bool isDeleted;
@@ -95,12 +95,12 @@ class Collection {
// hasLink returns true if there's any link attached to the collection
// including expired links
bool get hasLink => publicURLs != null && publicURLs!.isNotEmpty;
bool get hasLink => publicURLs.isNotEmpty;
bool get hasCover => (pubMagicMetadata.coverID ?? 0) > 0;
// hasSharees returns true if the collection is shared with other ente users
bool get hasSharees => sharees != null && sharees!.isNotEmpty;
bool get hasSharees => sharees.isNotEmpty;
bool get isPinned => (magicMetadata.order ?? 0) != 0;
@@ -121,52 +121,43 @@ class Collection {
}
List<User> getSharees() {
final List<User> result = [];
if (sharees == null) {
return result;
}
for (final User? u in sharees!) {
if (u != null) {
result.add(u);
}
}
return result;
return sharees;
}
bool isOwner(int userID) {
return (owner?.id ?? 0) == userID;
return (owner.id ?? -100) == userID;
}
bool isDownloadEnabledForPublicLink() {
if (publicURLs == null || publicURLs!.isEmpty) {
if (publicURLs.isEmpty) {
return false;
}
return publicURLs?.first?.enableDownload ?? true;
return publicURLs.first.enableDownload;
}
bool isCollectEnabledForPublicLink() {
if (publicURLs == null || publicURLs!.isEmpty) {
if (publicURLs.isEmpty) {
return false;
}
return publicURLs?.first?.enableCollect ?? false;
return publicURLs.first.enableCollect;
}
bool get isJoinEnabled {
if (publicURLs == null || publicURLs!.isEmpty) {
if (publicURLs.isEmpty) {
return false;
}
return publicURLs?.first?.enableJoin ?? false;
return publicURLs.first.enableJoin;
}
CollectionParticipantRole getRole(int userID) {
if (isOwner(userID)) {
return CollectionParticipantRole.owner;
}
if (sharees == null) {
if (sharees.isEmpty) {
return CollectionParticipantRole.unknown;
}
for (final User? u in sharees!) {
if (u != null && u.id == userID) {
for (final User u in sharees) {
if (u.id == userID) {
if (u.isViewer) {
return CollectionParticipantRole.viewer;
} else if (u.isCollaborator) {
@@ -185,8 +176,8 @@ class Collection {
}
void updateSharees(List<User> newSharees) {
sharees?.clear();
sharees?.addAll(newSharees);
sharees.clear();
sharees.addAll(newSharees);
}
static CollectionType typeFromString(String type) {

View File

@@ -139,7 +139,7 @@ class CollectionsService {
}
}
// remove reference for incoming collections when unshared/deleted
if (collection.isDeleted && ownerID != collection.owner?.id) {
if (collection.isDeleted && ownerID != collection.owner.id) {
await _db.deleteCollection(collection.id);
} else {
// keep entry for deletedCollection as collectionKey may be used during
@@ -394,7 +394,7 @@ class CollectionsService {
final List<Collection> collections =
getCollectionsForUI(includedShared: true);
for (final c in collections) {
if (c.owner!.id == Configuration.instance.getUserID()) {
if (c.owner.id == Configuration.instance.getUserID()) {
if (c.hasSharees || c.hasLink && !c.isQuickLinkCollection()) {
outgoing.add(c);
} else if (c.isQuickLinkCollection()) {
@@ -472,8 +472,8 @@ class CollectionsService {
if (collectionID != null) {
final Collection? collection = getCollectionByID(collectionID);
if (collection != null) {
if (collection.owner?.id == userID) {
_cachedUserIdToUser[userID] = collection.owner!;
if (collection.owner.id == userID) {
_cachedUserIdToUser[userID] = collection.owner;
} else {
final matchingUser = collection.getSharees().firstWhereOrNull(
(u) => u.id == userID,
@@ -698,7 +698,7 @@ class CollectionsService {
);
final encryptedKey = CryptoUtil.base642bin(collection.encryptedKey);
Uint8List? collectionKey;
if (collection.owner?.id == _config.getUserID()) {
if (collection.owner.id == _config.getUserID()) {
// If the collection is owned by the user, decrypt with the master key
if (_config.getKey() == null) {
// Possible during AppStore account migration, where SecureStorage
@@ -767,7 +767,7 @@ class CollectionsService {
) async {
final int ownerID = Configuration.instance.getUserID()!;
try {
if (collection.owner?.id != ownerID) {
if (collection.owner.id != ownerID) {
throw AssertionError("cannot modify albums not owned by you");
}
// read the existing magic metadata and apply new updates to existing data
@@ -826,7 +826,7 @@ class CollectionsService {
) async {
final int ownerID = Configuration.instance.getUserID()!;
try {
if (collection.owner?.id != ownerID) {
if (collection.owner.id != ownerID) {
throw AssertionError("cannot modify albums not owned by you");
}
// read the existing magic metadata and apply new updates to existing data
@@ -885,7 +885,7 @@ class CollectionsService {
) async {
final int ownerID = Configuration.instance.getUserID()!;
try {
if (collection.owner?.id == ownerID) {
if (collection.owner.id == ownerID) {
throw AssertionError("cannot modify sharee settings for albums owned "
"by you");
}
@@ -952,7 +952,7 @@ class CollectionsService {
"enableJoin": true,
},
);
collection.publicURLs?.add(PublicURL.fromMap(response.data["result"]));
collection.publicURLs.add(PublicURL.fromMap(response.data["result"]));
await _db.insert(List.from([collection]));
_collectionIDToCollections[collection.id] = collection;
Bus.instance.fire(
@@ -980,8 +980,8 @@ class CollectionsService {
data: json.encode(prop),
);
// remove existing url information
collection.publicURLs?.clear();
collection.publicURLs?.add(PublicURL.fromMap(response.data["result"]));
collection.publicURLs.clear();
collection.publicURLs.add(PublicURL.fromMap(response.data["result"]));
await _db.insert(List.from([collection]));
_collectionIDToCollections[collection.id] = collection;
Bus.instance.fire(
@@ -1003,7 +1003,7 @@ class CollectionsService {
await _enteDio.delete(
"/collections/share-url/" + collection.id.toString(),
);
collection.publicURLs?.clear();
collection.publicURLs.clear();
await _db.insert(List.from([collection]));
_collectionIDToCollections[collection.id] = collection;
Bus.instance.fire(

View File

@@ -230,7 +230,7 @@ class FavoritesService {
if (_cachedFavoritesCollectionID == null) {
final collections = _collectionsService.getActiveCollections();
for (final collection in collections) {
if (collection.owner!.id == _config.getUserID() &&
if (collection.owner.id == _config.getUserID() &&
collection.type == CollectionType.favorites) {
_cachedFavoritesCollectionID = collection.id;
return collection;

View File

@@ -32,7 +32,7 @@ extension HiddenService on CollectionsService {
final int userID = config.getUserID()!;
final allDefaultHidden = collectionIDToCollections.values
.where(
(element) => element.isDefaultHidden() && element.owner!.id == userID,
(element) => element.isDefaultHidden() && element.owner.id == userID,
)
.toList();
@@ -101,7 +101,7 @@ extension HiddenService on CollectionsService {
collectionIDToCollections.values.firstWhereOrNull(
(element) =>
element.type == CollectionType.uncategorized &&
element.owner!.id == userID,
element.owner.id == userID,
);
if (matchedCollection != null) {
cachedUncategorizedCollection = matchedCollection;
@@ -166,7 +166,9 @@ extension HiddenService on CollectionsService {
await dialog.hide();
} on AssertionError catch (e) {
await dialog.hide();
unawaited(showErrorDialog(context, S.of(context).oops, e.message as String));
unawaited(
showErrorDialog(context, S.of(context).oops, e.message as String),
);
return false;
} catch (e, s) {
_logger.severe("Could not hide", e, s);

View File

@@ -1311,34 +1311,30 @@ class UserService {
for (final c in CollectionsService.instance.getActiveCollections()) {
// Add collaborators and viewers of collections owned by user
if (c.owner?.id == ownerID) {
for (final User? u in c.sharees ?? []) {
if (u != null && u.id != null && u.email.isNotEmpty) {
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) {
} 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);
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 != null &&
u.id != null &&
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 != null &&
u.id != null &&
u.email.isNotEmpty &&
u.isCollaborator) {
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);
@@ -1392,32 +1388,28 @@ class UserService {
for (final c in CollectionsService.instance.getActiveCollections()) {
// Add collaborators and viewers of collections owned by user
if (c.owner?.id == ownerID) {
for (final User? u in c.sharees ?? []) {
if (u != null && u.id != null && u.email.isNotEmpty) {
if (c.owner.id == ownerID) {
for (final User u in c.sharees) {
if (u.id != null && u.email.isNotEmpty) {
if (!emailIDs.contains(u.email)) {
emailIDs.add(u.email);
}
}
}
} else if (c.owner?.id != null && c.owner!.email.isNotEmpty) {
} else if (c.owner.id != null && c.owner.email.isNotEmpty) {
// Add owners of collections shared with user
if (!emailIDs.contains(c.owner!.email)) {
emailIDs.add(c.owner!.email);
if (!emailIDs.contains(c.owner.email)) {
emailIDs.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 != null &&
u.id != null &&
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 != null &&
u.id != null &&
u.email.isNotEmpty &&
u.isCollaborator) {
for (final User u in c.sharees) {
if (u.id != null && u.email.isNotEmpty && u.isCollaborator) {
if (!emailIDs.contains(u.email)) {
emailIDs.add(u.email);
}

View File

@@ -147,7 +147,7 @@ extension CollectionFileActions on CollectionActions {
// Newly created collection might not be cached
final Collection? c =
CollectionsService.instance.getCollectionByID(collectionID);
if (c != null && c.owner!.id != currentUserID) {
if (c != null && c.owner.id != currentUserID) {
if (!showProgressDialog) {
dialog = createProgressDialog(
context,

View File

@@ -340,7 +340,7 @@ class CollectionActions {
) async {
final textTheme = getEnteTextTheme(bContext);
final currentUserID = Configuration.instance.getUserID()!;
if (collection.owner!.id != currentUserID) {
if (collection.owner.id != currentUserID) {
throw AssertionError("Can not delete album owned by others");
}
if (collection.hasSharees) {
@@ -495,7 +495,7 @@ class CollectionActions {
bool isHidden = false,
}) async {
final int currentUserID = Configuration.instance.getUserID()!;
final isCollectionOwner = collection.owner!.id == currentUserID;
final isCollectionOwner = collection.owner.id == currentUserID;
final FilesSplit split = FilesSplit.split(
files,
Configuration.instance.getUserID()!,
@@ -631,7 +631,7 @@ class CollectionActions {
if (targetCollection == null ||
(CollectionType.uncategorized == targetCollection.type ||
targetCollection.type == CollectionType.favorites) ||
targetCollection.owner!.id != userID) {
targetCollection.owner.id != userID) {
return false;
}
return true;

View File

@@ -41,7 +41,7 @@ class AlbumRowItemWidget extends StatelessWidget {
final Widget? linkIcon = c.hasLink && isOwner
? Icon(
Icons.link,
color: c.publicURLs!.first!.isExpired ? warning500 : strokeBaseDark,
color: c.publicURLs.first.isExpired ? warning500 : strokeBaseDark,
)
: null;
return GestureDetector(
@@ -115,7 +115,7 @@ class AlbumRowItemWidget extends StatelessWidget {
bottom: 8.0,
),
child: UserAvatarWidget(
c.owner!,
c.owner,
thumbnailView: true,
),
),

View File

@@ -287,8 +287,8 @@ class AlbumVerticalListWidget extends StatelessWidget {
CollectionActions(CollectionsService.instance);
if (collection.hasLink) {
if (collection.publicURLs!.first!.enableCollect) {
if (Configuration.instance.getUserID() == collection.owner!.id) {
if (collection.publicURLs.first.enableCollect) {
if (Configuration.instance.getUserID() == collection.owner.id) {
unawaited(
routeToPage(
context,
@@ -334,7 +334,7 @@ class AlbumVerticalListWidget extends StatelessWidget {
context,
S.of(context).collaborativeLinkCreatedFor(collection.displayName),
);
if (Configuration.instance.getUserID() == collection.owner!.id) {
if (Configuration.instance.getUserID() == collection.owner.id) {
unawaited(
routeToPage(
context,
@@ -353,7 +353,7 @@ class AlbumVerticalListWidget extends StatelessWidget {
BuildContext context,
Collection collection,
) {
if (Configuration.instance.getUserID() == collection.owner!.id) {
if (Configuration.instance.getUserID() == collection.owner.id) {
unawaited(
routeToPage(
context,

View File

@@ -363,8 +363,8 @@ class _AddParticipantPage extends State<AddParticipantPage> {
List<User> _getSuggestedUser() {
final Set<String> existingEmails = {};
for (final User? u in widget.collection.sharees ?? []) {
if (u != null && u.id != null && u.email.isNotEmpty) {
for (final User u in widget.collection.sharees) {
if (u.id != null && u.email.isNotEmpty) {
existingEmails.add(u.email);
}
}

View File

@@ -64,11 +64,11 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
@override
Widget build(BuildContext context) {
final isOwner =
widget.collection.owner?.id == Configuration.instance.getUserID();
widget.collection.owner.id == Configuration.instance.getUserID();
final colorScheme = getEnteColorScheme(context);
final currentUserID = Configuration.instance.getUserID()!;
final int participants = 1 + widget.collection.getSharees().length;
final User owner = widget.collection.owner!;
final User owner = widget.collection.owner;
if (owner.id == currentUserID && owner.email == "") {
owner.email = Configuration.instance.getEmail()!;
}
@@ -107,11 +107,9 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
captionedTextWidget: CaptionedTextWidget(
title: isOwner
? S.of(context).you
: widget.collection.owner != null
? _nameIfAvailableElseEmail(
widget.collection.owner!,
)
: '',
: _nameIfAvailableElseEmail(
widget.collection.owner,
),
makeTextBold: isOwner,
),
leadingIconWidget: UserAvatarWidget(

View File

@@ -47,13 +47,13 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
@override
Widget build(BuildContext context) {
final isCollectEnabled =
widget.collection!.publicURLs?.firstOrNull?.enableCollect ?? false;
widget.collection!.publicURLs.firstOrNull?.enableCollect ?? false;
final isDownloadEnabled =
widget.collection!.publicURLs?.firstOrNull?.enableDownload ?? true;
widget.collection!.publicURLs.firstOrNull?.enableDownload ?? true;
final isPasswordEnabled =
widget.collection!.publicURLs?.firstOrNull?.passwordEnabled ?? false;
widget.collection!.publicURLs.firstOrNull?.passwordEnabled ?? false;
final enteColorScheme = getEnteColorScheme(context);
final PublicURL url = widget.collection!.publicURLs!.firstOrNull!;
final PublicURL url = widget.collection!.publicURLs.firstOrNull!;
final String collectionKey = Base58Encode(
CollectionsService.instance.getCollectionKey(widget.collection!.id),
);

View File

@@ -72,7 +72,7 @@ class _ItemsWidgetState extends State<ItemsWidget> {
bool isCustomLimit = false;
@override
void initState() {
currentDeviceLimit = widget.collection.publicURLs!.first!.deviceLimit;
currentDeviceLimit = widget.collection.publicURLs.first.deviceLimit;
initialDeviceLimit = currentDeviceLimit;
if (!publicLinkDeviceLimits.contains(currentDeviceLimit)) {
isCustomLimit = true;

View File

@@ -61,7 +61,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
@override
Widget build(BuildContext context) {
_sharees = widget.collection.sharees ?? [];
_sharees = widget.collection.sharees;
final bool hasUrl = widget.collection.hasLink;
final children = <Widget>[];
children.add(
@@ -136,7 +136,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
}
final bool hasExpired =
widget.collection.publicURLs?.firstOrNull?.isExpired ?? false;
widget.collection.publicURLs.firstOrNull?.isExpired ?? false;
children.addAll([
const SizedBox(
height: 24,
@@ -166,7 +166,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
CollectionsService.instance.getCollectionKey(widget.collection.id),
);
final String url =
"${widget.collection.publicURLs!.first!.url}#$collectionKey";
"${widget.collection.publicURLs.first.url}#$collectionKey";
children.addAll(
[
MenuItemWidget(

View File

@@ -275,7 +275,7 @@ class _HomeWidgetState extends State<HomeWidget> {
final existingCollection =
CollectionsService.instance.getCollectionByID(collection.id);
if (collection.owner!.id! == Configuration.instance.getUserID() ||
if (collection.isOwner(Configuration.instance.getUserID() ?? -1) ||
(existingCollection != null && !existingCollection.isDeleted)) {
await routeToPage(
context,
@@ -286,8 +286,8 @@ class _HomeWidgetState extends State<HomeWidget> {
return;
}
final dialog = createProgressDialog(context, "Loading...");
final publicUrl = collection.publicURLs![0];
if (!publicUrl!.enableDownload) {
final publicUrl = collection.publicURLs[0];
if (!publicUrl.enableDownload) {
await showErrorDialog(
context,
context.l10n.canNotOpenTitle,

View File

@@ -109,7 +109,7 @@ class QuickLinkAlbumItem extends StatelessWidget {
style: textTheme.smallMuted,
),
c.hasLink
? (c.publicURLs!.first!.isExpired
? (c.publicURLs.first.isExpired
? Icon(
Icons.link_outlined,
color: colorScheme.warning500,

View File

@@ -804,7 +804,7 @@ class _FileSelectionActionsWidgetState
.getCollectionKey(_cachedCollectionForSharedLink!.id),
);
final String url =
"${_cachedCollectionForSharedLink!.publicURLs?.first?.url}#$collectionKey";
"${_cachedCollectionForSharedLink!.publicURLs.first.url}#$collectionKey";
unawaited(Clipboard.setData(ClipboardData(text: url)));
await shareImageAndUrl(
placeholderBytes,

View File

@@ -48,7 +48,7 @@ class _EmptyAlbumStateNewState extends State<CollectPhotosBottomButtons> {
final String collectionKey = Base58Encode(
CollectionsService.instance.getCollectionKey(widget.c.id),
);
final String url = "${widget.c.publicURLs!.first!.url}#$collectionKey";
final String url = "${widget.c.publicURLs.first.url}#$collectionKey";
await shareAlbumLinkWithPlaceholder(
context,
widget.c,

View File

@@ -820,7 +820,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
"Cannot share empty collection of type $galleryType",
);
}
if (Configuration.instance.getUserID() == widget.collection!.owner!.id) {
if (Configuration.instance.getUserID() == widget.collection!.owner.id) {
unawaited(
routeToPage(
context,