From e829386f1e477cffc9b54515a4329aa34d8eb38e Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Fri, 20 Jun 2025 16:34:19 +1000 Subject: [PATCH 01/52] Remove unused data from recipient class --- .../avatars/SystemContactPhoto.java | 66 --- .../libsession/database/StorageProtocol.kt | 8 - .../ReceivedMessageHandler.kt | 11 - .../utilities/recipients/Recipient.java | 411 +----------------- .../recipients/RecipientProvider.java | 34 +- .../securesms/configs/ConfigToDatabaseSync.kt | 1 - .../securesms/database/RecipientDatabase.java | 111 +---- .../securesms/database/SmsDatabase.java | 4 - .../securesms/database/Storage.kt | 27 -- .../securesms/database/ThreadDatabase.java | 2 - .../securesms/database/model/NotifyType.java | 13 + .../AbstractNotificationBuilder.java | 9 +- .../notifications/DefaultMessageNotifier.kt | 4 +- .../MultipleRecipientNotificationBuilder.kt | 6 - .../notifications/NotificationState.java | 10 +- .../SingleRecipientNotificationBuilder.java | 4 - .../securesms/util/MockDataGenerator.kt | 1 - 17 files changed, 53 insertions(+), 669 deletions(-) delete mode 100644 app/src/main/java/org/session/libsession/avatars/SystemContactPhoto.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/model/NotifyType.java diff --git a/app/src/main/java/org/session/libsession/avatars/SystemContactPhoto.java b/app/src/main/java/org/session/libsession/avatars/SystemContactPhoto.java deleted file mode 100644 index f4d31f7055..0000000000 --- a/app/src/main/java/org/session/libsession/avatars/SystemContactPhoto.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.session.libsession.avatars; - -import android.content.Context; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - - -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.Conversions; - -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.security.MessageDigest; - -public class SystemContactPhoto implements ContactPhoto { - - private final @NonNull - Address address; - private final @NonNull Uri contactPhotoUri; - private final long lastModifiedTime; - - public SystemContactPhoto(@NonNull Address address, @NonNull Uri contactPhotoUri, long lastModifiedTime) { - this.address = address; - this.contactPhotoUri = contactPhotoUri; - this.lastModifiedTime = lastModifiedTime; - } - - @Override - public InputStream openInputStream(Context context) throws FileNotFoundException { - return context.getContentResolver().openInputStream(contactPhotoUri); - } - - @Override - public @Nullable Uri getUri(@NonNull Context context) { - return contactPhotoUri; - } - - @Override - public boolean isProfilePhoto() { - return false; - } - - @Override - public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { - messageDigest.update(address.toString().getBytes()); - messageDigest.update(contactPhotoUri.toString().getBytes()); - messageDigest.update(Conversions.longToByteArray(lastModifiedTime)); - } - - @Override - public boolean equals(Object other) { - if (other == null || !(other instanceof SystemContactPhoto)) return false; - - SystemContactPhoto that = (SystemContactPhoto)other; - - return this.address.equals(that.address) && this.contactPhotoUri.equals(that.contactPhotoUri) && this.lastModifiedTime == that.lastModifiedTime; - } - - @Override - public int hashCode() { - return address.hashCode() ^ contactPhotoUri.hashCode() ^ (int)lastModifiedTime; - } - -} diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 971fa482ee..d4ce777407 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -176,9 +176,6 @@ interface StorageProtocol { // Groups fun getAllGroups(includeInactive: Boolean): List - // Settings - fun setProfileSharing(address: Address, value: Boolean) - // Thread fun getOrCreateThreadIdFor(address: Address): Long fun getThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?, createThread: Boolean): Long? @@ -274,11 +271,6 @@ interface StorageProtocol { fun getExpirationConfiguration(threadId: Long): ExpirationConfiguration? fun setExpirationConfiguration(config: ExpirationConfiguration) fun getExpiringMessages(messageIds: List = emptyList()): List> - fun updateDisappearingState( - messageSender: String, - threadID: Long, - disappearingState: Recipient.DisappearingState - ) // Shared configs fun conversationInConfig(publicKey: String?, groupPublicKey: String?, openGroupId: String?, visibleOnly: Boolean): Boolean diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 6a382903c9..ddb8450178 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -374,17 +374,6 @@ fun MessageReceiver.handleVisibleMessage( if (userPublicKey != messageSender && !isUserBlindedSender) { context.storage.setBlocksCommunityMessageRequests(recipient, message.blocksMessageRequests) } - - // update the disappearing / legacy banner for the sender - val disappearingState = when { - proto.dataMessage.expireTimer > 0 && !proto.hasExpirationType() -> Recipient.DisappearingState.LEGACY - else -> Recipient.DisappearingState.UPDATED - } - context.storage.updateDisappearingState( - messageSender, - context.threadId, - disappearingState - ) } // Handle group invite response if new closed group if (context.threadRecipient?.isGroupV2Recipient == true) { diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java index 71c0874477..73652ef802 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java +++ b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java @@ -31,7 +31,6 @@ import org.session.libsession.avatars.ContactPhoto; import org.session.libsession.avatars.GroupRecordContactPhoto; import org.session.libsession.avatars.ProfileContactPhoto; -import org.session.libsession.avatars.SystemContactPhoto; import org.session.libsession.avatars.TransparentContactPhoto; import org.session.libsession.messaging.MessagingModuleConfiguration; import org.session.libsession.messaging.contacts.Contact; @@ -48,6 +47,7 @@ import org.session.libsession.utilities.recipients.RecipientProvider.RecipientDetails; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.guava.Optional; +import org.thoughtcrime.securesms.database.model.NotifyType; import java.util.Arrays; import java.util.Collections; @@ -71,35 +71,22 @@ public class Recipient implements RecipientModifiedListener, Cloneable { private final Context context; private @Nullable String name; - private @Nullable String customLabel; private boolean resolving; private boolean isLocalNumber; - private @Nullable Uri systemContactPhoto; private @Nullable Long groupAvatarId; - private Uri contactUri; - private @Nullable Uri messageRingtone = null; - private @Nullable Uri callRingtone = null; public long mutedUntil = 0; + @NotifyType public int notifyType = 0; private boolean autoDownloadAttachments = false; private boolean blocked = false; private boolean approved = false; private boolean approvedMe = false; - private DisappearingState disappearingState = null; - private VibrateState messageVibrate = VibrateState.DEFAULT; - private VibrateState callVibrate = VibrateState.DEFAULT; - private int expireMessages = 0; - private Optional defaultSubscriptionId = Optional.absent(); - private @NonNull RegisteredState registered = RegisteredState.UNKNOWN; - - private @Nullable MaterialColor color; + private @Nullable byte[] profileKey; private @Nullable String profileName; private @Nullable String profileAvatar; - private boolean profileSharing; private String notificationChannel; - private boolean forceSmsSelection; private boolean blocksCommunityMessageRequests; @SuppressWarnings("ConstantConditions") @@ -131,36 +118,21 @@ public static boolean removeCached(@NonNull Address address) { { this.context = context.getApplicationContext(); this.address = address; - this.color = null; this.resolving = true; if (stale != null) { this.name = stale.name; - this.contactUri = stale.contactUri; - this.systemContactPhoto = stale.systemContactPhoto; this.groupAvatarId = stale.groupAvatarId; this.isLocalNumber = stale.isLocalNumber; - this.color = stale.color; - this.customLabel = stale.customLabel; - this.messageRingtone = stale.messageRingtone; - this.callRingtone = stale.callRingtone; this.mutedUntil = stale.mutedUntil; this.blocked = stale.blocked; this.approved = stale.approved; this.approvedMe = stale.approvedMe; - this.messageVibrate = stale.messageVibrate; - this.callVibrate = stale.callVibrate; - this.expireMessages = stale.expireMessages; - this.defaultSubscriptionId = stale.defaultSubscriptionId; - this.registered = stale.registered; this.notificationChannel = stale.notificationChannel; this.profileKey = stale.profileKey; this.profileName = stale.profileName; this.profileAvatar = stale.profileAvatar; - this.profileSharing = stale.profileSharing; - this.forceSmsSelection = stale.forceSmsSelection; this.notifyType = stale.notifyType; - this.disappearingState = stale.disappearingState; this.autoDownloadAttachments = stale.autoDownloadAttachments; this.participants.clear(); @@ -169,31 +141,19 @@ public static boolean removeCached(@NonNull Address address) { if (details.isPresent()) { this.name = details.get().name; - this.systemContactPhoto = details.get().systemContactPhoto; this.groupAvatarId = details.get().groupAvatarId; this.isLocalNumber = details.get().isLocalNumber; - this.color = details.get().color; - this.messageRingtone = details.get().messageRingtone; - this.callRingtone = details.get().callRingtone; this.mutedUntil = details.get().mutedUntil; this.blocked = details.get().blocked; this.approved = details.get().approved; this.approvedMe = details.get().approvedMe; - this.messageVibrate = details.get().messageVibrateState; - this.callVibrate = details.get().callVibrateState; - this.expireMessages = details.get().expireMessages; - this.defaultSubscriptionId = details.get().defaultSubscriptionId; - this.registered = details.get().registered; this.notificationChannel = details.get().notificationChannel; this.profileKey = details.get().profileKey; this.profileName = details.get().profileName; this.profileAvatar = details.get().profileAvatar; - this.profileSharing = details.get().profileSharing; - this.forceSmsSelection = details.get().forceSmsSelection; this.notifyType = details.get().notifyType; this.autoDownloadAttachments = details.get().autoDownloadAttachments; this.blocksCommunityMessageRequests = details.get().blocksCommunityMessageRequests; - this.disappearingState = details.get().disappearingState; this.participants.clear(); this.participants.addAll(details.get().participants); @@ -205,31 +165,17 @@ public void onSuccess(RecipientDetails result) { if (result != null) { synchronized (Recipient.this) { Recipient.this.name = result.name; - Recipient.this.contactUri = result.contactUri; - Recipient.this.systemContactPhoto = result.systemContactPhoto; Recipient.this.groupAvatarId = result.groupAvatarId; Recipient.this.isLocalNumber = result.isLocalNumber; - Recipient.this.color = result.color; - Recipient.this.customLabel = result.customLabel; - Recipient.this.messageRingtone = result.messageRingtone; - Recipient.this.callRingtone = result.callRingtone; Recipient.this.mutedUntil = result.mutedUntil; Recipient.this.blocked = result.blocked; Recipient.this.approved = result.approved; Recipient.this.approvedMe = result.approvedMe; - Recipient.this.messageVibrate = result.messageVibrateState; - Recipient.this.callVibrate = result.callVibrateState; - Recipient.this.expireMessages = result.expireMessages; - Recipient.this.defaultSubscriptionId = result.defaultSubscriptionId; - Recipient.this.registered = result.registered; Recipient.this.notificationChannel = result.notificationChannel; Recipient.this.profileKey = result.profileKey; Recipient.this.profileName = result.profileName; Recipient.this.profileAvatar = result.profileAvatar; - Recipient.this.profileSharing = result.profileSharing; - Recipient.this.forceSmsSelection = result.forceSmsSelection; Recipient.this.notifyType = result.notifyType; - Recipient.this.disappearingState = result.disappearingState; Recipient.this.autoDownloadAttachments = result.autoDownloadAttachments; Recipient.this.blocksCommunityMessageRequests = result.blocksCommunityMessageRequests; @@ -259,32 +205,19 @@ public void onFailure(ExecutionException error) { Recipient(@NonNull Context context, @NonNull Address address, @NonNull RecipientDetails details) { this.context = context.getApplicationContext(); this.address = address; - this.contactUri = details.contactUri; this.name = details.name; - this.systemContactPhoto = details.systemContactPhoto; this.groupAvatarId = details.groupAvatarId; this.isLocalNumber = details.isLocalNumber; - this.color = details.color; - this.customLabel = details.customLabel; - this.messageRingtone = details.messageRingtone; - this.callRingtone = details.callRingtone; this.mutedUntil = details.mutedUntil; this.notifyType = details.notifyType; this.autoDownloadAttachments = details.autoDownloadAttachments; this.blocked = details.blocked; this.approved = details.approved; this.approvedMe = details.approvedMe; - this.messageVibrate = details.messageVibrateState; - this.callVibrate = details.callVibrateState; - this.expireMessages = details.expireMessages; - this.defaultSubscriptionId = details.defaultSubscriptionId; - this.registered = details.registered; this.notificationChannel = details.notificationChannel; this.profileKey = details.profileKey; this.profileName = details.profileName; this.profileAvatar = details.profileAvatar; - this.profileSharing = details.profileSharing; - this.forceSmsSelection = details.forceSmsSelection; this.blocksCommunityMessageRequests = details.blocksCommunityMessageRequests; this.participants.addAll(details.participants); @@ -295,23 +228,6 @@ public boolean isLocalNumber() { return isLocalNumber; } - public synchronized @Nullable Uri getContactUri() { - return this.contactUri; - } - - public void setContactUri(@Nullable Uri contactUri) { - boolean notify = false; - - synchronized (this) { - if (!Util.equals(contactUri, this.contactUri)) { - this.contactUri = contactUri; - notify = true; - } - } - - if (notify) notifyListeners(); - } - public synchronized @NonNull String getName() { UsernameUtils usernameUtils = MessagingModuleConfiguration.getShared().getUsernameUtils(); String accountID = this.address.toString(); @@ -360,52 +276,14 @@ public void setBlocksCommunityMessageRequests(boolean blocksCommunityMessageRequ public synchronized @NonNull MaterialColor getColor() { if (isGroupOrCommunityRecipient()) return MaterialColor.GROUP; - else if (color != null) return color; else if (name != null) return ContactColors.generateFor(name); else return ContactColors.UNKNOWN_COLOR; } - public void setColor(@NonNull MaterialColor color) { - synchronized (this) { - this.color = color; - } - - notifyListeners(); - } - public @NonNull Address getAddress() { return address; } - public synchronized @Nullable String getCustomLabel() { - return customLabel; - } - - public void setCustomLabel(@Nullable String customLabel) { - boolean notify = false; - - synchronized (this) { - if (!Util.equals(customLabel, this.customLabel)) { - this.customLabel = customLabel; - notify = true; - } - } - - if (notify) notifyListeners(); - } - - public synchronized Optional getDefaultSubscriptionId() { - return defaultSubscriptionId; - } - - public void setDefaultSubscriptionId(Optional defaultSubscriptionId) { - synchronized (this) { - this.defaultSubscriptionId = defaultSubscriptionId; - } - - notifyListeners(); - } - public synchronized @Nullable String getProfileName() { return profileName; } @@ -431,18 +309,6 @@ public void setProfileAvatar(@Nullable String profileAvatar) { EventBus.getDefault().post(new ProfilePictureModifiedEvent(this)); } - public synchronized boolean isProfileSharing() { - return profileSharing; - } - - public void setProfileSharing(boolean value) { - synchronized (this) { - this.profileSharing = value; - } - - notifyListeners(); - } - public boolean isGroupOrCommunityRecipient() { return address.isGroupOrCommunity(); } @@ -517,24 +383,10 @@ public synchronized void removeListener(RecipientModifiedListener listener) { public synchronized @Nullable ContactPhoto getContactPhoto() { if (isLocalNumber) return new ProfileContactPhoto(address, String.valueOf(TextSecurePreferences.getProfileAvatarId(context))); else if (isGroupOrCommunityRecipient() && groupAvatarId != null) return new GroupRecordContactPhoto(address, groupAvatarId); - else if (systemContactPhoto != null) return new SystemContactPhoto(address, systemContactPhoto, 0); else if (profileAvatar != null) return new ProfileContactPhoto(address, profileAvatar); else return null; } - public void setSystemContactPhoto(@Nullable Uri systemContactPhoto) { - boolean notify = false; - - synchronized (this) { - if (!Util.equals(systemContactPhoto, this.systemContactPhoto)) { - this.systemContactPhoto = systemContactPhoto; - notify = true; - } - } - - if (notify) notifyListeners(); - } - public void setGroupAvatarId(@Nullable Long groupAvatarId) { boolean notify = false; @@ -553,38 +405,6 @@ public synchronized Long getGroupAvatarId() { return groupAvatarId; } - public synchronized @Nullable Uri getMessageRingtone() { - if (messageRingtone != null && messageRingtone.getScheme() != null && messageRingtone.getScheme().startsWith("file")) { - return null; - } - - return messageRingtone; - } - - public void setMessageRingtone(@Nullable Uri ringtone) { - synchronized (this) { - this.messageRingtone = ringtone; - } - - notifyListeners(); - } - - public synchronized @Nullable Uri getCallRingtone() { - if (callRingtone != null && callRingtone.getScheme() != null && callRingtone.getScheme().startsWith("file")) { - return null; - } - - return callRingtone; - } - - public void setCallRingtone(@Nullable Uri ringtone) { - synchronized (this) { - this.callRingtone = ringtone; - } - - notifyListeners(); - } - public synchronized boolean isMuted() { return System.currentTimeMillis() <= mutedUntil; } @@ -597,7 +417,7 @@ public void setMuted(long mutedUntil) { notifyListeners(); } - public void setNotifyType(int notifyType) { + public void setNotifyType(@NotifyType int notifyType) { synchronized (this) { this.notifyType = notifyType; } @@ -653,73 +473,6 @@ public void setHasApprovedMe(boolean approvedMe) { notifyListeners(); } - public synchronized VibrateState getMessageVibrate() { - return messageVibrate; - } - - public void setMessageVibrate(VibrateState vibrate) { - synchronized (this) { - this.messageVibrate = vibrate; - } - - notifyListeners(); - } - - public synchronized VibrateState getCallVibrate() { - return callVibrate; - } - - public void setCallVibrate(VibrateState vibrate) { - synchronized (this) { - this.callVibrate = vibrate; - } - - notifyListeners(); - } - - public synchronized int getExpireMessages() { - return expireMessages; - } - - public void setExpireMessages(int expireMessages) { - synchronized (this) { - this.expireMessages = expireMessages; - } - - notifyListeners(); - } - - public synchronized DisappearingState getDisappearingState() { - return disappearingState; - } - - public void setDisappearingState(DisappearingState disappearingState) { - synchronized (this) { - this.disappearingState = disappearingState; - } - - notifyListeners(); - } - - public synchronized RegisteredState getRegistered() { - if (isPushGroupRecipient()) return RegisteredState.REGISTERED; - - return registered; - } - - public void setRegistered(@NonNull RegisteredState value) { - boolean notify = false; - - synchronized (this) { - if (this.registered != value) { - this.registered = value; - notify = true; - } - } - - if (notify) notifyListeners(); - } - public synchronized @Nullable String getNotificationChannel() { return notificationChannel; } @@ -737,18 +490,6 @@ public void setNotificationChannel(@Nullable String value) { if (notify) notifyListeners(); } - public boolean isForceSmsSelection() { - return forceSmsSelection; - } - - public void setForceSmsSelection(boolean value) { - synchronized (this) { - this.forceSmsSelection = value; - } - - notifyListeners(); - } - public synchronized @Nullable byte[] getProfileKey() { return profileKey; } @@ -781,10 +522,8 @@ public boolean equals(Object o) { && blocked == recipient.blocked && approved == recipient.approved && approvedMe == recipient.approvedMe - && expireMessages == recipient.expireMessages && address.equals(recipient.address) && Objects.equals(name, recipient.name) - && Objects.equals(customLabel, recipient.customLabel) && Objects.equals(groupAvatarId, recipient.groupAvatarId) && Arrays.equals(profileKey, recipient.profileKey) && Objects.equals(profileName, recipient.profileName) @@ -797,7 +536,6 @@ public int hashCode() { int result = Objects.hash( address, name, - customLabel, resolving, groupAvatarId, mutedUntil, @@ -805,7 +543,6 @@ public int hashCode() { blocked, approved, approvedMe, - expireMessages, profileName, profileAvatar, blocksCommunityMessageRequests @@ -834,24 +571,6 @@ public synchronized boolean isResolving() { return resolving; } - public enum VibrateState { - DEFAULT(0), ENABLED(1), DISABLED(2); - - private final int id; - - VibrateState(int id) { - this.id = id; - } - - public int getId() { - return id; - } - - public static VibrateState fromId(int id) { - return values()[id]; - } - } - public enum DisappearingState { LEGACY(0), UPDATED(1); @@ -870,24 +589,6 @@ public static DisappearingState fromId(int id) { } } - public enum RegisteredState { - UNKNOWN(0), REGISTERED(1), NOT_REGISTERED(2); - - private final int id; - - RegisteredState(int id) { - this.id = id; - } - - public int getId() { - return id; - } - - public static RegisteredState fromId(int id) { - return values()[id]; - } - } - public static class RecipientSettings { private final boolean blocked; private final boolean approved; @@ -895,49 +596,23 @@ public static class RecipientSettings { private final long muteUntil; private final int notifyType; private final boolean autoDownloadAttachments; - private final DisappearingState disappearingState; - private final VibrateState messageVibrateState; - private final VibrateState callVibrateState; - private final Uri messageRingtone; - private final Uri callRingtone; - private final MaterialColor color; - private final int defaultSubscriptionId; private final int expireMessages; - private final RegisteredState registered; private final byte[] profileKey; private final String systemDisplayName; - private final String systemContactPhoto; - private final String systemPhoneLabel; - private final String systemContactUri; private final String signalProfileName; private final String signalProfileAvatar; - private final boolean profileSharing; private final String notificationChannel; - private final boolean forceSmsSelection; private final boolean blocksCommunityMessageRequests; public RecipientSettings(boolean blocked, boolean approved, boolean approvedMe, long muteUntil, int notifyType, boolean autoDownloadAttachments, - @NonNull DisappearingState disappearingState, - @NonNull VibrateState messageVibrateState, - @NonNull VibrateState callVibrateState, - @Nullable Uri messageRingtone, - @Nullable Uri callRingtone, - @Nullable MaterialColor color, - int defaultSubscriptionId, - int expireMessages, - @NonNull RegisteredState registered, - @Nullable byte[] profileKey, - @Nullable String systemDisplayName, - @Nullable String systemContactPhoto, - @Nullable String systemPhoneLabel, - @Nullable String systemContactUri, - @Nullable String signalProfileName, - @Nullable String signalProfileAvatar, - boolean profileSharing, - @Nullable String notificationChannel, - boolean forceSmsSelection, + int expireMessages, + @Nullable byte[] profileKey, + @Nullable String systemDisplayName, + @Nullable String signalProfileName, + @Nullable String signalProfileAvatar, + @Nullable String notificationChannel, boolean blocksCommunityMessageRequests ) { @@ -947,32 +622,15 @@ public RecipientSettings(boolean blocked, boolean approved, boolean approvedMe, this.muteUntil = muteUntil; this.notifyType = notifyType; this.autoDownloadAttachments = autoDownloadAttachments; - this.disappearingState = disappearingState; - this.messageVibrateState = messageVibrateState; - this.callVibrateState = callVibrateState; - this.messageRingtone = messageRingtone; - this.callRingtone = callRingtone; - this.color = color; - this.defaultSubscriptionId = defaultSubscriptionId; this.expireMessages = expireMessages; - this.registered = registered; this.profileKey = profileKey; this.systemDisplayName = systemDisplayName; - this.systemContactPhoto = systemContactPhoto; - this.systemPhoneLabel = systemPhoneLabel; - this.systemContactUri = systemContactUri; this.signalProfileName = signalProfileName; this.signalProfileAvatar = signalProfileAvatar; - this.profileSharing = profileSharing; this.notificationChannel = notificationChannel; - this.forceSmsSelection = forceSmsSelection; this.blocksCommunityMessageRequests = blocksCommunityMessageRequests; } - public @Nullable MaterialColor getColor() { - return color; - } - public boolean isBlocked() { return blocked; } @@ -989,46 +647,19 @@ public long getMuteUntil() { return muteUntil; } + @NotifyType public int getNotifyType() { return notifyType; } - public @NonNull DisappearingState getDisappearingState() { - return disappearingState; - } - public boolean getAutoDownloadAttachments() { return autoDownloadAttachments; } - public @NonNull VibrateState getMessageVibrateState() { - return messageVibrateState; - } - - public @NonNull VibrateState getCallVibrateState() { - return callVibrateState; - } - - public @Nullable Uri getMessageRingtone() { - return messageRingtone; - } - - public @Nullable Uri getCallRingtone() { - return callRingtone; - } - - public Optional getDefaultSubscriptionId() { - return defaultSubscriptionId != -1 ? Optional.of(defaultSubscriptionId) : Optional.absent(); - } - public int getExpireMessages() { return expireMessages; } - public RegisteredState getRegistered() { - return registered; - } - public @Nullable byte[] getProfileKey() { return profileKey; } @@ -1037,18 +668,6 @@ public RegisteredState getRegistered() { return systemDisplayName; } - public @Nullable String getSystemContactPhotoUri() { - return systemContactPhoto; - } - - public @Nullable String getSystemPhoneLabel() { - return systemPhoneLabel; - } - - public @Nullable String getSystemContactUri() { - return systemContactUri; - } - public @Nullable String getProfileName() { return signalProfileName; } @@ -1057,18 +676,10 @@ public RegisteredState getRegistered() { return signalProfileAvatar; } - public boolean isProfileSharing() { - return profileSharing; - } - public @Nullable String getNotificationChannel() { return notificationChannel; } - public boolean isForceSmsSelection() { - return forceSmsSelection; - } - public boolean getBlocksCommunityMessageRequests() { return blocksCommunityMessageRequests; } diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java index bb65805268..292be9479b 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java +++ b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java @@ -17,24 +17,18 @@ package org.session.libsession.utilities.recipients; import android.content.Context; -import android.net.Uri; import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import network.loki.messenger.R; import org.session.libsession.messaging.MessagingModuleConfiguration; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.GroupRecord; import org.session.libsession.utilities.ListenableFutureTask; -import org.session.libsession.utilities.MaterialColor; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.Recipient.DisappearingState; import org.session.libsession.utilities.recipients.Recipient.RecipientSettings; -import org.session.libsession.utilities.recipients.Recipient.RegisteredState; -import org.session.libsession.utilities.recipients.Recipient.VibrateState; import org.session.libsignal.utilities.guava.Optional; import java.util.LinkedList; @@ -44,6 +38,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; +import network.loki.messenger.R; + class RecipientProvider { @SuppressWarnings("unused") @@ -150,34 +146,21 @@ boolean removeCached(@NonNull Address address) { static class RecipientDetails { @Nullable final String name; - @Nullable final String customLabel; - @Nullable final Uri systemContactPhoto; - @Nullable final Uri contactUri; @Nullable final Long groupAvatarId; - @Nullable final MaterialColor color; - @Nullable final Uri messageRingtone; - @Nullable final Uri callRingtone; final long mutedUntil; final int notifyType; - @Nullable final DisappearingState disappearingState; final boolean autoDownloadAttachments; - @Nullable final VibrateState messageVibrateState; - @Nullable final VibrateState callVibrateState; final boolean blocked; final boolean approved; final boolean approvedMe; final int expireMessages; @NonNull final List participants; @Nullable final String profileName; - final Optional defaultSubscriptionId; - @NonNull final RegisteredState registered; @Nullable final byte[] profileKey; @Nullable final String profileAvatar; - final boolean profileSharing; final boolean systemContact; final boolean isLocalNumber; @Nullable final String notificationChannel; - final boolean forceSmsSelection; final boolean blocksCommunityMessageRequests; RecipientDetails(@Nullable String name, @Nullable Long groupAvatarId, @@ -185,33 +168,20 @@ static class RecipientDetails { @Nullable List participants) { this.groupAvatarId = groupAvatarId; - this.systemContactPhoto = settings != null ? Util.uri(settings.getSystemContactPhotoUri()) : null; - this.customLabel = settings != null ? settings.getSystemPhoneLabel() : null; - this.contactUri = settings != null ? Util.uri(settings.getSystemContactUri()) : null; - this.color = settings != null ? settings.getColor() : null; - this.messageRingtone = settings != null ? settings.getMessageRingtone() : null; - this.callRingtone = settings != null ? settings.getCallRingtone() : null; this.mutedUntil = settings != null ? settings.getMuteUntil() : 0; this.notifyType = settings != null ? settings.getNotifyType() : 0; this.autoDownloadAttachments = settings != null && settings.getAutoDownloadAttachments(); - this.disappearingState = settings != null ? settings.getDisappearingState() : null; - this.messageVibrateState = settings != null ? settings.getMessageVibrateState() : null; - this.callVibrateState = settings != null ? settings.getCallVibrateState() : null; this.blocked = settings != null && settings.isBlocked(); this.approved = settings != null && settings.isApproved(); this.approvedMe = settings != null && settings.hasApprovedMe(); this.expireMessages = settings != null ? settings.getExpireMessages() : 0; this.participants = participants == null ? new LinkedList<>() : participants; this.profileName = settings != null ? settings.getProfileName() : null; - this.defaultSubscriptionId = settings != null ? settings.getDefaultSubscriptionId() : Optional.absent(); - this.registered = settings != null ? settings.getRegistered() : RegisteredState.UNKNOWN; this.profileKey = settings != null ? settings.getProfileKey() : null; this.profileAvatar = settings != null ? settings.getProfileAvatar() : null; - this.profileSharing = settings != null && settings.isProfileSharing(); this.systemContact = systemContact; this.isLocalNumber = isLocalNumber; this.notificationChannel = settings != null ? settings.getNotificationChannel() : null; - this.forceSmsSelection = settings != null && settings.isForceSmsSelection(); this.blocksCommunityMessageRequests = settings != null && settings.getBlocksCommunityMessageRequests(); if (name == null && settings != null) this.name = settings.getSystemDisplayName(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt index 3f1ca68714..98d16b2f66 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt @@ -374,7 +374,6 @@ class ConfigToDatabaseSync @Inject constructor( val title = group.name val formationTimestamp = (group.joinedAtSecs * 1000L) storage.createGroup(groupId, title, admins + members, null, null, admins, formationTimestamp) - storage.setProfileSharing(fromSerialized(groupId), true) // Add the group to the user's set of public keys to poll for storage.addClosedGroupPublicKey(group.accountId) // Store the encryption key pair diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index daec3fe928..ddc19abcfb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -10,11 +10,8 @@ import com.annimon.stream.Stream; import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.MaterialColor; -import org.session.libsession.utilities.Util; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.Recipient.RecipientSettings; -import org.session.libsession.utilities.recipients.Recipient.RegisteredState; import org.session.libsignal.utilities.Base64; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.guava.Optional; @@ -36,28 +33,41 @@ public class RecipientDatabase extends Database { static final String BLOCK = "block"; static final String APPROVED = "approved"; private static final String APPROVED_ME = "approved_me"; + @Deprecated(forRemoval = true) private static final String NOTIFICATION = "notification"; + @Deprecated(forRemoval = true) private static final String VIBRATE = "vibrate"; private static final String MUTE_UNTIL = "mute_until"; + @Deprecated(forRemoval = true) private static final String COLOR = "color"; private static final String SEEN_INVITE_REMINDER = "seen_invite_reminder"; + @Deprecated(forRemoval = true) private static final String DEFAULT_SUBSCRIPTION_ID = "default_subscription_id"; static final String EXPIRE_MESSAGES = "expire_messages"; - private static final String DISAPPEARING_STATE = "disappearing_state"; + @Deprecated(forRemoval = true) + private static final String DISAPPEARING_STATE = "disappearing_state"; + @Deprecated(forRemoval = true) private static final String REGISTERED = "registered"; private static final String PROFILE_KEY = "profile_key"; private static final String SYSTEM_DISPLAY_NAME = "system_display_name"; + @Deprecated(forRemoval = true) private static final String SYSTEM_PHOTO_URI = "system_contact_photo"; + @Deprecated(forRemoval = true) private static final String SYSTEM_PHONE_LABEL = "system_phone_label"; + @Deprecated(forRemoval = true) private static final String SYSTEM_CONTACT_URI = "system_contact_uri"; private static final String SIGNAL_PROFILE_NAME = "signal_profile_name"; private static final String SESSION_PROFILE_AVATAR = "signal_profile_avatar"; + @Deprecated(forRemoval = true) private static final String PROFILE_SHARING = "profile_sharing_approval"; + @Deprecated(forRemoval = true) private static final String CALL_RINGTONE = "call_ringtone"; + @Deprecated(forRemoval = true) private static final String CALL_VIBRATE = "call_vibrate"; private static final String NOTIFICATION_CHANNEL = "notification_channel"; @Deprecated(forRemoval = true) private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode"; + @Deprecated(forRemoval = true) private static final String FORCE_SMS_SELECTION = "force_sms_selection"; private static final String NOTIFY_TYPE = "notify_type"; // all, mentions only, none @Deprecated(forRemoval = true) @@ -83,7 +93,7 @@ public class RecipientDatabase extends Database { ADDRESS + " TEXT UNIQUE, " + BLOCK + " INTEGER DEFAULT 0," + NOTIFICATION + " TEXT DEFAULT NULL, " + - VIBRATE + " INTEGER DEFAULT " + Recipient.VibrateState.DEFAULT.getId() + ", " + + VIBRATE + " INTEGER DEFAULT 0, " + MUTE_UNTIL + " INTEGER DEFAULT 0, " + COLOR + " TEXT DEFAULT NULL, " + SEEN_INVITE_REMINDER + " INTEGER DEFAULT 0, " + @@ -96,10 +106,10 @@ public class RecipientDatabase extends Database { SYSTEM_CONTACT_URI + " TEXT DEFAULT NULL, " + PROFILE_KEY + " TEXT DEFAULT NULL, " + SIGNAL_PROFILE_NAME + " TEXT DEFAULT NULL, " + - SESSION_PROFILE_AVATAR + " TEXT DEFAULT NULL, " + + SESSION_PROFILE_AVATAR + " TEXT DEFAULT NULL, " + PROFILE_SHARING + " INTEGER DEFAULT 0, " + CALL_RINGTONE + " TEXT DEFAULT NULL, " + - CALL_VIBRATE + " INTEGER DEFAULT " + Recipient.VibrateState.DEFAULT.getId() + ", " + + CALL_VIBRATE + " INTEGER DEFAULT 0, " + NOTIFICATION_CHANNEL + " TEXT DEFAULT NULL, " + UNIDENTIFIED_ACCESS_MODE + " INTEGER DEFAULT 0, " + FORCE_SMS_SELECTION + " INTEGER DEFAULT 0);"; @@ -197,40 +207,19 @@ Optional getRecipientSettings(@NonNull Cursor cursor) { boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCK)) == 1; boolean approved = cursor.getInt(cursor.getColumnIndexOrThrow(APPROVED)) == 1; boolean approvedMe = cursor.getInt(cursor.getColumnIndexOrThrow(APPROVED_ME)) == 1; - String messageRingtone = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION)); - String callRingtone = cursor.getString(cursor.getColumnIndexOrThrow(CALL_RINGTONE)); - int disappearingState = cursor.getInt(cursor.getColumnIndexOrThrow(DISAPPEARING_STATE)); - int messageVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(VIBRATE)); - int callVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(CALL_VIBRATE)); long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL)); int notifyType = cursor.getInt(cursor.getColumnIndexOrThrow(NOTIFY_TYPE)); boolean autoDownloadAttachments = cursor.getInt(cursor.getColumnIndexOrThrow(AUTO_DOWNLOAD)) == 1; - String serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR)); - int defaultSubscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(DEFAULT_SUBSCRIPTION_ID)); int expireMessages = cursor.getInt(cursor.getColumnIndexOrThrow(EXPIRE_MESSAGES)); - int registeredState = cursor.getInt(cursor.getColumnIndexOrThrow(REGISTERED)); String profileKeyString = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_KEY)); String systemDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_DISPLAY_NAME)); - String systemContactPhoto = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHOTO_URI)); - String systemPhoneLabel = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHONE_LABEL)); - String systemContactUri = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_CONTACT_URI)); String signalProfileName = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_NAME)); String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SESSION_PROFILE_AVATAR)); - boolean profileSharing = cursor.getInt(cursor.getColumnIndexOrThrow(PROFILE_SHARING)) == 1; String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL)); - boolean forceSmsSelection = cursor.getInt(cursor.getColumnIndexOrThrow(FORCE_SMS_SELECTION)) == 1; boolean blocksCommunityMessageRequests = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCKS_COMMUNITY_MESSAGE_REQUESTS)) == 1; - MaterialColor color; byte[] profileKey = null; - try { - color = serializedColor == null ? null : MaterialColor.fromSerialized(serializedColor); - } catch (MaterialColor.UnknownColorException e) { - Log.w(TAG, e); - color = null; - } - if (profileKeyString != null) { try { profileKey = Base64.decode(profileKeyString); @@ -241,18 +230,12 @@ Optional getRecipientSettings(@NonNull Cursor cursor) { } return Optional.of(new RecipientSettings(blocked, approved, approvedMe, muteUntil, - notifyType, autoDownloadAttachments, - Recipient.DisappearingState.fromId(disappearingState), - Recipient.VibrateState.fromId(messageVibrateState), - Recipient.VibrateState.fromId(callVibrateState), - Util.uri(messageRingtone), Util.uri(callRingtone), - color, defaultSubscriptionId, expireMessages, - Recipient.RegisteredState.fromId(registeredState), - profileKey, systemDisplayName, systemContactPhoto, - systemPhoneLabel, systemContactUri, - signalProfileName, signalProfileAvatar, profileSharing, - notificationChannel, - forceSmsSelection, blocksCommunityMessageRequests)); + notifyType, autoDownloadAttachments, + expireMessages, + profileKey, systemDisplayName, + signalProfileName, signalProfileAvatar, + notificationChannel, + blocksCommunityMessageRequests)); } public boolean isAutoDownloadFlagSet(Recipient recipient) { @@ -271,30 +254,6 @@ public boolean isAutoDownloadFlagSet(Recipient recipient) { return !flagUnset; } - public void setColor(@NonNull Recipient recipient, @NonNull MaterialColor color) { - ContentValues values = new ContentValues(); - values.put(COLOR, color.serialize()); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setColor(color); - notifyRecipientListeners(); - } - - public void setDefaultSubscriptionId(@NonNull Recipient recipient, int defaultSubscriptionId) { - ContentValues values = new ContentValues(); - values.put(DEFAULT_SUBSCRIPTION_ID, defaultSubscriptionId); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setDefaultSubscriptionId(Optional.of(defaultSubscriptionId)); - notifyRecipientListeners(); - } - - public void setForceSmsSelection(@NonNull Recipient recipient, boolean forceSmsSelection) { - ContentValues contentValues = new ContentValues(1); - contentValues.put(FORCE_SMS_SELECTION, forceSmsSelection ? 1 : 0); - updateOrInsert(recipient.getAddress(), contentValues); - recipient.resolve().setForceSmsSelection(forceSmsSelection); - notifyRecipientListeners(); - } - public boolean getApproved(@NonNull Address address) { SQLiteDatabase db = getReadableDatabase(); try (Cursor cursor = db.query(TABLE_NAME, new String[]{APPROVED}, ADDRESS + " = ?", new String[]{address.toString()}, null, null, null)) { @@ -408,14 +367,6 @@ public void setProfileName(@NonNull Recipient recipient, @Nullable String profil notifyRecipientListeners(); } - public void setProfileSharing(@NonNull Recipient recipient, boolean enabled) { - ContentValues contentValues = new ContentValues(1); - contentValues.put(PROFILE_SHARING, enabled ? 1 : 0); - updateOrInsert(recipient.getAddress(), contentValues); - recipient.setProfileSharing(enabled); - notifyRecipientListeners(); - } - public void setNotificationChannel(@NonNull Recipient recipient, @Nullable String notificationChannel) { ContentValues contentValues = new ContentValues(1); contentValues.put(NOTIFICATION_CHANNEL, notificationChannel); @@ -424,14 +375,6 @@ public void setNotificationChannel(@NonNull Recipient recipient, @Nullable Strin notifyRecipientListeners(); } - public void setRegistered(@NonNull Recipient recipient, RegisteredState registeredState) { - ContentValues contentValues = new ContentValues(1); - contentValues.put(REGISTERED, registeredState.getId()); - updateOrInsert(recipient.getAddress(), contentValues); - recipient.setRegistered(registeredState); - notifyRecipientListeners(); - } - public void setBlocksCommunityMessageRequests(@NonNull Recipient recipient, boolean isBlocked) { ContentValues contentValues = new ContentValues(1); contentValues.put(BLOCKS_COMMUNITY_MESSAGE_REQUESTS, isBlocked ? 1 : 0); @@ -496,14 +439,6 @@ public List getAllRecipients() { return returnList; } - public void setDisappearingState(@NonNull Recipient recipient, @NonNull Recipient.DisappearingState disappearingState) { - ContentValues values = new ContentValues(); - values.put(DISAPPEARING_STATE, disappearingState.getId()); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setDisappearingState(disappearingState); - notifyRecipientListeners(); - } - public static class RecipientReader implements Closeable { private final Context context; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index e977f9f978..59516d0cb3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -512,10 +512,6 @@ protected Optional insertMessageInbox(IncomingTextMessage message, DatabaseComponent.get(context).threadDatabase().update(threadId, true); } - if (message.getSubscriptionId() != -1) { - DatabaseComponent.get(context).recipientDatabase().setDefaultSubscriptionId(recipient, message.getSubscriptionId()); - } - notifyConversationListeners(threadId); return Optional.of(new InsertResult(messageId, threadId)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 408d99ce47..bfd0309b14 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1097,11 +1097,6 @@ open class Storage @Inject constructor( return jobDatabase.hasBackgroundGroupAddJob(groupJoinUrl) } - override fun setProfileSharing(address: Address, value: Boolean) { - val recipient = Recipient.from(context, address, false) - recipientDatabase.setProfileSharing(recipient, value) - } - override fun getOrCreateThreadIdFor(address: Address): Long { val recipient = Recipient.from(context, address, false) return threadDatabase.getOrCreateThreadIdFor(recipient) @@ -1928,26 +1923,4 @@ open class Storage @Inject constructor( } return expiringMessages } - - override fun updateDisappearingState( - messageSender: String, - threadID: Long, - disappearingState: Recipient.DisappearingState - ) { - val threadDb = threadDatabase - val lokiDb = lokiAPIDatabase - val recipient = threadDb.getRecipientForThreadId(threadID) ?: return - val recipientAddress = recipient.address.toString() - recipientDatabase - .setDisappearingState(recipient, disappearingState); - val currentLegacyRecipient = lokiDb.getLastLegacySenderAddress(recipientAddress) - val currentExpiry = getExpirationConfiguration(threadID) - if (disappearingState == DisappearingState.LEGACY - && currentExpiry?.isEnabled == true - && ExpirationConfiguration.isNewConfigEnabled) { // only set "this person is legacy" if new config enabled - lokiDb.setLastLegacySenderAddress(recipientAddress, messageSender) - } else if (messageSender == currentLegacyRecipient) { - lokiDb.setLastLegacySenderAddress(recipientAddress, null) - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index ee31899f99..5da9e8a462 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -656,8 +656,6 @@ public long getOrCreateThreadIdFor(Recipient recipient, int distributionType) { } if (created) { - DatabaseComponent.get(context).recipientDatabase().setProfileSharing(recipient, true); - if (updateListener != null) { updateListener.threadCreated(recipient.getAddress(), threadId); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/NotifyType.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/NotifyType.java new file mode 100644 index 0000000000..b9ea2508c5 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/NotifyType.java @@ -0,0 +1,13 @@ +package org.thoughtcrime.securesms.database.model; + +import androidx.annotation.IntDef; + +import org.thoughtcrime.securesms.database.RecipientDatabase; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.SOURCE) +@IntDef({RecipientDatabase.NOTIFY_TYPE_MENTIONS, RecipientDatabase.NOTIFY_TYPE_ALL, RecipientDatabase.NOTIFY_TYPE_NONE}) +public @interface NotifyType { +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java index b96159aab3..f9e3cdb330 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java @@ -15,7 +15,6 @@ import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.Recipient.VibrateState; import network.loki.messenger.R; @@ -49,18 +48,12 @@ protected CharSequence getStyledMessage(@NonNull Recipient recipient, @Nullable return builder; } - public void setAlarms(@Nullable Uri ringtone, VibrateState vibrate) { + public void setAlarms(@Nullable Uri ringtone) { Uri defaultRingtone = NotificationChannels.getMessageRingtone(context); boolean defaultVibrate = NotificationChannels.getMessageVibrate(context); if (ringtone == null && !TextUtils.isEmpty(defaultRingtone.toString())) setSound(defaultRingtone); else if (ringtone != null && !ringtone.toString().isEmpty()) setSound(ringtone); - - if (vibrate == VibrateState.ENABLED || - (vibrate == VibrateState.DEFAULT && defaultVibrate)) - { - setDefaults(Notification.DEFAULT_VIBRATE); - } } private void setLed() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt index 8c0761b57c..7e950f0b42 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt @@ -336,7 +336,7 @@ class DefaultMessageNotifier( } if (signal) { - builder.setAlarms(notificationState.getRingtone(context), notificationState.vibrate) + builder.setAlarms(notificationState.getRingtone(context)) builder.setTicker( notifications[0].individualRecipient, notifications[0].text @@ -416,7 +416,7 @@ class DefaultMessageNotifier( } if (signal) { - builder.setAlarms(notificationState.getRingtone(context), notificationState.vibrate) + builder.setAlarms(notificationState.getRingtone(context)) val text = notifications[0].text builder.setTicker( notifications[0].individualRecipient, diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.kt index e9da267dcd..9ab380a3c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.kt @@ -16,7 +16,6 @@ import org.session.libsession.utilities.StringSubstitutionConstants.MESSAGE_COUN import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.Util.getBoldedString import org.session.libsession.utilities.recipients.Recipient -import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get import org.thoughtcrime.securesms.home.HomeActivity import org.thoughtcrime.securesms.ui.getSubbedString import java.util.LinkedList @@ -82,11 +81,6 @@ class MultipleRecipientNotificationBuilder(context: Context, privacy: Notificati } else if (privacy.isDisplayContact) { messageBodies.add(getBoldedString(displayName)) } - - // TODO: What on earth is this? Why is it commented out? It's also commented out in dev... remove? -ACL 2024-08-29 - if (privacy.isDisplayContact && sender.contactUri != null) { -// addPerson(sender.getContactUri().toString()); - } } override fun build(): Notification { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java index bc85ff0874..fcd72e16fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java @@ -11,7 +11,7 @@ import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; -import org.session.libsession.utilities.recipients.Recipient.VibrateState; + import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; @@ -53,14 +53,6 @@ public void addNotification(NotificationItem item) { return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); } - public VibrateState getVibrate() { - if (!notifications.isEmpty()) { - Recipient recipient = notifications.getFirst().getRecipient(); - return recipient.resolve().getMessageVibrate(); - } - return VibrateState.DEFAULT; - } - public boolean hasMultipleThreads() { return threads.size() > 1; } public LinkedHashSet getThreads() { return threads; } public int getThreadCount() { return threads.size(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index 0c079d73a5..b0036dfd66 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -78,10 +78,6 @@ public void setThread(@NonNull Recipient recipient) { if (privacy.isDisplayContact()) { setContentTitle(recipient.getName()); - if (recipient.getContactUri() != null) { - addPerson(recipient.getContactUri().toString()); - } - ContactPhoto contactPhoto = recipient.getContactPhoto(); if (contactPhoto != null) { try { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt index 54fe4361f9..95878aec98 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt @@ -232,7 +232,6 @@ object MockDataGenerator { listOf(Address.fromSerialized(adminUserId)), timestampNow ) - storage.setProfileSharing(Address.fromSerialized(groupId), true) storage.addClosedGroupPublicKey(randomGroupPublicKey) // Add the group to the user's set of public keys to poll for and store the key pair From e4e9db7c5cf56999da2c7234259bc45745d23505 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Fri, 20 Jun 2025 17:52:49 +1000 Subject: [PATCH 02/52] No recipient anywhere --- .../libsession/database/StorageProtocol.kt | 18 ++- .../jobs/RetrieveProfileAvatarJob.kt | 20 ++- .../jobs/RetrieveProfileAvatarWork.kt | 134 ------------------ .../signal/OutgoingGroupMediaMessage.java | 3 +- .../messages/signal/OutgoingMediaMessage.java | 9 +- .../signal/OutgoingSecureMediaMessage.java | 3 +- .../messages/signal/OutgoingTextMessage.java | 11 +- .../ReceivedMessageHandler.kt | 25 ++-- .../libsession/utilities/SSKEnvironment.kt | 7 +- .../utilities/recipients/Recipient.java | 10 +- .../utilities/recipients/RecipientV2.kt | 29 ++++ .../thoughtcrime/securesms/ShareActivity.kt | 7 +- .../securesms/ShortcutLauncherActivity.kt | 5 +- .../securesms/configs/ConfigToDatabaseSync.kt | 30 ++-- .../contacts/ShareContactListFragment.kt | 2 +- .../start/newmessage/NewMessageFragment.kt | 7 +- .../conversation/v2/ConversationActivityV2.kt | 5 +- .../conversation/v2/ConversationViewModel.kt | 14 +- .../v2/MessageDetailsViewModel.kt | 2 +- .../conversation/v2/dialogs/BlockedDialog.kt | 4 +- .../settings/ConversationSettingsViewModel.kt | 6 +- .../securesms/database/RecipientDatabase.java | 32 ++--- .../securesms/database/SmsDatabase.java | 4 +- .../securesms/database/Storage.kt | 128 ++++++++--------- .../securesms/database/ThreadDatabase.java | 16 +-- .../securesms/groups/GroupManagerV2Impl.kt | 11 +- .../securesms/groups/JoinCommunityFragment.kt | 12 +- .../securesms/home/HomeActivity.kt | 11 +- .../securesms/home/HomeViewModel.kt | 9 +- .../securesms/mediasend/MediaSendActivity.kt | 9 +- .../securesms/mediasend/MediaSendFragment.kt | 5 +- .../MessageRequestsActivity.kt | 8 +- .../MessageRequestsViewModel.kt | 4 +- .../preferences/BlockedContactsViewModel.kt | 3 +- .../securesms/preferences/QRCodeActivity.kt | 6 +- .../repository/ConversationRepository.kt | 28 ++-- .../sskenvironment/ProfileManager.kt | 8 +- .../v2/ConversationViewModelTest.kt | 1 - 38 files changed, 256 insertions(+), 390 deletions(-) delete mode 100644 app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarWork.kt create mode 100644 app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index d4ce777407..47cbdc9da1 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -2,6 +2,7 @@ package org.session.libsession.database import android.content.Context import android.net.Uri +import kotlinx.coroutines.flow.Flow import network.loki.messenger.libsession_util.util.KeyPair import org.session.libsession.messaging.BlindedIdMapping import org.session.libsession.messaging.calls.CallMessageType @@ -30,6 +31,7 @@ import org.session.libsession.utilities.GroupDisplayInfo import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient.RecipientSettings +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceGroup @@ -48,13 +50,18 @@ interface StorageProtocol { fun getUserX25519KeyPair(): ECKeyPair fun getUserBlindedAccountId(serverPublicKey: String): AccountId? fun getUserProfile(): Profile - fun setProfilePicture(recipient: Recipient, newProfilePicture: String?, newProfileKey: ByteArray?) - fun setBlocksCommunityMessageRequests(recipient: Recipient, blocksMessageRequests: Boolean) + fun setProfilePicture(recipient: Address, newProfilePicture: String?, newProfileKey: ByteArray?) + fun setBlocksCommunityMessageRequests(recipient: Address, blocksMessageRequests: Boolean) fun setUserProfilePicture(newProfilePicture: String?, newProfileKey: ByteArray?) fun clearUserPic(clearConfig: Boolean = true) // Signal fun getOrGenerateRegistrationID(): Int + // Recipient + fun observeRecipient(address: Address): Flow + @Deprecated("Use observeRecipient instead", ReplaceWith("observeRecipient(address)")) + fun getRecipientSync(address: Address): RecipientV2 + // Jobs fun persistJob(job: Job) fun markJobAsSucceeded(jobId: String) @@ -182,7 +189,6 @@ interface StorageProtocol { fun getThreadId(publicKeyOrOpenGroupID: String): Long? fun getThreadId(openGroup: OpenGroup): Long? fun getThreadId(address: Address): Long? - fun getThreadId(recipient: Recipient): Long? fun getThreadIdForMms(mmsId: Long): Long fun getLastUpdated(threadID: Long): Long fun trimThread(threadID: Long, threadLimit: Int) @@ -225,9 +231,9 @@ interface StorageProtocol { fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) fun insertMessageRequestResponseFromContact(response: MessageRequestResponse) fun insertMessageRequestResponseFromYou(threadId: Long) - fun setRecipientApproved(recipient: Recipient, approved: Boolean) + fun setRecipientApproved(recipient: Address, approved: Boolean) fun getRecipientApproved(address: Address): Boolean - fun setRecipientApprovedMe(recipient: Recipient, approvedMe: Boolean) + fun setRecipientApprovedMe(recipient: Address, approvedMe: Boolean) fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long) fun conversationHasOutgoing(userPublicKey: String): Boolean fun deleteMessagesByHash(threadId: Long, hashes: List) @@ -266,7 +272,7 @@ interface StorageProtocol { fun updateReactionIfNeeded(message: Message, sender: String, openGroupSentTimestamp: Long) fun deleteReactions(messageId: MessageId) fun deleteReactions(messageIds: List, mms: Boolean) - fun setBlocked(recipients: Iterable, isBlocked: Boolean, fromConfigUpdate: Boolean = false) + fun setBlocked(recipients: Iterable
, isBlocked: Boolean, fromConfigUpdate: Boolean = false) fun blockedContacts(): List fun getExpirationConfiguration(threadId: Long): ExpirationConfiguration? fun setExpirationConfiguration(config: ExpirationConfiguration) diff --git a/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt b/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt index a60b3e2449..0d84b5fd41 100644 --- a/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt +++ b/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt @@ -1,6 +1,6 @@ package org.session.libsession.messaging.jobs -import network.loki.messenger.libsession_util.SessionEncrypt +import kotlinx.coroutines.flow.first import org.session.libsession.avatars.AvatarHelper import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.utilities.Data @@ -10,12 +10,10 @@ import org.session.libsession.utilities.DownloadUtilities.downloadFromFileServer import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfileAvatarId import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfilePictureURL import org.session.libsession.utilities.Util.equals -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.exceptions.NonRetryableException import org.session.libsignal.utilities.HTTP import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Util.SECURE_RANDOM -import java.io.File import java.io.FileOutputStream import java.util.concurrent.ConcurrentSkipListSet @@ -46,7 +44,7 @@ class RetrieveProfileAvatarJob( if (profileAvatar != null && profileAvatar in errorUrls) return delegate.handleJobFailed(this, dispatcherName, Exception("Profile URL 404'd this app instance")) val context = MessagingModuleConfiguration.shared.context val storage = MessagingModuleConfiguration.shared.storage - val recipient = Recipient.from(context, recipientAddress, true) + val recipient = storage.observeRecipient(recipientAddress).first() if (profileKey == null || (profileKey.size != 32 && profileKey.size != 16)) { return delegate.handleJobFailedPermanently(this, dispatcherName, Exception("Recipient profile key is gone!")) @@ -56,23 +54,23 @@ class RetrieveProfileAvatarJob( // it's now limited to just the current user case if ( recipient.isLocalNumber && - AvatarHelper.avatarFileExists(context, recipient.resolve().address) && - equals(profileAvatar, recipient.resolve().profileAvatar) + AvatarHelper.avatarFileExists(context, recipientAddress) && + equals(profileAvatar, recipient.profileAvatar) ) { Log.w(TAG, "Already retrieved profile avatar: $profileAvatar") return } if (profileAvatar.isNullOrEmpty()) { - Log.w(TAG, "Removing profile avatar for: " + recipient.address.toString()) + Log.w(TAG, "Removing profile avatar for: $recipientAddress" ) if (recipient.isLocalNumber) { setProfileAvatarId(context, SECURE_RANDOM.nextInt()) setProfilePictureURL(context, null) } - AvatarHelper.delete(context, recipient.address) - storage.setProfilePicture(recipient, null, null) + AvatarHelper.delete(context, recipientAddress) + storage.setProfilePicture(recipientAddress, null, null) return } @@ -86,7 +84,7 @@ class RetrieveProfileAvatarJob( symmetricKey = profileKey ) - FileOutputStream(AvatarHelper.getAvatarFile(context, recipient.address)).use { out -> + FileOutputStream(AvatarHelper.getAvatarFile(context, recipientAddress)).use { out -> out.write(decrypted) } @@ -95,7 +93,7 @@ class RetrieveProfileAvatarJob( setProfilePictureURL(context, profileAvatar) } - storage.setProfilePicture(recipient, profileAvatar, profileKey) + storage.setProfilePicture(recipientAddress, profileAvatar, profileKey) } catch (e: NonRetryableException){ Log.e("Loki", "Failed to download profile avatar from non-retryable error", e) diff --git a/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarWork.kt b/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarWork.kt deleted file mode 100644 index 8a18ac68c2..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarWork.kt +++ /dev/null @@ -1,134 +0,0 @@ -package org.session.libsession.messaging.jobs - -import android.content.Context -import androidx.hilt.work.HiltWorker -import androidx.work.CoroutineWorker -import androidx.work.Data -import androidx.work.ExistingWorkPolicy -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager -import androidx.work.WorkerParameters -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject -import kotlinx.coroutines.CancellationException -import org.session.libsession.avatars.AvatarHelper -import org.session.libsession.database.StorageProtocol -import org.session.libsession.utilities.AESGCM -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.DownloadUtilities.downloadFromFileServer -import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfileAvatarId -import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfilePictureURL -import org.session.libsession.utilities.Util -import org.session.libsession.utilities.recipients.Recipient -import org.session.libsignal.exceptions.NonRetryableException -import org.session.libsignal.utilities.HTTP -import org.session.libsignal.utilities.Log -import org.session.libsignal.utilities.Util.SECURE_RANDOM -import java.io.FileOutputStream - -@HiltWorker -class RetrieveProfileAvatarWork @AssistedInject constructor( - @Assisted private val appContext: Context, - @Assisted parameters: WorkerParameters, - private val storage: StorageProtocol, -) : CoroutineWorker(appContext, parameters) { - override suspend fun doWork(): Result { - val recipientAddress = requireNotNull( - inputData.getString(DATA_ADDRESS)?.let(Address::fromSerialized) - ) { "Recipient address is required" } - - val profileAvatarUrl = requireNotNull(inputData.getString(DATA_URL)) { - "Profile avatar URL is required" - } - - val profileAvatarKey = requireNotNull(inputData.getByteArray(DATA_KEY)) { - "Profile avatar key is required" - } - - require(profileAvatarKey.size == 16 || profileAvatarKey.size == 32) { - "Profile avatar key must be either 16 or 32 bytes long" - } - - val recipient by lazy { Recipient.from(appContext, recipientAddress, false) } - - // Commit '78d1e9d' (fix: open group threads and avatar downloads) had this commented out so - // it's now limited to just the current user case - if ( - recipient.isLocalNumber && - AvatarHelper.avatarFileExists(appContext, recipientAddress) && - Util.equals(profileAvatarUrl, recipient.resolve().profileAvatar) - ) { - Log.w(TAG, "Already retrieved profile avatar: $profileAvatarUrl") - return Result.success() - } - - try { - val downloaded = downloadFromFileServer(profileAvatarUrl) - val decrypted = AESGCM.decrypt( - downloaded.data, - offset = downloaded.offset, - len = downloaded.len, - symmetricKey = profileAvatarKey - ) - - FileOutputStream(AvatarHelper.getAvatarFile(appContext, recipient.address)).use { out -> - out.write(decrypted) - } - - if (recipient.isLocalNumber) { - setProfileAvatarId(appContext, SECURE_RANDOM.nextInt()) - setProfilePictureURL(appContext, profileAvatarUrl) - } - - storage.setProfilePicture(recipient, profileAvatarUrl, profileAvatarKey) - return Result.success() - } - catch (e: NonRetryableException) { - Log.e("Loki", "Failed to download profile avatar from non-retryable error", e) - return Result.failure() - } - catch (e: CancellationException) { - throw e - } - catch (e: Exception) { - if(e is HTTP.HTTPRequestFailedException && e.statusCode == 404){ - Log.e(TAG, "Failed to download profile avatar from non-retryable error", e) - return Result.failure() - } else { - Log.e(TAG, "Failed to download profile avatar", e) - return Result.retry() - } - } - } - - companion object { - private const val DATA_ADDRESS = "address" - private const val DATA_URL = "url" - private const val DATA_KEY = "key" - - private const val TAG = "RetrieveProfileAvatarWork" - - fun schedule( - context: Context, - recipientAddress: Address, - profileAvatarUrl: String, - profileAvatarKey: ByteArray, - ) { - val uniqueWorkName = "retrieve-avatar-$recipientAddress" - - val data = Data.Builder() - .putString(DATA_ADDRESS, recipientAddress.toString()) - .putString(DATA_URL, profileAvatarUrl) - .putByteArray(DATA_KEY, profileAvatarKey) - .build() - - val workRequest = OneTimeWorkRequestBuilder() - .setInputData(data) - .build() - - WorkManager.getInstance(context) - .beginUniqueWork(uniqueWorkName, ExistingWorkPolicy.REPLACE, workRequest) - .enqueue() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java index 86db70f435..10e2e93c2a 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java @@ -3,6 +3,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.session.libsession.utilities.Address; import org.session.libsession.utilities.DistributionTypes; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.utilities.Contact; @@ -18,7 +19,7 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { private final String groupID; private final boolean isUpdateMessage; - public OutgoingGroupMediaMessage(@NonNull Recipient recipient, + public OutgoingGroupMediaMessage(@NonNull Address recipient, @NonNull String body, @Nullable String groupId, @Nullable final Attachment avatar, diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java index 85dc0cc6a2..9a53a2c574 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java @@ -7,6 +7,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; +import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Contact; import org.session.libsession.utilities.DistributionTypes; import org.session.libsession.utilities.IdentityKeyMismatch; @@ -19,7 +20,7 @@ public class OutgoingMediaMessage { - private final Recipient recipient; + private final Address recipient; protected final String body; protected final List attachments; private final long sentTimeMillis; @@ -34,7 +35,7 @@ public class OutgoingMediaMessage { private final List contacts = new LinkedList<>(); private final List linkPreviews = new LinkedList<>(); - public OutgoingMediaMessage(Recipient recipient, String message, + public OutgoingMediaMessage(Address recipient, String message, List attachments, long sentTimeMillis, int subscriptionId, long expiresIn, long expireStartedAt, int distributionType, @@ -78,7 +79,7 @@ public OutgoingMediaMessage(OutgoingMediaMessage that) { } public static OutgoingMediaMessage from(VisibleMessage message, - Recipient recipient, + Address recipient, List attachments, @Nullable QuoteModel outgoingQuote, @Nullable LinkPreview linkPreview, @@ -94,7 +95,7 @@ public static OutgoingMediaMessage from(VisibleMessage message, Collections.emptyList(), previews, Collections.emptyList(), Collections.emptyList()); } - public Recipient getRecipient() { + public Address getRecipient() { return recipient; } diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java index e93c3c5986..3a897091a5 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java @@ -4,6 +4,7 @@ import androidx.annotation.Nullable; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; +import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Contact; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; @@ -14,7 +15,7 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage { - public OutgoingSecureMediaMessage(Recipient recipient, String body, + public OutgoingSecureMediaMessage(Address recipient, String body, List attachments, long sentTimeMillis, int distributionType, diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java index b62a75a1e3..41cb1bf6d2 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java @@ -2,11 +2,12 @@ import org.session.libsession.messaging.messages.visible.OpenGroupInvitation; import org.session.libsession.messaging.messages.visible.VisibleMessage; +import org.session.libsession.utilities.Address; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.messaging.utilities.UpdateMessageData; public class OutgoingTextMessage { - private final Recipient recipient; + private final Address recipient; private final String message; private final int subscriptionId; private final long expiresIn; @@ -14,7 +15,7 @@ public class OutgoingTextMessage { private final long sentTimestampMillis; private boolean isOpenGroupInvitation = false; - public OutgoingTextMessage(Recipient recipient, String message, long expiresIn, long expireStartedAt, int subscriptionId, long sentTimestampMillis) { + public OutgoingTextMessage(Address recipient, String message, long expiresIn, long expireStartedAt, int subscriptionId, long sentTimestampMillis) { this.recipient = recipient; this.message = message; this.expiresIn = expiresIn; @@ -23,11 +24,11 @@ public OutgoingTextMessage(Recipient recipient, String message, long expiresIn, this.sentTimestampMillis = sentTimestampMillis; } - public static OutgoingTextMessage from(VisibleMessage message, Recipient recipient, long expiresInMillis, long expireStartedAt) { + public static OutgoingTextMessage from(VisibleMessage message, Address recipient, long expiresInMillis, long expireStartedAt) { return new OutgoingTextMessage(recipient, message.getText(), expiresInMillis, expireStartedAt, -1, message.getSentTimestamp()); } - public static OutgoingTextMessage fromOpenGroupInvitation(OpenGroupInvitation openGroupInvitation, Recipient recipient, Long sentTimestamp, long expiresInMillis, long expireStartedAt) { + public static OutgoingTextMessage fromOpenGroupInvitation(OpenGroupInvitation openGroupInvitation, Address recipient, Long sentTimestamp, long expiresInMillis, long expireStartedAt) { String url = openGroupInvitation.getUrl(); String name = openGroupInvitation.getName(); if (url == null || name == null) { return null; } @@ -54,7 +55,7 @@ public String getMessageBody() { return message; } - public Recipient getRecipient() { + public Address getRecipient() { return recipient; } diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index ddb8450178..80aedb6b9e 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -59,15 +59,13 @@ import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.database.ConfigDatabase import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.ReactionRecord -import org.thoughtcrime.securesms.dependencies.ConfigFactory import java.security.MessageDigest import java.security.SignatureException import kotlin.math.min internal fun MessageReceiver.isBlocked(publicKey: String): Boolean { - val context = MessagingModuleConfiguration.shared.context - val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false) - return recipient.isBlocked + val recipient = MessagingModuleConfiguration.shared.storage.getRecipientSync(Address.fromSerialized(publicKey)) + return recipient.blocked } fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content, threadId: Long, openGroupID: String?, groupv2Id: AccountId?) { @@ -349,30 +347,31 @@ fun MessageReceiver.handleVisibleMessage( if (MessageReceiver.messageIsOutdated(message, context.threadId, context.openGroupID)) { return null } // Update profile if needed - val recipient = Recipient.from(context.context, Address.fromSerialized(messageSender!!), false) + val address = Address.fromSerialized(messageSender!!) + val recipient = context.storage.getRecipientSync(address) if (runProfileUpdate) { val profile = message.profile val isUserBlindedSender = messageSender == context.userBlindedKey if (profile != null && userPublicKey != messageSender && !isUserBlindedSender) { val name = profile.displayName!! if (name.isNotEmpty()) { - context.profileManager.setName(context.context, recipient, name) + context.profileManager.setName(context.context, address, name) } val newProfileKey = profile.profileKey val needsProfilePicture = !AvatarHelper.avatarFileExists(context.context, Address.fromSerialized(messageSender)) val profileKeyValid = newProfileKey?.isNotEmpty() == true && (newProfileKey.size == 16 || newProfileKey.size == 32) && profile.profilePictureURL?.isNotEmpty() == true - val profileKeyChanged = (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, newProfileKey)) + val profileKeyChanged = (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey.data, newProfileKey)) if ((profileKeyValid && profileKeyChanged) || (profileKeyValid && needsProfilePicture)) { - context.profileManager.setProfilePicture(context.context, recipient, profile.profilePictureURL, newProfileKey) + context.profileManager.setProfilePicture(context.context, address, profile.profilePictureURL, newProfileKey) } else if (newProfileKey == null || newProfileKey.isEmpty() || profile.profilePictureURL.isNullOrEmpty()) { - context.profileManager.setProfilePicture(context.context, recipient, null, null) + context.profileManager.setProfilePicture(context.context, address, null, null) } } if (userPublicKey != messageSender && !isUserBlindedSender) { - context.storage.setBlocksCommunityMessageRequests(recipient, message.blocksMessageRequests) + context.storage.setBlocksCommunityMessageRequests(address, message.blocksMessageRequests) } } // Handle group invite response if new closed group @@ -587,13 +586,13 @@ private fun MessageReceiver.handleGroupUpdated(message: GroupUpdated, closedGrou // Update profile if needed if (message.profile != null && !message.isSenderSelf) { val profile = message.profile - val recipient = Recipient.from(MessagingModuleConfiguration.shared.context, Address.fromSerialized(message.sender!!), false) + val address = Address.fromSerialized(message.sender!!) val profileManager = SSKEnvironment.shared.profileManager if (profile.displayName?.isNotEmpty() == true) { - profileManager.setName(MessagingModuleConfiguration.shared.context, recipient, profile.displayName) + profileManager.setName(MessagingModuleConfiguration.shared.context, address, profile.displayName) } if (profile.profileKey?.isNotEmpty() == true && !profile.profilePictureURL.isNullOrEmpty()) { - profileManager.setProfilePicture(MessagingModuleConfiguration.shared.context, recipient, profile.profilePictureURL, profile.profileKey) + profileManager.setProfilePicture(MessagingModuleConfiguration.shared.context, address, profile.profilePictureURL, profile.profileKey) } } diff --git a/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt b/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt index 4e92594d3c..004e840264 100644 --- a/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt +++ b/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt @@ -5,7 +5,6 @@ import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier -import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.database.model.MessageId class SSKEnvironment( @@ -31,9 +30,9 @@ class SSKEnvironment( const val NAME_PADDED_LENGTH = 100 } - fun setNickname(context: Context, recipient: Recipient, nickname: String?) - fun setName(context: Context, recipient: Recipient, name: String?) - fun setProfilePicture(context: Context, recipient: Recipient, profilePictureURL: String?, profileKey: ByteArray?) + fun setNickname(context: Context, recipient: Address, nickname: String?) + fun setName(context: Context, recipient: Address, name: String?) + fun setProfilePicture(context: Context, recipient: Address, profilePictureURL: String?, profileKey: ByteArray?) fun contactUpdatedInternal(contact: Contact): String? } diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java index 73652ef802..d4327cf4ae 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java +++ b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java @@ -59,6 +59,8 @@ import java.util.WeakHashMap; import java.util.concurrent.ExecutionException; +import kotlinx.coroutines.flow.Flow; + public class Recipient implements RecipientModifiedListener, Cloneable { private static final String TAG = Recipient.class.getSimpleName(); @@ -90,15 +92,15 @@ public class Recipient implements RecipientModifiedListener, Cloneable { private boolean blocksCommunityMessageRequests; @SuppressWarnings("ConstantConditions") - public static @NonNull Recipient from(@NonNull Context context, @NonNull Address address, boolean asynchronous) { + public static @NonNull Flow from(@NonNull Context context, @NonNull Address address, boolean asynchronous) { if (address == null) throw new AssertionError(address); - return provider.getRecipient(context, address, Optional.absent(), Optional.absent(), asynchronous); + throw new UnsupportedOperationException(); } @SuppressWarnings("ConstantConditions") - public static @NonNull Recipient from(@NonNull Context context, @NonNull Address address, @NonNull Optional settings, @NonNull Optional groupRecord, boolean asynchronous) { + public static @NonNull Flow from(@NonNull Context context, @NonNull Address address, @NonNull Optional settings, @NonNull Optional groupRecord, boolean asynchronous) { if (address == null) throw new AssertionError(address); - return provider.getRecipient(context, address, settings, groupRecord, asynchronous); + throw new UnsupportedOperationException(); } public static void applyCached(@NonNull Address address, Consumer consumer) { diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt new file mode 100644 index 0000000000..6963d614e4 --- /dev/null +++ b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt @@ -0,0 +1,29 @@ +package org.session.libsession.utilities.recipients + +import network.loki.messenger.libsession_util.util.Bytes +import org.session.libsession.avatars.ContactPhoto +import org.session.libsession.utilities.Address +import org.thoughtcrime.securesms.database.model.NotifyType +import java.time.ZonedDateTime + +data class RecipientV2( + val isLocalNumber: Boolean, + val address: Address, + val name: String, + val avatar: ContactPhoto?, + val approved: Boolean, + val approvedMe: Boolean, + val blocked: Boolean, + val mutedUntil: ZonedDateTime?, + val autoDownloadAttachments: Boolean, + @get:NotifyType + val notifyType: Int, + val profileAvatar: String?, + val profileKey: Bytes? +) { + val isGroupOrCommunityRecipient: Boolean get() = address.isGroupOrCommunity + val isCommunityRecipient: Boolean get() = address.isCommunity + val isCommunityInboxRecipient: Boolean get() = address.isCommunityInbox + val isCommunityOutboxRecipient: Boolean get() = address.isCommunityOutbox + val isGroupV2Recipient: Boolean get() = address.isGroupV2 +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt index efe800d81d..1ffa8c6ccf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt @@ -242,10 +242,9 @@ class ShareActivity : ScreenLockActionBarActivity(), OnContactSelectedListener { return MediaUtil.getJpegCorrectedMimeTypeIfRequired(intent.type) } - override fun onContactSelected(number: String?) { - val recipient = Recipient.from(this, fromExternal(this, number), true) - val existingThread = get(this).threadDatabase().getThreadIdIfExistsFor(recipient) - createConversation(existingThread, recipient.address, DistributionTypes.DEFAULT) + override fun onContactSelected(number: String) { + val existingThread = get(this).threadDatabase().getThreadIdIfExistsFor(number) + createConversation(existingThread, fromExternal(this, number), DistributionTypes.DEFAULT) } override fun onContactDeselected(number: String?) { /* Nothing */ } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.kt index 7c078510d8..d2c4142d65 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.kt @@ -40,11 +40,10 @@ class ShortcutLauncherActivity : AppCompatActivity() { val context = this@ShortcutLauncherActivity val address = fromSerialized(serializedAddress) - val recipient = Recipient.from(context, address, true) - val threadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient) + val threadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(address) val intent = Intent(context, ConversationActivityV2::class.java) - intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) + intent.putExtra(ConversationActivityV2.ADDRESS, address) intent.putExtra(ConversationActivityV2.THREAD_ID, threadId) backStack.addNextIntent(intent) diff --git a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt index 98d16b2f66..90e3e2be66 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt @@ -34,7 +34,6 @@ import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol.Co import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.UserConfigType import org.session.libsession.utilities.getGroup -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.ecc.DjbECPrivateKey import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.session.libsignal.crypto.ecc.ECKeyPair @@ -125,12 +124,12 @@ class ConfigToDatabaseSync @Inject constructor( private fun updateUser(userProfile: UpdateUserInfo, messageTimestamp: Long?) { val userPublicKey = storage.getUserPublicKey() ?: return // would love to get rid of recipient and context from this - val recipient = Recipient.from(context, fromSerialized(userPublicKey), false) + val address = fromSerialized(userPublicKey) // Update profile name userProfile.name?.takeUnless { it.isEmpty() }?.truncate(NAME_PADDED_LENGTH)?.let { preferences.setProfileName(it) - profileManager.setName(context, recipient, it) + profileManager.setName(context, address, it) } // Update profile picture @@ -147,7 +146,6 @@ class ConfigToDatabaseSync @Inject constructor( preferences.setHasHiddenNoteToSelf(true) } else { // create note to self thread if needed (?) - val address = recipient.address val ourThread = storage.getThreadId(address) ?: storage.getOrCreateThreadIdFor(address).also { storage.setThreadCreationDate(it, 0) } @@ -158,10 +156,10 @@ class ConfigToDatabaseSync @Inject constructor( // Set or reset the shared library to use latest expiration config if (messageTimestamp != null) { - storage.getThreadId(recipient)?.let { theadId -> + storage.getThreadId(address)?.let { threadId -> storage.setExpirationConfiguration( - storage.getExpirationConfiguration(theadId)?.takeIf { it.updatedTimestampMs > messageTimestamp } ?: - ExpirationConfiguration(theadId, userProfile.ntsExpiry, messageTimestamp) + storage.getExpirationConfiguration(threadId)?.takeIf { it.updatedTimestampMs > messageTimestamp } ?: + ExpirationConfiguration(threadId, userProfile.ntsExpiry, messageTimestamp) ) } } @@ -186,11 +184,11 @@ class ConfigToDatabaseSync @Inject constructor( } private fun updateGroup(groupInfoConfig: UpdateGroupInfo) { - val threadId = storage.getThreadId(fromSerialized(groupInfoConfig.id.hexString)) ?: return - val recipient = storage.getRecipientForThread(threadId) ?: return - profileManager.setName(context, recipient, groupInfoConfig.name.orEmpty()) + val address = fromSerialized(groupInfoConfig.id.hexString) + val threadId = storage.getThreadId(address) ?: return + profileManager.setName(context, address, groupInfoConfig.name.orEmpty()) profileManager.setProfilePicture( - context, recipient, + context, address, profilePictureURL = groupInfoConfig.profilePic?.url, profileKey = groupInfoConfig.profilePic?.key?.data ) @@ -325,11 +323,11 @@ class ConfigToDatabaseSync @Inject constructor( val groupThreadsToKeep = hashMapOf() for (closedGroup in userGroups.closedGroupInfo) { - val recipient = Recipient.from(context, fromSerialized(closedGroup.groupAccountId), false) - storage.setRecipientApprovedMe(recipient, true) - storage.setRecipientApproved(recipient, !closedGroup.invited) - profileManager.setName(context, recipient, closedGroup.name) - val threadId = storage.getOrCreateThreadIdFor(recipient.address) + val address = fromSerialized(closedGroup.groupAccountId) + storage.setRecipientApprovedMe(address, true) + storage.setRecipientApproved(address, !closedGroup.invited) + profileManager.setName(context, address, closedGroup.name) + val threadId = storage.getOrCreateThreadIdFor(address) // If we don't already have a date and the config has a date, use it if (closedGroup.joinedAtSecs > 0L && threadDatabase.getLastUpdated(threadId) <= 0L) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt index 1cccf0bb25..982547cdcd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt @@ -45,7 +45,7 @@ class ShareContactListFragment : Fragment(), LoaderManager.LoaderCallbacks - Recipient.from(context, fromSerialized(contact.hexString), true) + fromSerialized(contact.hexString) } repository.inviteContactsToCommunity(threadId, recipients) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index ddc19abcfb..5038e0ba75 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -264,31 +264,28 @@ public boolean getApproved(@NonNull Address address) { return false; } - public void setApproved(@NonNull Recipient recipient, boolean approved) { + public void setApproved(@NonNull Address recipient, boolean approved) { ContentValues values = new ContentValues(); values.put(APPROVED, approved ? 1 : 0); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setApproved(approved); + updateOrInsert(recipient, values); notifyRecipientListeners(); } - public void setApprovedMe(@NonNull Recipient recipient, boolean approvedMe) { + public void setApprovedMe(@NonNull Address recipient, boolean approvedMe) { ContentValues values = new ContentValues(); values.put(APPROVED_ME, approvedMe ? 1 : 0); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setHasApprovedMe(approvedMe); + updateOrInsert(recipient, values); notifyRecipientListeners(); } - public void setBlocked(@NonNull Iterable recipients, boolean blocked) { + public void setBlocked(@NonNull Iterable
recipients, boolean blocked) { SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); try { ContentValues values = new ContentValues(); values.put(BLOCK, blocked ? 1 : 0); - for (Recipient recipient : recipients) { - db.update(TABLE_NAME, values, ADDRESS + " = ?", new String[]{recipient.getAddress().toString()}); - recipient.resolve().setBlocked(blocked); + for (Address recipient : recipients) { + db.update(TABLE_NAME, values, ADDRESS + " = ?", new String[]{recipient.toString()}); } db.setTransactionSuccessful(); } finally { @@ -342,19 +339,17 @@ public void setNotifyType(@NonNull Recipient recipient, int notifyType) { notifyRecipientListeners(); } - public void setProfileKey(@NonNull Recipient recipient, @Nullable byte[] profileKey) { + public void setProfileKey(@NonNull Address recipient, @Nullable byte[] profileKey) { ContentValues values = new ContentValues(1); values.put(PROFILE_KEY, profileKey == null ? null : Base64.encodeBytes(profileKey)); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setProfileKey(profileKey); + updateOrInsert(recipient, values); notifyRecipientListeners(); } - public void setProfileAvatar(@NonNull Recipient recipient, @Nullable String profileAvatar) { + public void setProfileAvatar(@NonNull Address recipient, @Nullable String profileAvatar) { ContentValues contentValues = new ContentValues(1); contentValues.put(SESSION_PROFILE_AVATAR, profileAvatar); - updateOrInsert(recipient.getAddress(), contentValues); - recipient.resolve().setProfileAvatar(profileAvatar); + updateOrInsert(recipient, contentValues); notifyRecipientListeners(); } @@ -375,11 +370,10 @@ public void setNotificationChannel(@NonNull Recipient recipient, @Nullable Strin notifyRecipientListeners(); } - public void setBlocksCommunityMessageRequests(@NonNull Recipient recipient, boolean isBlocked) { + public void setBlocksCommunityMessageRequests(@NonNull Address recipient, boolean isBlocked) { ContentValues contentValues = new ContentValues(1); contentValues.put(BLOCKS_COMMUNITY_MESSAGE_REQUESTS, isBlocked ? 1 : 0); - updateOrInsert(recipient.getAddress(), contentValues); - recipient.resolve().setBlocksCommunityMessageRequests(isBlocked); + updateOrInsert(recipient, contentValues); notifyRecipientListeners(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 59516d0cb3..062e97d026 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -567,7 +567,7 @@ public long insertMessageOutbox(long threadId, OutgoingTextMessage message, if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT; if (message.isOpenGroupInvitation()) type |= Types.OPEN_GROUP_INVITATION_BIT; - Address address = message.getRecipient().getAddress(); + Address address = message.getRecipient(); Map earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(date); Map earlyReadReceipts = earlyReadReceiptCache.remove(date); @@ -708,7 +708,7 @@ private boolean isDuplicate(IncomingTextMessage message, long threadId) { private boolean isDuplicate(OutgoingTextMessage message, long threadId) { SQLiteDatabase database = getReadableDatabase(); Cursor cursor = database.query(TABLE_NAME, null, DATE_SENT + " = ? AND " + ADDRESS + " = ? AND " + THREAD_ID + " = ?", - new String[]{String.valueOf(message.getSentTimestampMillis()), message.getRecipient().getAddress().toString(), String.valueOf(threadId)}, + new String[]{String.valueOf(message.getSentTimestampMillis()), message.getRecipient().toString(), String.valueOf(threadId)}, null, null, null, "1"); try { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index bfd0309b14..28f6c278a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -4,6 +4,7 @@ import android.content.Context import android.net.Uri import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.Flow import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_PINNED import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE @@ -64,7 +65,7 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.recipients.Recipient -import org.session.libsession.utilities.recipients.Recipient.DisappearingState +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.upsertContact import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.session.libsignal.crypto.ecc.ECKeyPair @@ -220,27 +221,24 @@ open class Storage @Inject constructor( return Profile(displayName, profileKey, profilePictureUrl) } - override fun setProfilePicture(recipient: Recipient, newProfilePicture: String?, newProfileKey: ByteArray?) { + override fun setProfilePicture(recipient: Address, newProfilePicture: String?, newProfileKey: ByteArray?) { val db = recipientDatabase db.setProfileAvatar(recipient, newProfilePicture) db.setProfileKey(recipient, newProfileKey) } - override fun setBlocksCommunityMessageRequests(recipient: Recipient, blocksMessageRequests: Boolean) { + override fun setBlocksCommunityMessageRequests(recipient: Address, blocksMessageRequests: Boolean) { val db = recipientDatabase db.setBlocksCommunityMessageRequests(recipient, blocksMessageRequests) } override fun setUserProfilePicture(newProfilePicture: String?, newProfileKey: ByteArray?) { - val ourRecipient = fromSerialized(getUserPublicKey()!!).let { - Recipient.from(context, it, false) - } - ourRecipient.resolve().profileKey = newProfileKey + val ourRecipient = fromSerialized(getUserPublicKey()!!) preferences.setProfileKey(newProfileKey?.let { Base64.encodeBytes(it) }) preferences.setProfilePictureURL(newProfilePicture) if (newProfileKey != null) { - JobQueue.shared.add(RetrieveProfileAvatarJob(newProfilePicture, ourRecipient.address, newProfileKey)) + JobQueue.shared.add(RetrieveProfileAvatarJob(newProfilePicture, ourRecipient, newProfileKey)) } } @@ -253,6 +251,14 @@ open class Storage @Inject constructor( return registrationID } + override fun observeRecipient(address: Address): Flow { + TODO("Not yet implemented") + } + + override fun getRecipientSync(address: Address): RecipientV2 { + TODO("Not yet implemented") + } + override fun getAttachmentsForMessage(mmsMessageId: Long): List { return attachmentDatabase.getAttachmentsForMessage(mmsMessageId) } @@ -398,15 +404,14 @@ open class Storage @Inject constructor( } else { senderAddress } - val targetRecipient = Recipient.from(context, targetAddress, false) - if (!targetRecipient.isGroupOrCommunityRecipient) { + if (!targetAddress.isGroupOrCommunity) { if (isUserSender || isUserBlindedSender) { - setRecipientApproved(targetRecipient, true) + setRecipientApproved(targetAddress, true) } else { - setRecipientApprovedMe(targetRecipient, true) + setRecipientApprovedMe(targetAddress, true) } } - if (message.threadID == null && !targetRecipient.isCommunityRecipient) { + if (message.threadID == null && !targetAddress.isCommunity) { // open group recipients should explicitly create threads message.threadID = getOrCreateThreadIdFor(targetAddress) } @@ -436,7 +441,7 @@ open class Storage @Inject constructor( val mediaMessage = OutgoingMediaMessage.from( message, - targetRecipient, + targetAddress, pointers, quote.orNull(), linkPreviews.orNull()?.firstOrNull(), @@ -459,8 +464,8 @@ open class Storage @Inject constructor( val isOpenGroupInvitation = (message.openGroupInvitation != null) val insertResult = if (isUserSender || isUserBlindedSender) { - val textMessage = if (isOpenGroupInvitation) OutgoingTextMessage.fromOpenGroupInvitation(message.openGroupInvitation, targetRecipient, message.sentTimestamp, expiresInMillis, expireStartedAt) - else OutgoingTextMessage.from(message, targetRecipient, expiresInMillis, expireStartedAt) + val textMessage = if (isOpenGroupInvitation) OutgoingTextMessage.fromOpenGroupInvitation(message.openGroupInvitation, targetAddress, message.sentTimestamp, expiresInMillis, expireStartedAt) + else OutgoingTextMessage.from(message, targetAddress, expiresInMillis, expireStartedAt) smsDatabase.insertMessageOutbox(message.threadID ?: -1, textMessage, message.sentTimestamp!!, runThreadUpdate) } else { val textMessage = if (isOpenGroupInvitation) IncomingTextMessage.fromOpenGroupInvitation(message.openGroupInvitation, senderAddress, message.sentTimestamp, expiresInMillis, expireStartedAt) @@ -544,7 +549,7 @@ open class Storage @Inject constructor( override fun clearUserPic(clearConfig: Boolean) { val userPublicKey = getUserPublicKey() ?: return Log.w(TAG, "No user public key when trying to clear user pic") - val recipient = Recipient.from(context, fromSerialized(userPublicKey), false) + val recipient = fromSerialized(userPublicKey) // Clear details related to the user's profile picture preferences.setProfileKey(null) @@ -856,7 +861,7 @@ open class Storage @Inject constructor( override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, threadID: Long, sentTimestamp: Long): Long? { val userPublicKey = getUserPublicKey()!! - val recipient = Recipient.from(context, fromSerialized(groupID), false) + val recipient = fromSerialized(groupID) val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: "" val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, 0, true, null, listOf(), listOf()) val mmsDB = mmsDatabase @@ -1000,7 +1005,7 @@ open class Storage @Inject constructor( private fun insertUpdateControlMessage(updateData: UpdateMessageData, sentTimestamp: Long, senderPublicKey: String?, closedGroup: AccountId): MessageId? { val userPublicKey = getUserPublicKey()!! - val recipient = Recipient.from(context, fromSerialized(closedGroup.hexString), false) + val recipient = fromSerialized(closedGroup.hexString) val threadDb = threadDatabase val threadID = threadDb.getThreadIdIfExistsFor(recipient) val expirationConfig = getExpirationConfiguration(threadID) @@ -1098,25 +1103,24 @@ open class Storage @Inject constructor( } override fun getOrCreateThreadIdFor(address: Address): Long { - val recipient = Recipient.from(context, address, false) - return threadDatabase.getOrCreateThreadIdFor(recipient) + return threadDatabase.getOrCreateThreadIdFor(address) } override fun getThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?, createThread: Boolean): Long? { val database = threadDatabase return if (!openGroupID.isNullOrEmpty()) { - val recipient = Recipient.from(context, fromSerialized(GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())), false) + val recipient = fromSerialized(GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())) database.getThreadIdIfExistsFor(recipient).let { if (it == -1L) null else it } } else if (!groupPublicKey.isNullOrEmpty() && !groupPublicKey.startsWith(IdPrefix.GROUP.value)) { - val recipient = Recipient.from(context, fromSerialized(GroupUtil.doubleEncodeGroupID(groupPublicKey)), false) + val recipient = fromSerialized(GroupUtil.doubleEncodeGroupID(groupPublicKey)) if (createThread) database.getOrCreateThreadIdFor(recipient) else database.getThreadIdIfExistsFor(recipient).let { if (it == -1L) null else it } } else if (!groupPublicKey.isNullOrEmpty()) { - val recipient = Recipient.from(context, fromSerialized(groupPublicKey), false) + val recipient = fromSerialized(groupPublicKey) if (createThread) database.getOrCreateThreadIdFor(recipient) else database.getThreadIdIfExistsFor(recipient).let { if (it == -1L) null else it } } else { - val recipient = Recipient.from(context, fromSerialized(publicKey), false) + val recipient = fromSerialized(publicKey) if (createThread) database.getOrCreateThreadIdFor(recipient) else database.getThreadIdIfExistsFor(recipient).let { if (it == -1L) null else it } } @@ -1132,12 +1136,7 @@ open class Storage @Inject constructor( } override fun getThreadId(address: Address): Long? { - val recipient = Recipient.from(context, address, false) - return getThreadId(recipient) - } - - override fun getThreadId(recipient: Recipient): Long? { - val threadID = threadDatabase.getThreadIdIfExistsFor(recipient) + val threadID = threadDatabase.getThreadIdIfExistsFor(address) return if (threadID < 0) null else threadID } @@ -1202,27 +1201,26 @@ open class Storage @Inject constructor( } moreContacts.forEach { contact -> val address = fromSerialized(contact.id) - val recipient = Recipient.from(context, address, false) - setBlocked(listOf(recipient), contact.blocked, fromConfigUpdate = true) - setRecipientApproved(recipient, contact.approved) - setRecipientApprovedMe(recipient, contact.approvedMe) + setBlocked(listOf(address), contact.blocked, fromConfigUpdate = true) + setRecipientApproved(address, contact.approved) + setRecipientApprovedMe(address, contact.approvedMe) if (contact.name.isNotEmpty()) { - profileManager.setName(context, recipient, contact.name) + profileManager.setName(context, address, contact.name) } else { - profileManager.setName(context, recipient, null) + profileManager.setName(context, address, null) } if (contact.nickname.isNotEmpty()) { - profileManager.setNickname(context, recipient, contact.nickname) + profileManager.setNickname(context, address, contact.nickname) } else { - profileManager.setNickname(context, recipient, null) + profileManager.setNickname(context, address, null) } if (contact.profilePicture != UserPic.DEFAULT) { val (url, key) = contact.profilePicture if (key.data.size != ProfileKeyUtil.PROFILE_KEY_BYTES) return@forEach - profileManager.setProfilePicture(context, recipient, url, key.data) + profileManager.setProfilePicture(context, address, url, key.data) } else { - profileManager.setProfilePicture(context, recipient, null, null) + profileManager.setProfilePicture(context, address, null, null) } if (contact.priority == PRIORITY_HIDDEN) { getThreadId(fromSerialized(contact.id))?.let(::deleteConversation) @@ -1234,7 +1232,7 @@ open class Storage @Inject constructor( ).also { setPinned(it, contact.priority == PRIORITY_PINNED) } } if (timestamp != null) { - getThreadId(recipient)?.let { + getThreadId(address)?.let { setExpirationConfiguration( getExpirationConfiguration(it)?.takeIf { it.updatedTimestampMs > timestamp } ?: ExpirationConfiguration(it, contact.expiryMode, timestamp) @@ -1438,10 +1436,10 @@ open class Storage @Inject constructor( override fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) { val address = fromSerialized(senderPublicKey) - val recipient = Recipient.from(context, address, false) + val recipient = getRecipientSync(address) - if (recipient.isBlocked) return - val threadId = getThreadId(recipient) ?: return + if (recipient.blocked) return + val threadId = getThreadId(address) ?: return val expirationConfig = getExpirationConfiguration(threadId) val expiryMode = expirationConfig?.expiryMode ?: ExpiryMode.NONE val expiresInMillis = expiryMode.expiryMillis @@ -1487,13 +1485,13 @@ open class Storage @Inject constructor( ) return if (userPublicKey == senderPublicKey) { - val requestRecipient = Recipient.from(context, fromSerialized(recipientPublicKey), false) + val requestRecipient = fromSerialized(recipientPublicKey) recipientDatabase.setApproved(requestRecipient, true) val threadId = threadDatabase.getOrCreateThreadIdFor(requestRecipient) threadDatabase.setHasSent(threadId, true) } else { - val sender = Recipient.from(context, fromSerialized(senderPublicKey), false) - val threadId = getOrCreateThreadIdFor(sender.address) + val sender = fromSerialized(senderPublicKey) + val threadId = getOrCreateThreadIdFor(sender) val profile = response.profile if (profile != null) { val name = profile.displayName!! @@ -1502,7 +1500,7 @@ open class Storage @Inject constructor( } val newProfileKey = profile.profileKey - val needsProfilePicture = !AvatarHelper.avatarFileExists(context, sender.address) + val needsProfilePicture = !AvatarHelper.avatarFileExists(context, sender) val profileKeyValid = newProfileKey?.isNotEmpty() == true && (newProfileKey.size == 16 || newProfileKey.size == 32) && profile.profilePictureURL?.isNotEmpty() == true val profileKeyChanged = (sender.profileKey == null || !MessageDigest.isEqual(sender.profileKey, newProfileKey)) @@ -1538,7 +1536,7 @@ open class Storage @Inject constructor( } mappingDb.addBlindedIdMapping(mapping.value.copy(accountId = senderPublicKey)) - val blindedThreadId = threadDatabase.getOrCreateThreadIdFor(Recipient.from(context, fromSerialized(mapping.key), false)) + val blindedThreadId = threadDatabase.getOrCreateThreadIdFor(fromSerialized(mapping.key)) mmsDatabase.updateThreadId(blindedThreadId, threadId) smsDatabase.updateThreadId(blindedThreadId, threadId) deleteConversation(blindedThreadId) @@ -1547,7 +1545,7 @@ open class Storage @Inject constructor( var alreadyApprovedMe: Boolean = false configFactory.withUserConfigs { // check is the person had not yet approvedMe - alreadyApprovedMe = it.contacts.get(sender.address.toString())?.approvedMe ?: false + alreadyApprovedMe = it.contacts.get(sender.toString())?.approvedMe ?: false } setRecipientApprovedMe(sender, true) @@ -1555,7 +1553,7 @@ open class Storage @Inject constructor( // only show the message if wasn't already approvedMe before if(!alreadyApprovedMe) { val message = IncomingMediaMessage( - sender.address, + sender, response.sentTimestamp!!, -1, 0, @@ -1610,11 +1608,11 @@ open class Storage @Inject constructor( return address.isGroupV2 || recipientDatabase.getApproved(address) } - override fun setRecipientApproved(recipient: Recipient, approved: Boolean) { - recipientDatabase.setApproved(recipient, approved) - if (recipient.isLocalNumber || !recipient.isContactRecipient) return + override fun setRecipientApproved(address: Address, approved: Boolean) { + recipientDatabase.setApproved(address, approved) + if (!address.isContact || address.toString() == getUserPublicKey()) return configFactory.withMutableUserConfigs { - it.contacts.upsertContact(recipient.address.toString()) { + it.contacts.upsertContact(address.toString()) { // if the contact wasn't approved before but is approved now, make sure it's visible if(approved && !this.approved) this.priority = PRIORITY_VISIBLE @@ -1624,11 +1622,11 @@ open class Storage @Inject constructor( } } - override fun setRecipientApprovedMe(recipient: Recipient, approvedMe: Boolean) { - recipientDatabase.setApprovedMe(recipient, approvedMe) - if (recipient.isLocalNumber || !recipient.isContactRecipient) return + override fun setRecipientApprovedMe(address: Address, approvedMe: Boolean) { + recipientDatabase.setApprovedMe(address, approvedMe) + if (!address.isContact || address.toString() == getUserPublicKey()) return configFactory.withMutableUserConfigs { - it.contacts.upsertContact(recipient.address.toString()) { + it.contacts.upsertContact(address.toString()) { this.approvedMe = approvedMe } } @@ -1636,8 +1634,7 @@ open class Storage @Inject constructor( override fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long) { val address = fromSerialized(senderPublicKey) - val recipient = Recipient.from(context, address, false) - val threadId = threadDatabase.getOrCreateThreadIdFor(recipient) + val threadId = threadDatabase.getOrCreateThreadIdFor(address) val expirationConfig = getExpirationConfiguration(threadId) val expiryMode = expirationConfig?.expiryMode?.coerceSendToRead() ?: ExpiryMode.NONE val expiresInMillis = expiryMode.expiryMillis @@ -1808,15 +1805,16 @@ open class Storage @Inject constructor( ) } - override fun setBlocked(recipients: Iterable, isBlocked: Boolean, fromConfigUpdate: Boolean) { + override fun setBlocked(recipients: Iterable
, isBlocked: Boolean, fromConfigUpdate: Boolean) { val recipientDb = recipientDatabase recipientDb.setBlocked(recipients, isBlocked) if (!fromConfigUpdate) { + val currentUserKey = getUserPublicKey() configFactory.withMutableUserConfigs { configs -> - recipients.filter { it.isContactRecipient && !it.isLocalNumber } + recipients.filter { it.isContact && (it.toString() != currentUserKey) } .forEach { recipient -> - configs.contacts.upsertContact(recipient.address.toString()) { + configs.contacts.upsertContact(recipient.toString()) { this.blocked = isBlocked } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 5da9e8a462..eb6d882711 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -614,12 +614,12 @@ public long getThreadIdIfExistsFor(String address) { } } - public long getThreadIdIfExistsFor(Recipient recipient) { - return getThreadIdIfExistsFor(recipient.getAddress().toString()); + public long getThreadIdIfExistsFor(Address address) { + return getThreadIdIfExistsFor(address.toString()); } - public long getOrCreateThreadIdFor(Recipient recipient) { - return getOrCreateThreadIdFor(recipient, DistributionTypes.DEFAULT); + public long getOrCreateThreadIdFor(Address address) { + return getOrCreateThreadIdFor(address, DistributionTypes.DEFAULT); } public void setThreadArchived(long threadId) { @@ -633,10 +633,10 @@ public void setThreadArchived(long threadId) { notifyConversationListeners(threadId); } - public long getOrCreateThreadIdFor(Recipient recipient, int distributionType) { + public long getOrCreateThreadIdFor(Address address, int distributionType) { SQLiteDatabase db = getReadableDatabase(); String where = ADDRESS + " = ?"; - String[] recipientsArg = new String[]{recipient.getAddress().toString()}; + String[] recipientsArg = new String[]{address.toString()}; Cursor cursor = null; boolean created = false; @@ -650,14 +650,14 @@ public long getOrCreateThreadIdFor(Recipient recipient, int distributionType) { if (cursor != null && cursor.moveToFirst()) { threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); } else { - threadId = createThreadForRecipient(recipient.getAddress(), recipient.isGroupOrCommunityRecipient(), distributionType); + threadId = createThreadForRecipient(address, address.isGroupOrCommunity(), distributionType); created = true; } } if (created) { if (updateListener != null) { - updateListener.threadCreated(recipient.getAddress(), threadId); + updateListener.threadCreated(address, threadId); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt index 5eb6fa1602..a82c04bf24 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt @@ -793,8 +793,7 @@ class GroupManagerV2Impl @Inject constructor( inviteMessageTimestamp: Long, inviteMessageHash: String, ) { - val recipient = - Recipient.from(application, Address.fromSerialized(groupId.hexString), false) + val address = Address.fromSerialized(groupId.hexString) val shouldAutoApprove = storage.getRecipientApproved(Address.fromSerialized(inviter.hexString)) @@ -814,10 +813,10 @@ class GroupManagerV2Impl @Inject constructor( it.userGroups.set(closedGroupInfo) } - profileManager.setName(application, recipient, groupName) - val groupThreadId = storage.getOrCreateThreadIdFor(recipient.address) - storage.setRecipientApprovedMe(recipient, true) - storage.setRecipientApproved(recipient, shouldAutoApprove) + profileManager.setName(application, address, groupName) + val groupThreadId = storage.getOrCreateThreadIdFor(address) + storage.setRecipientApprovedMe(address, true) + storage.setRecipientApproved(address, shouldAutoApprove) if (shouldAutoApprove) { approveGroupInvite(closedGroupInfo, inviteMessageHash) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt index 86c3ccf372..4d6c8c5d92 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt @@ -23,7 +23,6 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.OpenGroupUrlParser import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.conversation.start.StartConversationDelegate import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 @@ -119,12 +118,7 @@ class JoinCommunityFragment : Fragment() { val groupID = GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray()) withContext(Dispatchers.Main) { - val recipient = Recipient.from( - requireContext(), - Address.fromSerialized(groupID), - false - ) - openConversationActivity(requireContext(), threadID, recipient) + openConversationActivity(requireContext(), threadID, Address.fromSerialized(groupID)) delegate.onDialogClosePressed() } } catch (e: Exception) { @@ -155,10 +149,10 @@ class JoinCommunityFragment : Fragment() { mediator.attach() } - private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) { + private fun openConversationActivity(context: Context, threadId: Long, address: Address) { val intent = Intent(context, ConversationActivityV2::class.java) intent.putExtra(ConversationActivityV2.THREAD_ID, threadId) - intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) + intent.putExtra(ConversationActivityV2.ADDRESS, address) context.startActivity(intent) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 214d81eec8..08a0332f6f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -12,8 +12,6 @@ import android.view.ViewGroup.MarginLayoutParams import android.widget.Toast import androidx.activity.viewModels import androidx.core.os.bundleOf -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding @@ -51,7 +49,6 @@ import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_K import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient -import org.session.libsession.utilities.wasKickedFromGroupV2 import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ApplicationContext @@ -165,7 +162,7 @@ class HomeActivity : ScreenLockActionBarActivity(), } is GlobalSearchAdapter.Model.GroupConversation -> model.groupId - .let { Recipient.from(this, Address.fromSerialized(it), false) } + .let { Address.fromSerialized(it) } .let(threadDb::getThreadIdIfExistsFor) .takeIf { it >= 0 } ?.let { @@ -559,7 +556,7 @@ class HomeActivity : ScreenLockActionBarActivity(), Toast.makeText(this, R.string.copied, Toast.LENGTH_SHORT).show() } else if (thread.recipient.isCommunityRecipient) { - val threadId = threadDb.getThreadIdIfExistsFor(thread.recipient) + val threadId = threadDb.getThreadIdIfExistsFor(thread.recipient.address) val openGroup = lokiThreadDatabase.getOpenGroupChat(threadId) ?: return@onCopyConversationId Unit val clip = ClipData.newPlainText("Community URL", openGroup.joinURL) @@ -619,7 +616,7 @@ class HomeActivity : ScreenLockActionBarActivity(), .format()) dangerButton(R.string.block, R.string.AccessibilityId_blockConfirm) { lifecycleScope.launch(Dispatchers.Default) { - storage.setBlocked(listOf(thread.recipient), true) + storage.setBlocked(listOf(thread.recipient.address), true) withContext(Dispatchers.Main) { binding.conversationsRecyclerView.adapter!!.notifyDataSetChanged() @@ -639,7 +636,7 @@ class HomeActivity : ScreenLockActionBarActivity(), text(Phrase.from(context, R.string.blockUnblockName).put(NAME_KEY, thread.recipient.name).format()) dangerButton(R.string.blockUnblock, R.string.AccessibilityId_unblockConfirm) { lifecycleScope.launch(Dispatchers.Default) { - storage.setBlocked(listOf(thread.recipient), false) + storage.setBlocked(listOf(thread.recipient.address), false) withContext(Dispatchers.Main) { binding.conversationsRecyclerView.adapter!!.notifyDataSetChanged() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index 3ce691e367..3f108bc29f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -2,12 +2,10 @@ package org.thoughtcrime.securesms.home import android.content.ContentResolver import android.content.Context -import android.widget.Toast import androidx.annotation.AttrRes import androidx.lifecycle.ViewModel import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope -import com.squareup.phrase.Phrase import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers @@ -28,22 +26,18 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.onErrorResume import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import network.loki.messenger.R import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigUpdateNotification -import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Log import org.session.libsession.utilities.UsernameUtils -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.ThreadDatabase @@ -222,8 +216,7 @@ class HomeViewModel @Inject constructor( fun blockContact(accountId: String) { viewModelScope.launch(Dispatchers.Default) { - val recipient = Recipient.from(context, Address.fromSerialized(accountId), false) - storage.setBlocked(listOf(recipient), isBlocked = true) + storage.setBlocked(listOf(Address.fromSerialized(accountId)), isBlocked = true) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt index df0ae05419..579547e250 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt @@ -23,6 +23,7 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.MediasendActivityBinding +import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.MediaTypes import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY @@ -50,7 +51,7 @@ import java.io.IOException class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragment.Controller, MediaPickerItemFragment.Controller, MediaSendFragment.Controller, ImageEditorFragment.Controller, CameraXFragment.Controller{ - private var recipient: Recipient? = null + private var recipient: Address? = null private val viewModel: MediaSendViewModel by viewModels() private lateinit var binding: MediasendActivityBinding @@ -76,10 +77,8 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme // Apply windowInsets for our own UI (not the fragment ones because they will want to do their own things) binding.mediasendBottomBar.applySafeInsetsPaddings() - recipient = Recipient.from( - this, fromSerialized( - intent.getStringExtra(KEY_ADDRESS)!! - ), true + recipient = fromSerialized( + intent.getStringExtra(KEY_ADDRESS)!! ) viewModel.onBodyChanged(intent.getStringExtra(KEY_BODY)!!) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt index 762ca90992..89dae31613 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext import network.loki.messenger.R import network.loki.messenger.databinding.MediasendFragmentBinding +import org.session.libsession.utilities.Address import org.session.libsession.utilities.MediaTypes import org.session.libsession.utilities.TextSecurePreferences.Companion.isEnterSendsEnabled import org.session.libsession.utilities.recipients.Recipient @@ -449,9 +450,9 @@ class MediaSendFragment : Fragment(), RailItemListener, private const val KEY_ADDRESS = "address" - fun newInstance(recipient: Recipient): MediaSendFragment { + fun newInstance(address: Address): MediaSendFragment { val args = Bundle() - args.putParcelable(KEY_ADDRESS, recipient.address) + args.putParcelable(KEY_ADDRESS, address) val fragment = MediaSendFragment() fragment.arguments = args diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt index 004c8a7050..3bbd148e22 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt @@ -3,11 +3,8 @@ package org.thoughtcrime.securesms.messagerequests import android.content.Intent import android.database.Cursor import android.os.Bundle -import android.view.ViewGroup.MarginLayoutParams import androidx.activity.viewModels import androidx.core.view.isVisible -import androidx.core.view.updateLayoutParams -import androidx.core.view.updatePadding import androidx.loader.app.LoaderManager import androidx.loader.content.Loader import com.bumptech.glide.Glide @@ -19,7 +16,6 @@ import network.loki.messenger.R import network.loki.messenger.databinding.ActivityMessageRequestsBinding import org.session.libsession.utilities.Address import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY -import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.ScreenLockActionBarActivity import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.database.ThreadDatabase @@ -92,8 +88,8 @@ class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClick override fun onBlockConversationClick(thread: ThreadRecord) { fun doBlock() { val recipient = thread.invitingAdminId?.let { - Recipient.from(this, Address.fromSerialized(it), false) - } ?: thread.recipient + Address.fromSerialized(it) + } ?: thread.recipient.address viewModel.blockMessageRequest(thread, recipient) LoaderManager.getInstance(this).restartLoader(0, null, this) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsViewModel.kt index 720ac2b31c..c7ab032e76 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.Address import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.repository.ConversationRepository import javax.inject.Inject @@ -15,7 +15,7 @@ class MessageRequestsViewModel @Inject constructor( ) : ViewModel() { // We assume thread.recipient is a contact or thread.invitingAdmin is not null - fun blockMessageRequest(thread: ThreadRecord, blockRecipient: Recipient) = viewModelScope.launch { + fun blockMessageRequest(thread: ThreadRecord, blockRecipient: Address) = viewModelScope.launch { repository.setBlocked(blockRecipient, true) deleteMessageRequest(thread) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt index 612cb69af7..e26a69c100 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt @@ -24,7 +24,6 @@ import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.database.StorageProtocol import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.database.DatabaseContentProviders -import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.util.adapter.SelectableItem import javax.inject.Inject @@ -65,7 +64,7 @@ class BlockedContactsViewModel @Inject constructor(private val storage: StorageP } fun unblock() { - storage.setBlocked(state.selectedItems, false) + storage.setBlocked(state.selectedItems.map { it.address }, false) _state.value = state.copy(selectedItems = emptySet()) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt index 98d4ef0e0b..3ed441cf42 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt @@ -73,11 +73,11 @@ class QRCodeActivity : ScreenLockActionBarActivity() { if (!PublicKeyValidation.isValid(string)) { errors.tryEmit(getString(R.string.qrNotAccountId)) } else if (!isFinishing) { - val recipient = Recipient.from(this, Address.fromSerialized(string), false) + val address = Address.fromSerialized(string) start { - putExtra(ConversationActivityV2.ADDRESS, recipient.address) + putExtra(ConversationActivityV2.ADDRESS, address) setDataAndType(intent.data, intent.type) - val existingThread = threadDatabase().getThreadIdIfExistsFor(recipient) + val existingThread = threadDatabase().getThreadIdIfExistsFor(address) putExtra(ConversationActivityV2.THREAD_ID, existingThread) } finish() diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index d08904b27d..8465c9f9c2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -56,12 +56,12 @@ interface ConversationRepository { fun saveDraft(threadId: Long, text: String) fun getDraft(threadId: Long): String? fun clearDrafts(threadId: Long) - fun inviteContactsToCommunity(threadId: Long, contacts: List) - fun setBlocked(recipient: Recipient, blocked: Boolean) + fun inviteContactsToCommunity(threadId: Long, contacts: List
) + fun setBlocked(recipient: Address, blocked: Boolean) fun markAsDeletedLocally(messages: Set, displayedMessage: String) fun deleteMessages(messages: Set, threadId: Long) fun deleteAllLocalMessagesInThreadFromSenderOfMessage(messageRecord: MessageRecord) - fun setApproved(recipient: Recipient, isApproved: Boolean) + fun setApproved(recipient: Address, isApproved: Boolean) fun isGroupReadOnly(recipient: Recipient): Boolean fun getLastSentMessageID(threadId: Long): Flow @@ -88,7 +88,7 @@ interface ConversationRepository { suspend fun deleteThread(threadId: Long): Result suspend fun deleteMessageRequest(thread: ThreadRecord): Result suspend fun clearAllMessageRequests(block: Boolean): Result - suspend fun acceptMessageRequest(threadId: Long, recipient: Recipient): Result + suspend fun acceptMessageRequest(threadId: Long, recipient: Address): Result suspend fun declineMessageRequest(threadId: Long, recipient: Recipient): Result fun hasReceived(threadId: Long): Boolean fun getInvitingAdmin(threadId: Long): Recipient? @@ -161,7 +161,7 @@ class DefaultConversationRepository @Inject constructor( draftDb.clearDrafts(threadId) } - override fun inviteContactsToCommunity(threadId: Long, contacts: List) { + override fun inviteContactsToCommunity(threadId: Long, contacts: List
) { val openGroup = lokiThreadDb.getOpenGroupChat(threadId) ?: return for (contact in contacts) { val message = VisibleMessage() @@ -182,7 +182,7 @@ class DefaultConversationRepository @Inject constructor( expireStartedAt ) smsDb.insertMessageOutbox(-1, outgoingTextMessage, message.sentTimestamp!!, true) - MessageSender.send(message, contact.address) + MessageSender.send(message, contact) } } @@ -209,8 +209,8 @@ class DefaultConversationRepository @Inject constructor( } // This assumes that recipient.isContactRecipient is true - override fun setBlocked(recipient: Recipient, blocked: Boolean) { - if (recipient.isContactRecipient) { + override fun setBlocked(recipient: Address, blocked: Boolean) { + if (recipient.isContact) { storage.setBlocked(listOf(recipient), blocked) } } @@ -277,7 +277,7 @@ class DefaultConversationRepository @Inject constructor( } } - override fun setApproved(recipient: Recipient, isApproved: Boolean) { + override fun setApproved(recipient: Address, isApproved: Boolean) { storage.setRecipientApproved(recipient, isApproved) } @@ -419,7 +419,7 @@ class DefaultConversationRepository @Inject constructor( deleteMessageRequest(reader.current) val recipient = reader.current.recipient if (block && !recipient.isGroupV2Recipient) { - setBlocked(recipient, true) + setBlocked(recipient.address, true) } } } @@ -441,18 +441,18 @@ class DefaultConversationRepository @Inject constructor( } } - override suspend fun acceptMessageRequest(threadId: Long, recipient: Recipient) = runCatching { + override suspend fun acceptMessageRequest(threadId: Long, recipient: Address) = runCatching { withContext(Dispatchers.Default) { storage.setRecipientApproved(recipient, true) - if (recipient.isGroupV2Recipient) { + if (recipient.isGroupV2) { groupManager.respondToInvitation( - AccountId(recipient.address.toString()), + AccountId(recipient.toString()), approved = true ) } else { val message = MessageRequestResponse(true) - MessageSender.send(message = message, address = recipient.address) + MessageSender.send(message = message, address = recipient) // add a control message for our user storage.insertMessageRequestResponseFromYou(threadId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index 340c56fd4f..f2270ff1ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -7,10 +7,10 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.RetrieveProfileAvatarJob +import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.upsertContact import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.IdPrefix @@ -30,7 +30,7 @@ class ProfileManager @Inject constructor( private val preferences: TextSecurePreferences, ) : SSKEnvironment.ProfileManagerProtocol { - override fun setNickname(context: Context, recipient: Recipient, nickname: String?) { + override fun setNickname(context: Context, recipient: Address, nickname: String?) { if (recipient.isLocalNumber) return val accountID = recipient.address.toString() var contact = contactDatabase.getContactWithAccountID(accountID) @@ -43,7 +43,7 @@ class ProfileManager @Inject constructor( contactUpdatedInternal(contact) } - override fun setName(context: Context, recipient: Recipient, name: String?) { + override fun setName(context: Context, recipient: Address, name: String?) { // New API if (recipient.isLocalNumber) return val accountID = recipient.address.toString() @@ -62,7 +62,7 @@ class ProfileManager @Inject constructor( override fun setProfilePicture( context: Context, - recipient: Recipient, + recipient: Address, profilePictureURL: String?, profileKey: ByteArray? ) { diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt index 97f12c2821..8ee7184cbe 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.first import network.loki.messenger.libsession_util.util.KeyPair import org.hamcrest.CoreMatchers.equalTo -import org.hamcrest.CoreMatchers.notNullValue import org.hamcrest.CoreMatchers.nullValue import org.hamcrest.MatcherAssert.assertThat import org.junit.Before From 6b3efdd4386113790d6a888b865ec9755f34d82d Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Tue, 24 Jun 2025 11:56:17 +1000 Subject: [PATCH 03/52] Tidy up address --- .../session/libsession/utilities/Address.kt | 104 +----------------- .../thoughtcrime/securesms/ShareActivity.kt | 3 +- .../securesms/database/MmsDatabase.kt | 3 +- .../securesms/search/SearchRepository.kt | 3 +- .../service/QuickResponseService.java | 2 +- 5 files changed, 9 insertions(+), 106 deletions(-) diff --git a/app/src/main/java/org/session/libsession/utilities/Address.kt b/app/src/main/java/org/session/libsession/utilities/Address.kt index 6fc8c545d8..8c501ce23a 100644 --- a/app/src/main/java/org/session/libsession/utilities/Address.kt +++ b/app/src/main/java/org/session/libsession/utilities/Address.kt @@ -1,23 +1,13 @@ package org.session.libsession.utilities -import android.content.Context -import android.os.Parcel import android.os.Parcelable -import android.util.Pair -import androidx.annotation.VisibleForTesting +import kotlinx.parcelize.Parcelize import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Util -import org.session.libsignal.utilities.guava.Optional import java.util.LinkedList -import java.util.concurrent.atomic.AtomicReference -import java.util.regex.Matcher -import java.util.regex.Pattern - -class Address private constructor(address: String) : Parcelable, Comparable { - private val address: String = address.lowercase() - - constructor(`in`: Parcel) : this(`in`.readString()!!) {} +@Parcelize +data class Address private constructor(val address: String) : Parcelable, Comparable
{ val isLegacyGroup: Boolean get() = GroupUtil.isLegacyClosedGroup(address) val isGroupV2: Boolean @@ -50,97 +40,13 @@ class Address private constructor(address: String) : Parcelable, Comparable - private val localCountryCode: String - private val ALPHA_PATTERN = Pattern.compile("[a-zA-Z]") - fun format(number: String?): String { - return number ?: "Unknown" - } - - private fun parseAreaCode(e164Number: String, countryCode: Int): String? { - when (countryCode) { - 1 -> return e164Number.substring(2, 5) - 55 -> return e164Number.substring(3, 5) - } - return null - } - - private fun applyAreaCodeRules(localNumber: Optional, testNumber: String): String { - if (!localNumber.isPresent || !localNumber.get().areaCode.isPresent) { - return testNumber - } - val matcher: Matcher - when (localNumber.get().countryCode) { - 1 -> { - matcher = US_NO_AREACODE.matcher(testNumber) - if (matcher.matches()) { - return localNumber.get().areaCode.toString() + matcher.group() - } - } - 55 -> { - matcher = BR_NO_AREACODE.matcher(testNumber) - if (matcher.matches()) { - return localNumber.get().areaCode.toString() + matcher.group() - } - } - } - return testNumber - } - - private class PhoneNumber internal constructor(val e164Number: String, val countryCode: Int, areaCode: String?) { - val areaCode: Optional - - init { - this.areaCode = Optional.fromNullable(areaCode) - } - } - - companion object { - private val TAG = ExternalAddressFormatter::class.java.simpleName - private val SHORT_COUNTRIES: HashSet = object : HashSet() { - init { - add("NU") - add("TK") - add("NC") - add("AC") - } - } - private val US_NO_AREACODE = Pattern.compile("^(\\d{7})$") - private val BR_NO_AREACODE = Pattern.compile("^(9?\\d{8})$") - } - - init { - localNumber = Optional.absent() - this.localCountryCode = localCountryCode - } - } + override fun compareTo(other: Address): Int = address.compareTo(other.address) companion object { - @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(`in`: Parcel): Address = Address(`in`) - override fun newArray(size: Int): Array = arrayOfNulls(size) - } val UNKNOWN = Address("Unknown") - private val TAG = Address::class.java.simpleName - private val cachedFormatter = AtomicReference>() - - @JvmStatic - fun fromSerialized(serialized: String): Address = Address(serialized) @JvmStatic - fun fromExternal(context: Context, external: String?): Address = fromSerialized(external!!) + fun fromSerialized(serialized: String): Address = Address(serialized.lowercase()) @JvmStatic fun fromSerializedList(serialized: String, delimiter: Char): List
{ diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt index 2b423047f5..278a042521 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt @@ -32,7 +32,6 @@ import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import org.session.libsession.utilities.Address -import org.session.libsession.utilities.Address.Companion.fromExternal import org.session.libsession.utilities.DistributionTypes import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.ViewUtil @@ -244,7 +243,7 @@ class ShareActivity : ScreenLockActionBarActivity(), OnContactSelectedListener { override fun onContactSelected(number: String) { val existingThread = get(this).threadDatabase().getThreadIdIfExistsFor(number) - createConversation(existingThread, fromExternal(this, number), DistributionTypes.DEFAULT) + createConversation(existingThread, Address.fromSerialized(number), DistributionTypes.DEFAULT) } override fun onContactDeselected(number: String?) { /* Nothing */ } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index f52070a90f..0db87cf56d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -37,7 +37,6 @@ import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.UNKNOWN -import org.session.libsession.utilities.Address.Companion.fromExternal import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.Contact import org.session.libsession.utilities.IdentityKeyMismatch @@ -1410,7 +1409,7 @@ class MmsDatabase(context: Context, databaseHelper: Provider { val numbers = contactAccessor.getNumbersForThreadSearchFilter(context, query) - val addresses = numbers.map { fromExternal(context, it) } + val addresses = numbers.map { fromSerialized(it) } val conversations = threadDatabase.getFilteredConversationList(addresses) return if (conversations != null) diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java b/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java index 22f0addd71..09803bb793 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java @@ -68,7 +68,7 @@ protected void onHandleIntent(Intent intent) { VisibleMessage message = new VisibleMessage(); message.setText(content); message.setSentTimestamp(SnodeAPI.getNowWithOffset()); - MessageSender.send(message, Address.fromExternal(this, number)); + MessageSender.send(message, Address.fromSerialized(number)); } } catch (URISyntaxException e) { Toast.makeText(this, R.string.errorUnknown, Toast.LENGTH_LONG).show(); From 95f989a026398b85b23c50cfd9cd5d0b688a8e21 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:12:55 +1000 Subject: [PATCH 04/52] Initial implementation of recipient repository --- .../securesms/database/RecipientRepository.kt | 345 ++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt new file mode 100644 index 0000000000..847d8daeee --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -0,0 +1,345 @@ +package org.thoughtcrime.securesms.database + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import network.loki.messenger.libsession_util.ReadableGroupInfoConfig +import network.loki.messenger.libsession_util.util.Contact +import network.loki.messenger.libsession_util.util.GroupInfo +import network.loki.messenger.libsession_util.util.UserPic +import org.session.libsession.database.StorageProtocol +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.ConfigFactoryProtocol +import org.session.libsession.utilities.ConfigUpdateNotification +import org.session.libsession.utilities.GroupRecord +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.getGroup +import org.session.libsession.utilities.recipients.Recipient.RecipientSettings +import org.session.libsession.utilities.recipients.RecipientAvatar +import org.session.libsession.utilities.recipients.RecipientAvatar.Companion.toRecipientAvatar +import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.userConfigsChanged +import org.session.libsignal.utilities.AccountId +import org.session.libsignal.utilities.IdPrefix +import org.session.libsignal.utilities.Log +import java.lang.ref.WeakReference +import java.time.Instant +import java.time.ZonedDateTime +import javax.inject.Inject +import javax.inject.Singleton + +/** + * This repository is responsible for observing and retrieving recipient data from different sources. + * + * Not to be confused with [RecipientDatabase], where it manages the actual database storage of + * some recipient data. Note that not all recipient data is stored in the database, as we've moved + * them to the config system. Details in the [RecipientDatabase]. + * + * This class will source the correct recipient data from different sources based on their types. + */ +@Singleton +class RecipientRepository @Inject constructor( + private val configFactory: ConfigFactoryProtocol, + private val groupDatabase: GroupDatabase, + private val recipientDatabase: RecipientDatabase, + private val preferences: TextSecurePreferences, +) { + private val recipientCache = HashMap>>() + + fun observeRecipient(address: Address): Flow { + synchronized(recipientCache) { + var cached = recipientCache[address]?.get() + if (cached == null) { + cached = createRecipientFlow(address) + recipientCache[address] = WeakReference(cached) + } + + return cached + } + } + + // This function creates a flow that emits the recipient information for the given address, + // the function itself must be fast, not directly access db and lock free, as it is called from a locked context. + private fun createRecipientFlow(address: Address): SharedFlow { + return flow { + while (true) { + val (value, changeSource) = fetchRecipient(address) + ?: run { + // In this case, we won't be able to fetch the recipient forever, so we + // will emit null and stop the flow. + emit(null) + return@flow + } + + emit(value) + changeSource.first() + Log.d(TAG, "Recipient changed for ${address.address.substring(0..10)}") + } + + }.shareIn(GlobalScope, SharingStarted.WhileSubscribed(), replay = 1) + } + + private suspend fun fetchRecipient(address: Address): Pair>? { + val changeSource: Flow<*> + val value: RecipientV2? + val myAddress by lazy { preferences.getLocalNumber()?.let(Address::fromSerialized) } + + when { + address.isContact && address == myAddress -> { + // Ourselves + changeSource = configFactory.userConfigsChanged() + value = configFactory.withUserConfigs { + createLocalRecipient( + address, + it.userProfile.getName().orEmpty(), + it.userProfile.getPic() + ) + } + } + + address.isContact -> { + // When an address is said to be a "contact", it doesn't mean it is the user's contact, + // it just means the type of address is a person really. + // So we'll need to look it up in the contacts config first, if it's not there, + // we'll look it up in the recipient database. + val accountId = AccountId.fromStringOrNull(address.address) + if (accountId == null || accountId.prefix != IdPrefix.STANDARD) { + // When it's impossible to create a normal AccountId from the address, we + // have exhausted all options, so we bail and never be able recover. + return null + } + + // We know this address can be looked up in the contacts config or the recipient database. + changeSource = merge( + configFactory.userConfigsChanged(), + recipientDatabase.updateNotifications.filter { it == address } + ) + + val contactInConfig = configFactory.withUserConfigs { configs -> + configs.contacts.get(accountId.hexString) + } + + val settings: RecipientSettings? = withContext(Dispatchers.Default) { + recipientDatabase.getRecipientSettings(address).orNull() + } + + value = if (contactInConfig != null) { + createContactRecipient(address, contactInConfig, settings) + } else if (settings != null) { + createGenericRecipient(address, settings) + } else { + null + } + } + + address.isGroupV2 -> { + val groupId = AccountId(address.address) + val group = configFactory.getGroup(groupId) + + if (group == null) { + value = null + changeSource = configFactory.userConfigsChanged() + } else { + val settings: RecipientSettings? = withContext(Dispatchers.Default) { + recipientDatabase.getRecipientSettings(address).orNull() + } + + value = configFactory.withGroupConfigs(groupId) { configs -> + createGroupV2Recipient(address, group, configs.groupInfo, settings) + } + + changeSource = merge( + configFactory.userConfigsChanged(), + configFactory.configUpdateNotifications + .filterIsInstance() + .filter { it.groupId.hexString == address.address } + ) + } + } + + address.isCommunity || address.isLegacyGroup -> { + changeSource = groupDatabase.updateNotification + + val group: GroupRecord? = groupDatabase.getGroup(address.toGroupString()).orNull() + val settings: RecipientSettings? = withContext(Dispatchers.Default) { + recipientDatabase.getRecipientSettings(address).orNull() + } + + value = group?.let { createCommunityOrLegacyGroupRecipient(address, it, settings) } + } + + else -> { + changeSource = recipientDatabase.updateNotifications.filter { it == address } + val settings: RecipientSettings? = withContext(Dispatchers.Default) { + recipientDatabase.getRecipientSettings(address).orNull() + } + + value = settings?.let { createGenericRecipient(address, it) } + } + } + + return value to changeSource + } + + suspend fun getRecipient(address: Address): RecipientV2? { + return observeRecipient(address).first() + } + + @Deprecated( + "Use the suspend version of getRecipient instead", + ReplaceWith("getRecipient(address)") + ) + fun getRecipientSync(address: Address): RecipientV2? { + val flow = observeRecipient(address) + + // If the flow is a SharedFlow, we might be able to access its last cached value directly. + if (flow is SharedFlow) { + val lastCacheValue = flow.replayCache.lastOrNull() + if (lastCacheValue != null) { + return lastCacheValue + } + } + + return runBlocking { flow.first() } + } + + companion object { + private const val TAG = "RecipientRepository" + + private fun createLocalRecipient(address: Address, name: String, avatar: UserPic?): RecipientV2 { + return RecipientV2( + isLocalNumber = true, + address = address, + name = name, + approved = true, + approvedMe = true, + blocked = false, + mutedUntil = null, + autoDownloadAttachments = true, + notifyType = RecipientDatabase.NOTIFY_TYPE_ALL, + avatar = avatar?.toRecipientAvatar(), + nickname = null, + ) + } + + private val RecipientSettings.muteUntilDate: ZonedDateTime? + get() = if (muteUntil > 0) { + ZonedDateTime.from(Instant.ofEpochMilli(muteUntil)) + } else { + null + } + + private fun createGroupV2Recipient( + address: Address, + group: GroupInfo.ClosedGroupInfo, + groupInfo: ReadableGroupInfoConfig, + settings: RecipientSettings? + ): RecipientV2 { + return RecipientV2( + name = groupInfo.getName() ?: group.name, + address = address, + isLocalNumber = false, + nickname = null, + approvedMe = true, + approved = !group.invited, + avatar = groupInfo.getProfilePic().toRecipientAvatar(), + blocked = false, + mutedUntil = settings?.muteUntilDate, + notifyType = settings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, + autoDownloadAttachments = settings?.autoDownloadAttachments == true, + ) + + } + + /** + * Creates a RecipientV2 instance from the provided Contact config and optional fallback settings. + */ + private fun createContactRecipient( + address: Address, + contactInConfig: Contact, + fallbackSettings: RecipientSettings? + ): RecipientV2 { + return RecipientV2( + isLocalNumber = false, + address = address, + name = contactInConfig.name, + nickname = contactInConfig.nickname.takeIf { it.isNotBlank() }, + avatar = contactInConfig.profilePicture.toRecipientAvatar(), + approved = contactInConfig.approved, + approvedMe = contactInConfig.approvedMe, + blocked = contactInConfig.blocked, + mutedUntil = fallbackSettings?.muteUntilDate, + autoDownloadAttachments = fallbackSettings?.autoDownloadAttachments == true, + notifyType = fallbackSettings?.notifyType + ?: RecipientDatabase.NOTIFY_TYPE_ALL, + ) + } + + private fun createCommunityOrLegacyGroupRecipient( + address: Address, + group: GroupRecord, + settings: RecipientSettings? + ): RecipientV2 { + return RecipientV2( + isLocalNumber = false, + address = address, + name = group.title, + nickname = null, + avatar = RecipientAvatar.fromBytes(group.avatar), + approved = true, + approvedMe = true, + blocked = false, + mutedUntil = settings?.muteUntilDate, + autoDownloadAttachments = settings?.autoDownloadAttachments == true, + notifyType = settings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL + ) + } + + /** + * Creates a RecipientV2 instance from the provided Address and RecipientSettings. + * Note that this method assumes the recipient is not ourselves. + */ + private fun createGenericRecipient(address: Address, settings: RecipientSettings): RecipientV2 { + return RecipientV2( + isLocalNumber = false, + address = address, + name = settings.profileName.orEmpty(), + nickname = settings.systemDisplayName, + avatar = settings.profileAvatar?.let { RecipientAvatar.from(it, settings.profileKey) }, + approved = settings.isApproved, + approvedMe = settings.hasApprovedMe(), + blocked = settings.isBlocked, + mutedUntil = settings.muteUntil.takeIf { it > 0 } + ?.let { ZonedDateTime.from(Instant.ofEpochMilli(it)) }, + autoDownloadAttachments = settings.autoDownloadAttachments, + notifyType = settings.notifyType + ) + } + + fun empty(address: Address): RecipientV2 { + return RecipientV2( + isLocalNumber = false, + address = address, + name = "", + nickname = null, + approved = false, + approvedMe = false, + blocked = false, + mutedUntil = null, + autoDownloadAttachments = false, + notifyType = RecipientDatabase.NOTIFY_TYPE_ALL, + avatar = null + ) + } + } +} \ No newline at end of file From 18f3d46e05813ce7cb52b18d625c6f56ff5c302f Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:13:00 +1000 Subject: [PATCH 05/52] WIP --- .../libsession/database/StorageProtocol.kt | 8 --- .../utilities/ConfigFactoryProtocol.kt | 6 ++ .../utilities/recipients/RecipientV2.kt | 41 ++++++++++-- .../securesms/MediaPreviewActivity.kt | 29 +++----- .../conversation/v2/ConversationActivityV2.kt | 4 +- .../securesms/database/GroupDatabase.java | 66 +++++++++---------- .../securesms/database/MmsDatabase.kt | 9 ++- .../securesms/database/RecipientDatabase.java | 62 ++++++++++++----- .../securesms/database/Storage.kt | 4 -- .../database/loaders/PagingMediaLoader.java | 5 +- 10 files changed, 138 insertions(+), 96 deletions(-) diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 47cbdc9da1..10475b4641 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -2,7 +2,6 @@ package org.session.libsession.database import android.content.Context import android.net.Uri -import kotlinx.coroutines.flow.Flow import network.loki.messenger.libsession_util.util.KeyPair import org.session.libsession.messaging.BlindedIdMapping import org.session.libsession.messaging.calls.CallMessageType @@ -31,7 +30,6 @@ import org.session.libsession.utilities.GroupDisplayInfo import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient.RecipientSettings -import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceGroup @@ -57,11 +55,6 @@ interface StorageProtocol { // Signal fun getOrGenerateRegistrationID(): Int - // Recipient - fun observeRecipient(address: Address): Flow - @Deprecated("Use observeRecipient instead", ReplaceWith("observeRecipient(address)")) - fun getRecipientSync(address: Address): RecipientV2 - // Jobs fun persistJob(job: Job) fun markJobAsSucceeded(jobId: String) @@ -143,7 +136,6 @@ interface StorageProtocol { fun getZombieMembers(groupID: String): Set fun removeMember(groupID: String, member: Address) fun updateMembers(groupID: String, members: List
) - fun setZombieMembers(groupID: String, members: List
) fun getAllLegacyGroupPublicKeys(): Set fun getAllActiveClosedGroupPublicKeys(): Set fun addClosedGroupPublicKey(groupPublicKey: String) diff --git a/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt b/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt index 66734f463a..85c06628a5 100644 --- a/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt +++ b/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt @@ -154,6 +154,12 @@ suspend fun ConfigFactoryProtocol.waitUntilUserConfigsPushed(timeoutMills: Long } != null } +/** + * Flow that emits when the user configs are modified or merged. + */ +fun ConfigFactoryProtocol.userConfigsChanged(): Flow<*> = + configUpdateNotifications.filter { it is ConfigUpdateNotification.UserConfigsModified || it is ConfigUpdateNotification.UserConfigsMerged } + /** * Wait until all configs of given group are pushed to the server. * diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt index 6963d614e4..75491572ac 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt @@ -1,7 +1,7 @@ package org.session.libsession.utilities.recipients import network.loki.messenger.libsession_util.util.Bytes -import org.session.libsession.avatars.ContactPhoto +import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.utilities.Address import org.thoughtcrime.securesms.database.model.NotifyType import java.time.ZonedDateTime @@ -9,8 +9,8 @@ import java.time.ZonedDateTime data class RecipientV2( val isLocalNumber: Boolean, val address: Address, + val nickname: String?, val name: String, - val avatar: ContactPhoto?, val approved: Boolean, val approvedMe: Boolean, val blocked: Boolean, @@ -18,8 +18,7 @@ data class RecipientV2( val autoDownloadAttachments: Boolean, @get:NotifyType val notifyType: Int, - val profileAvatar: String?, - val profileKey: Bytes? + val avatar: RecipientAvatar? ) { val isGroupOrCommunityRecipient: Boolean get() = address.isGroupOrCommunity val isCommunityRecipient: Boolean get() = address.isCommunity @@ -27,3 +26,37 @@ data class RecipientV2( val isCommunityOutboxRecipient: Boolean get() = address.isCommunityOutbox val isGroupV2Recipient: Boolean get() = address.isGroupV2 } + + +sealed interface RecipientAvatar { + data class EncryptedRemotePic(val url: String, val key: Bytes) : RecipientAvatar + data class Inline(val bytes: Bytes) : RecipientAvatar + + companion object { + fun UserPic.toRecipientAvatar(): RecipientAvatar? { + return when { + url.isBlank() -> null + else -> EncryptedRemotePic( + url = url, + key = key + ) + } + } + + fun from(url: String, bytes: ByteArray?): RecipientAvatar? { + return if (url.isNotBlank() && bytes != null && bytes.isNotEmpty()) { + EncryptedRemotePic(url, Bytes(bytes)) + } else { + null + } + } + + fun fromBytes(bytes: ByteArray?): RecipientAvatar? { + return if (bytes == null || bytes.isEmpty()) { + null + } else { + Inline(Bytes(bytes)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt index a984411a8d..df60bb7498 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt @@ -36,6 +36,7 @@ import android.view.ViewTreeObserver import android.view.Window import android.widget.Toast import androidx.activity.viewModels +import androidx.core.content.IntentCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toDrawable import androidx.core.util.Pair @@ -72,11 +73,10 @@ import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ShareActivity import org.thoughtcrime.securesms.components.MediaView import org.thoughtcrime.securesms.components.dialogs.DeleteMediaPreviewDialog -import org.thoughtcrime.securesms.conversation.v2.DimensionUnit import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader import org.thoughtcrime.securesms.database.model.MmsMessageRecord -import org.thoughtcrime.securesms.media.MediaOverviewActivity.Companion.createIntent +import org.thoughtcrime.securesms.media.MediaOverviewActivity import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel.PreviewData import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter @@ -107,7 +107,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), RecipientModifiedLis private var initialMediaType: String? = null private var initialMediaSize: Long = 0 private var initialCaption: String? = null - private var conversationRecipient: Recipient? = null + private var conversationRecipient: Address? = null private var leftIsRecent = false private val viewModel: MediaPreviewViewModel by viewModels() private var viewPagerListener: ViewPagerListener? = null @@ -329,8 +329,9 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), RecipientModifiedLis } private fun initializeResources() { - val address = intent.getParcelableExtra
( - ADDRESS_EXTRA + conversationRecipient = IntentCompat.getParcelableExtra(intent, + ADDRESS_EXTRA, + Address::class.java ) initialMediaUri = intent.data @@ -338,16 +339,6 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), RecipientModifiedLis initialMediaSize = intent.getLongExtra(SIZE_EXTRA, 0) initialCaption = intent.getStringExtra(CAPTION_EXTRA) leftIsRecent = intent.getBooleanExtra(LEFT_IS_RECENT_EXTRA, false) - - conversationRecipient = if (address != null) { - Recipient.from( - this, - address, - true - ) - } else { - null - } } override fun onConfigurationChanged(newConfig: Configuration) { @@ -428,7 +419,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), RecipientModifiedLis } private fun showOverview() { - conversationRecipient?.address?.let { startActivity(createIntent(this, it)) } + conversationRecipient?.let { startActivity(MediaOverviewActivity.createIntent(this, it)) } } private fun forward() { @@ -512,13 +503,13 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), RecipientModifiedLis .format().toString() private fun sendMediaSavedNotificationIfNeeded() { - if (conversationRecipient == null || conversationRecipient?.isGroupOrCommunityRecipient == true) return + if (conversationRecipient == null || conversationRecipient?.isGroupOrCommunity == true) return val message = DataExtractionNotification( MediaSaved( nowWithOffset ) ) - send(message, conversationRecipient!!.address) + send(message, conversationRecipient!!) } @SuppressLint("StaticFieldLeak") @@ -542,7 +533,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), RecipientModifiedLis inflater.inflate(R.menu.media_preview, menu) val isDeprecatedLegacyGroup = conversationRecipient != null && - conversationRecipient?.isLegacyGroupRecipient == true && + conversationRecipient?.isLegacyGroup == true && deprecationManager.deprecationState.value == LegacyGroupDeprecationManager.DeprecationState.DEPRECATED if (!isMediaInDb || isDeprecatedLegacyGroup) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 59b120f481..9907dc5188 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -1957,7 +1957,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, override fun sendMessage() { val recipient = viewModel.recipient ?: return if (recipient.isContactRecipient && recipient.isBlocked) { - BlockedDialog(recipient, viewModel.getUsername(recipient.address.toString())).show(supportFragmentManager, "Blocked Dialog") + BlockedDialog(recipient.address, viewModel.getUsername(recipient.address.toString())).show(supportFragmentManager, "Blocked Dialog") return } val sentMessageInfo = if (binding.inputBar.linkPreview != null || binding.inputBar.quote != null) { @@ -2076,7 +2076,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, val expireStartedAtMs = if (viewModel.expirationConfiguration?.expiryMode is ExpiryMode.AfterSend) { sentTimestamp } else 0 - val outgoingTextMessage = OutgoingMediaMessage.from(message, recipient, attachments, localQuote, linkPreview, expiresInMs, expireStartedAtMs) + val outgoingTextMessage = OutgoingMediaMessage.from(message, recipient.address, attachments, localQuote, linkPreview, expiresInMs, expireStartedAtMs) // Clear the input bar binding.inputBar.text = "" diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index 867f29e17d..5ff235a4da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -15,7 +15,6 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase; -import org.jetbrains.annotations.NotNull; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.GroupRecord; import org.session.libsession.utilities.TextSecurePreferences; @@ -34,6 +33,11 @@ import javax.inject.Provider; +import kotlinx.coroutines.channels.BufferOverflow; +import kotlinx.coroutines.flow.MutableSharedFlow; +import kotlinx.coroutines.flow.SharedFlow; +import kotlinx.coroutines.flow.SharedFlowKt; + /** * @deprecated This database table management is only used for * legacy group and community management. It is not used in groupv2. For group v2 data, you generally need @@ -102,10 +106,17 @@ public static String getCreateUpdatedTimestampCommand() { "ADD COLUMN " + UPDATED + " INTEGER DEFAULT 0;"; } + private final MutableSharedFlow updateNotification = SharedFlowKt.MutableSharedFlow(0, 128, BufferOverflow.DROP_OLDEST); + public GroupDatabase(Context context, Provider databaseHelper) { super(context, databaseHelper); } + @NonNull + public SharedFlow getUpdateNotification() { + return updateNotification; + } + public Optional getGroup(String groupId) { try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?", new String[] {groupId}, @@ -232,6 +243,9 @@ public long create(@NonNull String groupId, @Nullable String title, @NonNull Lis notifyConversationListeners(threadId); notifyConversationListListeners(); + + updateNotification.tryEmit(groupId); + return threadId; } @@ -241,6 +255,7 @@ public boolean delete(@NonNull String groupId) { if (result > 0) { Recipient.removeCached(Address.fromSerialized(groupId)); notifyConversationListListeners(); + updateNotification.tryEmit(groupId); return true; } else { return false; @@ -268,6 +283,8 @@ public void update(String groupId, String title, SignalServiceAttachmentPointer recipient.setGroupAvatarId(avatar != null ? avatar.getId() : null); }); + updateNotification.tryEmit(groupId); + notifyConversationListListeners(); } @@ -284,6 +301,8 @@ public void updateTitle(String groupID, String newValue) { if (nameChanged) { notifyConversationListListeners(); + + updateNotification.tryEmit(groupId); } } @@ -308,6 +327,7 @@ public void updateProfilePicture(String groupID, byte[] newValue) { Recipient.applyCached(Address.fromSerialized(groupID), recipient -> recipient.setGroupAvatarId(avatarId == 0 ? null : avatarId)); notifyConversationListListeners(); + updateNotification.tryEmit(groupID); } @Override @@ -327,6 +347,7 @@ public void removeProfilePicture(String groupID) { Recipient.applyCached(Address.fromSerialized(groupID), recipient -> recipient.setGroupAvatarId(null)); notifyConversationListListeners(); + updateNotification.tryEmit(groupID); } public boolean hasDownloadedProfilePicture(String groupId) { @@ -355,25 +376,8 @@ public void updateMembers(String groupId, List
members) { Recipient.applyCached(Address.fromSerialized(groupId), recipient -> { recipient.setParticipants(Stream.of(members).map(a -> Recipient.from(context, a, false)).toList()); }); - } - - public void updateZombieMembers(String groupId, List
members) { - Collections.sort(members); - - ContentValues contents = new ContentValues(); - contents.put(ZOMBIE_MEMBERS, Address.toSerializedList(members, ',')); - getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", - new String[] {groupId}); - } - public void updateAdmins(String groupId, List
admins) { - Collections.sort(admins); - - ContentValues contents = new ContentValues(); - contents.put(ADMINS, Address.toSerializedList(admins, ',')); - contents.put(ACTIVE, 1); - - getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", new String[] {groupId}); + updateNotification.tryEmit(groupId); } public void updateFormationTimestamp(String groupId, Long formationTimestamp) { @@ -381,6 +385,8 @@ public void updateFormationTimestamp(String groupId, Long formationTimestamp) { contents.put(TIMESTAMP, formationTimestamp); getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", new String[] {groupId}); + + updateNotification.tryEmit(groupId); } public void updateTimestampUpdated(String groupId, Long updatedTimestamp) { @@ -388,6 +394,7 @@ public void updateTimestampUpdated(String groupId, Long updatedTimestamp) { contents.put(UPDATED, updatedTimestamp); getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", new String[] {groupId}); + updateNotification.tryEmit(groupId); } public void removeMember(String groupId, Address source) { @@ -407,6 +414,8 @@ public void removeMember(String groupId, Address source) { current.remove(removal); recipient.setParticipants(current); }); + + updateNotification.tryEmit(groupId); } private List
getCurrentMembers(String groupId, boolean zombieMembers) { @@ -448,26 +457,11 @@ public void setActive(String groupId, boolean active) { ContentValues values = new ContentValues(); values.put(ACTIVE, active ? 1 : 0); database.update(TABLE_NAME, values, GROUP_ID + " = ?", new String[] {groupId}); - } - - public boolean hasGroup(@NonNull String groupId) { - try (Cursor cursor = getReadableDatabase().rawQuery( - "SELECT 1 FROM " + TABLE_NAME + " WHERE " + GROUP_ID + " = ? LIMIT 1", - new String[]{groupId} - )) { - return cursor.getCount() > 0; - } - } - public void migrateEncodedGroup(@NotNull String legacyEncodedGroupId, @NotNull String newEncodedGroupId) { - String query = GROUP_ID+" = ?"; - ContentValues contentValues = new ContentValues(1); - contentValues.put(GROUP_ID, newEncodedGroupId); - SQLiteDatabase db = getWritableDatabase(); - db.update(TABLE_NAME, contentValues, query, new String[]{legacyEncodedGroupId}); + updateNotification.tryEmit(groupId); } - public static class Reader implements Closeable { + public static class Reader implements Closeable { private final Cursor cursor; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index 0db87cf56d..ba4e2971cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -458,7 +458,6 @@ class MmsDatabase(context: Context, databaseHelper: Provider obj.isQuote || contactAttachments.contains(obj) || previewAttachments.contains(obj) } .toList() - val recipient = Recipient.from(context, fromSerialized(address), false) var networkFailures: List? = LinkedList() var mismatches: List? = LinkedList() var quote: QuoteModel? = null @@ -490,7 +489,7 @@ class MmsDatabase(context: Context, databaseHelper: Provider obj } @@ -768,9 +767,9 @@ class MmsDatabase(context: Context, databaseHelper: Provider obj.address } .toList(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 5038e0ba75..20d223ee41 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -23,6 +23,21 @@ import javax.inject.Provider; +import kotlinx.coroutines.channels.BufferOverflow; +import kotlinx.coroutines.flow.MutableSharedFlow; +import kotlinx.coroutines.flow.SharedFlow; +import kotlinx.coroutines.flow.SharedFlowKt; + +/** + * The database(table) where some recipient data is stored, including names, avatars, notification settings, etc. + * + * Note: We have moved a large chunk of recipient data into the config system, so most of the time you + * should really get them from {@link org.session.libsession.utilities.ConfigFactoryProtocol} instead. + * + * This database is only used for data that is not in the config system, such as blinded contacts, + * the people who are not your contacts or not your blinded convo, such as unknown people in groups, + * communities, etc. Their data will be stored here instead. + */ public class RecipientDatabase extends Database { private static final String TAG = RecipientDatabase.class.getSimpleName(); @@ -178,10 +193,17 @@ public static String getAddBlocksCommunityMessageRequests() { public static final int NOTIFY_TYPE_MENTIONS = 1; public static final int NOTIFY_TYPE_NONE = 2; + private final MutableSharedFlow
updateNotifications = SharedFlowKt.MutableSharedFlow(0, 256, BufferOverflow.DROP_OLDEST); + public RecipientDatabase(Context context, Provider databaseHelper) { super(context, databaseHelper); } + @NonNull + public SharedFlow
getUpdateNotifications() { + return updateNotifications; + } + public RecipientReader getRecipientsWithNotificationChannels() { SQLiteDatabase database = getReadableDatabase(); Cursor cursor = database.query(TABLE_NAME, new String[] {ID, ADDRESS}, NOTIFICATION_CHANNEL + " NOT NULL", @@ -269,6 +291,8 @@ public void setApproved(@NonNull Address recipient, boolean approved) { values.put(APPROVED, approved ? 1 : 0); updateOrInsert(recipient, values); notifyRecipientListeners(); + + updateNotifications.tryEmit(recipient); } public void setApprovedMe(@NonNull Address recipient, boolean approvedMe) { @@ -276,6 +300,8 @@ public void setApprovedMe(@NonNull Address recipient, boolean approvedMe) { values.put(APPROVED_ME, approvedMe ? 1 : 0); updateOrInsert(recipient, values); notifyRecipientListeners(); + + updateNotifications.tryEmit(recipient); } public void setBlocked(@NonNull Iterable
recipients, boolean blocked) { @@ -292,6 +318,8 @@ public void setBlocked(@NonNull Iterable
recipients, boolean blocked) { db.endTransaction(); } notifyRecipientListeners(); + + recipients.forEach(updateNotifications::tryEmit); } // Delete a recipient with the given address from the database @@ -302,27 +330,27 @@ public void deleteRecipient(@NonNull String recipientAddress) { notifyRecipientListeners(); } - public void setAutoDownloadAttachments(@NonNull Recipient recipient, boolean shouldAutoDownloadAttachments) { + public void setAutoDownloadAttachments(@NonNull Address recipient, boolean shouldAutoDownloadAttachments) { SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); try { ContentValues values = new ContentValues(); values.put(AUTO_DOWNLOAD, shouldAutoDownloadAttachments ? 1 : 0); - db.update(TABLE_NAME, values, ADDRESS+ " = ?", new String[]{recipient.getAddress().toString()}); - recipient.resolve().setAutoDownloadAttachments(shouldAutoDownloadAttachments); + db.update(TABLE_NAME, values, ADDRESS+ " = ?", new String[]{recipient.toString()}); db.setTransactionSuccessful(); } finally { db.endTransaction(); } notifyRecipientListeners(); + updateNotifications.tryEmit(recipient); } - public void setMuted(@NonNull Recipient recipient, long until) { + public void setMuted(@NonNull Address recipient, long until) { ContentValues values = new ContentValues(); values.put(MUTE_UNTIL, until); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setMuted(until); + updateOrInsert(recipient, values); notifyRecipientListeners(); + updateNotifications.tryEmit(recipient); } /** @@ -330,13 +358,13 @@ public void setMuted(@NonNull Recipient recipient, long until) { * @param recipient to modify notifications for * @param notifyType the new notification type {@link #NOTIFY_TYPE_ALL}, {@link #NOTIFY_TYPE_MENTIONS} or {@link #NOTIFY_TYPE_NONE} */ - public void setNotifyType(@NonNull Recipient recipient, int notifyType) { + public void setNotifyType(@NonNull Address recipient, int notifyType) { ContentValues values = new ContentValues(); values.put(NOTIFY_TYPE, notifyType); - updateOrInsert(recipient.getAddress(), values); - recipient.resolve().setNotifyType(notifyType); + updateOrInsert(recipient, values); notifyConversationListListeners(); notifyRecipientListeners(); + updateNotifications.tryEmit(recipient); } public void setProfileKey(@NonNull Address recipient, @Nullable byte[] profileKey) { @@ -344,6 +372,7 @@ public void setProfileKey(@NonNull Address recipient, @Nullable byte[] profileKe values.put(PROFILE_KEY, profileKey == null ? null : Base64.encodeBytes(profileKey)); updateOrInsert(recipient, values); notifyRecipientListeners(); + updateNotifications.tryEmit(recipient); } public void setProfileAvatar(@NonNull Address recipient, @Nullable String profileAvatar) { @@ -351,23 +380,23 @@ public void setProfileAvatar(@NonNull Address recipient, @Nullable String profil contentValues.put(SESSION_PROFILE_AVATAR, profileAvatar); updateOrInsert(recipient, contentValues); notifyRecipientListeners(); + updateNotifications.tryEmit(recipient); } - public void setProfileName(@NonNull Recipient recipient, @Nullable String profileName) { + public void setProfileName(@NonNull Address recipient, @Nullable String profileName) { ContentValues contentValues = new ContentValues(1); contentValues.put(SYSTEM_DISPLAY_NAME, profileName); - updateOrInsert(recipient.getAddress(), contentValues); - recipient.resolve().setName(profileName); - recipient.resolve().setProfileName(profileName); + updateOrInsert(recipient, contentValues); notifyRecipientListeners(); + updateNotifications.tryEmit(recipient); } - public void setNotificationChannel(@NonNull Recipient recipient, @Nullable String notificationChannel) { + public void setNotificationChannel(@NonNull Address recipient, @Nullable String notificationChannel) { ContentValues contentValues = new ContentValues(1); contentValues.put(NOTIFICATION_CHANNEL, notificationChannel); - updateOrInsert(recipient.getAddress(), contentValues); - recipient.setNotificationChannel(notificationChannel); + updateOrInsert(recipient, contentValues); notifyRecipientListeners(); + updateNotifications.tryEmit(recipient); } public void setBlocksCommunityMessageRequests(@NonNull Address recipient, boolean isBlocked) { @@ -375,6 +404,7 @@ public void setBlocksCommunityMessageRequests(@NonNull Address recipient, boolea contentValues.put(BLOCKS_COMMUNITY_MESSAGE_REQUESTS, isBlocked ? 1 : 0); updateOrInsert(recipient, contentValues); notifyRecipientListeners(); + updateNotifications.tryEmit(recipient); } private void updateOrInsert(Address address, ContentValues contentValues) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 7cc5220962..26c9f220eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -840,10 +840,6 @@ open class Storage @Inject constructor( groupDatabase.updateMembers(groupID, members) } - override fun setZombieMembers(groupID: String, members: List
) { - groupDatabase.updateZombieMembers(groupID, members) - } - override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, sentTimestamp: Long): Long? { val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList()) val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, 0, true, false) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java index 6e861dad0d..3855886e2f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java @@ -10,6 +10,7 @@ import androidx.core.util.Pair; import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId; +import org.session.libsession.utilities.Address; import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; @@ -21,11 +22,11 @@ public class PagingMediaLoader extends AsyncLoader> { @SuppressWarnings("unused") private static final String TAG = PagingMediaLoader.class.getSimpleName(); - private final Recipient recipient; + private final Address recipient; private final Uri uri; private final boolean leftIsRecent; - public PagingMediaLoader(@NonNull Context context, @NonNull Recipient recipient, @NonNull Uri uri, boolean leftIsRecent) { + public PagingMediaLoader(@NonNull Context context, @NonNull Address recipient, @NonNull Uri uri, boolean leftIsRecent) { super(context); this.recipient = recipient; this.uri = uri; From 05fac2a0118448fd66f313526b549952b4bc5d47 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Tue, 24 Jun 2025 17:26:12 +1000 Subject: [PATCH 06/52] WIP --- .../database/MessageDataProvider.kt | 2 - .../libsession/database/StorageProtocol.kt | 6 +- .../messaging/MessagingModuleConfiguration.kt | 4 +- .../libsession/messaging/contacts/Contact.kt | 3 +- .../ReceivedMessageHandler.kt | 11 ++- .../utilities/recipients/Recipient.java | 5 +- .../utilities/recipients/RecipientV2.kt | 38 ++++++++- .../securesms/ApplicationContext.kt | 5 +- .../securesms/MediaPreviewActivity.kt | 15 ++-- .../attachments/DatabaseAttachmentProvider.kt | 9 --- .../components/TypingStatusSender.java | 12 ++- .../contacts/ShareContactListLoader.kt | 10 +-- .../DisappearingMessages.kt | 2 +- .../DisappearingMessagesViewModel.kt | 24 +++--- .../v2/ConversationReactionOverlay.kt | 7 +- .../v2/MessageDetailsViewModel.kt | 11 ++- .../v2/components/AlbumThumbnailView.kt | 4 +- .../conversation/v2/dialogs/DownloadDialog.kt | 6 +- .../v2/mention/MentionViewModel.kt | 26 +++--- .../menus/ConversationActionModeCallback.kt | 4 +- .../v2/messages/AttachmentControlView.kt | 5 +- .../v2/messages/ControlMessageView.kt | 9 ++- .../conversation/v2/messages/QuoteView.kt | 3 +- .../v2/messages/VisibleMessageContentView.kt | 8 +- .../v2/messages/VisibleMessageView.kt | 20 ++--- .../NotificationSettingsViewModel.kt | 20 +++-- .../v2/utilities/ResendMessageUtilities.kt | 11 ++- .../securesms/database/MmsDatabase.kt | 33 -------- .../securesms/database/RecipientDatabase.java | 23 ++---- .../securesms/database/RecipientRepository.kt | 80 +++++++------------ .../securesms/database/Storage.kt | 64 ++++++--------- .../securesms/database/ThreadDatabase.java | 55 +++++-------- .../database/model/DisplayRecord.java | 7 +- .../database/model/MediaMmsMessageRecord.java | 5 +- .../database/model/MessageRecord.java | 16 ++-- .../database/model/MmsMessageRecord.java | 7 +- .../database/model/ThreadRecord.java | 9 ++- .../securesms/dependencies/DatabaseModule.kt | 3 - .../repository/ConversationRepository.kt | 68 ++++++---------- .../securesms/util/AvatarUtils.kt | 7 +- .../securesms/util/ContactUtilities.kt | 6 +- .../securesms/util/SessionMetaProtocol.kt | 9 ++- 42 files changed, 311 insertions(+), 361 deletions(-) diff --git a/app/src/main/java/org/session/libsession/database/MessageDataProvider.kt b/app/src/main/java/org/session/libsession/database/MessageDataProvider.kt index dbe35e6f4f..3d1e5a91e9 100644 --- a/app/src/main/java/org/session/libsession/database/MessageDataProvider.kt +++ b/app/src/main/java/org/session/libsession/database/MessageDataProvider.kt @@ -9,7 +9,6 @@ import org.session.libsession.messaging.sending_receiving.attachments.SessionSer import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentStream import org.session.libsession.utilities.Address import org.session.libsession.utilities.UploadResult -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceAttachmentStream import org.thoughtcrime.securesms.database.model.MessageId @@ -45,5 +44,4 @@ interface MessageDataProvider { fun getMessageBodyFor(timestamp: Long, author: String): String fun getAttachmentIDsFor(mmsMessageId: Long): List fun getLinkPreviewAttachmentIDFor(mmsMessageId: Long): Long? - fun getIndividualRecipientForMms(mmsId: Long): Recipient? } \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 10475b4641..2424ec8644 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -200,12 +200,10 @@ interface StorageProtocol { fun getAllContacts(): Set fun setContact(contact: Contact) fun deleteContactAndSyncConfig(accountId: String) - fun getRecipientForThread(threadId: Long): Recipient? + fun getRecipientForThread(threadId: Long): Address? fun getRecipientSettings(address: Address): RecipientSettings? fun syncLibSessionContacts(contacts: List, timestamp: Long?) - fun hasAutoDownloadFlagBeenSet(recipient: Recipient): Boolean - fun shouldAutoDownloadAttachments(recipient: Recipient): Boolean - fun setAutoDownloadAttachments(recipient: Recipient, shouldAutoDownloadAttachments: Boolean) + fun setAutoDownloadAttachments(recipient: Address, shouldAutoDownloadAttachments: Boolean) // Attachments fun getAttachmentDataUri(attachmentId: AttachmentId): Uri diff --git a/app/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt b/app/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt index e78af8da8b..4df6f724f9 100644 --- a/app/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt +++ b/app/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt @@ -12,6 +12,7 @@ import org.session.libsession.utilities.Device import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.Toaster import org.session.libsession.utilities.UsernameUtils +import org.thoughtcrime.securesms.database.RecipientRepository class MessagingModuleConfiguration( val context: Context, @@ -25,7 +26,8 @@ class MessagingModuleConfiguration( val clock: SnodeClock, val preferences: TextSecurePreferences, val deprecationManager: LegacyGroupDeprecationManager, - val usernameUtils: UsernameUtils + val usernameUtils: UsernameUtils, + val recipientRepository: RecipientRepository, ) { companion object { diff --git a/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt b/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt index 6d899da758..9b41df6f08 100644 --- a/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt +++ b/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt @@ -3,6 +3,7 @@ package org.session.libsession.messaging.contacts import android.os.Parcelable import kotlinx.parcelize.Parcelize import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.truncateIdForDisplay @Parcelize @@ -69,7 +70,7 @@ class Contact( } companion object { - fun contextForRecipient(recipient: Recipient): ContactContext { + fun contextForRecipient(recipient: RecipientV2): ContactContext { return if (recipient.isCommunityRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR } } diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 80aedb6b9e..36c02da0a4 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -57,6 +57,7 @@ import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.database.ConfigDatabase +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.ReactionRecord import java.security.MessageDigest @@ -64,8 +65,8 @@ import java.security.SignatureException import kotlin.math.min internal fun MessageReceiver.isBlocked(publicKey: String): Boolean { - val recipient = MessagingModuleConfiguration.shared.storage.getRecipientSync(Address.fromSerialized(publicKey)) - return recipient.blocked + val recipient = MessagingModuleConfiguration.shared.recipientRepository.getRecipientSync(Address.fromSerialized(publicKey)) + return recipient?.blocked == true } fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content, threadId: Long, openGroupID: String?, groupv2Id: AccountId?) { @@ -294,6 +295,7 @@ class VisibleMessageHandlerContext( val groupManagerV2: GroupManagerV2, val messageExpirationManager: SSKEnvironment.MessageExpirationManagerProtocol, val messageDataProvider: MessageDataProvider, + val recipientRepository: RecipientRepository, ) { constructor(module: MessagingModuleConfiguration, threadId: Long, openGroupID: String?): this( @@ -304,7 +306,8 @@ class VisibleMessageHandlerContext( profileManager = SSKEnvironment.shared.profileManager, groupManagerV2 = module.groupManagerV2, messageExpirationManager = SSKEnvironment.shared.messageExpirationManager, - messageDataProvider = module.messageDataProvider + messageDataProvider = module.messageDataProvider, + recipientRepository = module.recipientRepository, ) val openGroup: OpenGroup? by lazy { @@ -348,7 +351,7 @@ fun MessageReceiver.handleVisibleMessage( // Update profile if needed val address = Address.fromSerialized(messageSender!!) - val recipient = context.storage.getRecipientSync(address) + val recipient = context.recipientRepository.getRecipientSync(address) if (runProfileUpdate) { val profile = message.profile val isUserBlindedSender = messageSender == context.userBlindedKey diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java index d4327cf4ae..cb68ba5714 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java +++ b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java @@ -597,7 +597,8 @@ public static class RecipientSettings { private final boolean approvedMe; private final long muteUntil; private final int notifyType; - private final boolean autoDownloadAttachments; + @Nullable + private final Boolean autoDownloadAttachments; private final int expireMessages; private final byte[] profileKey; private final String systemDisplayName; @@ -608,7 +609,7 @@ public static class RecipientSettings { public RecipientSettings(boolean blocked, boolean approved, boolean approvedMe, long muteUntil, int notifyType, - boolean autoDownloadAttachments, + @Nullable Boolean autoDownloadAttachments, int expireMessages, @Nullable byte[] profileKey, @Nullable String systemDisplayName, diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt index 75491572ac..173188ecbb 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt @@ -1,8 +1,11 @@ package org.session.libsession.utilities.recipients import network.loki.messenger.libsession_util.util.Bytes +import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.UserPic +import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.utilities.Address +import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.model.NotifyType import java.time.ZonedDateTime @@ -15,16 +18,47 @@ data class RecipientV2( val approvedMe: Boolean, val blocked: Boolean, val mutedUntil: ZonedDateTime?, - val autoDownloadAttachments: Boolean, + val autoDownloadAttachments: Boolean?, @get:NotifyType val notifyType: Int, - val avatar: RecipientAvatar? + val avatar: RecipientAvatar?, + val expiryMode: ExpiryMode, ) { val isGroupOrCommunityRecipient: Boolean get() = address.isGroupOrCommunity val isCommunityRecipient: Boolean get() = address.isCommunity val isCommunityInboxRecipient: Boolean get() = address.isCommunityInbox val isCommunityOutboxRecipient: Boolean get() = address.isCommunityOutbox val isGroupV2Recipient: Boolean get() = address.isGroupV2 + val isLegacyGroupRecipient: Boolean get() = address.isLegacyGroup + + val displayName: String + get() = nickname?.takeIf { it.isNotBlank() } ?: name + + fun isMuted(now: ZonedDateTime = ZonedDateTime.now()): Boolean { + return mutedUntil?.isAfter(now) == true + } + + val mutedUntilMills: Long? + get() = mutedUntil?.toInstant()?.toEpochMilli() + + companion object { + fun empty(address: Address): RecipientV2 { + return RecipientV2( + isLocalNumber = false, + address = address, + nickname = null, + name = "", + approved = false, + approvedMe = false, + blocked = false, + mutedUntil = null, + autoDownloadAttachments = true, + notifyType = RecipientDatabase.NOTIFY_TYPE_ALL, + avatar = null, + expiryMode = ExpiryMode.NONE, + ) + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt index e58c30351c..2a1b11417b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt @@ -71,6 +71,7 @@ import org.thoughtcrime.securesms.components.TypingStatusSender import org.thoughtcrime.securesms.configs.ConfigUploader import org.thoughtcrime.securesms.database.EmojiSearchDatabase import org.thoughtcrime.securesms.database.LokiAPIDatabase +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.EmojiSearchData import org.thoughtcrime.securesms.debugmenu.DebugActivity @@ -188,6 +189,7 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, @Inject lateinit var cleanupInvitationHandler: Lazy @Inject lateinit var usernameUtils: Lazy @Inject lateinit var pollerManager: Lazy + @Inject lateinit var recipientRepository: Lazy @Inject lateinit var backgroundPollManager: Lazy // Exists here only to start upon app starts @@ -285,7 +287,8 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, clock = snodeClock.get(), preferences = textSecurePreferences.get(), deprecationManager = legacyGroupDeprecationManager.get(), - usernameUtils = usernameUtils.get() + usernameUtils = usernameUtils.get(), + recipientRepository = recipientRepository.get(), ) startKovenant() diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt index df60bb7498..88731b3e23 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt @@ -69,6 +69,7 @@ import org.session.libsession.utilities.Util.runOnMain import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientModifiedListener +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ShareActivity import org.thoughtcrime.securesms.components.MediaView @@ -99,7 +100,7 @@ import kotlin.math.min * Activity for displaying media attachments in-app */ @AndroidEntryPoint -class MediaPreviewActivity : ScreenLockActionBarActivity(), RecipientModifiedListener, +class MediaPreviewActivity : ScreenLockActionBarActivity(), LoaderManager.LoaderCallbacks?>, RailItemListener, MediaView.FullscreenToggleListener { private lateinit var binding: MediaPreviewActivityBinding @@ -253,10 +254,6 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), RecipientModifiedLis Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults) } - override fun onModified(recipient: Recipient) { - runOnMain { this.updateActionBar() } - } - override fun onRailItemClicked(distanceFromActive: Int) { binding.mediaPager.currentItem = binding.mediaPager.currentItem + distanceFromActive } @@ -783,7 +780,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), RecipientModifiedLis } class MediaItem( - val recipient: Recipient?, + val recipient: RecipientV2?, val attachment: DatabaseAttachment?, val uri: Uri, val mimeType: String, @@ -812,7 +809,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), RecipientModifiedLis fun getPreviewIntent(context: Context?, args: MediaPreviewArgs): Intent? { return getPreviewIntent( context, args.slide, - args.mmsRecord, args.thread + args.mmsRecord, args.thread.address ) } @@ -820,14 +817,14 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), RecipientModifiedLis context: Context?, slide: Slide, mms: MmsMessageRecord, - threadRecipient: Recipient + threadRecipient: Address ): Intent? { var previewIntent: Intent? = null if (isContentTypeSupported(slide.contentType) && slide.uri != null) { previewIntent = Intent(context, MediaPreviewActivity::class.java) previewIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .setDataAndType(slide.uri, slide.contentType) - .putExtra(ADDRESS_EXTRA, threadRecipient.address) + .putExtra(ADDRESS_EXTRA, threadRecipient) .putExtra(OUTGOING_EXTRA, mms.isOutgoing) .putExtra(DATE_EXTRA, mms.timestamp) .putExtra(SIZE_EXTRA, slide.asAttachment().size) diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt index 57188d6fe1..76a0ea1dcb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -16,7 +16,6 @@ import org.session.libsession.messaging.sending_receiving.attachments.SessionSer import org.session.libsession.utilities.Address import org.session.libsession.utilities.UploadResult import org.session.libsession.utilities.Util -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.messages.SignalServiceAttachment import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceAttachmentStream @@ -104,14 +103,6 @@ class DatabaseAttachmentProvider(context: Context, helper: Provider selfTypingTimers; private final ThreadDatabase threadDatabase; + private final RecipientRepository recipientRepository; @Inject - public TypingStatusSender(ThreadDatabase threadDatabase) { + public TypingStatusSender(ThreadDatabase threadDatabase, RecipientRepository recipientRepository) { this.threadDatabase = threadDatabase; + this.recipientRepository = recipientRepository; this.selfTypingTimers = new HashMap<>(); } @@ -80,8 +85,11 @@ private synchronized void onTypingStopped(long threadId, boolean notify) { } private void sendTyping(long threadId, boolean typingStarted) { - Recipient recipient = threadDatabase.getRecipientForThreadId(threadId); + Address address = threadDatabase.getRecipientForThreadId(threadId); + if (address == null) { return; } + RecipientV2 recipient = recipientRepository.getRecipientSync(address); if (recipient == null) { return; } + if (!SessionMetaProtocol.shouldSendTypingIndicator(recipient)) { return; } TypingIndicator typingIndicator; if (typingStarted) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt index d3374b37bf..35786f3b68 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt @@ -3,13 +3,13 @@ package org.thoughtcrime.securesms.contacts import android.content.Context import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.util.AsyncLoader import org.thoughtcrime.securesms.util.ContactUtilities import org.thoughtcrime.securesms.util.LastMessageSentTimestamp sealed class ContactSelectionListItem { - class Contact(val recipient: Recipient) : ContactSelectionListItem() + class Contact(val recipient: RecipientV2) : ContactSelectionListItem() } class ShareContactListLoader( @@ -25,14 +25,14 @@ class ShareContactListLoader( if(it.first.isLegacyGroupRecipient && deprecationManager.isDeprecated) return@filter false // ignore legacy group when deprecated if(it.first.isCommunityRecipient) { // ignore communities without write access val storage = MessagingModuleConfiguration.shared.storage - val threadId = storage.getThreadId(it.first) ?: return@filter false + val threadId = storage.getThreadId(it.first.address) ?: return@filter false val openGroup = storage.getOpenGroup(threadId) ?: return@filter false return@filter openGroup.canWrite } if (filter.isNullOrEmpty()) return@filter true it.first.name.contains(filter.trim(), true) || it.first.address.toString().contains(filter.trim(), true) }.sortedWith( - compareBy> { !it.first.isLocalNumber } // NTS come first + compareBy> { !it.first.isLocalNumber } // NTS come first .thenByDescending { it.second } // then order by last message time ) .map { it.first }.toList() @@ -40,7 +40,7 @@ class ShareContactListLoader( return getItems(contacts) } - private fun getItems(contacts: List): List { + private fun getItems(contacts: List): List { val items = contacts.map { ContactSelectionListItem.Contact(it) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt index ec938d39e6..798007b463 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt @@ -69,7 +69,7 @@ class DisappearingMessages @Inject constructor( text = if (message.expiresIn == 0L) R.string.confirm else R.string.set, contentDescriptionRes = if (message.expiresIn == 0L) R.string.AccessibilityId_confirm else R.string.AccessibilityId_setButton ) { - set(message.threadId, message.recipient.address, message.expiryMode, message.recipient.isGroupRecipient) + set(message.threadId, message.recipient.address, message.expiryMode, message.recipient.address.isGroup) } cancelButton() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt index 3ebd1a938f..a603f69a8b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt @@ -18,13 +18,17 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.libsession_util.util.ExpiryMode +import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.getGroup +import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.UiState import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.toUiState import org.thoughtcrime.securesms.conversation.v2.settings.ConversationSettingsNavigator import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.ThreadDatabase +import org.thoughtcrime.securesms.dependencies.ConfigFactory @HiltViewModel(assistedFactory = DisappearingMessagesViewModel.Factory::class) class DisappearingMessagesViewModel @AssistedInject constructor( @@ -38,6 +42,7 @@ class DisappearingMessagesViewModel @AssistedInject constructor( private val groupDb: GroupDatabase, private val storage: Storage, private val navigator: ConversationSettingsNavigator, + private val configFactory: ConfigFactoryProtocol, ) : ViewModel() { private val _state = MutableStateFlow( @@ -55,26 +60,27 @@ class DisappearingMessagesViewModel @AssistedInject constructor( init { viewModelScope.launch { val expiryMode = storage.getExpirationConfiguration(threadId)?.expiryMode ?: ExpiryMode.NONE - val recipient = threadDb.getRecipientForThreadId(threadId)?: return@launch + val address = threadDb.getRecipientForThreadId(threadId)?: return@launch val isAdmin = when { - recipient.isGroupV2Recipient -> { + address.isGroupV2 -> { // Handle the new closed group functionality - storage.getMembers(recipient.address.toString()).any { it.accountId() == textSecurePreferences.getLocalNumber() && it.admin } + storage.getMembers(address.toString()).any { it.accountId() == textSecurePreferences.getLocalNumber() && it.admin } } - recipient.isLegacyGroupRecipient -> { - val groupRecord = groupDb.getGroup(recipient.address.toGroupString()).orNull() + + address.isLegacyGroup -> { + val groupRecord = groupDb.getGroup(address.toGroupString()).orNull() // Handle as legacy group groupRecord?.admins?.any{ it.toString() == textSecurePreferences.getLocalNumber() } == true } - else -> !recipient.isGroupOrCommunityRecipient + else -> !address.isGroupOrCommunity } _state.update { it.copy( - address = recipient.address, - isGroup = recipient.isGroupRecipient, - isNoteToSelf = recipient.address.toString() == textSecurePreferences.getLocalNumber(), + address = address, + isGroup = address.isGroup, + isNoteToSelf = address.toString() == textSecurePreferences.getLocalNumber(), isSelfAdmin = isAdmin, expiryMode = expiryMode, persistedMode = expiryMode diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt index 3ada7066c2..ece56f3f2d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt @@ -39,6 +39,7 @@ import org.session.libsession.LocalisedTimeUtil.toShortTwoPartString import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.snode.SnodeAPI +import org.session.libsession.utilities.Address import org.session.libsession.utilities.StringSubstitutionConstants.TIME_LARGE_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber @@ -542,7 +543,7 @@ class ConversationReactionOverlay : FrameLayout { .firstOrNull() ?.let(ReactionRecord::emoji) - private fun getMenuActionItems(message: MessageRecord, recipient: Recipient): List { + private fun getMenuActionItems(message: MessageRecord, recipient: Address): List { val items: MutableList = ArrayList() // Prepare @@ -552,7 +553,7 @@ class ConversationReactionOverlay : FrameLayout { val openGroup = lokiThreadDatabase.getOpenGroupChat(message.threadId) val userPublicKey = textSecurePreferences.getLocalNumber()!! - val isDeprecatedLegacyGroup = recipient.isLegacyGroupRecipient && + val isDeprecatedLegacyGroup = recipient.isLegacyGroup && deprecationManager.isDeprecated // control messages and "marked as deleted" messages can only delete @@ -579,7 +580,7 @@ class ConversationReactionOverlay : FrameLayout { items += ActionItem(R.attr.menu_copy_icon, R.string.copy, { handleActionItemClicked(Action.COPY_MESSAGE) }) } // Copy Account ID - if (!recipient.isCommunityRecipient && message.isIncoming && !isDeleteOnly) { + if (!recipient.isCommunity && message.isIncoming && !isDeleteOnly) { items += ActionItem(R.attr.menu_copy_icon, R.string.accountIDCopy, { handleActionItemClicked(Action.COPY_ACCOUNT_ID) }) } // Delete message diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index ab004eef11..5579794460 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -27,12 +27,14 @@ import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAt import org.session.libsession.utilities.Address import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.MediaPreviewArgs import org.thoughtcrime.securesms.database.AttachmentDatabase import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.LokiMessageDatabase import org.thoughtcrime.securesms.database.MmsSmsDatabase +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.MessageId @@ -62,7 +64,8 @@ class MessageDetailsViewModel @AssistedInject constructor( private val context: ApplicationContext, private val avatarUtils: AvatarUtils, messageDataProvider: MessageDataProvider, - private val storage: Storage + private val storage: Storage, + private val recipientRepository: RecipientRepository, ) : ViewModel() { private val state = MutableStateFlow(MessageDetailsState()) val stateFlow = state.asStateFlow() @@ -112,7 +115,7 @@ class MessageDetailsViewModel @AssistedInject constructor( state.value = messageRecord.run { val slides = mmsRecord?.slideDeck?.slides ?: emptyList() - val conversation = threadDb.getRecipientForThreadId(threadId)!! + val conversation = recipientRepository.getRecipient(threadDb.getRecipientForThreadId(threadId)!!) val isDeprecatedLegacyGroup = conversation.isLegacyGroupRecipient && deprecationManager.isDeprecated @@ -130,7 +133,7 @@ class MessageDetailsViewModel @AssistedInject constructor( } val sender = if(messageRecord.isOutgoing){ - Recipient.from(context, Address.fromSerialized(prefs.getLocalNumber() ?: ""), false) + recipientRepository.getRecipient(Address.fromSerialized(prefs.getLocalNumber()!!))!! } else individualRecipient val attachments = slides.map(::Attachment) @@ -250,7 +253,7 @@ data class MessageDetailsState( val status: MessageStatus? = null, val senderInfo: TitledText? = null, val senderAvatarData: AvatarUIData? = null, - val thread: Recipient? = null, + val thread: RecipientV2? = null, val readOnly: Boolean = false, ) { val fromTitle = GetString(R.string.from) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt index b85f0f6d9c..5e3114a92d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt @@ -16,8 +16,10 @@ import com.squareup.phrase.Phrase import network.loki.messenger.R import network.loki.messenger.databinding.AlbumThumbnailViewBinding import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment +import org.session.libsession.utilities.Address import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.components.CornerMask import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView @@ -49,7 +51,7 @@ class AlbumThumbnailView : RelativeLayout { // region Interaction - fun calculateHitObject(event: MotionEvent, mms: MmsMessageRecord, threadRecipient: Recipient, downloadPendingAttachment: (DatabaseAttachment) -> Unit) { + fun calculateHitObject(event: MotionEvent, mms: MmsMessageRecord, threadRecipient: Address, downloadPendingAttachment: (DatabaseAttachment) -> Unit) { val rawXInt = event.rawX.toInt() val rawYInt = event.rawY.toInt() val eventRect = Rect(rawXInt, rawYInt, rawXInt, rawYInt) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt index baba827140..361738169b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt @@ -9,8 +9,8 @@ import network.loki.messenger.R import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.StringSubstitutionConstants.CONVERSATION_NAME_KEY +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.createSessionDialog import org.thoughtcrime.securesms.database.SessionContactDatabase import org.thoughtcrime.securesms.util.createAndStartAttachmentDownload @@ -19,7 +19,7 @@ import javax.inject.Inject /** Shown when receiving media from a contact for the first time, to confirm that * they are to be trusted and files sent by them are to be downloaded. */ @AndroidEntryPoint -class AutoDownloadDialog(private val threadRecipient: Recipient, +class AutoDownloadDialog(private val threadRecipient: RecipientV2, private val databaseAttachment: DatabaseAttachment ) : DialogFragment() { @@ -42,7 +42,7 @@ class AutoDownloadDialog(private val threadRecipient: Recipient, } private fun setAutoDownload() { - storage.setAutoDownloadAttachments(threadRecipient, true) + storage.setAutoDownloadAttachments(threadRecipient.address, true) JobQueue.shared.createAndStartAttachmentDownload(databaseAttachment) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt index 45dc18ba3e..966058eae9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt @@ -32,7 +32,6 @@ import network.loki.messenger.libsession_util.allWithStatus import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsignal.utilities.AccountId -import org.session.libsignal.utilities.IdPrefix import org.thoughtcrime.securesms.database.DatabaseContentProviders.Conversation import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.GroupMemberDatabase @@ -40,7 +39,6 @@ import org.thoughtcrime.securesms.database.MmsDatabase import org.thoughtcrime.securesms.database.SessionContactDatabase import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.ThreadDatabase -import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.util.observeChanges /** @@ -89,31 +87,31 @@ class MentionViewModel( .debounce(500L) .onStart { emit(Unit) } .mapLatest { - val recipient = checkNotNull(threadDatabase.getRecipientForThreadId(threadID)) { + val address = checkNotNull(threadDatabase.getRecipientForThreadId(threadID)) { "Recipient not found for thread ID: $threadID" } val memberIDs = when { - recipient.isLegacyGroupRecipient -> { - groupDatabase.getGroupMemberAddresses(recipient.address.toGroupString(), false) + address.isLegacyGroup -> { + groupDatabase.getGroupMemberAddresses(address.toGroupString(), false) .map { it.toString() } } - recipient.isGroupV2Recipient -> { - storage.getMembers(recipient.address.toString()).map { it.accountId() } + address.isGroupV2 -> { + storage.getMembers(address.toString()).map { it.accountId() } } - recipient.isCommunityRecipient -> mmsDatabase.getRecentChatMemberIDs(threadID, 20) - recipient.isContactRecipient -> listOf(recipient.address.toString()) + address.isCommunity -> mmsDatabase.getRecentChatMemberIDs(threadID, 20) + address.isContact -> listOf(address.address) else -> listOf() } - val openGroup = if (recipient.isCommunityRecipient) { + val openGroup = if (address.isCommunity) { storage.getOpenGroup(threadID) } else { null } - val moderatorIDs = if (recipient.isCommunityRecipient) { + val moderatorIDs = if (address.isCommunity) { val groupId = openGroup?.id if (groupId.isNullOrBlank()) { emptySet() @@ -123,8 +121,8 @@ class MentionViewModel( memberId.takeIf { roles.any { it.isModerator } } } } - } else if (recipient.isGroupV2Recipient) { - configFactory.withGroupConfigs(AccountId(recipient.address.toString())) { + } else if (address.isGroupV2) { + configFactory.withGroupConfigs(AccountId(address.toString())) { it.groupMembers.allWithStatus() .filter { (member, status) -> member.isAdminOrBeingPromoted(status) } .mapTo(hashSetOf()) { (member, _) -> member.accountId() } @@ -133,7 +131,7 @@ class MentionViewModel( emptySet() } - val contactContext = if (recipient.isCommunityRecipient) { + val contactContext = if (address.isCommunity) { Contact.ContactContext.OPEN_GROUP } else { Contact.ContactContext.REGULAR diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt index 61017dbff7..859e62e041 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt @@ -67,7 +67,7 @@ class ConversationActionModeCallback( )?.pubKey?.data } ?.let { AccountId(IdPrefix.BLINDED, it) }?.hexString - val isDeprecatedLegacyGroup = thread.isLegacyGroupRecipient && + val isDeprecatedLegacyGroup = thread.isLegacyGroup && deprecationManager.isDeprecated // Embedded function @@ -95,7 +95,7 @@ class ConversationActionModeCallback( menu.findItem(R.id.menu_context_copy).isVisible = !containsControlMessage && hasText // Copy Account ID menu.findItem(R.id.menu_context_copy_public_key).isVisible = - (thread.isGroupOrCommunityRecipient && !thread.isCommunityRecipient && selectedItems.size == 1 && firstMessage.individualRecipient.address.toString() != userPublicKey) + (thread.isGroupOrCommunity && !thread.isCommunity && selectedItems.size == 1 && firstMessage.individualRecipient.address.toString() != userPublicKey) // Message detail menu.findItem(R.id.menu_message_details).isVisible = selectedItems.size == 1 && !isDeprecatedLegacyGroup // Resend diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt index e7958fa984..dff4b23c56 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt @@ -17,6 +17,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.StringSubstitutionConstants.FILE_TYPE_KEY import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.conversation.v2.ViewUtil import org.thoughtcrime.securesms.conversation.v2.dialogs.AutoDownloadDialog import org.thoughtcrime.securesms.mms.Slide @@ -144,8 +145,8 @@ class AttachmentControlView: LinearLayout { // endregion // region Interaction - fun showDownloadDialog(threadRecipient: Recipient, attachment: DatabaseAttachment) { - if (!storage.shouldAutoDownloadAttachments(threadRecipient)) { + fun showDownloadDialog(threadRecipient: RecipientV2, attachment: DatabaseAttachment) { + if (!threadRecipient.autoDownloadAttachments) { // just download (context.findActivity() as? ActivityDispatcher)?.showDialog(AutoDownloadDialog(threadRecipient, attachment)) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index d0f2b6d47f..eea33b4a07 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -30,6 +30,7 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOT import org.session.libsession.utilities.getColorFromAttr import org.thoughtcrime.securesms.conversation.disappearingmessages.DisappearingMessages import org.thoughtcrime.securesms.conversation.disappearingmessages.expiryMode +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.permissions.Permissions @@ -68,6 +69,7 @@ class ControlMessageView : LinearLayout { @Inject lateinit var disappearingMessages: DisappearingMessages @Inject lateinit var dateUtils: DateUtils + @Inject lateinit var recipientRepository: RecipientRepository val controlContentView: View get() = binding.controlContentView @@ -91,7 +93,7 @@ class ControlMessageView : LinearLayout { val threadRecipient = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(message.threadId) - if (threadRecipient?.isGroupRecipient == true) { + if (threadRecipient?.isGroup == true) { expirationTimerView.setTimerIcon() } else { expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn) @@ -100,7 +102,7 @@ class ControlMessageView : LinearLayout { followSetting.isVisible = ExpirationConfiguration.isNewConfigEnabled && !message.isOutgoing && message.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId)?.expiryMode ?: ExpiryMode.NONE) - && threadRecipient?.isGroupOrCommunityRecipient != true + && threadRecipient?.isGroupOrCommunity != true if (followSetting.isVisible) { binding.controlContentView.setOnClickListener { disappearingMessages.showFollowSettingDialog(context, message) } @@ -130,9 +132,10 @@ class ControlMessageView : LinearLayout { val me = TextSecurePreferences.getLocalNumber(context) binding.textView.text = if(me == msgRecipient) { // you accepted the user's request val threadRecipient = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(message.threadId) + ?.let { recipientRepository.getRecipientSync(it) } context.getSubbedCharSequence( R.string.messageRequestYouHaveAccepted, - NAME_KEY to (threadRecipient?.name ?: "") + NAME_KEY to (threadRecipient?.displayName ?: "") ) } else { // they accepted your request context.getString(R.string.messageRequestsAccepted) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt index 07a7d05623..b43c95bbe0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt @@ -17,6 +17,7 @@ import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities import org.thoughtcrime.securesms.database.SessionContactDatabase @@ -66,7 +67,7 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? // endregion // region Updating - fun bind(authorPublicKey: String, body: String?, attachments: SlideDeck?, thread: Recipient, + fun bind(authorPublicKey: String, body: String?, attachments: SlideDeck?, thread: RecipientV2, isOutgoingMessage: Boolean, isOpenGroupInvitation: Boolean, threadID: Long, isOriginalMissing: Boolean, glide: RequestManager) { // Author diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index c2d1ba568d..ba48dd1144 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -30,10 +30,12 @@ import network.loki.messenger.databinding.ViewVisibleMessageContentBinding import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment +import org.session.libsession.utilities.Address import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.modifyLayoutParams import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.messages.AttachmentControlView.AttachmentType.AUDIO import org.thoughtcrime.securesms.conversation.v2.messages.AttachmentControlView.AttachmentType.DOCUMENT @@ -70,7 +72,7 @@ class VisibleMessageContentView : ConstraintLayout { isStartOfMessageCluster: Boolean = true, isEndOfMessageCluster: Boolean = true, glide: RequestManager = Glide.with(this), - thread: Recipient, + thread: RecipientV2, searchQuery: String? = null, downloadPendingAttachment: (DatabaseAttachment) -> Unit, retryFailedAttachments: (List) -> Unit, @@ -297,7 +299,7 @@ class VisibleMessageContentView : ConstraintLayout { binding.albumThumbnailView.root.calculateHitObject( event, message, - thread, + thread.address, downloadPendingAttachment ) } @@ -359,7 +361,7 @@ class VisibleMessageContentView : ConstraintLayout { } private fun showAttachmentControl( - thread: Recipient, + thread: RecipientV2, message: MmsMessageRecord, attachments: List, type: AttachmentControlView.AttachmentType, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index c24520b0fc..e9c62da16a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -179,14 +179,14 @@ class VisibleMessageView : FrameLayout { replyDisabled = message.isOpenGroupInvitation val threadID = message.threadId val thread = threadDb.getRecipientForThreadId(threadID) ?: return - val isGroupThread = thread.isGroupOrCommunityRecipient + val isGroupThread = thread.isGroupOrCommunity val isStartOfMessageCluster = isStartOfMessageCluster(message, previous, isGroupThread) val isEndOfMessageCluster = isEndOfMessageCluster(message, next, isGroupThread) // Show profile picture and sender name if this is a group thread AND the message is incoming binding.moderatorIconImageView.isVisible = false binding.profilePictureView.visibility = when { - thread.isGroupOrCommunityRecipient && !message.isOutgoing && isEndOfMessageCluster -> View.VISIBLE - thread.isGroupOrCommunityRecipient -> View.INVISIBLE + thread.isGroupOrCommunity && !message.isOutgoing && isEndOfMessageCluster -> View.VISIBLE + thread.isGroupOrCommunity -> View.INVISIBLE else -> View.GONE } @@ -208,7 +208,7 @@ class VisibleMessageView : FrameLayout { binding.profilePictureView.publicKey = senderAccountID binding.profilePictureView.update(message.individualRecipient) binding.profilePictureView.setOnClickListener { - if (thread.isCommunityRecipient) { + if (thread.isCommunity) { val openGroup = lokiThreadDb.getOpenGroupChat(threadID) if (IdPrefix.fromValue(senderAccountID) == IdPrefix.BLINDED && openGroup?.canWrite == true) { // TODO: support v2 soon @@ -221,7 +221,7 @@ class VisibleMessageView : FrameLayout { maybeShowUserDetails(senderAccountID, threadID) } } - if (thread.isCommunityRecipient) { + if (thread.isCommunity) { val openGroup = lokiThreadDb.getOpenGroupChat(threadID) ?: return var standardPublicKey = "" var blindedPublicKey: String? = null @@ -237,14 +237,14 @@ class VisibleMessageView : FrameLayout { ) binding.moderatorIconImageView.isVisible = isModerator } - else if (thread.isLegacyGroupRecipient) { // legacy groups - val groupRecord = groupDb.getGroup(thread.address.toGroupString()).orNull() + else if (thread.isLegacyGroup) { // legacy groups + val groupRecord = groupDb.getGroup(thread.toGroupString()).orNull() val isAdmin: Boolean = groupRecord?.admins?.contains(fromSerialized(senderAccountID)) ?: false binding.moderatorIconImageView.isVisible = isAdmin } - else if (thread.isGroupV2Recipient) { // groups v2 - val isAdmin = configFactory.withGroupConfigs(AccountId(thread.address.toString())) { + else if (thread.isGroupV2) { // groups v2 + val isAdmin = configFactory.withGroupConfigs(AccountId(thread.toString())) { it.groupMembers.getOrNull(senderAccountID)?.admin == true } @@ -254,7 +254,7 @@ class VisibleMessageView : FrameLayout { } binding.senderNameTextView.isVisible = !message.isOutgoing && (isStartOfMessageCluster && (isGroupThread || snIsSelected)) val contactContext = - if (thread.isCommunityRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR + if (thread.isCommunity) ContactContext.OPEN_GROUP else ContactContext.REGULAR binding.senderNameTextView.text = usernameUtils.getContactNameWithAccountID( contact = contact, accountID = senderAccountID, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt index 6cfb6b0fe7..ad598284e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt @@ -13,6 +13,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -22,10 +23,12 @@ import org.session.libsession.LocalisedTimeUtil import org.session.libsession.utilities.StringSubstitutionConstants.DATE_TIME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_LARGE_KEY import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_ALL import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_MENTIONS import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_NONE +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.OptionsCardData @@ -45,8 +48,9 @@ class NotificationSettingsViewModel @AssistedInject constructor( private val recipientDatabase: RecipientDatabase, private val repository: ConversationRepository, private val dateUtils: DateUtils, + private val recipientRepository: RecipientRepository, ) : ViewModel() { - private var thread: Recipient? = null + private var thread: RecipientV2? = null private val durationForever: Long = Long.MAX_VALUE @@ -64,11 +68,15 @@ class NotificationSettingsViewModel @AssistedInject constructor( init { // update data when we have a recipient and update when there are changes from the thread or recipient viewModelScope.launch(Dispatchers.Default) { - repository.recipientUpdateFlow(threadId).collect { + val address = repository.maybeGetRecipientForThreadId(threadId) + ?: // if we don't have a recipient, we can't do anything + return@launch + + recipientRepository.observeRecipient(address).collectLatest { thread = it // update the user's current choice of notification - currentMutedUntil = if(it?.isMuted == true) it.mutedUntil else null + currentMutedUntil = if(it?.isMuted() == true) it.mutedUntilMills else null val hasMutedUntil = currentMutedUntil != null && currentMutedUntil!! > 0L currentOption = when{ @@ -250,21 +258,21 @@ class NotificationSettingsViewModel @AssistedInject constructor( private suspend fun unmute() { val conversation = thread ?: return withContext(Dispatchers.Default) { - recipientDatabase.setMuted(conversation, 0) + recipientDatabase.setMuted(conversation.address, 0) } } private suspend fun mute(until: Long) { val conversation = thread ?: return withContext(Dispatchers.Default) { - recipientDatabase.setMuted(conversation, until) + recipientDatabase.setMuted(conversation.address, until) } } private suspend fun setNotifyType(notifyType: Int) { val conversation = thread ?: return withContext(Dispatchers.Default) { - recipientDatabase.setNotifyType(conversation, notifyType) + recipientDatabase.setNotifyType(conversation.address, notifyType) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt index 3fb6a77bac..2c24c19d4b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt @@ -10,14 +10,13 @@ import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.utilities.UpdateMessageData import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord object ResendMessageUtilities { fun resend(context: Context, messageRecord: MessageRecord, userBlindedKey: String?, isResync: Boolean = false) { - val recipient: Recipient = messageRecord.recipient + val recipient = messageRecord.recipient.address val message = VisibleMessage() message.id = messageRecord.messageId if (messageRecord.isOpenGroupInvitation) { @@ -34,8 +33,8 @@ object ResendMessageUtilities { message.text = messageRecord.body } message.sentTimestamp = messageRecord.timestamp - if (recipient.isGroupOrCommunityRecipient) { - message.groupPublicKey = recipient.address.toGroupString() + if (recipient.isGroupOrCommunity) { + message.groupPublicKey = recipient.toGroupString() } else { message.recipient = messageRecord.recipient.address.toString() } @@ -56,10 +55,10 @@ object ResendMessageUtilities { if (sentTimestamp != null && sender != null) { if (isResync) { MessagingModuleConfiguration.shared.storage.markAsResyncing(messageRecord.messageId) - MessageSender.sendNonDurably(message, Destination.from(recipient.address), isSyncMessage = true) + MessageSender.sendNonDurably(message, Destination.from(recipient), isSyncMessage = true) } else { MessagingModuleConfiguration.shared.storage.markAsSending(messageRecord.messageId) - MessageSender.send(message, recipient.address) + MessageSender.send(message, recipient) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index ba4e2971cf..286f57a99b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -48,7 +48,6 @@ import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.ThreadUtils.queue -import org.session.libsignal.utilities.Util.SECURE_RANDOM import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord @@ -1232,8 +1231,6 @@ class MmsDatabase(context: Context, databaseHelper: Provider getRecipientSettings(@NonNull Cursor cursor) { boolean approvedMe = cursor.getInt(cursor.getColumnIndexOrThrow(APPROVED_ME)) == 1; long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL)); int notifyType = cursor.getInt(cursor.getColumnIndexOrThrow(NOTIFY_TYPE)); - boolean autoDownloadAttachments = cursor.getInt(cursor.getColumnIndexOrThrow(AUTO_DOWNLOAD)) == 1; + Boolean autoDownloadAttachments = switch (cursor.getInt(cursor.getColumnIndexOrThrow(AUTO_DOWNLOAD))) { + case 1 -> true; + case -1 -> null; + default -> false; + }; + int expireMessages = cursor.getInt(cursor.getColumnIndexOrThrow(EXPIRE_MESSAGES)); String profileKeyString = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_KEY)); String systemDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_DISPLAY_NAME)); @@ -260,22 +265,6 @@ Optional getRecipientSettings(@NonNull Cursor cursor) { blocksCommunityMessageRequests)); } - public boolean isAutoDownloadFlagSet(Recipient recipient) { - SQLiteDatabase db = getReadableDatabase(); - Cursor cursor = db.query(TABLE_NAME, new String[]{ AUTO_DOWNLOAD }, ADDRESS+" = ?", new String[]{ recipient.getAddress().toString() }, null, null, null); - boolean flagUnset = false; - try { - if (cursor.moveToFirst()) { - // flag isn't set if it is -1 - flagUnset = cursor.getInt(cursor.getColumnIndexOrThrow(AUTO_DOWNLOAD)) == -1; - } - } finally { - cursor.close(); - } - // negate result (is flag set) - return !flagUnset; - } - public boolean getApproved(@NonNull Address address) { SQLiteDatabase db = getReadableDatabase(); try (Cursor cursor = db.query(TABLE_NAME, new String[]{APPROVED}, ADDRESS + " = ?", new String[]{address.toString()}, null, null, null)) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index 847d8daeee..9235bcc1f1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -17,7 +17,6 @@ import network.loki.messenger.libsession_util.ReadableGroupInfoConfig import network.loki.messenger.libsession_util.util.Contact import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.UserPic -import org.session.libsession.database.StorageProtocol import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigUpdateNotification @@ -90,35 +89,30 @@ class RecipientRepository @Inject constructor( } private suspend fun fetchRecipient(address: Address): Pair>? { - val changeSource: Flow<*> - val value: RecipientV2? val myAddress by lazy { preferences.getLocalNumber()?.let(Address::fromSerialized) } - when { - address.isContact && address == myAddress -> { - // Ourselves - changeSource = configFactory.userConfigsChanged() - value = configFactory.withUserConfigs { - createLocalRecipient( - address, - it.userProfile.getName().orEmpty(), - it.userProfile.getPic() - ) - } - } + // Short-circuit for our own address. + if (address.isContact && address == myAddress) { + return configFactory.withUserConfigs { + createLocalRecipient( + address, + it.userProfile.getName().orEmpty(), + it.userProfile.getPic() + ) + } to configFactory.userConfigsChanged() + } - address.isContact -> { - // When an address is said to be a "contact", it doesn't mean it is the user's contact, - // it just means the type of address is a person really. - // So we'll need to look it up in the contacts config first, if it's not there, - // we'll look it up in the recipient database. - val accountId = AccountId.fromStringOrNull(address.address) - if (accountId == null || accountId.prefix != IdPrefix.STANDARD) { - // When it's impossible to create a normal AccountId from the address, we - // have exhausted all options, so we bail and never be able recover. - return null - } + val changeSource: Flow<*> + val value: RecipientV2? + + // We'll always try to fetch the recipient settings from the database first + val settings: RecipientSettings? = withContext(Dispatchers.Default) { + recipientDatabase.getRecipientSettings(address).orNull() + } + when { + // Address is a legit 05 person (not necessarily a "real" contact) + address.isContact && AccountId.fromStringOrNull(address.address)?.prefix == IdPrefix.STANDARD -> { // We know this address can be looked up in the contacts config or the recipient database. changeSource = merge( configFactory.userConfigsChanged(), @@ -126,11 +120,7 @@ class RecipientRepository @Inject constructor( ) val contactInConfig = configFactory.withUserConfigs { configs -> - configs.contacts.get(accountId.hexString) - } - - val settings: RecipientSettings? = withContext(Dispatchers.Default) { - recipientDatabase.getRecipientSettings(address).orNull() + configs.contacts.get(address.address) } value = if (contactInConfig != null) { @@ -150,10 +140,6 @@ class RecipientRepository @Inject constructor( value = null changeSource = configFactory.userConfigsChanged() } else { - val settings: RecipientSettings? = withContext(Dispatchers.Default) { - recipientDatabase.getRecipientSettings(address).orNull() - } - value = configFactory.withGroupConfigs(groupId) { configs -> createGroupV2Recipient(address, group, configs.groupInfo, settings) } @@ -162,28 +148,24 @@ class RecipientRepository @Inject constructor( configFactory.userConfigsChanged(), configFactory.configUpdateNotifications .filterIsInstance() - .filter { it.groupId.hexString == address.address } + .filter { it.groupId.hexString == address.address }, + recipientDatabase.updateNotifications.filter { it == address } ) } } address.isCommunity || address.isLegacyGroup -> { - changeSource = groupDatabase.updateNotification + changeSource = merge( + groupDatabase.updateNotification, + recipientDatabase.updateNotifications.filter { it == address } + ) val group: GroupRecord? = groupDatabase.getGroup(address.toGroupString()).orNull() - val settings: RecipientSettings? = withContext(Dispatchers.Default) { - recipientDatabase.getRecipientSettings(address).orNull() - } - value = group?.let { createCommunityOrLegacyGroupRecipient(address, it, settings) } } else -> { changeSource = recipientDatabase.updateNotifications.filter { it == address } - val settings: RecipientSettings? = withContext(Dispatchers.Default) { - recipientDatabase.getRecipientSettings(address).orNull() - } - value = settings?.let { createGenericRecipient(address, it) } } } @@ -256,7 +238,7 @@ class RecipientRepository @Inject constructor( blocked = false, mutedUntil = settings?.muteUntilDate, notifyType = settings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, - autoDownloadAttachments = settings?.autoDownloadAttachments == true, + autoDownloadAttachments = settings?.autoDownloadAttachments, ) } @@ -279,7 +261,7 @@ class RecipientRepository @Inject constructor( approvedMe = contactInConfig.approvedMe, blocked = contactInConfig.blocked, mutedUntil = fallbackSettings?.muteUntilDate, - autoDownloadAttachments = fallbackSettings?.autoDownloadAttachments == true, + autoDownloadAttachments = fallbackSettings?.autoDownloadAttachments, notifyType = fallbackSettings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, ) @@ -300,7 +282,7 @@ class RecipientRepository @Inject constructor( approvedMe = true, blocked = false, mutedUntil = settings?.muteUntilDate, - autoDownloadAttachments = settings?.autoDownloadAttachments == true, + autoDownloadAttachments = settings?.autoDownloadAttachments, notifyType = settings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL ) } @@ -336,7 +318,7 @@ class RecipientRepository @Inject constructor( approvedMe = false, blocked = false, mutedUntil = null, - autoDownloadAttachments = false, + autoDownloadAttachments = null, notifyType = RecipientDatabase.NOTIFY_TYPE_ALL, avatar = null ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 26c9f220eb..588abc94fe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -4,7 +4,6 @@ import android.content.Context import android.net.Uri import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.flow.Flow import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_PINNED import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE @@ -65,7 +64,6 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.recipients.Recipient -import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.upsertContact import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.session.libsignal.crypto.ecc.ECKeyPair @@ -131,6 +129,7 @@ open class Storage @Inject constructor( private val preferences: TextSecurePreferences, private val usernameUtils: UsernameUtils, private val openGroupManager: Lazy, + private val recipientRepository: RecipientRepository, ) : Database(context, helper), StorageProtocol, ThreadDatabase.ConversationThreadUpdateListener { init { @@ -251,14 +250,6 @@ open class Storage @Inject constructor( return registrationID } - override fun observeRecipient(address: Address): Flow { - TODO("Not yet implemented") - } - - override fun getRecipientSync(address: Address): RecipientV2 { - TODO("Not yet implemented") - } - override fun getAttachmentsForMessage(mmsMessageId: Long): List { return attachmentDatabase.getAttachmentsForMessage(mmsMessageId) } @@ -318,26 +309,26 @@ open class Storage @Inject constructor( getRecipientForThread(threadId)?.let { recipient -> val currentLastRead = threadDb.getLastSeenAndHasSent(threadId).first() // don't set the last read in the volatile if we didn't set it in the DB - if (!threadDb.markAllAsRead(threadId, recipient.isGroupOrCommunityRecipient, lastSeenTime, force) && !force) return + if (!threadDb.markAllAsRead(threadId, recipient.isGroupOrCommunity, lastSeenTime, force) && !force) return // don't process configs for inbox recipients - if (recipient.isCommunityInboxRecipient) return + if (recipient.isCommunityInbox) return configFactory.withMutableUserConfigs { configs -> val config = configs.convoInfoVolatile val convo = when { // recipient closed group - recipient.isLegacyGroupRecipient -> config.getOrConstructLegacyGroup(GroupUtil.doubleDecodeGroupId(recipient.address.toString())) - recipient.isGroupV2Recipient -> config.getOrConstructClosedGroup(recipient.address.toString()) + recipient.isLegacyGroup -> config.getOrConstructLegacyGroup(GroupUtil.doubleDecodeGroupId(recipient.address.toString())) + recipient.isGroupV2 -> config.getOrConstructClosedGroup(recipient.address.toString()) // recipient is open group - recipient.isCommunityRecipient -> { + recipient.isCommunity -> { val openGroupJoinUrl = getOpenGroup(threadId)?.joinURL ?: return@withMutableUserConfigs BaseCommunityInfo.parseFullUrl(openGroupJoinUrl)?.let { (base, room, pubKey) -> config.getOrConstructCommunity(base, room, pubKey) } ?: return@withMutableUserConfigs } // otherwise recipient is one to one - recipient.isContactRecipient -> { + recipient.isContact -> { // don't process non-standard account IDs though if (IdPrefix.fromValue(recipient.address.toString()) != IdPrefix.STANDARD) return@withMutableUserConfigs config.getOrConstructOneToOne(recipient.address.toString()) @@ -1177,7 +1168,7 @@ open class Storage @Inject constructor( notifyRecipientListeners() } - override fun getRecipientForThread(threadId: Long): Recipient? { + override fun getRecipientForThread(threadId: Long): Address? { return threadDatabase.getRecipientForThreadId(threadId) } @@ -1185,10 +1176,6 @@ open class Storage @Inject constructor( return recipientDatabase.getRecipientSettings(address).orNull() } - override fun hasAutoDownloadFlagBeenSet(recipient: Recipient): Boolean { - return recipientDatabase.isAutoDownloadFlagSet(recipient) - } - override fun syncLibSessionContacts(contacts: List, timestamp: Long?) { val mappingDb = blindedIdMappingDatabase val moreContacts = contacts.filter { contact -> @@ -1253,13 +1240,9 @@ open class Storage @Inject constructor( deleteContact(it.address.toString()) } } - - override fun shouldAutoDownloadAttachments(recipient: Recipient): Boolean { - return recipient.autoDownloadAttachments - } override fun setAutoDownloadAttachments( - recipient: Recipient, + recipient: Address, shouldAutoDownloadAttachments: Boolean ) { val recipientDb = recipientDatabase @@ -1289,32 +1272,33 @@ open class Storage @Inject constructor( override fun setPinned(threadID: Long, isPinned: Boolean) { val threadDB = threadDatabase threadDB.setPinned(threadID, isPinned) - val threadRecipient = getRecipientForThread(threadID) ?: return + val address = getRecipientForThread(threadID) ?: return + val isLocalNumber = address == getUserPublicKey()?.let { fromSerialized(it) } configFactory.withMutableUserConfigs { configs -> - if (threadRecipient.isLocalNumber) { + if (isLocalNumber) { configs.userProfile.setNtsPriority(if (isPinned) PRIORITY_PINNED else PRIORITY_VISIBLE) - } else if (threadRecipient.isContactRecipient) { - configs.contacts.upsertContact(threadRecipient.address.toString()) { + } else if (address.isContact) { + configs.contacts.upsertContact(address.toString()) { priority = if (isPinned) PRIORITY_PINNED else PRIORITY_VISIBLE } - } else if (threadRecipient.isGroupOrCommunityRecipient) { + } else if (address.isGroupOrCommunity) { when { - threadRecipient.isLegacyGroupRecipient -> { - threadRecipient.address.toString() + address.isLegacyGroup -> { + address.toString() .let(GroupUtil::doubleDecodeGroupId) .let(configs.userGroups::getOrConstructLegacyGroupInfo) .copy(priority = if (isPinned) PRIORITY_PINNED else PRIORITY_VISIBLE) .let(configs.userGroups::set) } - threadRecipient.isGroupV2Recipient -> { + address.isGroupV2 -> { val newGroupInfo = configs.userGroups - .getOrConstructClosedGroup(threadRecipient.address.toString()) + .getOrConstructClosedGroup(address.toString()) .copy(priority = if (isPinned) PRIORITY_PINNED else PRIORITY_VISIBLE) configs.userGroups.set(newGroupInfo) } - threadRecipient.isCommunityRecipient -> { + address.isCommunity -> { val openGroup = getOpenGroup(threadID) ?: return@withMutableUserConfigs val (baseUrl, room, pubKeyHex) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: return@withMutableUserConfigs @@ -1351,7 +1335,7 @@ open class Storage @Inject constructor( val threadDB = threadDatabase val groupDB = groupDatabase - val recipientAddress = getRecipientForThread(threadID)?.address + val recipientAddress = getRecipientForThread(threadID) // Delete the conversation and its messages smsDatabase.deleteThread(threadID) @@ -1828,19 +1812,19 @@ open class Storage @Inject constructor( val dbExpirationMetadata = expirationConfigurationDatabase.getExpirationConfiguration(threadId) return when { recipient.isLocalNumber -> configFactory.withUserConfigs { it.userProfile.getNtsExpiry() } - recipient.isContactRecipient -> { + recipient.isContact -> { // read it from contacts config if exists recipient.address.toString().takeIf { it.startsWith(IdPrefix.STANDARD.value) } ?.let { configFactory.withUserConfigs { configs -> configs.contacts.get(it)?.expiryMode } } } - recipient.isGroupV2Recipient -> { + recipient.isGroupV2 -> { configFactory.withGroupConfigs(AccountId(recipient.address.toString())) { configs -> configs.groupInfo.getExpiryTimer() }.let { if (it == 0L) ExpiryMode.NONE else ExpiryMode.AfterSend(it) } } - recipient.isLegacyGroupRecipient -> { + recipient.isLegacyGroup -> { // read it from group config if exists GroupUtil.doubleDecodeGroupId(recipient.address.toString()) .let { id -> configFactory.withUserConfigs { it.userGroups.getLegacyGroupInfo(id) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index eb6d882711..cffd38827e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -46,6 +46,7 @@ import org.session.libsession.utilities.Util; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.Recipient.RecipientSettings; +import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.AccountId; import org.session.libsignal.utilities.IdPrefix; import org.session.libsignal.utilities.Log; @@ -72,10 +73,14 @@ import java.util.Map; import java.util.Set; +import javax.inject.Inject; import javax.inject.Provider; +import javax.inject.Singleton; +import dagger.Lazy; import network.loki.messenger.libsession_util.util.GroupInfo; +@Singleton public class ThreadDatabase extends Database { public interface ConversationThreadUpdateListener { @@ -85,7 +90,6 @@ public interface ConversationThreadUpdateListener { private static final String TAG = ThreadDatabase.class.getSimpleName(); // Map of threadID -> Address - private final Map addressCache = new HashMap<>(); public static final String TABLE_NAME = "thread"; public static final String ID = "_id"; @@ -157,8 +161,14 @@ public static String getUnreadMentionCountCommand() { private ConversationThreadUpdateListener updateListener; - public ThreadDatabase(Context context, Provider databaseHelper) { + private final Lazy recipientRepository; + + @Inject + public ThreadDatabase(@dagger.hilt.android.qualifiers.ApplicationContext Context context, + Provider databaseHelper, + Lazy recipientRepository) { super(context, databaseHelper); + this.recipientRepository = recipientRepository; } public void setUpdateListener(ConversationThreadUpdateListener updateListener) { @@ -234,7 +244,6 @@ public void updateSnippet(long threadId, String snippet, @Nullable Uri attachmen public void deleteThread(long threadId) { SQLiteDatabase db = getWritableDatabase(); db.delete(TABLE_NAME, ID_WHERE, new String[] {threadId + ""}); - addressCache.remove(threadId); notifyConversationListListeners(); } @@ -247,16 +256,12 @@ private void deleteThreads(Set threadIds) { where = where.substring(0, where.length() - 4); db.delete(TABLE_NAME, where, null); - for (long threadId: threadIds) { - addressCache.remove(threadId); - } notifyConversationListListeners(); } private void deleteAllThreads() { SQLiteDatabase db = getWritableDatabase(); db.delete(TABLE_NAME, null, null); - addressCache.clear(); notifyConversationListListeners(); } @@ -514,8 +519,8 @@ public Cursor getDirectShareList() { public boolean setLastSeen(long threadId, long timestamp) { // edge case where we set the last seen time for a conversation before it loads messages (joining community for example) MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase(); - Recipient forThreadId = getRecipientForThreadId(threadId); - if (mmsSmsDatabase.getConversationCount(threadId) <= 0 && forThreadId != null && forThreadId.isCommunityRecipient()) return false; + Address forThreadId = getRecipientForThreadId(threadId); + if (mmsSmsDatabase.getConversationCount(threadId) <= 0 && forThreadId != null && forThreadId.isCommunity()) return false; SQLiteDatabase db = getWritableDatabase(); @@ -668,25 +673,13 @@ public long getOrCreateThreadIdFor(Address address, int distributionType) { } } - public @Nullable Recipient getRecipientForThreadId(long threadId) { - if (addressCache.containsKey(threadId) && addressCache.get(threadId) != null) { - return Recipient.from(context, addressCache.get(threadId), false); - } - + public @Nullable Address getRecipientForThreadId(long threadId) { SQLiteDatabase db = getReadableDatabase(); - Cursor cursor = null; - - try { - cursor = db.query(TABLE_NAME, null, ID + " = ?", new String[] {threadId+""}, null, null, null); + try(final Cursor cursor = db.query(TABLE_NAME, null, ID + " = ?", new String[] {threadId+""}, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { - Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))); - addressCache.put(threadId, address); - return Recipient.from(context, address, false); + return Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))); } - } finally { - if (cursor != null) - cursor.close(); } return null; @@ -866,18 +859,10 @@ public ThreadRecord getCurrent() { int distributionType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.DISTRIBUTION_TYPE)); Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.ADDRESS))); - Optional settings; - Optional groupRecord; - - if (distributionType != DistributionTypes.ARCHIVE && distributionType != DistributionTypes.INBOX_ZERO) { - settings = DatabaseComponent.get(context).recipientDatabase().getRecipientSettings(cursor); - groupRecord = DatabaseComponent.get(context).groupDatabase().getGroup(cursor); - } else { - settings = Optional.absent(); - groupRecord = Optional.absent(); + RecipientV2 recipient = recipientRepository.get().getRecipientSync(address); + if (recipient == null) { + recipient = RecipientV2.Companion.empty(address); } - - Recipient recipient = Recipient.from(context, address, settings, groupRecord, true); String body = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET)); long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.THREAD_CREATION_DATE)); long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java index 7eeec84d07..7e946b8469 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -21,6 +21,7 @@ import androidx.annotation.NonNull; import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; @@ -34,7 +35,7 @@ public abstract class DisplayRecord { protected final long type; - private final Recipient recipient; + private final RecipientV2 recipient; private final long dateSent; private final long dateReceived; private final long threadId; @@ -43,7 +44,7 @@ public abstract class DisplayRecord { private final int deliveryReceiptCount; private final int readReceiptCount; - DisplayRecord(String body, Recipient recipient, long dateSent, + DisplayRecord(String body, RecipientV2 recipient, long dateSent, long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, long type, int readReceiptCount) { @@ -62,7 +63,7 @@ public abstract class DisplayRecord { return body == null ? "" : body; } public abstract CharSequence getDisplayBody(@NonNull Context context); - public Recipient getRecipient() { return recipient; } + public RecipientV2 getRecipient() { return recipient; } public long getDateSent() { return dateSent; } public long getDateReceived() { return dateReceived; } public long getThreadId() { return threadId; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java index fdba0510e3..c506d263e2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java @@ -26,6 +26,7 @@ import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.NetworkFailure; import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import org.thoughtcrime.securesms.database.SmsDatabase.Status; import org.thoughtcrime.securesms.mms.SlideDeck; @@ -42,8 +43,8 @@ public class MediaMmsMessageRecord extends MmsMessageRecord { private final int partCount; - public MediaMmsMessageRecord(long id, Recipient conversationRecipient, - Recipient individualRecipient, int recipientDeviceId, + public MediaMmsMessageRecord(long id, RecipientV2 conversationRecipient, + RecipientV2 individualRecipient, int recipientDeviceId, long dateSent, long dateReceived, int deliveryReceiptCount, long threadId, String body, @NonNull SlideDeck slideDeck, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index e5e375dac9..c9c08a41f8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -32,10 +32,12 @@ import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage; import org.session.libsession.messaging.utilities.UpdateMessageBuilder; import org.session.libsession.messaging.utilities.UpdateMessageData; +import org.session.libsession.utilities.Address; import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.NetworkFailure; import org.session.libsession.utilities.ThemeUtil; import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.AccountId; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; @@ -50,7 +52,7 @@ * */ public abstract class MessageRecord extends DisplayRecord { - private final Recipient individualRecipient; + private final RecipientV2 individualRecipient; private final List mismatches; private final List networkFailures; private final long expiresIn; @@ -73,8 +75,8 @@ public final MessageId getMessageId() { return new MessageId(getId(), isMms()); } - MessageRecord(long id, String body, Recipient conversationRecipient, - Recipient individualRecipient, + MessageRecord(long id, String body, RecipientV2 conversationRecipient, + RecipientV2 individualRecipient, long dateSent, long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, long type, List mismatches, @@ -100,7 +102,7 @@ public long getId() { public long getTimestamp() { return getDateSent(); } - public Recipient getIndividualRecipient() { + public RecipientV2 getIndividualRecipient() { return individualRecipient; } public long getType() { @@ -136,7 +138,7 @@ public UpdateMessageData getGroupUpdateMessage() { public CharSequence getDisplayBody(@NonNull Context context) { if (isGroupUpdateMessage()) { UpdateMessageData updateMessageData = getGroupUpdateMessage(); - Recipient groupRecipient = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(getThreadId()); + Address groupRecipient = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(getThreadId()); if (updateMessageData == null || groupRecipient == null) { return ""; @@ -144,7 +146,7 @@ public CharSequence getDisplayBody(@NonNull Context context) { SpannableString text = new SpannableString(UpdateMessageBuilder.buildGroupUpdateMessage( context, - groupRecipient.isGroupV2Recipient() ? new AccountId(groupRecipient.getAddress().toString()) : null, // accountId is only used for GroupsV2 + groupRecipient.isGroupV2() ? new AccountId(groupRecipient.toString()) : null, // accountId is only used for GroupsV2 updateMessageData, MessagingModuleConfiguration.getShared().getConfigFactory(), isOutgoing(), @@ -161,7 +163,7 @@ public CharSequence getDisplayBody(@NonNull Context context) { return text; } else if (isExpirationTimerUpdate()) { int seconds = (int) (getExpiresIn() / 1000); - boolean isGroup = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(getThreadId()).isGroupOrCommunityRecipient(); + boolean isGroup = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(getThreadId()).isGroupOrCommunity(); return new SpannableString(UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, seconds, isGroup, getIndividualRecipient().getAddress().toString(), isOutgoing(), getTimestamp(), expireStarted)); } else if (isDataExtractionNotification()) { if (isScreenshotNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT, getIndividualRecipient().getAddress().toString()))); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java index 320eb4e739..57b7c9129d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java @@ -7,11 +7,10 @@ import org.session.libsession.utilities.Contact; import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.NetworkFailure; -import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; -import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -21,8 +20,8 @@ public abstract class MmsMessageRecord extends MessageRecord { private final @NonNull List contacts = new LinkedList<>(); private final @NonNull List linkPreviews = new LinkedList<>(); - MmsMessageRecord(long id, String body, Recipient conversationRecipient, - Recipient individualRecipient, long dateSent, + MmsMessageRecord(long id, String body, RecipientV2 conversationRecipient, + RecipientV2 individualRecipient, long dateSent, long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, long type, List mismatches, List networkFailures, long expiresIn, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 4a4b8ae462..50bbc615aa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -35,6 +35,7 @@ import org.session.libsession.messaging.utilities.UpdateMessageData; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.ui.UtilKt; @@ -65,9 +66,9 @@ public class ThreadRecord extends DisplayRecord { private final GroupThreadStatus groupThreadStatus; public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri, - @Nullable MessageRecord lastMessage, @NonNull Recipient recipient, long date, long count, int unreadCount, + @Nullable MessageRecord lastMessage, @NonNull RecipientV2 recipient, long date, long count, int unreadCount, int unreadMentionCount, long threadId, int deliveryReceiptCount, int status, - long snippetType, int distributionType, boolean archived, long expiresIn, + long snippetType, int distributionType, boolean archived, long expiresIn, long lastSeen, int readReceiptCount, boolean pinned, String invitingAdminId, @NonNull GroupThreadStatus groupThreadStatus) { @@ -194,11 +195,11 @@ else if (isGroupUpdateMessage()) { * Logic to get the body for non control messages */ public CharSequence getNonControlMessageDisplayBody(@NonNull Context context) { - Recipient recipient = getRecipient(); + RecipientV2 recipient = getRecipient(); // The logic will differ depending on the type. // 1-1, note to self and control messages (we shouldn't have any in here, but leaving the // logic to be safe) do not need author details - if (recipient.isLocalNumber() || recipient.is1on1() || + if (recipient.isLocalNumber() || recipient.getAddress().isContact() || (lastMessage != null && lastMessage.isControlMessage()) ) { return getBody(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt index ddad988b59..dbbb8bd52f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt @@ -78,9 +78,6 @@ object DatabaseModule { @Singleton fun provideMediaDatbase(@ApplicationContext context: Context, openHelper: Provider) = MediaDatabase(context, openHelper) - @Provides - @Singleton - fun provideThread(@ApplicationContext context: Context, openHelper: Provider) = ThreadDatabase(context,openHelper) @Provides @Singleton diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index 677852d705..73f97f9ad9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -1,10 +1,8 @@ package org.thoughtcrime.securesms.repository import android.content.ContentResolver -import android.content.Context import app.cash.copper.Query import app.cash.copper.flow.observeQuery -import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -35,7 +33,6 @@ import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.DraftDatabase import org.thoughtcrime.securesms.database.LokiMessageDatabase import org.thoughtcrime.securesms.database.LokiThreadDatabase -import org.thoughtcrime.securesms.database.MmsDatabase import org.thoughtcrime.securesms.database.MmsSmsDatabase import org.thoughtcrime.securesms.database.SessionJobDatabase import org.thoughtcrime.securesms.database.SmsDatabase @@ -49,10 +46,9 @@ import org.thoughtcrime.securesms.util.observeChanges import javax.inject.Inject interface ConversationRepository { - fun maybeGetRecipientForThreadId(threadId: Long): Recipient? - fun maybeGetBlindedRecipient(recipient: Recipient): Recipient? + fun maybeGetRecipientForThreadId(threadId: Long): Address? + fun maybeGetBlindedRecipient(address: Address): Address? fun changes(threadId: Long): Flow - fun recipientUpdateFlow(threadId: Long): Flow fun saveDraft(threadId: Long, text: String) fun getDraft(threadId: Long): String? fun clearDrafts(threadId: Long) @@ -83,15 +79,15 @@ interface ConversationRepository { suspend fun deleteGroupV2MessagesRemotely(recipient: Recipient, messages: Set) - suspend fun banUser(threadId: Long, recipient: Recipient): Result - suspend fun banAndDeleteAll(threadId: Long, recipient: Recipient): Result + suspend fun banUser(threadId: Long, recipient: Address): Result + suspend fun banAndDeleteAll(threadId: Long, recipient: Address): Result suspend fun deleteThread(threadId: Long): Result suspend fun deleteMessageRequest(thread: ThreadRecord): Result suspend fun clearAllMessageRequests(block: Boolean): Result suspend fun acceptMessageRequest(threadId: Long, recipient: Address): Result - suspend fun declineMessageRequest(threadId: Long, recipient: Recipient): Result + suspend fun declineMessageRequest(threadId: Long, recipient: Address): Result fun hasReceived(threadId: Long): Boolean - fun getInvitingAdmin(threadId: Long): Recipient? + fun getInvitingAdmin(threadId: Long): Address? /** * This will delete all messages from the database. @@ -105,14 +101,12 @@ interface ConversationRepository { } class DefaultConversationRepository @Inject constructor( - @ApplicationContext private val context: Context, private val textSecurePreferences: TextSecurePreferences, private val messageDataProvider: MessageDataProvider, private val threadDb: ThreadDatabase, private val draftDb: DraftDatabase, private val lokiThreadDb: LokiThreadDatabase, private val smsDb: SmsDatabase, - private val mmsDb: MmsDatabase, private val mmsSmsDb: MmsSmsDatabase, private val storage: Storage, private val lokiMessageDb: LokiMessageDatabase, @@ -123,28 +117,18 @@ class DefaultConversationRepository @Inject constructor( private val clock: SnodeClock, ) : ConversationRepository { - override fun maybeGetRecipientForThreadId(threadId: Long): Recipient? { + override fun maybeGetRecipientForThreadId(threadId: Long): Address? { return threadDb.getRecipientForThreadId(threadId) } - override fun maybeGetBlindedRecipient(recipient: Recipient): Recipient? { - if (!recipient.isCommunityInboxRecipient) return null - return Recipient.from( - context, - Address.fromSerialized(GroupUtil.getDecodedOpenGroupInboxAccountId(recipient.address.toString())), - false - ) + override fun maybeGetBlindedRecipient(address: Address): Address? { + if (!address.isCommunityInbox) return null + return Address.fromSerialized(GroupUtil.getDecodedOpenGroupInboxAccountId(address.toString())) } override fun changes(threadId: Long): Flow = contentResolver.observeQuery(DatabaseContentProviders.Conversation.getUriForThread(threadId)) - override fun recipientUpdateFlow(threadId: Long): Flow { - return contentResolver.observeQuery(DatabaseContentProviders.Conversation.getUriForThread(threadId)).map { - maybeGetRecipientForThreadId(threadId) - } - } - override fun saveDraft(threadId: Long, text: String) { if (text.isEmpty()) return val drafts = DraftDatabase.Drafts() @@ -181,10 +165,8 @@ class DefaultConversationRepository @Inject constructor( expiresInMillis, expireStartedAt ) - message.id = MessageId( - smsDb.insertMessageOutbox(-1, outgoingTextMessage, message.sentTimestamp!!, true), - false - ) + message.id = smsDb.insertMessageOutbox(-1, outgoingTextMessage, message.sentTimestamp!!, true).orNull() + ?.let { MessageId(it.messageId, false) } MessageSender.send(message, contact) } @@ -392,18 +374,16 @@ class DefaultConversationRepository @Inject constructor( ) } - override suspend fun banUser(threadId: Long, recipient: Recipient): Result = runCatching { - val accountID = recipient.address.toString() + override suspend fun banUser(threadId: Long, recipient: Address): Result = runCatching { val openGroup = lokiThreadDb.getOpenGroupChat(threadId)!! - OpenGroupApi.ban(accountID, openGroup.room, openGroup.server).await() + OpenGroupApi.ban(recipient.toString(), openGroup.room, openGroup.server).await() } - override suspend fun banAndDeleteAll(threadId: Long, recipient: Recipient) = runCatching { + override suspend fun banAndDeleteAll(threadId: Long, recipient: Address) = runCatching { // Note: This accountId could be the blinded Id - val accountID = recipient.address.toString() val openGroup = lokiThreadDb.getOpenGroupChat(threadId)!! - OpenGroupApi.banAndDeleteAll(accountID, openGroup.room, openGroup.server).await() + OpenGroupApi.banAndDeleteAll(recipient.toString(), openGroup.room, openGroup.server).await() } override suspend fun deleteThread(threadId: Long) = runCatching { @@ -414,7 +394,7 @@ class DefaultConversationRepository @Inject constructor( } override suspend fun deleteMessageRequest(thread: ThreadRecord) - = declineMessageRequest(thread.threadId, thread.recipient) + = declineMessageRequest(thread.threadId, thread.recipient.address) override suspend fun clearAllMessageRequests(block: Boolean) = runCatching { withContext(Dispatchers.Default) { @@ -466,16 +446,16 @@ class DefaultConversationRepository @Inject constructor( } } - override suspend fun declineMessageRequest(threadId: Long, recipient: Recipient): Result = runCatching { + override suspend fun declineMessageRequest(threadId: Long, recipient: Address): Result = runCatching { withContext(Dispatchers.Default) { sessionJobDb.cancelPendingMessageSendJobs(threadId) - if (recipient.isGroupV2Recipient) { + if (recipient.isGroupV2) { groupManager.respondToInvitation( - AccountId(recipient.address.toString()), + AccountId(recipient.toString()), approved = false ) } else { - storage.deleteContactAndSyncConfig(recipient.address.toString()) + storage.deleteContactAndSyncConfig(recipient.toString()) } } } @@ -491,9 +471,7 @@ class DefaultConversationRepository @Inject constructor( } // Only call this with a closed group thread ID - override fun getInvitingAdmin(threadId: Long): Recipient? { - return lokiMessageDb.groupInviteReferrer(threadId)?.let { id -> - Recipient.from(context, Address.fromSerialized(id), false) - } + override fun getInvitingAdmin(threadId: Long): Address? { + return lokiMessageDb.groupInviteReferrer(threadId)?.let(Address::fromSerialized) } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt index 0f5b1200cb..b794388972 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt @@ -27,8 +27,10 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.utilities.Address import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.IdPrefix import org.thoughtcrime.securesms.database.GroupDatabase +import org.thoughtcrime.securesms.database.RecipientRepository import java.math.BigInteger import java.security.MessageDigest import java.util.Locale @@ -41,6 +43,7 @@ class AvatarUtils @Inject constructor( private val usernameUtils: UsernameUtils, private val groupDatabase: GroupDatabase, // for legacy groups private val storage: Lazy, + private val recipientRepository: RecipientRepository, ) { // Hardcoded possible bg colors for avatar backgrounds private val avatarBgColors = arrayOf( @@ -55,10 +58,10 @@ class AvatarUtils @Inject constructor( suspend fun getUIDataFromAccountId(accountId: String): AvatarUIData = withContext(Dispatchers.Default) { - getUIDataFromRecipient(Recipient.from(context, Address.fromSerialized(accountId), false)) + getUIDataFromRecipient(recipientRepository.getRecipient(Address.fromSerialized(accountId))) } - suspend fun getUIDataFromRecipient(recipient: Recipient?): AvatarUIData { + suspend fun getUIDataFromRecipient(recipient: RecipientV2?): AvatarUIData { if (recipient == null) { return AvatarUIData(elements = emptyList()) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ContactUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ContactUtilities.kt index c9b91d50a4..e93905a399 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ContactUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ContactUtilities.kt @@ -1,17 +1,17 @@ package org.thoughtcrime.securesms.util import android.content.Context -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.dependencies.DatabaseComponent typealias LastMessageSentTimestamp = Long object ContactUtilities { @JvmStatic - fun getAllContacts(context: Context): Set> { + fun getAllContacts(context: Context): Set> { val threadDatabase = DatabaseComponent.get(context).threadDatabase() val cursor = threadDatabase.conversationList - val result = mutableSetOf>() + val result = mutableSetOf>() threadDatabase.readerFor(cursor).use { reader -> while (reader.next != null) { val thread = reader.current diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt index cf140baefe..86ebe9eb1a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.util import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.messages.SignalServiceDataMessage object SessionMetaProtocol { @@ -48,12 +49,12 @@ object SessionMetaProtocol { } @JvmStatic - fun shouldSendReadReceipt(recipient: Recipient): Boolean { - return !recipient.isGroupOrCommunityRecipient && recipient.isApproved && !recipient.isBlocked + fun shouldSendReadReceipt(recipient: RecipientV2): Boolean { + return !recipient.isGroupOrCommunityRecipient && recipient.approved && !recipient.blocked } @JvmStatic - fun shouldSendTypingIndicator(recipient: Recipient): Boolean { - return !recipient.isGroupOrCommunityRecipient && recipient.isApproved && !recipient.isBlocked + fun shouldSendTypingIndicator(recipient: RecipientV2): Boolean { + return !recipient.isGroupOrCommunityRecipient && recipient.approved && !recipient.blocked } } \ No newline at end of file From b016a6f44af8d8bfe3f50207e7e89f836b1cbe9c Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Tue, 24 Jun 2025 17:37:55 +1000 Subject: [PATCH 07/52] WIP --- .../libsession/database/StorageProtocol.kt | 5 ++-- .../ReceivedMessageHandler.kt | 2 +- .../securesms/configs/ConfigToDatabaseSync.kt | 4 ++-- .../DisappearingMessages.kt | 7 +----- .../ExpirationConfigurationDatabase.kt | 1 + .../securesms/database/RecipientRepository.kt | 24 ++++++++++++++++--- .../securesms/database/Storage.kt | 14 +++++------ 7 files changed, 35 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 2424ec8644..48c156449c 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -2,6 +2,7 @@ package org.session.libsession.database import android.content.Context import android.net.Uri +import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.KeyPair import org.session.libsession.messaging.BlindedIdMapping import org.session.libsession.messaging.calls.CallMessageType @@ -9,7 +10,6 @@ import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.MessageSendJob -import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.GroupUpdated import org.session.libsession.messaging.messages.control.MessageRequestResponse @@ -264,8 +264,7 @@ interface StorageProtocol { fun deleteReactions(messageIds: List, mms: Boolean) fun setBlocked(recipients: Iterable
, isBlocked: Boolean, fromConfigUpdate: Boolean = false) fun blockedContacts(): List - fun getExpirationConfiguration(threadId: Long): ExpirationConfiguration? - fun setExpirationConfiguration(config: ExpirationConfiguration) + fun setExpirationConfiguration(threadId: Long, expiryMode: ExpiryMode) fun getExpiringMessages(messageIds: List = emptyList()): List> // Shared configs diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 36c02da0a4..c28e1d3509 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -186,7 +186,7 @@ private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimer threadId, message.expiryMode, message.sentTimestamp!! - ) + ), ) } catch (e: Exception) { Log.e("Loki", "Failed to update expiration configuration.") diff --git a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt index 90e3e2be66..9bb2c6481c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt @@ -159,7 +159,7 @@ class ConfigToDatabaseSync @Inject constructor( storage.getThreadId(address)?.let { threadId -> storage.setExpirationConfiguration( storage.getExpirationConfiguration(threadId)?.takeIf { it.updatedTimestampMs > messageTimestamp } ?: - ExpirationConfiguration(threadId, userProfile.ntsExpiry, messageTimestamp) + ExpirationConfiguration(threadId, userProfile.ntsExpiry, messageTimestamp), ) } } @@ -392,7 +392,7 @@ class ConfigToDatabaseSync @Inject constructor( storage.getThreadId(fromSerialized(groupId))?.let { theadId -> storage.setExpirationConfiguration( storage.getExpirationConfiguration(theadId)?.takeIf { it.updatedTimestampMs > messageTimestamp } - ?: ExpirationConfiguration(theadId, afterSend(group.disappearingTimer), messageTimestamp) + ?: ExpirationConfiguration(theadId, afterSend(group.disappearingTimer), messageTimestamp), ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt index 798007b463..376f97c608 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt @@ -1,10 +1,6 @@ package org.thoughtcrime.securesms.conversation.disappearingmessages import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.database.StorageProtocol @@ -12,7 +8,6 @@ import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.sending_receiving.MessageSender -import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.Address import org.session.libsession.utilities.ExpirationUtil @@ -37,7 +32,7 @@ class DisappearingMessages @Inject constructor( ) { fun set(threadId: Long, address: Address, mode: ExpiryMode, isGroup: Boolean) { val expiryChangeTimestampMs = clock.currentTimeMills() - storage.setExpirationConfiguration(ExpirationConfiguration(threadId, mode, expiryChangeTimestampMs)) + storage.setExpirationConfiguration(ExpirationConfiguration(threadId, mode, expiryChangeTimestampMs),) if (address.isGroupV2) { groupManagerV2.setExpirationTimer(AccountId(address.toString()), mode, expiryChangeTimestampMs) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ExpirationConfigurationDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ExpirationConfigurationDatabase.kt index a8ff610ac6..cea284f9b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ExpirationConfigurationDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ExpirationConfigurationDatabase.kt @@ -11,6 +11,7 @@ import org.session.libsession.utilities.GroupUtil.COMMUNITY_PREFIX import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import javax.inject.Provider +@Deprecated("We no longer store the expiration configuration in the database. ") class ExpirationConfigurationDatabase(context: Context, helper: Provider) : Database(context, helper) { companion object { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index 9235bcc1f1..690d6e0ad3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import network.loki.messenger.libsession_util.ReadableGroupInfoConfig import network.loki.messenger.libsession_util.util.Contact +import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.utilities.Address @@ -211,6 +212,7 @@ class RecipientRepository @Inject constructor( notifyType = RecipientDatabase.NOTIFY_TYPE_ALL, avatar = avatar?.toRecipientAvatar(), nickname = null, + expiryMode = ExpiryMode.NONE, ) } @@ -221,12 +223,23 @@ class RecipientRepository @Inject constructor( null } + private val ReadableGroupInfoConfig.expiryMode: ExpiryMode + get() { + val timer = getExpiryTimer() + return when { + timer > 0 -> ExpiryMode.AfterSend(timer) + else -> ExpiryMode.NONE + } + } + private fun createGroupV2Recipient( address: Address, group: GroupInfo.ClosedGroupInfo, groupInfo: ReadableGroupInfoConfig, settings: RecipientSettings? ): RecipientV2 { + val timer = groupInfo.getExpiryTimer() + return RecipientV2( name = groupInfo.getName() ?: group.name, address = address, @@ -239,6 +252,7 @@ class RecipientRepository @Inject constructor( mutedUntil = settings?.muteUntilDate, notifyType = settings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, autoDownloadAttachments = settings?.autoDownloadAttachments, + expiryMode = groupInfo.expiryMode, ) } @@ -264,6 +278,7 @@ class RecipientRepository @Inject constructor( autoDownloadAttachments = fallbackSettings?.autoDownloadAttachments, notifyType = fallbackSettings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, + expiryMode = contactInConfig.expiryMode, ) } @@ -283,7 +298,8 @@ class RecipientRepository @Inject constructor( blocked = false, mutedUntil = settings?.muteUntilDate, autoDownloadAttachments = settings?.autoDownloadAttachments, - notifyType = settings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL + notifyType = settings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, + expiryMode = ExpiryMode.NONE, ) } @@ -304,7 +320,8 @@ class RecipientRepository @Inject constructor( mutedUntil = settings.muteUntil.takeIf { it > 0 } ?.let { ZonedDateTime.from(Instant.ofEpochMilli(it)) }, autoDownloadAttachments = settings.autoDownloadAttachments, - notifyType = settings.notifyType + notifyType = settings.notifyType, + expiryMode = ExpiryMode.NONE, // A generic recipient does not have an expiry mode ) } @@ -320,7 +337,8 @@ class RecipientRepository @Inject constructor( mutedUntil = null, autoDownloadAttachments = null, notifyType = RecipientDatabase.NOTIFY_TYPE_ALL, - avatar = null + avatar = null, + expiryMode = ExpiryMode.NONE, ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 588abc94fe..e70e118276 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1218,7 +1218,7 @@ open class Storage @Inject constructor( getThreadId(address)?.let { setExpirationConfiguration( getExpirationConfiguration(it)?.takeIf { it.updatedTimestampMs > timestamp } - ?: ExpirationConfiguration(it, contact.expiryMode, timestamp) + ?: ExpirationConfiguration(it, contact.expiryMode, timestamp), ) } } @@ -1839,13 +1839,13 @@ open class Storage @Inject constructor( ) } } - override fun setExpirationConfiguration(config: ExpirationConfiguration) { - val recipient = getRecipientForThread(config.threadId) ?: return + override fun setExpirationConfiguration(threadId: Long, expiryMode: ExpiryMode) { + val recipient = getRecipientForThread(threadId) ?: return val expirationDb = expirationConfigurationDatabase - val currentConfig = expirationDb.getExpirationConfiguration(config.threadId) - if (currentConfig != null && currentConfig.updatedTimestampMs >= config.updatedTimestampMs) return - val expiryMode = config.expiryMode + val currentConfig = expirationDb.getExpirationConfiguration(threadId.threadId) + if (currentConfig != null && currentConfig.updatedTimestampMs >= threadId.updatedTimestampMs) return + val expiryMode = threadId.expiryMode if (expiryMode == ExpiryMode.NONE) { // Clear the legacy recipients on updating config to be none @@ -1877,7 +1877,7 @@ open class Storage @Inject constructor( } } expirationDb.setExpirationConfiguration( - config.run { copy(expiryMode = expiryMode) } + threadId.run { copy(expiryMode = expiryMode) } ) } From e184eb1d64036b4f236466b9fb232303cabb9eee Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Thu, 26 Jun 2025 14:28:15 +1000 Subject: [PATCH 08/52] WIP --- .../libsession/database/StorageProtocol.kt | 11 +- .../messaging/groups/GroupManagerV2.kt | 7 +- .../messaging/jobs/AttachmentDownloadJob.kt | 15 +- .../jobs/RetrieveProfileAvatarJob.kt | 4 +- .../messaging/mentions/MentionsManager.kt | 43 --- .../libsession/messaging/messages/Message.kt | 2 +- .../messages/ProfileUpdateHandler.kt | 152 +++++++++++ .../OutgoingExpirationUpdateMessage.java | 5 +- .../sending_receiving/MessageSender.kt | 3 +- .../ReceivedMessageHandler.kt | 94 ++----- .../libsession/utilities/SSKEnvironment.kt | 28 +- .../utilities/TextSecurePreferences.kt | 41 --- .../libsession/utilities/UsernameUtils.kt | 25 -- .../utilities/recipients/MessageType.kt | 2 +- .../utilities/recipients/RecipientV2.kt | 26 +- .../securesms/ApplicationContext.kt | 14 +- .../securesms/MediaPreviewArgs.kt | 4 +- .../components/ProfilePictureView.kt | 21 +- .../securesms/configs/ConfigToDatabaseSync.kt | 59 +---- .../contacts/ContactSelectionListAdapter.kt | 12 +- .../contacts/ShareContactListFragment.kt | 8 +- .../securesms/contacts/UserView.kt | 5 +- .../DisappearingMessages.kt | 9 +- .../DisappearingMessagesViewModel.kt | 5 +- .../v2/AttachmentDownloadHandler.kt | 8 +- .../conversation/v2/ConversationActivityV2.kt | 46 ++-- .../conversation/v2/ConversationAdapter.kt | 3 +- .../conversation/v2/ConversationViewModel.kt | 174 +++++------- .../v2/MessageDetailsViewModel.kt | 12 +- .../conversation/v2/input_bar/InputBar.kt | 4 +- .../v2/messages/AttachmentControlView.kt | 2 +- .../v2/messages/ControlMessageView.kt | 2 +- .../v2/messages/VisibleMessageView.kt | 24 +- .../settings/ConversationSettingsViewModel.kt | 63 ++--- .../v2/utilities/AttachmentManager.java | 7 +- .../securesms/database/MmsDatabase.kt | 17 +- .../securesms/database/RecipientDatabase.java | 49 +++- .../securesms/database/RecipientRepository.kt | 57 ++-- .../database/SessionContactDatabase.kt | 1 + .../securesms/database/Storage.kt | 248 +++++------------- .../securesms/debugmenu/DebugMenuViewModel.kt | 2 +- .../securesms/dependencies/AppModule.kt | 5 - .../securesms/dependencies/CallModule.kt | 5 - .../securesms/dependencies/ConfigFactory.kt | 77 +----- .../securesms/dependencies/DatabaseModule.kt | 4 - .../securesms/groups/CreateGroupViewModel.kt | 6 +- .../securesms/groups/GroupManagerV2Impl.kt | 28 +- .../groups/SelectContactsViewModel.kt | 18 +- .../home/ConversationOptionsBottomSheet.kt | 6 +- .../securesms/home/ConversationView.kt | 9 +- .../securesms/home/HomeActivity.kt | 4 +- .../securesms/home/HomeViewModel.kt | 2 +- .../securesms/home/UserDetailsBottomSheet.kt | 34 ++- .../home/search/GlobalSearchAdapter.kt | 9 +- .../home/search/GlobalSearchAdapterUtils.kt | 12 +- .../emoji/search/EmojiSearchRepository.kt | 8 +- .../securesms/media/MediaOverviewViewModel.kt | 21 +- .../mediasend/MediaPickerFolderFragment.java | 9 +- .../securesms/mediasend/MediaSendActivity.kt | 28 +- .../messagerequests/MessageRequestView.kt | 3 +- .../notifications/DefaultMessageNotifier.kt | 36 +-- .../notifications/MarkReadReceiver.kt | 2 +- .../notifications/NotificationChannels.java | 12 +- .../notifications/NotificationItem.java | 20 +- .../notifications/NotificationState.java | 10 +- .../SingleRecipientNotificationBuilder.java | 20 +- .../manager/CreateAccountManager.kt | 2 - .../pickname/PickDisplayNameActivity.kt | 14 +- .../pickname/PickDisplayNameViewModel.kt | 32 +-- .../preferences/BlockedContactsAdapter.kt | 3 +- .../preferences/BlockedContactsViewModel.kt | 12 +- .../securesms/preferences/SettingsActivity.kt | 4 +- .../preferences/SettingsViewModel.kt | 12 +- .../securesms/reactions/ReactionDetails.kt | 4 +- .../reactions/ReactionsDialogFragment.java | 12 +- .../reactions/ReactionsRepository.kt | 12 +- .../reactions/ReactionsViewModel.java | 25 +- .../any/ReactWithAnyEmojiDialogFragment.java | 14 +- .../any/ReactWithAnyEmojiRepository.java | 2 +- .../any/ReactWithAnyEmojiViewModel.java | 44 ++-- .../repository/ConversationRepository.kt | 45 ++-- .../securesms/search/SearchModule.kt | 38 --- .../securesms/search/SearchRepository.kt | 25 +- .../securesms/search/model/MessageResult.java | 11 +- .../service/CallForegroundService.kt | 19 +- .../service/ExpiringMessageManager.kt | 13 +- .../sskenvironment/ProfileManager.kt | 113 -------- .../securesms/tokenpage/TokenPageViewModel.kt | 3 +- .../securesms/util/UsernameUtilsImpl.kt | 54 ---- .../securesms/webrtc/CallManager.kt | 48 ++-- .../securesms/webrtc/CallMessageProcessor.kt | 6 +- .../webrtc/CallNotificationBuilder.kt | 5 +- .../securesms/webrtc/CallViewModel.kt | 7 +- .../thoughtcrime/securesms/webrtc/PreOffer.kt | 6 +- .../securesms/webrtc/WebRtcCallBridge.kt | 55 ++-- 95 files changed, 970 insertions(+), 1372 deletions(-) delete mode 100644 app/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt create mode 100644 app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt delete mode 100644 app/src/main/java/org/session/libsession/utilities/UsernameUtils.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/search/SearchModule.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtilsImpl.kt diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 48c156449c..749bfcd6b0 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -6,7 +6,6 @@ import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.KeyPair import org.session.libsession.messaging.BlindedIdMapping import org.session.libsession.messaging.calls.CallMessageType -import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.MessageSendJob @@ -28,8 +27,8 @@ import org.session.libsession.messaging.utilities.UpdateMessageData import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupDisplayInfo import org.session.libsession.utilities.GroupRecord -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient.RecipientSettings +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceGroup @@ -48,7 +47,6 @@ interface StorageProtocol { fun getUserX25519KeyPair(): ECKeyPair fun getUserBlindedAccountId(serverPublicKey: String): AccountId? fun getUserProfile(): Profile - fun setProfilePicture(recipient: Address, newProfilePicture: String?, newProfileKey: ByteArray?) fun setBlocksCommunityMessageRequests(recipient: Address, blocksMessageRequests: Boolean) fun setUserProfilePicture(newProfilePicture: String?, newProfileKey: ByteArray?) fun clearUserPic(clearConfig: Boolean = true) @@ -130,7 +128,6 @@ interface StorageProtocol { fun getGroup(groupID: String): GroupRecord? fun createGroup(groupID: String, title: String?, members: List
, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List
, formationTimestamp: Long) fun createInitialConfigGroup(groupPublicKey: String, name: String, members: Map, formationTimestamp: Long, encryptionKeyPair: ECKeyPair, expirationTimer: Int) - fun updateGroupConfig(groupPublicKey: String) fun isGroupActive(groupPublicKey: String): Boolean fun setActive(groupID: String, value: Boolean) fun getZombieMembers(groupID: String): Set @@ -196,9 +193,6 @@ interface StorageProtocol { fun clearMedia(threadID: Long, fromUser: Address? = null): Boolean // Contacts - fun getContactWithAccountID(accountID: String): Contact? - fun getAllContacts(): Set - fun setContact(contact: Contact) fun deleteContactAndSyncConfig(accountId: String) fun getRecipientForThread(threadId: Long): Address? fun getRecipientSettings(address: Address): RecipientSettings? @@ -263,7 +257,8 @@ interface StorageProtocol { fun deleteReactions(messageId: MessageId) fun deleteReactions(messageIds: List, mms: Boolean) fun setBlocked(recipients: Iterable
, isBlocked: Boolean, fromConfigUpdate: Boolean = false) - fun blockedContacts(): List + fun blockedContacts(): List + fun getExpirationConfiguration(threadId: Long): ExpiryMode fun setExpirationConfiguration(threadId: Long, expiryMode: ExpiryMode) fun getExpiringMessages(messageIds: List = emptyList()): List> diff --git a/app/src/main/java/org/session/libsession/messaging/groups/GroupManagerV2.kt b/app/src/main/java/org/session/libsession/messaging/groups/GroupManagerV2.kt index cd48ece0a1..2d2a2dc37d 100644 --- a/app/src/main/java/org/session/libsession/messaging/groups/GroupManagerV2.kt +++ b/app/src/main/java/org/session/libsession/messaging/groups/GroupManagerV2.kt @@ -3,10 +3,9 @@ package org.session.libsession.messaging.groups import androidx.annotation.StringRes import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.messaging.messages.control.GroupUpdated -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateDeleteMemberContentMessage import org.session.libsignal.utilities.AccountId -import org.thoughtcrime.securesms.groups.GroupManagerV2Impl /** * Business logic handling group v2 operations like inviting members, @@ -17,7 +16,7 @@ interface GroupManagerV2 { groupName: String, groupDescription: String, members: Set - ): Recipient + ): RecipientV2 suspend fun inviteMembers( group: AccountId, @@ -109,7 +108,7 @@ interface GroupManagerV2 { senderIsVerifiedAdmin: Boolean, ) - fun setExpirationTimer(groupId: AccountId, mode: ExpiryMode, expiryChangeTimestampMs: Long) + fun setExpirationTimer(groupId: AccountId, mode: ExpiryMode) fun handleGroupInfoChange(message: GroupUpdated, groupId: AccountId) diff --git a/app/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/app/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt index ea76dbfea4..cd01781a72 100644 --- a/app/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt +++ b/app/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt @@ -18,6 +18,7 @@ import org.session.libsignal.streams.AttachmentCipherInputStream import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.HTTP import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.model.MessageId import java.io.File import java.io.FileInputStream @@ -56,9 +57,11 @@ class AttachmentDownloadJob(val attachmentID: Long, val mmsMessageId: Long) : Jo */ fun eligibleForDownload(threadID: Long, storage: StorageProtocol, + recipientRepository: RecipientRepository, messageDataProvider: MessageDataProvider, mmsId: Long): Boolean { - val threadRecipient = storage.getRecipientForThread(threadID) ?: return false + val threadRecipient = storage.getRecipientForThread(threadID) + ?.let(recipientRepository::getRecipientSync) ?: return false // if we are the sender we are always eligible val selfSend = messageDataProvider.isOutgoingMessage(MessageId(mmsId, true)) @@ -66,7 +69,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val mmsMessageId: Long) : Jo return true } - return storage.shouldAutoDownloadAttachments(threadRecipient) + return threadRecipient.autoDownloadAttachments == true } } @@ -124,7 +127,13 @@ class AttachmentDownloadJob(val attachmentID: Long, val mmsMessageId: Long) : Jo return } - if (!eligibleForDownload(threadID, storage, messageDataProvider, mmsMessageId)) { + if (!eligibleForDownload( + threadID = threadID, + storage = storage, + recipientRepository = MessagingModuleConfiguration.shared.recipientRepository, + messageDataProvider = messageDataProvider, + mmsId = mmsMessageId + )) { handleFailure(Error.NoSender, null) return } diff --git a/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt b/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt index 0d84b5fd41..98c810acee 100644 --- a/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt +++ b/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt @@ -10,6 +10,7 @@ import org.session.libsession.utilities.DownloadUtilities.downloadFromFileServer import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfileAvatarId import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfilePictureURL import org.session.libsession.utilities.Util.equals +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.exceptions.NonRetryableException import org.session.libsignal.utilities.HTTP import org.session.libsignal.utilities.Log @@ -44,7 +45,8 @@ class RetrieveProfileAvatarJob( if (profileAvatar != null && profileAvatar in errorUrls) return delegate.handleJobFailed(this, dispatcherName, Exception("Profile URL 404'd this app instance")) val context = MessagingModuleConfiguration.shared.context val storage = MessagingModuleConfiguration.shared.storage - val recipient = storage.observeRecipient(recipientAddress).first() + val recipient = MessagingModuleConfiguration.shared.recipientRepository.getRecipient(recipientAddress) + ?: RecipientV2.empty(recipientAddress) if (profileKey == null || (profileKey.size != 32 && profileKey.size != 16)) { return delegate.handleJobFailedPermanently(this, dispatcherName, Exception("Recipient profile key is gone!")) diff --git a/app/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt b/app/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt deleted file mode 100644 index a60b4fd5b5..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.session.libsession.messaging.mentions - -import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.contacts.Contact -import java.util.Locale - -object MentionsManager { - var userPublicKeyCache = mutableMapOf>() // Thread ID to set of user hex encoded public keys - - fun cache(publicKey: String, threadID: Long) { - val cache = userPublicKeyCache[threadID] - if (cache != null) { - userPublicKeyCache[threadID] = cache.plus(publicKey) - } else { - userPublicKeyCache[threadID] = setOf( publicKey ) - } - } - - fun getMentionCandidates(query: String, threadID: Long, isOpenGroup: Boolean): List { - val cache = userPublicKeyCache[threadID] ?: return listOf() - // Prepare - val context = if (isOpenGroup) Contact.ContactContext.OPEN_GROUP else Contact.ContactContext.REGULAR - val storage = MessagingModuleConfiguration.shared.storage - val userPublicKey = storage.getUserPublicKey() - // Gather candidates - var candidates: List = cache.mapNotNull { publicKey -> - val contact = storage.getContactWithAccountID(publicKey) - val displayName = contact?.displayName(context) ?: return@mapNotNull null - Mention(publicKey, displayName) - } - candidates = candidates.filter { it.publicKey != userPublicKey } - // Sort alphabetically first - candidates.sortedBy { it.displayName } - if (query.length >= 2) { - // Filter out any non-matching candidates - candidates = candidates.filter { it.displayName.lowercase(Locale.getDefault()).contains(query.lowercase(Locale.getDefault())) } - // Sort based on where in the candidate the query occurs - candidates.sortedBy { it.displayName.lowercase(Locale.getDefault()).indexOf(query.lowercase(Locale.getDefault())) } - } - // Return - return candidates - } -} diff --git a/app/src/main/java/org/session/libsession/messaging/messages/Message.kt b/app/src/main/java/org/session/libsession/messaging/messages/Message.kt index 366b49d24a..4c995bac0f 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/Message.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/Message.kt @@ -88,5 +88,5 @@ fun SignalServiceProtos.Content.expiryMode(): ExpiryMode = */ inline fun M.applyExpiryMode(thread: Long): M = apply { val storage = MessagingModuleConfiguration.shared.storage - expiryMode = storage.getExpirationConfiguration(thread)?.expiryMode?.coerceSendToRead(coerceDisappearAfterSendToRead) ?: ExpiryMode.NONE + expiryMode = storage.getExpirationConfiguration(thread).coerceSendToRead(coerceDisappearAfterSendToRead) } diff --git a/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt b/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt new file mode 100644 index 0000000000..96fd3a6c33 --- /dev/null +++ b/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt @@ -0,0 +1,152 @@ +package org.session.libsession.messaging.messages + +import network.loki.messenger.BuildConfig +import network.loki.messenger.libsession_util.util.UserPic +import org.session.libsession.database.StorageProtocol +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.ConfigFactoryProtocol +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsignal.utilities.AccountId +import org.session.libsignal.utilities.IdPrefix +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.database.BlindedIdMappingDatabase +import org.thoughtcrime.securesms.database.RecipientDatabase +import javax.inject.Inject +import javax.inject.Singleton + +/** + * This class handles the profile updates coming from user's messages. The messages can be + * from a 1to1, groups or community conversations. + * + * Although [handleProfileUpdate] takes an [Address] or [AccountId], this class can only handle + * the profile updates for **users**, not groups or communities' profile, as they have very different + * mechanisms and storage for their updates. + */ +@Singleton +class ProfileUpdateHandler @Inject constructor( + private val configFactory: ConfigFactoryProtocol, + private val recipientDatabase: RecipientDatabase, + private val blindedIdMappingDatabase: BlindedIdMappingDatabase, + private val prefs: TextSecurePreferences, + private val storage: StorageProtocol, +) { + + fun handleProfileUpdate(sender: Address, updates: Updates, communityServerPubKey: String?) { + val senderAccountId = AccountId.fromStringOrNull(sender.address) + if (senderAccountId == null) { + Log.e(TAG, "Invalid sender address") + return + } + + handleProfileUpdate(senderAccountId, updates, communityServerPubKey) + } + + fun handleProfileUpdate(sender: AccountId, updates: Updates, communityServerPubKey: String?) { + if (sender.hexString == prefs.getLocalNumber() || + (communityServerPubKey != null && storage.getUserBlindedAccountId(communityServerPubKey) == sender) + ) { + Log.w(TAG, "Ignoring profile update for local number") + return + } + + + if (updates.name.isNullOrBlank() && + updates.pic == null && + updates.acceptsCommunityRequests == null + ) { + Log.i(TAG, "No valid profile updated data provided for $sender") + return + } + + + Log.d(TAG, "Handling profile update for $sender") + + // If the sender is blind, we need to figure out their real address first. + val actualSender = + if (sender.prefix == IdPrefix.BLINDED || sender.prefix == IdPrefix.BLINDEDV2) { + blindedIdMappingDatabase.getBlindedIdMapping(sender.hexString) + .firstNotNullOfOrNull { it.accountId } + ?.let(AccountId::fromStringOrNull) + ?: sender + } else { + sender + } + + if (actualSender.hexString == prefs.getLocalNumber()) { + Log.w(TAG, "Ignoring profile update for local number: $actualSender") + return + } + + // First, if the user is a contact, update the config and that's all we need to do. + val isExistingContact = actualSender.prefix == IdPrefix.STANDARD && + configFactory.withMutableUserConfigs { configs -> + val existingContact = configs.contacts.get(actualSender.hexString) + if (existingContact != null) { + configs.contacts.set( + existingContact.copy( + name = updates.name ?: existingContact.name, + profilePicture = updates.pic ?: existingContact.profilePicture + ) + ) + true + } else { + false + } + } + + if (isExistingContact && updates.acceptsCommunityRequests == null) { + Log.d(TAG, "Updated existing contact profile for $sender (actualSender: $actualSender)") + return + } + + // If the actual sender is still blinded or unknown contact, we need to update their + // settings in the recipient database instead, as we don't have a place in the config + // for them. + if (actualSender.prefix == IdPrefix.BLINDED || actualSender.prefix == IdPrefix.BLINDEDV2 || + actualSender.prefix == IdPrefix.STANDARD + ) { + Log.d(TAG, "Updating recipient profile for $actualSender") + recipientDatabase.updateProfile( + Address.fromSerialized(actualSender.hexString), + updates.name, + updates.pic, + updates.acceptsCommunityRequests + ) + return + } + + if (BuildConfig.DEBUG) { + throw IllegalArgumentException("Unsupported profile update for sender: $sender") + } + + Log.e(TAG, "Unsupported profile updating for sender: $sender") + } + + data class Updates( + val name: String? = null, + val pic: UserPic? = null, + val acceptsCommunityRequests: Boolean? = null, + ) { + constructor( + name: String?, + picUrl: String?, + picKey: ByteArray?, + acceptsCommunityRequests: Boolean? + ) : this( + name = name, + pic = if (!picUrl.isNullOrBlank() && picKey != null && picKey.size in VALID_PROFILE_KEY_LENGTH) { + UserPic(picUrl, picKey) + } else { + null + }, acceptsCommunityRequests = acceptsCommunityRequests + ) + } + + companion object { + const val TAG = "ProfileUpdateHandler" + + const val MAX_PROFILE_NAME_LENGTH = 100 + + private val VALID_PROFILE_KEY_LENGTH = listOf(16, 32) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java index b42d6d1bde..078729ab29 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java @@ -1,8 +1,9 @@ package org.session.libsession.messaging.messages.signal; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; +import org.session.libsession.utilities.Address; import org.session.libsession.utilities.DistributionTypes; -import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import java.util.Collections; import java.util.LinkedList; @@ -11,7 +12,7 @@ public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage private final String groupId; - public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn, long expireStartedAt, String groupId) { + public OutgoingExpirationUpdateMessage(Address recipient, long sentTimeMillis, long expiresIn, long expireStartedAt, String groupId) { super(recipient, "", new LinkedList(), sentTimeMillis, DistributionTypes.CONVERSATION, expiresIn, expireStartedAt, null, Collections.emptyList(), Collections.emptyList()); diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 26915e8493..3573fe5ca3 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -293,10 +293,9 @@ object MessageSender { ?.let(MessagingModuleConfiguration.shared.storage::getThreadId) } ?.let(MessagingModuleConfiguration.shared.storage::getExpirationConfiguration) - ?.takeIf { it.isEnabled } - ?.expiryMode ?.takeIf { it is ExpiryMode.AfterSend || isSyncMessage } ?.expiryMillis + ?.takeIf { it > 0 } } // Open Groups diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index c28e1d3509..673b553de7 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -9,7 +9,6 @@ import network.loki.messenger.R import network.loki.messenger.libsession_util.ED25519 import network.loki.messenger.libsession_util.util.BlindKeyAPI import network.loki.messenger.libsession_util.util.ExpiryMode -import org.session.libsession.avatars.AvatarHelper import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.StorageProtocol import org.session.libsession.database.userAuth @@ -17,9 +16,8 @@ import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.jobs.AttachmentDownloadJob import org.session.libsession.messaging.jobs.JobQueue -import org.session.libsession.messaging.messages.ExpirationConfiguration -import org.session.libsession.messaging.messages.ExpirationConfiguration.Companion.isNewConfigEnabled import org.session.libsession.messaging.messages.Message +import org.session.libsession.messaging.messages.ProfileUpdateHandler import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.messages.control.DataExtractionNotification import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate @@ -48,7 +46,7 @@ import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.GroupUtil.doubleEncodeGroupID import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.recipients.MessageType -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.recipients.getType import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.AccountId @@ -60,7 +58,6 @@ import org.thoughtcrime.securesms.database.ConfigDatabase import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.ReactionRecord -import java.security.MessageDigest import java.security.SignatureException import kotlin.math.min @@ -114,13 +111,13 @@ fun MessageReceiver.messageIsOutdated(message: Message, threadId: Long, openGrou val userPublicKey = storage.getUserPublicKey()!! val threadRecipient = storage.getRecipientForThread(threadId) val conversationVisibleInConfig = storage.conversationInConfig( - if (message.groupPublicKey == null) threadRecipient?.address?.toString() else null, + if (message.groupPublicKey == null) threadRecipient?.toString() else null, message.groupPublicKey, openGroupID, true ) val canPerformChange = storage.canPerformConfigChange( - if (threadRecipient?.address?.toString() == userPublicKey) ConfigDatabase.USER_PROFILE_VARIANT else ConfigDatabase.CONTACTS_VARIANT, + if (threadRecipient?.toString() == userPublicKey) ConfigDatabase.USER_PROFILE_VARIANT else ConfigDatabase.CONTACTS_VARIANT, userPublicKey, message.sentTimestamp!! ) @@ -171,26 +168,6 @@ fun MessageReceiver.cancelTypingIndicatorsIfNeeded(senderPublicKey: String) { private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimerUpdate) { SSKEnvironment.shared.messageExpirationManager.insertExpirationTimerMessage(message) - - val isLegacyGroup = message.groupPublicKey != null && message.groupPublicKey?.startsWith(IdPrefix.GROUP.value) == false - - if (isNewConfigEnabled && !isLegacyGroup) return - - val module = MessagingModuleConfiguration.shared - try { - val threadId = Address.fromSerialized(message.groupPublicKey?.let(::doubleEncodeGroupID) ?: message.sender!!) - .let(module.storage::getOrCreateThreadIdFor) - - module.storage.setExpirationConfiguration( - ExpirationConfiguration( - threadId, - message.expiryMode, - message.sentTimestamp!! - ), - ) - } catch (e: Exception) { - Log.e("Loki", "Failed to update expiration configuration.") - } } private fun MessageReceiver.handleDataExtractionNotification(message: DataExtractionNotification) { @@ -291,11 +268,11 @@ class VisibleMessageHandlerContext( val threadId: Long, val openGroupID: String?, val storage: StorageProtocol, - val profileManager: SSKEnvironment.ProfileManagerProtocol, val groupManagerV2: GroupManagerV2, val messageExpirationManager: SSKEnvironment.MessageExpirationManagerProtocol, val messageDataProvider: MessageDataProvider, val recipientRepository: RecipientRepository, + val profileUpdateHandler: ProfileUpdateHandler, ) { constructor(module: MessagingModuleConfiguration, threadId: Long, openGroupID: String?): this( @@ -303,11 +280,11 @@ class VisibleMessageHandlerContext( threadId = threadId, openGroupID = openGroupID, storage = module.storage, - profileManager = SSKEnvironment.shared.profileManager, groupManagerV2 = module.groupManagerV2, messageExpirationManager = SSKEnvironment.shared.messageExpirationManager, messageDataProvider = module.messageDataProvider, recipientRepository = module.recipientRepository, + profileUpdateHandler = SSKEnvironment.shared.profileUpdateHandler, ) val openGroup: OpenGroup? by lazy { @@ -331,8 +308,8 @@ class VisibleMessageHandlerContext( storage.getUserPublicKey() } - val threadRecipient: Recipient? by lazy { - storage.getRecipientForThread(threadId) + val threadRecipient: RecipientV2? by lazy { + storage.getRecipientForThread(threadId)?.let(recipientRepository::getRecipientSync) } } @@ -351,31 +328,17 @@ fun MessageReceiver.handleVisibleMessage( // Update profile if needed val address = Address.fromSerialized(messageSender!!) - val recipient = context.recipientRepository.getRecipientSync(address) if (runProfileUpdate) { - val profile = message.profile - val isUserBlindedSender = messageSender == context.userBlindedKey - if (profile != null && userPublicKey != messageSender && !isUserBlindedSender) { - val name = profile.displayName!! - if (name.isNotEmpty()) { - context.profileManager.setName(context.context, address, name) - } - val newProfileKey = profile.profileKey - - val needsProfilePicture = !AvatarHelper.avatarFileExists(context.context, Address.fromSerialized(messageSender)) - val profileKeyValid = newProfileKey?.isNotEmpty() == true && (newProfileKey.size == 16 || newProfileKey.size == 32) && profile.profilePictureURL?.isNotEmpty() == true - val profileKeyChanged = (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey.data, newProfileKey)) - - if ((profileKeyValid && profileKeyChanged) || (profileKeyValid && needsProfilePicture)) { - context.profileManager.setProfilePicture(context.context, address, profile.profilePictureURL, newProfileKey) - } else if (newProfileKey == null || newProfileKey.isEmpty() || profile.profilePictureURL.isNullOrEmpty()) { - context.profileManager.setProfilePicture(context.context, address, null, null) - } - } - - if (userPublicKey != messageSender && !isUserBlindedSender) { - context.storage.setBlocksCommunityMessageRequests(address, message.blocksMessageRequests) - } + context.profileUpdateHandler.handleProfileUpdate( + sender = address, + updates = ProfileUpdateHandler.Updates( + name = message.profile?.displayName, + picUrl = message.profile?.profilePictureURL, + picKey = message.profile?.profileKey, + acceptsCommunityRequests = !message.blocksMessageRequests + ), + communityServerPubKey = context.openGroup?.publicKey + ) } // Handle group invite response if new closed group if (context.threadRecipient?.isGroupV2Recipient == true) { @@ -587,17 +550,16 @@ private fun MessageReceiver.handleGroupUpdated(message: GroupUpdated, closedGrou } // Update profile if needed - if (message.profile != null && !message.isSenderSelf) { - val profile = message.profile - val address = Address.fromSerialized(message.sender!!) - val profileManager = SSKEnvironment.shared.profileManager - if (profile.displayName?.isNotEmpty() == true) { - profileManager.setName(MessagingModuleConfiguration.shared.context, address, profile.displayName) - } - if (profile.profileKey?.isNotEmpty() == true && !profile.profilePictureURL.isNullOrEmpty()) { - profileManager.setProfilePicture(MessagingModuleConfiguration.shared.context, address, profile.profilePictureURL, profile.profileKey) - } - } + SSKEnvironment.shared.profileUpdateHandler.handleProfileUpdate( + sender = Address.fromSerialized(message.sender!!), + updates = ProfileUpdateHandler.Updates( + name = message.profile?.displayName, + picUrl = message.profile?.profilePictureURL, + picKey = message.profile?.profileKey, + acceptsCommunityRequests = null, + ), + communityServerPubKey = null // Groupv2 is not a community + ) when { inner.hasInviteMessage() -> handleNewLibSessionClosedGroupMessage(message) diff --git a/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt b/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt index 004e840264..3b7b40817d 100644 --- a/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt +++ b/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt @@ -3,6 +3,7 @@ package org.session.libsession.utilities import android.content.Context import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.messages.Message +import org.session.libsession.messaging.messages.ProfileUpdateHandler import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier import org.thoughtcrime.securesms.database.model.MessageId @@ -10,9 +11,9 @@ import org.thoughtcrime.securesms.database.model.MessageId class SSKEnvironment( val typingIndicators: TypingIndicatorsProtocol, val readReceiptManager: ReadReceiptManagerProtocol, - val profileManager: ProfileManagerProtocol, val notificationManager: MessageNotifier, - val messageExpirationManager: MessageExpirationManagerProtocol + val messageExpirationManager: MessageExpirationManagerProtocol, + val profileUpdateHandler: ProfileUpdateHandler, ) { interface TypingIndicatorsProtocol { @@ -25,17 +26,6 @@ class SSKEnvironment( fun processReadReceipts(context: Context, fromRecipientId: String, sentTimestamps: List, readTimestamp: Long) } - interface ProfileManagerProtocol { - companion object { - const val NAME_PADDED_LENGTH = 100 - } - - fun setNickname(context: Context, recipient: Address, nickname: String?) - fun setName(context: Context, recipient: Address, name: String?) - fun setProfilePicture(context: Context, recipient: Address, profilePictureURL: String?, profileKey: ByteArray?) - fun contactUpdatedInternal(contact: Contact): String? - } - interface MessageExpirationManagerProtocol { fun insertExpirationTimerMessage(message: ExpirationTimerUpdate) @@ -56,11 +46,17 @@ class SSKEnvironment( fun configure(typingIndicators: TypingIndicatorsProtocol, readReceiptManager: ReadReceiptManagerProtocol, - profileManager: ProfileManagerProtocol, notificationManager: MessageNotifier, - messageExpirationManager: MessageExpirationManagerProtocol) { + messageExpirationManager: MessageExpirationManagerProtocol, + profileUpdateHandler: ProfileUpdateHandler) { if (Companion::shared.isInitialized) { return } - shared = SSKEnvironment(typingIndicators, readReceiptManager, profileManager, notificationManager, messageExpirationManager) + shared = SSKEnvironment( + typingIndicators = typingIndicators, + readReceiptManager = readReceiptManager, + notificationManager = notificationManager, + messageExpirationManager = messageExpirationManager, + profileUpdateHandler = profileUpdateHandler + ) } } } diff --git a/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 9887d9b4b4..8ace9d81a6 100644 --- a/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -88,14 +88,6 @@ interface TextSecurePreferences { fun setHasSeenGIFMetaDataWarning() fun isGifSearchInGridLayout(): Boolean fun setIsGifSearchInGridLayout(isGrid: Boolean) - fun getProfileKey(): String? - fun setProfileKey(key: String?) - fun setProfileName(name: String?) - fun getProfileName(): String? - fun setProfileAvatarId(id: Int) - fun getProfileAvatarId(): Int - fun setProfilePictureURL(url: String?) - fun getProfilePictureURL(): String? fun getNotificationPriority(): Int fun getMessageBodyTextSize(): Int fun setPreferredCameraDirection(value: CameraSelector) @@ -1166,39 +1158,6 @@ class AppTextSecurePreferences @Inject constructor( setBooleanPreference(TextSecurePreferences.GIF_GRID_LAYOUT, isGrid) } - override fun getProfileKey(): String? { - return getStringPreference(TextSecurePreferences.PROFILE_KEY_PREF, null) - } - - override fun setProfileKey(key: String?) { - setStringPreference(TextSecurePreferences.PROFILE_KEY_PREF, key) - } - - override fun setProfileName(name: String?) { - setStringPreference(TextSecurePreferences.PROFILE_NAME_PREF, name) - _events.tryEmit(TextSecurePreferences.PROFILE_NAME_PREF) - } - - override fun getProfileName(): String? { - return getStringPreference(TextSecurePreferences.PROFILE_NAME_PREF, null) - } - - override fun setProfileAvatarId(id: Int) { - setIntegerPreference(TextSecurePreferences.PROFILE_AVATAR_ID_PREF, id) - } - - override fun getProfileAvatarId(): Int { - return getIntegerPreference(TextSecurePreferences.PROFILE_AVATAR_ID_PREF, 0) - } - - override fun setProfilePictureURL(url: String?) { - setStringPreference(TextSecurePreferences.PROFILE_AVATAR_URL_PREF, url) - } - - override fun getProfilePictureURL(): String? { - return getStringPreference(TextSecurePreferences.PROFILE_AVATAR_URL_PREF, null) - } - override fun getNotificationPriority(): Int { return getStringPreference( TextSecurePreferences.NOTIFICATION_PRIORITY_PREF, NotificationCompat.PRIORITY_HIGH.toString())!!.toInt() diff --git a/app/src/main/java/org/session/libsession/utilities/UsernameUtils.kt b/app/src/main/java/org/session/libsession/utilities/UsernameUtils.kt deleted file mode 100644 index beb61769f5..0000000000 --- a/app/src/main/java/org/session/libsession/utilities/UsernameUtils.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.session.libsession.utilities - -import org.session.libsession.messaging.contacts.Contact -import org.session.libsignal.utilities.AccountId - -interface UsernameUtils { - fun getCurrentUsernameWithAccountIdFallback(): String - - fun getCurrentUsername(): String? - - fun saveCurrentUserName(name: String) - - fun getContactNameWithAccountID( - accountID: String, - groupId: AccountId? = null, - contactContext: Contact.ContactContext = Contact.ContactContext.REGULAR - ): String - - fun getContactNameWithAccountID( - contact: Contact?, - accountID: String, - groupId: AccountId? = null, - contactContext: Contact.ContactContext = Contact.ContactContext.REGULAR - ): String -} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/MessageType.kt b/app/src/main/java/org/session/libsession/utilities/recipients/MessageType.kt index 591300cd12..afbd55d142 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/MessageType.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/MessageType.kt @@ -4,7 +4,7 @@ enum class MessageType { ONE_ON_ONE, LEGACY_GROUP, GROUPS_V2, NOTE_TO_SELF, COMMUNITY } -fun Recipient.getType(): MessageType = +fun RecipientV2.getType(): MessageType = when{ isCommunityRecipient -> MessageType.COMMUNITY isLocalNumber -> MessageType.NOTE_TO_SELF diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt index 173188ecbb..da76b95992 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt @@ -3,8 +3,9 @@ package org.session.libsession.utilities.recipients import network.loki.messenger.libsession_util.util.Bytes import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.UserPic -import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.utilities.Address +import org.session.libsession.utilities.recipients.RecipientAvatar.EncryptedRemotePic +import org.session.libsession.utilities.recipients.RecipientAvatar.Inline import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.model.NotifyType import java.time.ZonedDateTime @@ -23,6 +24,7 @@ data class RecipientV2( val notifyType: Int, val avatar: RecipientAvatar?, val expiryMode: ExpiryMode, + val acceptsCommunityMessageRequests: Boolean, ) { val isGroupOrCommunityRecipient: Boolean get() = address.isGroupOrCommunity val isCommunityRecipient: Boolean get() = address.isCommunity @@ -30,6 +32,9 @@ data class RecipientV2( val isCommunityOutboxRecipient: Boolean get() = address.isCommunityOutbox val isGroupV2Recipient: Boolean get() = address.isGroupV2 val isLegacyGroupRecipient: Boolean get() = address.isLegacyGroup + val isContactRecipient: Boolean get() = address.isContact + val is1on1: Boolean get() = !isLocalNumber && address.isContact + val isGroupRecipient: Boolean get() = address.isGroup val displayName: String get() = nickname?.takeIf { it.isNotBlank() } ?: name @@ -38,9 +43,20 @@ data class RecipientV2( return mutedUntil?.isAfter(now) == true } + val showCallMenu: Boolean + get() = !isGroupOrCommunityRecipient && approvedMe && approved; + val mutedUntilMills: Long? get() = mutedUntil?.toInstant()?.toEpochMilli() + @Deprecated("Use `avatar` property instead", ReplaceWith("avatar")) + val profileAvatar: String? + get() = (avatar as? EncryptedRemotePic)?.url + + @Deprecated("Use `avatar` property instead", ReplaceWith("avatar?.toUserPic()")) + val profileKey: ByteArray? + get() = (avatar as? EncryptedRemotePic)?.key?.data + companion object { fun empty(address: Address): RecipientV2 { return RecipientV2( @@ -56,6 +72,7 @@ data class RecipientV2( notifyType = RecipientDatabase.NOTIFY_TYPE_ALL, avatar = null, expiryMode = ExpiryMode.NONE, + acceptsCommunityMessageRequests = false, ) } } @@ -93,4 +110,11 @@ sealed interface RecipientAvatar { } } } +} + +fun RecipientAvatar.toUserPic(): UserPic? { + return when (this) { + is EncryptedRemotePic -> UserPic(url, key) + is Inline -> null + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt index 2a1b11417b..d9314014e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt @@ -47,6 +47,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration.Companion.configure import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager +import org.session.libsession.messaging.messages.ProfileUpdateHandler import org.session.libsession.messaging.notifications.TokenFetcher import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPollerManager @@ -57,7 +58,6 @@ import org.session.libsession.utilities.Device import org.session.libsession.utilities.Environment import org.session.libsession.utilities.ProfilePictureUtilities.resubmitProfilePictureIfNeeded import org.session.libsession.utilities.SSKEnvironment.Companion.configure -import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.pushSuffix import org.session.libsession.utilities.Toaster @@ -151,7 +151,6 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, @Inject lateinit var pushRegistrationHandler: Lazy @Inject lateinit var tokenFetcher: Lazy @Inject lateinit var groupManagerV2: Lazy - @Inject lateinit var profileManager: Lazy @Inject lateinit var callMessageProcessor: Lazy private var messagingModuleConfiguration: MessagingModuleConfiguration? = null @@ -206,6 +205,9 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, @Inject lateinit var openGroupPollerManager: Lazy + @Inject + lateinit var profileUpdateHandler: Lazy + @Volatile var isAppVisible: Boolean = false @@ -302,8 +304,11 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, val useTestNet = textSecurePreferences.get().getEnvironment() == Environment.TEST_NET configure(apiDB.get(), broadcaster!!, useTestNet) configure( - typingStatusRepository.get(), readReceiptManager.get(), profileManager.get(), - messageNotifier, expiringMessageManager.get() + typingIndicators = typingStatusRepository.get(), + readReceiptManager = readReceiptManager.get(), + notificationManager = messageNotifier, + messageExpirationManager = expiringMessageManager.get(), + profileUpdateHandler = profileUpdateHandler.get(), ) initializeWebRtc() initializeBlobProvider() @@ -355,7 +360,6 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, pushRegistrationHandler.get() tokenFetcher.get() groupManagerV2.get() - profileManager.get() callMessageProcessor.get() configUploader.get() adminStateSync.get() diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt index 3a812cbc6d..92d0ae537c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt @@ -1,11 +1,11 @@ package org.thoughtcrime.securesms -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.Address import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.Slide data class MediaPreviewArgs( val slide: Slide, val mmsRecord: MmsMessageRecord, - val thread: Recipient, + val thread: Address, ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index 4a8848522a..2321a3f39e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -23,9 +23,12 @@ import org.session.libsession.utilities.AppTextSecurePreferences import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientAvatar +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.truncateIdForDisplay import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.GroupDatabase +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.util.AvatarUtils import org.thoughtcrime.securesms.util.avatarOptions import javax.inject.Inject @@ -45,7 +48,7 @@ class ProfilePictureView @JvmOverloads constructor( var displayName: String? = null var additionalPublicKey: String? = null var additionalDisplayName: String? = null - var recipient: Recipient? = null + var recipient: RecipientV2? = null @Inject lateinit var groupDatabase: GroupDatabase @@ -59,14 +62,17 @@ class ProfilePictureView @JvmOverloads constructor( @Inject lateinit var avatarUtils: AvatarUtils - private val profilePicturesCache = mutableMapOf() + @Inject + lateinit var recipientRepository: RecipientRepository + + private val profilePicturesCache = mutableMapOf() private val resourcePadding by lazy { context.resources.getDimensionPixelSize(R.dimen.normal_padding).toFloat() } private val unknownOpenGroupDrawable by lazy { ResourceContactPhoto(R.drawable.ic_notification) .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false, resourcePadding) } - constructor(context: Context, sender: Recipient): this(context) { + constructor(context: Context, sender: RecipientV2): this(context) { update(sender) } @@ -77,14 +83,14 @@ class ProfilePictureView @JvmOverloads constructor( .asDrawable(context, color, false, resourcePadding) } - fun update(recipient: Recipient) { + fun update(recipient: RecipientV2) { this.recipient = recipient recipient.run { update( address = address, profileViewDataType = when { isGroupV2Recipient -> ProfileViewDataType.GroupvV2( - customGroupImage = profileAvatar + customGroupImage = (avatar as? RecipientAvatar.EncryptedRemotePic)?.url ) isLegacyGroupRecipient -> ProfileViewDataType.LegacyGroup isCommunityRecipient -> ProfileViewDataType.Community @@ -187,13 +193,14 @@ class ProfilePictureView @JvmOverloads constructor( this.recipient!! } else { - this.recipient = Recipient.from(context, Address.fromSerialized(publicKey), false) + val address = Address.fromSerialized(publicKey) + this.recipient = recipientRepository.getRecipientSync(address) ?: RecipientV2.empty(address) this.recipient!! } if (profilePicturesCache[imageView] == recipient) return // recipient is mutable so without cloning it the line above always returns true as the changes to the underlying recipient happens on both shared instances - profilePicturesCache[imageView] = recipient.clone() + profilePicturesCache[imageView] = recipient val signalProfilePicture = recipient.contactPhoto val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject diff --git a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt index 9bb2c6481c..d3c37fa0ef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt @@ -17,11 +17,9 @@ import network.loki.messenger.libsession_util.util.Conversation import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.UserPic -import network.loki.messenger.libsession_util.util.afterSend import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.jobs.BackgroundGroupAddJob import org.session.libsession.messaging.jobs.JobQueue -import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1 import org.session.libsession.snode.OwnedSwarmAuth @@ -30,7 +28,6 @@ import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol.Companion.NAME_PADDED_LENGTH import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.UserConfigType import org.session.libsession.utilities.getGroup @@ -45,7 +42,6 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.groups.ClosedGroupManager import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.repository.ConversationRepository -import org.thoughtcrime.securesms.sskenvironment.ProfileManager import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -62,7 +58,6 @@ class ConfigToDatabaseSync @Inject constructor( private val storage: StorageProtocol, private val threadDatabase: ThreadDatabase, private val clock: SnodeClock, - private val profileManager: ProfileManager, private val preferences: TextSecurePreferences, private val conversationRepository: ConversationRepository, private val mmsSmsDatabase: MmsSmsDatabase, @@ -99,7 +94,7 @@ class ConfigToDatabaseSync @Inject constructor( } when (configUpdate) { - is UpdateUserInfo -> updateUser(configUpdate, updateTimestamp) + is UpdateUserInfo -> updateUser(configUpdate) is UpdateUserGroupsInfo -> updateUserGroups(configUpdate, updateTimestamp) is UpdateContacts -> updateContacts(configUpdate, updateTimestamp) is UpdateConvoVolatile -> updateConvoVolatile(configUpdate) @@ -121,26 +116,10 @@ class ConfigToDatabaseSync @Inject constructor( ) } - private fun updateUser(userProfile: UpdateUserInfo, messageTimestamp: Long?) { + private fun updateUser(userProfile: UpdateUserInfo) { val userPublicKey = storage.getUserPublicKey() ?: return - // would love to get rid of recipient and context from this val address = fromSerialized(userPublicKey) - // Update profile name - userProfile.name?.takeUnless { it.isEmpty() }?.truncate(NAME_PADDED_LENGTH)?.let { - preferences.setProfileName(it) - profileManager.setName(context, address, it) - } - - // Update profile picture - if (userProfile.userPic == UserPic.DEFAULT) { - storage.clearUserPic(clearConfig = false) - } else if (userProfile.userPic.key.data.isNotEmpty() && userProfile.userPic.url.isNotEmpty() - && preferences.getProfilePictureURL() != userProfile.userPic.url - ) { - storage.setUserProfilePicture(userProfile.userPic.url, userProfile.userPic.key.data) - } - if (userProfile.ntsPriority == PRIORITY_HIDDEN) { // hide nts thread if needed preferences.setHasHiddenNoteToSelf(true) @@ -153,16 +132,6 @@ class ConfigToDatabaseSync @Inject constructor( storage.setPinned(ourThread, userProfile.ntsPriority > 0) preferences.setHasHiddenNoteToSelf(false) } - - // Set or reset the shared library to use latest expiration config - if (messageTimestamp != null) { - storage.getThreadId(address)?.let { threadId -> - storage.setExpirationConfiguration( - storage.getExpirationConfiguration(threadId)?.takeIf { it.updatedTimestampMs > messageTimestamp } ?: - ExpirationConfiguration(threadId, userProfile.ntsExpiry, messageTimestamp), - ) - } - } } private data class UpdateGroupInfo( @@ -186,12 +155,6 @@ class ConfigToDatabaseSync @Inject constructor( private fun updateGroup(groupInfoConfig: UpdateGroupInfo) { val address = fromSerialized(groupInfoConfig.id.hexString) val threadId = storage.getThreadId(address) ?: return - profileManager.setName(context, address, groupInfoConfig.name.orEmpty()) - profileManager.setProfilePicture( - context, address, - profilePictureURL = groupInfoConfig.profilePic?.url, - profileKey = groupInfoConfig.profilePic?.key?.data - ) // Also update the name in the user groups config configFactory.withMutableUserConfigs { configs -> @@ -326,7 +289,6 @@ class ConfigToDatabaseSync @Inject constructor( val address = fromSerialized(closedGroup.groupAccountId) storage.setRecipientApprovedMe(address, true) storage.setRecipientApproved(address, !closedGroup.invited) - profileManager.setName(context, address, closedGroup.name) val threadId = storage.getOrCreateThreadIdFor(address) // If we don't already have a date and the config has a date, use it @@ -387,15 +349,6 @@ class ConfigToDatabaseSync @Inject constructor( // which in turn allows us to show the `groupNoMessages` control message text. //insertOutgoingInfoMessage(context, groupId, SignalServiceGroup.Type.CREATION, title, members.map { it.serialize() }, admins.map { it.serialize() }, threadID, formationTimestamp) } - - if (messageTimestamp != null) { - storage.getThreadId(fromSerialized(groupId))?.let { theadId -> - storage.setExpirationConfiguration( - storage.getExpirationConfiguration(theadId)?.takeIf { it.updatedTimestampMs > messageTimestamp } - ?: ExpirationConfiguration(theadId, afterSend(group.disappearingTimer), messageTimestamp), - ) - } - } } } @@ -425,11 +378,3 @@ class ConfigToDatabaseSync @Inject constructor( } } } - -/** - * Truncate a string to a specified number of bytes - * - * This could split multi-byte characters/emojis. - */ -private fun String.truncate(sizeInBytes: Int): String = - toByteArray().takeIf { it.size > sizeInBytes }?.take(sizeInBytes)?.toByteArray()?.let(::String) ?: this diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.kt index 229e7b6cba..44b2c3dfb2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.kt @@ -4,11 +4,11 @@ import android.content.Context import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.RequestManager -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 class ContactSelectionListAdapter(private val context: Context, private val multiSelect: Boolean) : RecyclerView.Adapter() { lateinit var glide: RequestManager - val selectedContacts = mutableSetOf() + val selectedContacts = mutableSetOf() var items = listOf() set(value) { field = value; notifyDataSetChanged() } var contactClickListener: ContactClickListener? = null @@ -53,7 +53,7 @@ class ContactSelectionListAdapter(private val context: Context, private val mult } } - fun onContactClick(recipient: Recipient) { + fun onContactClick(recipient: RecipientV2) { if (selectedContacts.contains(recipient)) { selectedContacts.remove(recipient) contactClickListener?.onContactDeselected(recipient) @@ -71,7 +71,7 @@ class ContactSelectionListAdapter(private val context: Context, private val mult } interface ContactClickListener { - fun onContactClick(contact: Recipient) - fun onContactSelected(contact: Recipient) - fun onContactDeselected(contact: Recipient) + fun onContactClick(contact: RecipientV2) + fun onContactSelected(contact: RecipientV2) + fun onContactDeselected(contact: RecipientV2) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt index 982547cdcd..4df9ca4c28 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt @@ -15,7 +15,7 @@ import com.bumptech.glide.Glide import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.databinding.ShareContactListFragmentBinding import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.Log import javax.inject.Inject @@ -118,15 +118,15 @@ class ShareContactListFragment : Fragment(), LoaderManager.LoaderCallbacks { + it.create(threadId) + } + }) + private var actionMode: ActionMode? = null private var unreadCount = Int.MAX_VALUE // Attachments @@ -377,7 +383,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, }, onItemLongPress = { message, position, view -> // long pressing message for blocked users should show unblock dialog - if(viewModel.recipient?.isBlocked == true) unblock() + if(viewModel.recipient?.blocked == true) unblock() else { if (!viewModel.isMessageRequestThread) { showConversationReaction(message, view) @@ -599,7 +605,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, withContext(Dispatchers.Main) { setUpRecyclerView() setUpTypingObserver() - setUpRecipientObserver() setUpSearchResultObserver() scrollToFirstUnreadMessageIfNeeded() setUpOutdatedClientBanner() @@ -923,7 +928,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, AttachmentManager.MediaType.VIDEO == mediaType) ) { val media = Media(mediaURI, filename, mimeType, 0, 0, 0, 0, null, null) - startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), viewModel.recipient!!, ""), PICK_FROM_LIBRARY) + startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), viewModel.recipient!!.address, ""), PICK_FROM_LIBRARY) return } else { prepMediaForSending(mediaURI, mediaType).addListener(object : ListenableFuture.Listener { @@ -965,9 +970,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } } - private fun setUpRecipientObserver() = viewModel.recipient?.addListener(this) - private fun tearDownRecipientObserver() = viewModel.recipient?.removeListener(this) - // called from onCreate private fun setUpExpiredGroupBanner() { lifecycleScope.launch { @@ -1153,7 +1155,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, if(::binding.isInitialized) { viewModel.saveDraft(binding.inputBar.text.trim()) cancelVoiceMessage() - tearDownRecipientObserver() } // Delete any files we might have locally cached when sharing (which we need to do @@ -1391,7 +1392,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } // If we're trying to message someone who has blocked community message requests - blindedRecipient?.blocksCommunityMessageRequests == true -> { + blindedRecipient?.acceptsCommunityMessageRequests == false -> { Phrase.from(applicationContext, R.string.messageRequestsTurnedOff) .put(NAME_KEY, recipient.name) .format() @@ -1439,7 +1440,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, if(viewModel.recipient == null) return // if the user is blocked, show unblock modal - if(viewModel.recipient?.isBlocked == true){ + if(viewModel.recipient?.blocked == true){ unblock() return } @@ -1956,7 +1957,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, override fun sendMessage() { val recipient = viewModel.recipient ?: return - if (recipient.isContactRecipient && recipient.isBlocked) { + if (recipient.isContactRecipient && recipient.blocked) { BlockedDialog(recipient.address, viewModel.getUsername(recipient.address.toString())).show(supportFragmentManager, "Blocked Dialog") return } @@ -1978,7 +1979,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, val mimeType = MediaUtil.getMimeType(this, contentUri)!! val filename = FilenameUtils.getFilenameFromUri(this, contentUri, mimeType) val media = Media(contentUri, filename, mimeType, 0, 0, 0, 0, null, null) - startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), recipient, getMessageBody()), PICK_FROM_LIBRARY) + startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), recipient.address, getMessageBody()), PICK_FROM_LIBRARY) } // If we previously approve this recipient, either implicitly or explicitly, we need to wait for @@ -2012,8 +2013,9 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, val message = VisibleMessage().applyExpiryMode(viewModel.threadId) message.sentTimestamp = sentTimestamp message.text = text - val expiresInMillis = viewModel.expirationConfiguration?.expiryMode?.expiryMillis ?: 0 - val expireStartedAt = if (viewModel.expirationConfiguration?.expiryMode is ExpiryMode.AfterSend) { + val expiryMode = viewModel.recipient?.expiryMode + val expiresInMillis = expiryMode?.expiryMillis ?: 0 + val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) { message.sentTimestamp } else 0 val outgoingTextMessage = OutgoingTextMessage.from(message, recipient.address, expiresInMillis, expireStartedAt!!) @@ -2072,8 +2074,8 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, else it.individualRecipient.address quote?.copy(author = sender) } - val expiresInMs = viewModel.expirationConfiguration?.expiryMode?.expiryMillis ?: 0 - val expireStartedAtMs = if (viewModel.expirationConfiguration?.expiryMode is ExpiryMode.AfterSend) { + val expiresInMs = viewModel.recipient?.expiryMode?.expiryMillis ?: 0 + val expireStartedAtMs = if (viewModel.recipient?.expiryMode is ExpiryMode.AfterSend) { sentTimestamp } else 0 val outgoingTextMessage = OutgoingMediaMessage.from(message, recipient.address, attachments, localQuote, linkPreview, expiresInMs, expireStartedAtMs) @@ -2141,11 +2143,11 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, private fun pickFromLibrary() { val recipient = viewModel.recipient ?: return binding.inputBar.text?.trim()?.let { text -> - AttachmentManager.selectGallery(this, PICK_FROM_LIBRARY, recipient, text) + AttachmentManager.selectGallery(this, PICK_FROM_LIBRARY, recipient.address, text) } } - private fun showCamera() { attachmentManager.capturePhoto(this, TAKE_PHOTO, viewModel.recipient) } + private fun showCamera() { attachmentManager.capturePhoto(this, TAKE_PHOTO, viewModel.recipient?.address) } override fun onAttachmentChanged() { /* Do nothing */ } @@ -2367,7 +2369,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, showSessionDialog { title(R.string.banUser) text(R.string.communityBanDescription) - dangerButton(R.string.theContinue) { viewModel.banUser(messages.first().individualRecipient); endActionMode() } + dangerButton(R.string.theContinue) { viewModel.banUser(messages.first().individualRecipient.address); endActionMode() } cancelButton(::endActionMode) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 4c4b6311fa..7c880a065a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.launch import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView @@ -35,7 +36,7 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent class ConversationAdapter( context: Context, cursor: Cursor, - conversation: Recipient?, + conversation: RecipientV2?, originalLastSeen: Long, private val isReversed: Boolean, private val onItemPress: (MessageRecord, Int, VisibleMessageView, MotionEvent) -> Unit, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index d76a5e5561..3bdc4cfb79 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -5,13 +5,13 @@ import android.content.Context import android.widget.Toast import androidx.annotation.StringRes import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.bumptech.glide.Glide import com.squareup.phrase.Phrase import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job @@ -37,10 +37,10 @@ import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager -import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment +import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.ExpirationUtil import org.session.libsession.utilities.StringSubstitutionConstants.DATE_KEY @@ -49,7 +49,6 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.recipients.MessageType -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.recipients.getType import org.session.libsignal.utilities.AccountId @@ -62,6 +61,7 @@ import org.thoughtcrime.securesms.database.LokiAPIDatabase import org.thoughtcrime.securesms.database.LokiMessageDatabase import org.thoughtcrime.securesms.database.ReactionDatabase import org.thoughtcrime.securesms.database.RecipientDatabase +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.GroupThreadStatus import org.thoughtcrime.securesms.database.model.MessageId @@ -85,10 +85,9 @@ import org.thoughtcrime.securesms.webrtc.data.State import java.time.ZoneId import java.util.UUID - -class ConversationViewModel( - val threadId: Long, - val edKeyPair: KeyPair?, +@HiltViewModel(assistedFactory = ConversationViewModel.Factory::class) +class ConversationViewModel @AssistedInject constructor( + @Assisted val threadId: Long, private val application: Application, private val repository: ConversationRepository, private val storage: StorageProtocol, @@ -109,12 +108,17 @@ class ConversationViewModel( private val avatarUtils: AvatarUtils, private val recipientChangeSource: RecipientChangeSource, private val openGroupManager: OpenGroupManager, + private val recipientRepository: RecipientRepository, ) : ViewModel() { + private val edKeyPair by lazy { + storage.getUserED25519KeyPair() + } + val showSendAfterApprovalText: Boolean get() = recipient?.run { // if the contact is a 1on1 or a blinded 1on1 that doesn't block requests - and is not the current user - and has not yet approved us - (getBlindedRecipient(recipient)?.blocksCommunityMessageRequests == false || isContactRecipient) && !isLocalNumber && !hasApprovedMe() + (getBlindedRecipient(recipient)?.acceptsCommunityMessageRequests == true || isContactRecipient) && !isLocalNumber && !approvedMe } ?: false private val _uiState = MutableStateFlow(ConversationUiState()) @@ -141,8 +145,8 @@ class ConversationViewModel( )) val appBarData: StateFlow = _appBarData - private var _recipient: RetrieveOnce = RetrieveOnce { - val conversation = repository.maybeGetRecipientForThreadId(threadId) + private var _recipient: RetrieveOnce = RetrieveOnce { + val conversation = repository.maybeGetRecipientForThreadId(threadId)?.let(recipientRepository::getRecipientSync) // set admin from current conversation val conversationType = conversation?.getType() @@ -176,23 +180,22 @@ class ConversationViewModel( conversation } - val expirationConfiguration: ExpirationConfiguration? - get() = storage.getExpirationConfiguration(threadId) - - val recipient: Recipient? + val recipient: RecipientV2? get() = _recipient.value - val blindedRecipient: Recipient? + val blindedRecipient: RecipientV2? get() = _recipient.value?.let { recipient -> getBlindedRecipient(recipient) } private var currentAppBarNotificationState: String? = null - private fun getBlindedRecipient(recipient: Recipient?): Recipient? = + private fun getBlindedRecipient(recipient: RecipientV2?): RecipientV2? = when { recipient?.isCommunityOutboxRecipient == true -> recipient - recipient?.isCommunityInboxRecipient == true -> repository.maybeGetBlindedRecipient(recipient) + recipient?.isCommunityInboxRecipient == true -> + repository.maybeGetBlindedRecipient(recipient.address) + ?.let(recipientRepository::getRecipientSync) else -> null } @@ -201,12 +204,12 @@ class ConversationViewModel( * * null if this convo is not a group(v2) conversation, or error getting the info */ - val invitingAdmin: Recipient? + val invitingAdmin: RecipientV2? get() { val recipient = recipient ?: return null if (!recipient.isGroupV2Recipient) return null - return repository.getInvitingAdmin(threadId) + return repository.getInvitingAdmin(threadId)?.let(recipientRepository::getRecipientSync) } val groupV2ThreadState: GroupThreadStatus @@ -236,7 +239,7 @@ class ConversationViewModel( val blindedPublicKey: String? get() = if (openGroup == null || edKeyPair == null || !serverCapabilities.contains(OpenGroupApi.Capability.BLIND.name.lowercase())) null else { BlindKeyAPI.blind15KeyPairOrNull( - ed25519SecretKey = edKeyPair.secretKey.data, + ed25519SecretKey = edKeyPair!!.secretKey.data, serverPubKey = Hex.fromStringCondensed(openGroup!!.publicKey), )?.pubKey?.data ?.let { AccountId(IdPrefix.BLINDED, it) }?.hexString @@ -245,7 +248,7 @@ class ConversationViewModel( val isMessageRequestThread : Boolean get() { val recipient = recipient ?: return false - return !recipient.isLocalNumber && !recipient.isLegacyGroupRecipient && !recipient.isCommunityRecipient && !recipient.isApproved + return !recipient.isLocalNumber && !recipient.isLegacyGroupRecipient && !recipient.isCommunityRecipient && !recipient.approved } /** @@ -254,7 +257,7 @@ class ConversationViewModel( val isOutgoingMessageRequest: Boolean get() { val recipient = recipient ?: return false - return (recipient.is1on1 || recipient.isCommunityInboxRecipient) && !recipient.hasApprovedMe() + return (recipient.is1on1 || recipient.isCommunityInboxRecipient) && !recipient.approvedMe } val showOptionsMenu: Boolean @@ -311,11 +314,12 @@ class ConversationViewModel( storage = storage, messageDataProvider = messageDataProvider, scope = viewModelScope, + recipientRepository = recipientRepository, ) val callBanner: StateFlow = callManager.currentConnectionStateFlow.map { // a call is in progress if it isn't idle nor disconnected and the recipient is the person on the call - if(it !is State.Idle && it !is State.Disconnected && callManager.recipient?.address == recipient?.address){ + if(it !is State.Idle && it !is State.Disconnected && callManager.recipient == recipient?.address){ // call is started, we need to differentiate between in progress vs incoming if(it is State.Connected) application.getString(R.string.callsInProgress) else application.getString(R.string.callsIncomingUnknown) @@ -328,7 +332,7 @@ class ConversationViewModel( init { viewModelScope.launch(Dispatchers.Default) { combine( - repository.recipientUpdateFlow(threadId), + recipientRepository.observeRecipient(repository.maybeGetRecipientForThreadId(threadId)!!), _openGroup, legacyGroupDeprecationManager.deprecationState, ::Triple @@ -375,7 +379,7 @@ class ConversationViewModel( } private fun getInputBarState( - recipient: Recipient?, + recipient: RecipientV2?, community: OpenGroup?, deprecationState: LegacyGroupDeprecationManager.DeprecationState ): InputBarState { @@ -388,7 +392,7 @@ class ConversationViewModel( // next are cases where the input is visible but disabled // when the recipient is blocked - recipient?.isBlocked == true -> InputBarState( + recipient?.blocked == true -> InputBarState( contentState = InputBarContentState.Disabled( text = application.getString(R.string.blockBlockedDescription), onClick = { @@ -414,20 +418,20 @@ class ConversationViewModel( } } - private fun updateAppBarData(conversation: Recipient?) { + private fun updateAppBarData(conversation: RecipientV2?) { viewModelScope.launch { // sort out the pager data, if any val pagerData: MutableList = mutableListOf() if (conversation != null) { // Specify the disappearing messages subtitle if we should - val config = expirationConfiguration - if (config?.isEnabled == true) { + val expiryMode = conversation.expiryMode + if (expiryMode.expiryMillis > 0) { // Get the type of disappearing message and the abbreviated duration.. - val dmTypeString = when (config.expiryMode) { + val dmTypeString = when (expiryMode) { is ExpiryMode.AfterRead -> R.string.disappearingMessagesDisappearAfterReadState else -> R.string.disappearingMessagesDisappearAfterSendState } - val durationAbbreviated = ExpirationUtil.getExpirationAbbreviatedDisplayValue(config.expiryMode.expirySeconds) + val durationAbbreviated = ExpirationUtil.getExpirationAbbreviatedDisplayValue(expiryMode.expirySeconds) // ..then substitute into the string.. val subtitleTxt = application.getSubbedString(dmTypeString, @@ -446,7 +450,7 @@ class ConversationViewModel( } currentAppBarNotificationState = null - if (conversation.isMuted || conversation.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS) { + if (conversation.isMuted() || conversation.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS) { currentAppBarNotificationState = getNotificationStatusTitle(conversation) pagerData += ConversationAppBarPagerData( title = currentAppBarNotificationState!!, @@ -456,7 +460,7 @@ class ConversationViewModel( ) } - if (conversation.isGroupOrCommunityRecipient && conversation.isApproved) { + if (conversation.isGroupOrCommunityRecipient && conversation.approved) { val title = if (conversation.isCommunityRecipient) { val userCount = openGroup?.let { lokiAPIDb.getUserCount(it.room, it.server) } ?: 0 application.resources.getQuantityString(R.plurals.membersActive, userCount, userCount) @@ -482,7 +486,7 @@ class ConversationViewModel( _appBarData.value = ConversationAppBarData( title = conversation.takeUnless { it?.isLocalNumber == true }?.name ?: application.getString(R.string.noteToSelf), pagerData = pagerData, - showCall = conversation?.showCallMenu() ?: false, + showCall = conversation?.showCallMenu ?: false, showAvatar = showOptionsMenu, showSearch = _appBarData.value.showSearch, avatarUIData = avatarData @@ -497,8 +501,8 @@ class ConversationViewModel( } } - private fun getNotificationStatusTitle(conversation: Recipient): String{ - return if(conversation.isMuted) application.getString(R.string.notificationsHeaderMute) + private fun getNotificationStatusTitle(conversation: RecipientV2): String{ + return if(conversation.isMuted()) application.getString(R.string.notificationsHeaderMute) else application.getString(R.string.notificationsHeaderMentionsOnly) } @@ -509,7 +513,7 @@ class ConversationViewModel( * 1. First time we send message to a person. * Since we haven't been approved by them, we can't send them any media, only text */ - private fun shouldEnableInputMediaControls(recipient: Recipient?): Boolean { + private fun shouldEnableInputMediaControls(recipient: RecipientV2?): Boolean { // Specifically disallow multimedia if we don't have a recipient to send anything to if (recipient == null) { @@ -518,14 +522,14 @@ class ConversationViewModel( } // disable for blocked users - if (recipient.isBlocked) return false + if (recipient.blocked) return false // Specifically allow multimedia in our note-to-self if (recipient.isLocalNumber) return true // To send multimedia content to other people: // - For 1-on-1 conversations they must have approved us as a contact. - val allowedFor1on1 = recipient.is1on1 && recipient.hasApprovedMe() + val allowedFor1on1 = recipient.is1on1 && recipient.approvedMe // - For groups you just have to be a member of the group. Note: `isGroupRecipient` convers both legacy and V2 groups. val allowedForGroup = recipient.isGroupRecipient @@ -552,7 +556,7 @@ class ConversationViewModel( * 3. The legacy group is deprecated, OR * 4. Blinded recipient who have disabled message request from community members */ - private fun shouldShowInput(recipient: Recipient?, + private fun shouldShowInput(recipient: RecipientV2?, community: OpenGroup?, deprecationState: LegacyGroupDeprecationManager.DeprecationState ): Boolean { @@ -562,12 +566,12 @@ class ConversationViewModel( groupDb.getGroup(recipient.address.toGroupString()).orNull()?.isActive == true && deprecationState != LegacyGroupDeprecationManager.DeprecationState.DEPRECATED } - getBlindedRecipient(recipient)?.blocksCommunityMessageRequests == true -> false + getBlindedRecipient(recipient)?.acceptsCommunityMessageRequests == false -> false else -> true } } - private fun buildMessageRequestState(recipient: Recipient?): MessageRequestUiState { + private fun buildMessageRequestState(recipient: RecipientV2?): MessageRequestUiState { // The basic requirement of showing a message request is: // 1. The other party has not been approved by us, AND // 2. We haven't sent a message to them before (if we do, we would be the one requesting permission), AND @@ -578,7 +582,7 @@ class ConversationViewModel( recipient != null && // Req 1: we haven't approved the other party - (!recipient.isApproved && !recipient.isLocalNumber) && + (!recipient.approved && !recipient.isLocalNumber) && // Req 4: the type of conversation supports message request (recipient.is1on1 || recipient.isGroupV2Recipient) && @@ -807,7 +811,7 @@ class ConversationViewModel( // delete remotely try { - repository.deleteNoteToSelfMessagesRemotely(threadId, recipient!!, data.messages) + repository.deleteNoteToSelfMessagesRemotely(threadId, recipient!!.address, data.messages) // When this is done we simply need to remove the message locally (leave nothing behind) repository.deleteMessages(messages = data.messages, threadId = threadId) @@ -855,7 +859,7 @@ class ConversationViewModel( // delete remotely try { - repository.delete1on1MessagesRemotely(threadId, recipient!!, data.messages) + repository.delete1on1MessagesRemotely(threadId, recipient!!.address, data.messages) // When this is done we simply need to remove the message locally repository.markAsDeletedLocally( @@ -903,7 +907,7 @@ class ConversationViewModel( viewModelScope.launch(Dispatchers.IO) { // delete remotely try { - repository.deleteLegacyGroupMessagesRemotely(recipient!!, messages) + repository.deleteLegacyGroupMessagesRemotely(recipient!!.address, messages) // When this is done we simply need to remove the message locally repository.markAsDeletedLocally( @@ -946,7 +950,7 @@ class ConversationViewModel( _uiState.update { it.copy(showLoader = true) } try { - repository.deleteGroupV2MessagesRemotely(recipient!!, data.messages) + repository.deleteGroupV2MessagesRemotely(recipient!!.address, data.messages) // the repo will handle the internal logic (calling `/delete` on the swarm // and sending 'GroupUpdateDeleteMemberContentMessage' @@ -1047,7 +1051,7 @@ class ConversationViewModel( AudioSlidePlayer.getInstance()?.takeIf { it.audioSlide == audioSlide }?.stop() } - fun banUser(recipient: Recipient) = viewModelScope.launch { + fun banUser(recipient: Address) = viewModelScope.launch { repository.banUser(threadId, recipient) .onSuccess { showMessage(application.getString(R.string.banUserBanned)) @@ -1059,7 +1063,7 @@ class ConversationViewModel( fun banAndDeleteAll(messageRecord: MessageRecord) = viewModelScope.launch { - repository.banAndDeleteAll(threadId, messageRecord.individualRecipient) + repository.banAndDeleteAll(threadId, messageRecord.individualRecipient.address) .onSuccess { // At this point the server side messages have been successfully deleted.. showMessage(application.getString(R.string.banUserBanned)) @@ -1097,7 +1101,7 @@ class ConversationViewModel( } fun declineMessageRequest() = viewModelScope.launch { - repository.declineMessageRequest(threadId, recipient!!) + repository.declineMessageRequest(threadId, recipient!!.address) .onSuccess { _uiState.update { it.copy(shouldExit = true) } } @@ -1124,12 +1128,12 @@ class ConversationViewModel( } fun updateRecipient() { - _recipient.updateTo(repository.maybeGetRecipientForThreadId(threadId)) + _recipient.updateTo(repository.maybeGetRecipientForThreadId(threadId)?.let(recipientRepository::getRecipientSync)) updateAppBarData(recipient) } fun legacyBannerRecipient(context: Context): RecipientV2? = recipient?.run { - storage.getLastLegacyRecipient(address.toString())?.let { storage.getRecipientSync(fromSerialized(it)) } + storage.getLastLegacyRecipient(address.toString())?.let { recipientRepository.getRecipientSync(fromSerialized(it)) } } fun downloadPendingAttachment(attachment: DatabaseAttachment) { @@ -1154,7 +1158,7 @@ class ConversationViewModel( if (uiState.value.messageRequestState is MessageRequestUiState.Visible) { return acceptMessageRequest() - } else if (recipient?.isApproved == false) { + } else if (recipient?.approved == false) { // edge case for new outgoing thread on new recipient without sending approval messages repository.setApproved(recipient.address, true) } @@ -1299,65 +1303,9 @@ class ConversationViewModel( } } - @dagger.assisted.AssistedFactory - interface AssistedFactory { - fun create(threadId: Long, edKeyPair: KeyPair?): Factory - } - - @Suppress("UNCHECKED_CAST") - class Factory @AssistedInject constructor( - @Assisted private val threadId: Long, - @Assisted private val edKeyPair: KeyPair?, - private val application: Application, - private val repository: ConversationRepository, - private val storage: StorageProtocol, - private val messageDataProvider: MessageDataProvider, - private val groupDb: GroupDatabase, - private val threadDb: ThreadDatabase, - private val reactionDb: ReactionDatabase, - @ApplicationContext - private val context: Context, - private val lokiMessageDb: LokiMessageDatabase, - private val lokiAPIDb: LokiAPIDatabase, - private val textSecurePreferences: TextSecurePreferences, - private val configFactory: ConfigFactory, - private val groupManagerV2: GroupManagerV2, - private val callManager: CallManager, - private val legacyGroupDeprecationManager: LegacyGroupDeprecationManager, - private val dateUtils: DateUtils, - private val expiredGroupManager: ExpiredGroupManager, - private val usernameUtils: UsernameUtils, - private val avatarUtils: AvatarUtils, - private val recipientChangeSource: RecipientChangeSource, - private val openGroupManager: OpenGroupManager, - ) : ViewModelProvider.Factory { - - override fun create(modelClass: Class): T { - return ConversationViewModel( - threadId = threadId, - edKeyPair = edKeyPair, - application = application, - repository = repository, - storage = storage, - messageDataProvider = messageDataProvider, - groupDb = groupDb, - threadDb = threadDb, - reactionDb = reactionDb, - lokiMessageDb = lokiMessageDb, - lokiAPIDb = lokiAPIDb, - textSecurePreferences = textSecurePreferences, - configFactory = configFactory, - groupManagerV2 = groupManagerV2, - callManager = callManager, - legacyGroupDeprecationManager = legacyGroupDeprecationManager, - dateUtils = dateUtils, - expiredGroupManager = expiredGroupManager, - usernameUtils = usernameUtils, - avatarUtils = avatarUtils, - recipientChangeSource = recipientChangeSource, - openGroupManager = openGroupManager, - ) as T - } + @AssistedFactory + interface Factory { + fun create(threadId: Long): ConversationViewModel } data class DialogsState( diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index 5579794460..301d5592c2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -26,7 +26,6 @@ import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.Address import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.MediaPreviewArgs @@ -64,7 +63,7 @@ class MessageDetailsViewModel @AssistedInject constructor( private val context: ApplicationContext, private val avatarUtils: AvatarUtils, messageDataProvider: MessageDataProvider, - private val storage: Storage, + storage: Storage, private val recipientRepository: RecipientRepository, ) : ViewModel() { private val state = MutableStateFlow(MessageDetailsState()) @@ -77,6 +76,7 @@ class MessageDetailsViewModel @AssistedInject constructor( storage = storage, messageDataProvider = messageDataProvider, scope = viewModelScope, + recipientRepository = recipientRepository, ) init { @@ -115,9 +115,9 @@ class MessageDetailsViewModel @AssistedInject constructor( state.value = messageRecord.run { val slides = mmsRecord?.slideDeck?.slides ?: emptyList() - val conversation = recipientRepository.getRecipient(threadDb.getRecipientForThreadId(threadId)!!) - val isDeprecatedLegacyGroup = conversation.isLegacyGroupRecipient && - deprecationManager.isDeprecated + val conversationAddress = threadDb.getRecipientForThreadId(threadId)!! + val conversation = recipientRepository.getRecipient(conversationAddress) ?: RecipientV2.empty(conversationAddress) + val isDeprecatedLegacyGroup = conversationAddress.isLegacyGroup && deprecationManager.isDeprecated val errorString = lokiMessageDatabase.getErrorMessage(messageId) @@ -224,7 +224,7 @@ class MessageDetailsViewModel @AssistedInject constructor( if(state.thread == null) return viewModelScope.launch { - MediaPreviewArgs(slide, state.mmsRecord, state.thread) + MediaPreviewArgs(slide, state.mmsRecord, state.thread.address) .let(Event::StartMediaPreview) .let { event.send(it) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index f0ab330b73..4a8b7de0a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -19,8 +19,10 @@ import com.bumptech.glide.RequestManager import network.loki.messenger.R import network.loki.messenger.databinding.ViewInputBarBinding import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview +import org.session.libsession.utilities.Address import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.conversation.v2.InputBarContentState import org.thoughtcrime.securesms.conversation.v2.InputBarState import org.thoughtcrime.securesms.conversation.v2.components.LinkPreviewDraftView @@ -190,7 +192,7 @@ class InputBar @JvmOverloads constructor( delegate?.startRecordingVoiceMessage() } - fun draftQuote(thread: Recipient, message: MessageRecord, glide: RequestManager) { + fun draftQuote(thread: RecipientV2, message: MessageRecord, glide: RequestManager) { quoteView?.let(binding.inputBarAdditionalContentContainer::removeView) quote = message diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt index dff4b23c56..b76d43030f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt @@ -146,7 +146,7 @@ class AttachmentControlView: LinearLayout { // region Interaction fun showDownloadDialog(threadRecipient: RecipientV2, attachment: DatabaseAttachment) { - if (!threadRecipient.autoDownloadAttachments) { + if (threadRecipient.autoDownloadAttachments != true) { // just download (context.findActivity() as? ActivityDispatcher)?.showDialog(AutoDownloadDialog(threadRecipient, attachment)) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index eea33b4a07..45535937ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -101,7 +101,7 @@ class ControlMessageView : LinearLayout { followSetting.isVisible = ExpirationConfiguration.isNewConfigEnabled && !message.isOutgoing - && message.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId)?.expiryMode ?: ExpiryMode.NONE) + && message.expiryMode != MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId) && threadRecipient?.isGroupOrCommunity != true if (followSetting.isVisible) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index e9c62da16a..fb1503bfc6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -53,6 +53,7 @@ import org.thoughtcrime.securesms.database.LokiAPIDatabase import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.database.MmsDatabase import org.thoughtcrime.securesms.database.MmsSmsDatabase +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.SmsDatabase import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.MessageId @@ -88,6 +89,7 @@ class VisibleMessageView : FrameLayout { @Inject lateinit var configFactory: ConfigFactoryProtocol @Inject lateinit var usernameUtils: UsernameUtils @Inject lateinit var openGroupManager: OpenGroupManager + @Inject lateinit var recipientRepository: RecipientRepository private val binding = ViewVisibleMessageBinding.inflate(LayoutInflater.from(context), this, true) @@ -178,15 +180,15 @@ class VisibleMessageView : FrameLayout { isOutgoing = message.isOutgoing replyDisabled = message.isOpenGroupInvitation val threadID = message.threadId - val thread = threadDb.getRecipientForThreadId(threadID) ?: return - val isGroupThread = thread.isGroupOrCommunity + val thread = threadDb.getRecipientForThreadId(threadID)?.let(recipientRepository::getRecipientSync) ?: return + val isGroupThread = thread.isGroupOrCommunityRecipient val isStartOfMessageCluster = isStartOfMessageCluster(message, previous, isGroupThread) val isEndOfMessageCluster = isEndOfMessageCluster(message, next, isGroupThread) // Show profile picture and sender name if this is a group thread AND the message is incoming binding.moderatorIconImageView.isVisible = false binding.profilePictureView.visibility = when { - thread.isGroupOrCommunity && !message.isOutgoing && isEndOfMessageCluster -> View.VISIBLE - thread.isGroupOrCommunity -> View.INVISIBLE + thread.isGroupOrCommunityRecipient && !message.isOutgoing && isEndOfMessageCluster -> View.VISIBLE + thread.isGroupOrCommunityRecipient -> View.INVISIBLE else -> View.GONE } @@ -208,7 +210,7 @@ class VisibleMessageView : FrameLayout { binding.profilePictureView.publicKey = senderAccountID binding.profilePictureView.update(message.individualRecipient) binding.profilePictureView.setOnClickListener { - if (thread.isCommunity) { + if (thread.isCommunityRecipient) { val openGroup = lokiThreadDb.getOpenGroupChat(threadID) if (IdPrefix.fromValue(senderAccountID) == IdPrefix.BLINDED && openGroup?.canWrite == true) { // TODO: support v2 soon @@ -221,7 +223,7 @@ class VisibleMessageView : FrameLayout { maybeShowUserDetails(senderAccountID, threadID) } } - if (thread.isCommunity) { + if (thread.isCommunityRecipient) { val openGroup = lokiThreadDb.getOpenGroupChat(threadID) ?: return var standardPublicKey = "" var blindedPublicKey: String? = null @@ -237,14 +239,14 @@ class VisibleMessageView : FrameLayout { ) binding.moderatorIconImageView.isVisible = isModerator } - else if (thread.isLegacyGroup) { // legacy groups - val groupRecord = groupDb.getGroup(thread.toGroupString()).orNull() + else if (thread.isLegacyGroupRecipient) { // legacy groups + val groupRecord = groupDb.getGroup(thread.address.toGroupString()).orNull() val isAdmin: Boolean = groupRecord?.admins?.contains(fromSerialized(senderAccountID)) ?: false binding.moderatorIconImageView.isVisible = isAdmin } - else if (thread.isGroupV2) { // groups v2 - val isAdmin = configFactory.withGroupConfigs(AccountId(thread.toString())) { + else if (thread.isGroupV2Recipient) { // groups v2 + val isAdmin = configFactory.withGroupConfigs(AccountId(thread.address.toString())) { it.groupMembers.getOrNull(senderAccountID)?.admin == true } @@ -254,7 +256,7 @@ class VisibleMessageView : FrameLayout { } binding.senderNameTextView.isVisible = !message.isOutgoing && (isStartOfMessageCluster && (isGroupThread || snIsSelected)) val contactContext = - if (thread.isCommunity) ContactContext.OPEN_GROUP else ContactContext.REGULAR + if (thread.isCommunityRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR binding.senderNameTextView.text = usernameUtils.getContactNameWithAccountID( contact = contact, accountID = senderAccountID, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt index 1f5204de8d..0bf54cb4a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt @@ -11,7 +11,6 @@ import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity.CLIPBOARD_SERVICE import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import app.cash.copper.flow.observeQuery import com.bumptech.glide.Glide import com.squareup.phrase.Phrase import dagger.assisted.Assisted @@ -22,16 +21,9 @@ import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -47,7 +39,6 @@ import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.ConfigFactoryProtocol -import org.session.libsession.utilities.ConfigUpdateNotification import org.session.libsession.utilities.ExpirationUtil import org.session.libsession.utilities.StringSubstitutionConstants.COMMUNITY_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY @@ -55,16 +46,17 @@ import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getGroup -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.upsertContact import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.textSizeInBytes -import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.database.RecipientDatabase +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.dependencies.ConfigFactory.Companion.MAX_GROUP_DESCRIPTION_BYTES import org.thoughtcrime.securesms.dependencies.ConfigFactory.Companion.MAX_NAME_BYTES @@ -75,7 +67,6 @@ import org.thoughtcrime.securesms.ui.getSubbedString import org.thoughtcrime.securesms.util.AvatarUIData import org.thoughtcrime.securesms.util.AvatarUtils import org.thoughtcrime.securesms.util.avatarOptions -import org.thoughtcrime.securesms.util.observeChanges import kotlin.math.min @@ -97,6 +88,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( private val lokiThreadDatabase: LokiThreadDatabase, private val groupManager: GroupManagerV2, private val openGroupManager: OpenGroupManager, + private val recipientRepository: RecipientRepository, ) : ViewModel() { private val _uiState: MutableStateFlow = MutableStateFlow( @@ -109,7 +101,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( private val _dialogState: MutableStateFlow = MutableStateFlow(DialogsState()) val dialogState: StateFlow = _dialogState - private var recipient: Recipient? = null + private var recipient: RecipientV2? = null private var groupV2: GroupInfo.ClosedGroupInfo? = null @@ -120,22 +112,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( init { // update data when we have a recipient and update when there are changes from the thread or recipient viewModelScope.launch(Dispatchers.Default) { - repository.recipientUpdateFlow(threadId) // get the recipient - .flatMapLatest { recipient -> // get updates from the thread or recipient - merge( - context.contentResolver - .observeQuery(DatabaseContentProviders.Recipient.CONTENT_URI), // recipient updates - (context.contentResolver.observeChanges( - DatabaseContentProviders.Conversation.getUriForThread(threadId) - ) as Flow<*>), // thread updates - configFactory.configUpdateNotifications.filterIsInstance() - .filter { it.groupId.hexString == recipient?.address?.toString() } - ).map { - recipient // return the recipient - } - .debounce(200L) - .onStart { emit(recipient) } // make sure there's a value straight away - } + (repository.maybeGetRecipientForThreadId(threadId)?.let(recipientRepository::observeRecipient) ?: flowOf(null)) .collect { recipient = it getStateFromRecipient() @@ -263,15 +240,15 @@ class ConversationSettingsViewModel @AssistedInject constructor( } // disappearing message type - val expiration = storage.getExpirationConfiguration(threadId) - val disappearingSubtitle = if(expiration?.isEnabled == true) { + val expiryMode = recipient?.expiryMode + val disappearingSubtitle = if(expiryMode != null && expiryMode != ExpiryMode.NONE) { // Get the type of disappearing message and the abbreviated duration.. - val dmTypeString = when (expiration.expiryMode) { + val dmTypeString = when (expiryMode) { is ExpiryMode.AfterRead -> R.string.disappearingMessagesDisappearAfterReadState else -> R.string.disappearingMessagesDisappearAfterSendState } val durationAbbreviated = - ExpirationUtil.getExpirationAbbreviatedDisplayValue(expiration.expiryMode.expirySeconds) + ExpirationUtil.getExpirationAbbreviatedDisplayValue(expiryMode.expirySeconds) // ..then substitute into the string.. context.getSubbedString( @@ -330,7 +307,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( )) // these options are only for users who aren't blocked - if(!conversation.isBlocked) { + if(!conversation.blocked) { mainOptions.addAll(listOf( optionDisappearingMessage(disappearingSubtitle), if(pinned) optionUnpin else optionPin, @@ -342,7 +319,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( mainOptions.add(optionAttachments) dangerOptions.addAll(listOf( - if(recipient?.isBlocked == true) optionUnblock else optionBlock, + if(recipient?.blocked == true) optionUnblock else optionBlock, optionClearMessages, optionDeleteConversation, optionDeleteContact @@ -517,9 +494,9 @@ class ConversationSettingsViewModel @AssistedInject constructor( } } - private fun getNotificationsData(conversation: Recipient): Pair { + private fun getNotificationsData(conversation: RecipientV2): Pair { return when{ - conversation.isMuted -> R.drawable.ic_volume_off to context.getString(R.string.notificationsMuted) + conversation.isMuted() -> R.drawable.ic_volume_off to context.getString(R.string.notificationsMuted) conversation.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS -> R.drawable.ic_at_sign to context.getString(R.string.notificationsMentionsOnly) else -> R.drawable.ic_volume_2 to context.getString(R.string.notificationsAllMessages) @@ -1087,9 +1064,13 @@ class ConversationSettingsViewModel @AssistedInject constructor( viewModelScope.launch(Dispatchers.Default) { val publicKey = conversation.address.toString() - val contact = storage.getContactWithAccountID(publicKey) ?: Contact(publicKey) - contact.nickname = nickname - storage.setContact(contact) + if (AccountId.fromStringOrNull(publicKey)?.prefix == IdPrefix.STANDARD) { + configFactory.withMutableUserConfigs { configs -> + configs.contacts.upsertContact(publicKey) { + this.nickname = nickname.orEmpty() + } + } + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java index 9f54f9d9c9..b68b398c49 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java @@ -41,7 +41,10 @@ import java.util.LinkedList; import java.util.List; import network.loki.messenger.R; + +import org.session.libsession.utilities.Address; import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.ListenableFuture; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.SettableFuture; @@ -264,7 +267,7 @@ public static void selectDocument(Activity activity, int requestCode) { .execute(); } - public static void selectGallery(Activity activity, int requestCode, @NonNull Recipient recipient, @NonNull String body) { + public static void selectGallery(Activity activity, int requestCode, @NonNull Address recipient, @NonNull String body) { Context c = activity.getApplicationContext(); @@ -318,7 +321,7 @@ public static void selectGif(Activity activity, int requestCode) { return captureUri; } - public void capturePhoto(Activity activity, int requestCode, Recipient recipient) { + public void capturePhoto(Activity activity, int requestCode, Address recipient) { String cameraPermissionDeniedTxt = Phrase.from(context, R.string.permissionsCameraDenied) .put(APP_NAME_KEY, context.getString(R.string.app_name)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index 286f57a99b..c3b30f5674 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -20,6 +20,7 @@ import android.content.ContentValues import android.content.Context import android.database.Cursor import com.annimon.stream.Stream +import dagger.hilt.android.qualifiers.ApplicationContext import org.apache.commons.lang3.StringUtils import org.json.JSONArray import org.json.JSONException @@ -45,6 +46,7 @@ import org.session.libsession.utilities.NetworkFailure import org.session.libsession.utilities.NetworkFailureList import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.ThreadUtils.queue @@ -62,9 +64,16 @@ import org.thoughtcrime.securesms.util.asSequence import java.io.Closeable import java.io.IOException import java.util.LinkedList +import javax.inject.Inject import javax.inject.Provider - -class MmsDatabase(context: Context, databaseHelper: Provider) : MessagingDatabase(context, databaseHelper) { +import javax.inject.Singleton + +@Singleton +class MmsDatabase @Inject constructor( + @ApplicationContext context: Context, + databaseHelper: Provider, + private val recipientRepository: RecipientRepository, +) : MessagingDatabase(context, databaseHelper) { private val earlyDeliveryReceiptCache = EarlyReceiptCache() private val earlyReadReceiptCache = EarlyReceiptCache() override fun getTableName() = TABLE_NAME @@ -1321,13 +1330,13 @@ class MmsDatabase(context: Context, databaseHelper: Provider? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index fda623df6d..d53aac9e22 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -8,6 +8,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.annimon.stream.Stream; +import com.esotericsoftware.kryo.util.Null; + import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.recipients.Recipient; @@ -27,6 +29,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow; import kotlinx.coroutines.flow.SharedFlow; import kotlinx.coroutines.flow.SharedFlowKt; +import network.loki.messenger.libsession_util.util.UserPic; /** * The database(table) where some recipient data is stored, including names, avatars, notification settings, etc. @@ -317,6 +320,7 @@ public void deleteRecipient(@NonNull String recipientAddress) { int rowCount = db.delete(TABLE_NAME, ADDRESS + " = ?", new String[] { recipientAddress }); if (rowCount == 0) { Log.w(TAG, "Could not find to delete recipient with address: " + recipientAddress); } notifyRecipientListeners(); + updateNotifications.tryEmit(Address.fromSerialized(recipientAddress)); } public void setAutoDownloadAttachments(@NonNull Address recipient, boolean shouldAutoDownloadAttachments) { @@ -380,6 +384,31 @@ public void setProfileName(@NonNull Address recipient, @Nullable String profileN updateNotifications.tryEmit(recipient); } + public void updateProfile(@NonNull Address recipient, + @Nullable String newName, + @Nullable UserPic profilePic, + @Nullable Boolean acceptsCommunityRequests) { + if (newName == null && profilePic == null) { + return; // nothing to update + } + + ContentValues contentValues = new ContentValues(4); + if (newName != null) { + contentValues.put(SYSTEM_DISPLAY_NAME, newName); + } + if (profilePic != null) { + contentValues.put(SESSION_PROFILE_AVATAR, profilePic.getUrl()); + contentValues.put(PROFILE_KEY, Base64.encodeBytes(profilePic.getKeyAsByteArray())); + } + + if (acceptsCommunityRequests != null) { + contentValues.put(BLOCKS_COMMUNITY_MESSAGE_REQUESTS, acceptsCommunityRequests ? 0 : 1); + } + + updateOrInsert(recipient, contentValues); + updateNotifications.tryEmit(recipient); + } + public void setNotificationChannel(@NonNull Address recipient, @Nullable String notificationChannel) { ContentValues contentValues = new ContentValues(1); contentValues.put(NOTIFICATION_CHANNEL, notificationChannel); @@ -413,20 +442,18 @@ private void updateOrInsert(Address address, ContentValues contentValues) { database.endTransaction(); } - public List getBlockedContacts() { + public List
getBlockedContacts() { SQLiteDatabase database = getReadableDatabase(); - Cursor cursor = database.query(TABLE_NAME, new String[] {ID, ADDRESS}, BLOCK + " = 1", - null, null, null, null, null); - - RecipientReader reader = new RecipientReader(context, cursor); - List returnList = new ArrayList<>(); - Recipient current; - while ((current = reader.getNext()) != null) { - returnList.add(current); + try (Cursor cursor = database.query(TABLE_NAME, new String[] {ID, ADDRESS}, BLOCK + " = 1", + null, null, null, null, null)) { + List
blockedContacts = new ArrayList<>(cursor.getCount()); + while (cursor.moveToNext()) { + String serialized = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)); + blockedContacts.add(Address.fromSerialized(serialized)); + } + return blockedContacts; } - reader.close(); - return returnList; } /** diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index 690d6e0ad3..246010d142 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import network.loki.messenger.libsession_util.ReadableGroupInfoConfig +import network.loki.messenger.libsession_util.ReadableUserProfile import network.loki.messenger.libsession_util.util.Contact import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.GroupInfo @@ -74,32 +75,24 @@ class RecipientRepository @Inject constructor( return flow { while (true) { val (value, changeSource) = fetchRecipient(address) - ?: run { - // In this case, we won't be able to fetch the recipient forever, so we - // will emit null and stop the flow. - emit(null) - return@flow - } - emit(value) changeSource.first() Log.d(TAG, "Recipient changed for ${address.address.substring(0..10)}") } - }.shareIn(GlobalScope, SharingStarted.WhileSubscribed(), replay = 1) + }.shareIn(GlobalScope, + // replay must be cleared one when no one is subscribed, so that if no one is subscribed, + // we will always fetch the latest data. The cache is only valid while there is at least one subscriber. + SharingStarted.WhileSubscribed(replayExpirationMillis = 0L), replay = 1) } - private suspend fun fetchRecipient(address: Address): Pair>? { + private suspend fun fetchRecipient(address: Address): Pair> { val myAddress by lazy { preferences.getLocalNumber()?.let(Address::fromSerialized) } // Short-circuit for our own address. if (address.isContact && address == myAddress) { return configFactory.withUserConfigs { - createLocalRecipient( - address, - it.userProfile.getName().orEmpty(), - it.userProfile.getPic() - ) + createLocalRecipient(address, it.userProfile) } to configFactory.userConfigsChanged() } @@ -196,23 +189,36 @@ class RecipientRepository @Inject constructor( return runBlocking { flow.first() } } + /** + * Returns a recipient for the given address, or an empty recipient if not found. + * This is useful to avoid null checks in the UI. + */ + @Deprecated( + "Use the suspend version of getRecipient instead", + ReplaceWith("getRecipient(address)") + ) + fun getRecipientSyncOrEmpty(address: Address): RecipientV2 { + return getRecipientSync(address) ?: empty(address) + } + companion object { private const val TAG = "RecipientRepository" - private fun createLocalRecipient(address: Address, name: String, avatar: UserPic?): RecipientV2 { + private fun createLocalRecipient(address: Address, config: ReadableUserProfile): RecipientV2 { return RecipientV2( isLocalNumber = true, address = address, - name = name, + name = config.getName().orEmpty(), approved = true, approvedMe = true, blocked = false, mutedUntil = null, autoDownloadAttachments = true, notifyType = RecipientDatabase.NOTIFY_TYPE_ALL, - avatar = avatar?.toRecipientAvatar(), + avatar = config.getPic().toRecipientAvatar(), nickname = null, - expiryMode = ExpiryMode.NONE, + expiryMode = config.getNtsExpiry(), + acceptsCommunityMessageRequests = config.getCommunityMessageRequests(), ) } @@ -238,8 +244,6 @@ class RecipientRepository @Inject constructor( groupInfo: ReadableGroupInfoConfig, settings: RecipientSettings? ): RecipientV2 { - val timer = groupInfo.getExpiryTimer() - return RecipientV2( name = groupInfo.getName() ?: group.name, address = address, @@ -253,6 +257,7 @@ class RecipientRepository @Inject constructor( notifyType = settings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, autoDownloadAttachments = settings?.autoDownloadAttachments, expiryMode = groupInfo.expiryMode, + acceptsCommunityMessageRequests = false, ) } @@ -262,8 +267,8 @@ class RecipientRepository @Inject constructor( */ private fun createContactRecipient( address: Address, - contactInConfig: Contact, - fallbackSettings: RecipientSettings? + contactInConfig: Contact, // Config data + fallbackSettings: RecipientSettings?, // Local db data ): RecipientV2 { return RecipientV2( isLocalNumber = false, @@ -279,13 +284,14 @@ class RecipientRepository @Inject constructor( notifyType = fallbackSettings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, expiryMode = contactInConfig.expiryMode, + acceptsCommunityMessageRequests = fallbackSettings?.blocksCommunityMessageRequests != true ) } private fun createCommunityOrLegacyGroupRecipient( address: Address, - group: GroupRecord, - settings: RecipientSettings? + group: GroupRecord, // Local db data + settings: RecipientSettings?, // Local db data ): RecipientV2 { return RecipientV2( isLocalNumber = false, @@ -300,6 +306,7 @@ class RecipientRepository @Inject constructor( autoDownloadAttachments = settings?.autoDownloadAttachments, notifyType = settings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, expiryMode = ExpiryMode.NONE, + acceptsCommunityMessageRequests = false, ) } @@ -322,6 +329,7 @@ class RecipientRepository @Inject constructor( autoDownloadAttachments = settings.autoDownloadAttachments, notifyType = settings.notifyType, expiryMode = ExpiryMode.NONE, // A generic recipient does not have an expiry mode + acceptsCommunityMessageRequests = !settings.blocksCommunityMessageRequests ) } @@ -339,6 +347,7 @@ class RecipientRepository @Inject constructor( notifyType = RecipientDatabase.NOTIFY_TYPE_ALL, avatar = null, expiryMode = ExpiryMode.NONE, + acceptsCommunityMessageRequests = false, ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt index df8bf98be0..9535872c50 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt @@ -12,6 +12,7 @@ import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import javax.inject.Provider +@Deprecated("We no longer store contacts in the database, use the one from config instead") class SessionContactDatabase(context: Context, helper: Provider) : Database(context, helper) { companion object { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index e70e118276..224a5db8d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -14,12 +14,10 @@ import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.KeyPair import network.loki.messenger.libsession_util.util.UserPic -import org.session.libsession.avatars.AvatarHelper import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.BlindedIdMapping import org.session.libsession.messaging.calls.CallMessageType -import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob import org.session.libsession.messaging.jobs.Job @@ -27,8 +25,8 @@ import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageReceiveJob import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.jobs.RetrieveProfileAvatarJob -import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.messages.Message +import org.session.libsession.messaging.messages.ProfileUpdateHandler import org.session.libsession.messaging.messages.control.GroupUpdated import org.session.libsession.messaging.messages.control.MessageRequestResponse import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage @@ -64,6 +62,7 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.upsertContact import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.session.libsignal.crypto.ecc.ECKeyPair @@ -90,7 +89,6 @@ import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.util.FilenameUtils import org.thoughtcrime.securesms.util.SessionMetaProtocol import org.thoughtcrime.securesms.util.SessionMetaProtocol.clearReceivedMessages -import java.security.MessageDigest import javax.inject.Inject import javax.inject.Provider import javax.inject.Singleton @@ -119,9 +117,6 @@ open class Storage @Inject constructor( private val groupMemberDatabase: GroupMemberDatabase, private val reactionDatabase: ReactionDatabase, private val lokiThreadDatabase: LokiThreadDatabase, - private val sessionContactDatabase: SessionContactDatabase, - private val expirationConfigurationDatabase: ExpirationConfigurationDatabase, - private val profileManager: SSKEnvironment.ProfileManagerProtocol, private val notificationManager: MessageNotifier, private val messageDataProvider: MessageDataProvider, private val messageExpirationManager: SSKEnvironment.MessageExpirationManagerProtocol, @@ -130,6 +125,7 @@ open class Storage @Inject constructor( private val usernameUtils: UsernameUtils, private val openGroupManager: Lazy, private val recipientRepository: RecipientRepository, + private val profileUpdateHandler: ProfileUpdateHandler, ) : Database(context, helper), StorageProtocol, ThreadDatabase.ConversationThreadUpdateListener { init { @@ -220,12 +216,6 @@ open class Storage @Inject constructor( return Profile(displayName, profileKey, profilePictureUrl) } - override fun setProfilePicture(recipient: Address, newProfilePicture: String?, newProfileKey: ByteArray?) { - val db = recipientDatabase - db.setProfileAvatar(recipient, newProfilePicture) - db.setProfileKey(recipient, newProfileKey) - } - override fun setBlocksCommunityMessageRequests(recipient: Address, blocksMessageRequests: Boolean) { val db = recipientDatabase db.setBlocksCommunityMessageRequests(recipient, blocksMessageRequests) @@ -779,38 +769,6 @@ open class Storage @Inject constructor( } } - override fun updateGroupConfig(groupPublicKey: String) { - val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - val groupAddress = fromSerialized(groupID) - val existingGroup = getGroup(groupID) - ?: return Log.w("Loki-DBG", "No existing group for ${groupPublicKey.take(4)}} when updating group config") - configFactory.withMutableUserConfigs { - val userGroups = it.userGroups - if (!existingGroup.isActive) { - userGroups.eraseLegacyGroup(groupPublicKey) - return@withMutableUserConfigs - } - val name = existingGroup.title - val admins = existingGroup.admins.map { it.toString() } - val members = existingGroup.members.map { it.toString() } - val membersMap = GroupUtil.createConfigMemberMap(admins = admins, members = members) - val latestKeyPair = getLatestClosedGroupEncryptionKeyPair(groupPublicKey) - ?: return@withMutableUserConfigs Log.w("Loki-DBG", "No latest closed group encryption key pair for ${groupPublicKey.take(4)}} when updating group config") - - val threadID = getThreadId(groupAddress) ?: return@withMutableUserConfigs - val groupInfo = userGroups.getOrConstructLegacyGroupInfo(groupPublicKey).copy( - name = name, - members = membersMap, - encPubKey = Bytes((latestKeyPair.publicKey as DjbECPublicKey).publicKey), // 'serialize()' inserts an extra byte - encSecKey = Bytes(latestKeyPair.privateKey.serialize()), - priority = if (isPinned(threadID)) PRIORITY_PINNED else PRIORITY_VISIBLE, - disappearingTimer = getExpirationConfiguration(threadID)?.expiryMode?.expirySeconds ?: 0L, - joinedAtSecs = (existingGroup.formationTimestamp / 1000L) - ) - userGroups.set(groupInfo) - } - } - override fun isGroupActive(groupPublicKey: String): Boolean { return groupDatabase.getGroup(GroupUtil.doubleEncodeGroupID(groupPublicKey)).orNull()?.isActive == true } @@ -992,11 +950,11 @@ open class Storage @Inject constructor( private fun insertUpdateControlMessage(updateData: UpdateMessageData, sentTimestamp: Long, senderPublicKey: String?, closedGroup: AccountId): MessageId? { val userPublicKey = getUserPublicKey()!! - val recipient = fromSerialized(closedGroup.hexString) + val address = fromSerialized(closedGroup.hexString) + val recipient = recipientRepository.getRecipientSync(address) val threadDb = threadDatabase - val threadID = threadDb.getThreadIdIfExistsFor(recipient) - val expirationConfig = getExpirationConfiguration(threadID) - val expiryMode = expirationConfig?.expiryMode + val threadID = threadDb.getThreadIdIfExistsFor(address) + val expiryMode = recipient?.expiryMode val expiresInMillis = expiryMode?.expiryMillis ?: 0 val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 val inviteJson = updateData.toJSON() @@ -1004,7 +962,7 @@ open class Storage @Inject constructor( if (senderPublicKey == null || senderPublicKey == userPublicKey) { val infoMessage = OutgoingGroupMediaMessage( - recipient, + address, inviteJson, closedGroup.hexString, null, @@ -1136,21 +1094,6 @@ open class Storage @Inject constructor( return threadId ?: -1 } - override fun getContactWithAccountID(accountID: String): Contact? { - return sessionContactDatabase.getContactWithAccountID(accountID) - } - - override fun getAllContacts(): Set { - return sessionContactDatabase.getAllContacts() - } - - override fun setContact(contact: Contact) { - sessionContactDatabase.setContact(contact) - val address = fromSerialized(contact.accountID) - if (!getRecipientApproved(address)) return - profileManager.contactUpdatedInternal(contact) - } - override fun deleteContactAndSyncConfig(accountId: String) { deleteContact(accountId) // also handle the contact removal from the config's point of view @@ -1158,8 +1101,6 @@ open class Storage @Inject constructor( } private fun deleteContact(accountId: String){ - sessionContactDatabase.deleteContact(accountId) - Recipient.removeCached(fromSerialized(accountId)) recipientDatabase.deleteRecipient(accountId) val threadId: Long = threadDatabase.getThreadIdIfExistsFor(accountId) @@ -1184,29 +1125,9 @@ open class Storage @Inject constructor( } moreContacts.forEach { contact -> val address = fromSerialized(contact.id) - setBlocked(listOf(address), contact.blocked, fromConfigUpdate = true) - setRecipientApproved(address, contact.approved) - setRecipientApprovedMe(address, contact.approvedMe) - if (contact.name.isNotEmpty()) { - profileManager.setName(context, address, contact.name) - } else { - profileManager.setName(context, address, null) - } - if (contact.nickname.isNotEmpty()) { - profileManager.setNickname(context, address, contact.nickname) - } else { - profileManager.setNickname(context, address, null) - } - if (contact.profilePicture != UserPic.DEFAULT) { - val (url, key) = contact.profilePicture - if (key.data.size != ProfileKeyUtil.PROFILE_KEY_BYTES) return@forEach - profileManager.setProfilePicture(context, address, url, key.data) - } else { - profileManager.setProfilePicture(context, address, null, null) - } if (contact.priority == PRIORITY_HIDDEN) { - getThreadId(fromSerialized(contact.id))?.let(::deleteConversation) + getThreadId(address)?.let(::deleteConversation) } else { ( getThreadId(address) ?: getOrCreateThreadIdFor(address).also { @@ -1214,14 +1135,6 @@ open class Storage @Inject constructor( } ).also { setPinned(it, contact.priority == PRIORITY_PINNED) } } - if (timestamp != null) { - getThreadId(address)?.let { - setExpirationConfiguration( - getExpirationConfiguration(it)?.takeIf { it.updatedTimestampMs > timestamp } - ?: ExpirationConfiguration(it, contact.expiryMode, timestamp), - ) - } - } } // if we have contacts locally but that are missing from the config, remove their corresponding thread @@ -1416,14 +1329,12 @@ open class Storage @Inject constructor( override fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) { val address = fromSerialized(senderPublicKey) - val recipient = getRecipientSync(address) + val recipient = recipientRepository.getRecipientSync(address) - if (recipient.blocked) return + if (recipient?.blocked == true) return val threadId = getThreadId(address) ?: return - val expirationConfig = getExpirationConfiguration(threadId) - val expiryMode = expirationConfig?.expiryMode ?: ExpiryMode.NONE - val expiresInMillis = expiryMode.expiryMillis - val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 + val expiresInMillis = recipient?.expiryMode?.expiryMillis ?: 0 + val expireStartedAt = if (recipient?.expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 val mediaMessage = IncomingMediaMessage( address, sentTimestamp, @@ -1474,19 +1385,15 @@ open class Storage @Inject constructor( val threadId = getOrCreateThreadIdFor(sender) val profile = response.profile if (profile != null) { - val name = profile.displayName!! - if (name.isNotEmpty()) { - profileManager.setName(context, sender, name) - } - val newProfileKey = profile.profileKey - - val needsProfilePicture = !AvatarHelper.avatarFileExists(context, sender) - val profileKeyValid = newProfileKey?.isNotEmpty() == true && (newProfileKey.size == 16 || newProfileKey.size == 32) && profile.profilePictureURL?.isNotEmpty() == true - val profileKeyChanged = (sender.profileKey == null || !MessageDigest.isEqual(sender.profileKey, newProfileKey)) - - if ((profileKeyValid && profileKeyChanged) || (profileKeyValid && needsProfilePicture)) { - profileManager.setProfilePicture(context, sender, profile.profilePictureURL!!, newProfileKey!!) - } + profileUpdateHandler.handleProfileUpdate( + sender, + ProfileUpdateHandler.Updates( + name = profile.displayName, + picUrl = profile.profilePictureURL, + picKey = profile.profileKey, + acceptsCommunityRequests = null + ), + communityServerPubKey = null) } val mappingDb = blindedIdMappingDatabase @@ -1614,9 +1521,8 @@ open class Storage @Inject constructor( override fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long) { val address = fromSerialized(senderPublicKey) - val threadId = threadDatabase.getOrCreateThreadIdFor(address) - val expirationConfig = getExpirationConfiguration(threadId) - val expiryMode = expirationConfig?.expiryMode?.coerceSendToRead() ?: ExpiryMode.NONE + val recipient = recipientRepository.getRecipientSync(address) + val expiryMode = recipient?.expiryMode?.coerceSendToRead() ?: ExpiryMode.NONE val expiresInMillis = expiryMode.expiryMillis val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 val callMessage = IncomingTextMessage.fromCallInfo(callMessageType, address, Optional.absent(), sentTimestamp, expiresInMillis, expireStartedAt) @@ -1671,19 +1577,21 @@ open class Storage @Inject constructor( if (mapping.accountId != null) { return mapping } - getAllContacts().forEach { contact -> - val accountId = AccountId(contact.accountID) - if (accountId.prefix == IdPrefix.STANDARD && BlindKeyAPI.sessionIdMatchesBlindedId( - sessionId = accountId.hexString, - blindedId = blindedId, - serverPubKey = serverPublicKey - ) - ) { - val contactMapping = mapping.copy(accountId = accountId.hexString) - db.addBlindedIdMapping(contactMapping) - return contactMapping + + configFactory.withUserConfigs { it.contacts.all() } + .forEach { contact -> + val accountId = AccountId(contact.id) + if (accountId.prefix == IdPrefix.STANDARD && BlindKeyAPI.sessionIdMatchesBlindedId( + sessionId = accountId.hexString, + blindedId = blindedId, + serverPubKey = serverPublicKey + ) + ) { + val contactMapping = mapping.copy(accountId = accountId.hexString) + db.addBlindedIdMapping(contactMapping) + return contactMapping + } } - } db.getBlindedIdMappingsExceptFor(server).forEach { if (BlindKeyAPI.sessionIdMatchesBlindedId( sessionId = it.accountId!!, @@ -1802,83 +1710,63 @@ open class Storage @Inject constructor( } } - override fun blockedContacts(): List { - val recipientDb = recipientDatabase - return recipientDb.blockedContacts - } - - override fun getExpirationConfiguration(threadId: Long): ExpirationConfiguration? { - val recipient = getRecipientForThread(threadId) ?: return null - val dbExpirationMetadata = expirationConfigurationDatabase.getExpirationConfiguration(threadId) - return when { - recipient.isLocalNumber -> configFactory.withUserConfigs { it.userProfile.getNtsExpiry() } - recipient.isContact -> { - // read it from contacts config if exists - recipient.address.toString().takeIf { it.startsWith(IdPrefix.STANDARD.value) } - ?.let { configFactory.withUserConfigs { configs -> configs.contacts.get(it)?.expiryMode } } - } - recipient.isGroupV2 -> { - configFactory.withGroupConfigs(AccountId(recipient.address.toString())) { configs -> - configs.groupInfo.getExpiryTimer() - }.let { - if (it == 0L) ExpiryMode.NONE else ExpiryMode.AfterSend(it) - } - } - recipient.isLegacyGroup -> { - // read it from group config if exists - GroupUtil.doubleDecodeGroupId(recipient.address.toString()) - .let { id -> configFactory.withUserConfigs { it.userGroups.getLegacyGroupInfo(id) } } - ?.run { disappearingTimer.takeIf { it != 0L }?.let(ExpiryMode::AfterSend) ?: ExpiryMode.NONE } - } - else -> null - }?.let { ExpirationConfiguration( - threadId, - it, - // This will be 0L for new closed groups, apparently we don't need this anymore? - dbExpirationMetadata?.updatedTimestampMs ?: 0L - ) } + override fun blockedContacts(): List { + val allBlockedContacts = hashSetOf
() + + // Source data from config first + configFactory.withUserConfigs { + it.contacts.all() + }.asSequence() + .filter { it.blocked } + .mapTo(allBlockedContacts) { Address.fromSerialized(it.id) } + + // Source data from the local database. This might contain something that is not synced + // to the config system. + allBlockedContacts.addAll(recipientDatabase.blockedContacts) + + return allBlockedContacts.map { + recipientRepository.getRecipientSync(it) ?: RecipientV2.empty(it) + } + } + + override fun getExpirationConfiguration(threadId: Long): ExpiryMode { + val recipient = getRecipientForThread(threadId) ?: return ExpiryMode.NONE + + return recipientRepository.getRecipientSync(recipient)?.expiryMode ?: ExpiryMode.NONE } override fun setExpirationConfiguration(threadId: Long, expiryMode: ExpiryMode) { val recipient = getRecipientForThread(threadId) ?: return - val expirationDb = expirationConfigurationDatabase - val currentConfig = expirationDb.getExpirationConfiguration(threadId.threadId) - if (currentConfig != null && currentConfig.updatedTimestampMs >= threadId.updatedTimestampMs) return - val expiryMode = threadId.expiryMode - if (expiryMode == ExpiryMode.NONE) { // Clear the legacy recipients on updating config to be none - lokiAPIDatabase.setLastLegacySenderAddress(recipient.address.toString(), null) + lokiAPIDatabase.setLastLegacySenderAddress(recipient.toString(), null) } - if (recipient.isLegacyGroupRecipient) { - val groupPublicKey = GroupUtil.addressToGroupAccountId(recipient.address) + if (recipient.isLegacyGroup) { + val groupPublicKey = GroupUtil.addressToGroupAccountId(recipient) configFactory.withMutableUserConfigs { val groupInfo = it.userGroups.getLegacyGroupInfo(groupPublicKey) ?.copy(disappearingTimer = expiryMode.expirySeconds) ?: return@withMutableUserConfigs it.userGroups.set(groupInfo) } - } else if (recipient.isGroupV2Recipient) { - val groupSessionId = AccountId(recipient.address.toString()) + } else if (recipient.isGroupV2) { + val groupSessionId = AccountId(recipient.toString()) configFactory.withMutableGroupConfigs(groupSessionId) { configs -> configs.groupInfo.setExpiryTimer(expiryMode.expirySeconds) } - } else if (recipient.isLocalNumber) { + } else if (recipient.address == getUserPublicKey()) { configFactory.withMutableUserConfigs { it.userProfile.setNtsExpiry(expiryMode) } - } else if (recipient.isContactRecipient) { + } else if (recipient.isContact) { configFactory.withMutableUserConfigs { val contact = it.contacts.get(recipient.address.toString())?.copy(expiryMode = expiryMode) ?: return@withMutableUserConfigs it.contacts.set(contact) } } - expirationDb.setExpirationConfiguration( - threadId.run { copy(expiryMode = expiryMode) } - ) } override fun getExpiringMessages(messageIds: List): List> { diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt index 285bb07f8e..4ed1c0fa04 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt @@ -264,7 +264,7 @@ class DebugMenuViewModel @Inject constructor( } conversations.filter { !it.recipient.isLocalNumber }.forEach { - recipientDatabase.setAutoDownloadAttachments(it.recipient, false) + recipientDatabase.setAutoDownloadAttachments(it.recipient.address, false) } // set all attachments back to pending diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt index 5bf323d5f6..14ef65b94e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt @@ -12,13 +12,11 @@ import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier import org.session.libsession.utilities.AppTextSecurePreferences import org.session.libsession.utilities.ConfigFactoryProtocol -import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences import org.thoughtcrime.securesms.groups.GroupManagerV2Impl import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.repository.DefaultConversationRepository -import org.thoughtcrime.securesms.sskenvironment.ProfileManager import org.thoughtcrime.securesms.tokenpage.TokenRepository import org.thoughtcrime.securesms.tokenpage.TokenRepositoryImpl import javax.inject.Singleton @@ -44,9 +42,6 @@ abstract class AppBindings { @Binds abstract fun bindGroupManager(groupManager: GroupManagerV2Impl): GroupManagerV2 - @Binds - abstract fun bindProfileManager(profileManager: ProfileManager): SSKEnvironment.ProfileManagerProtocol - @Binds abstract fun bindConfigFactory(configFactory: ConfigFactory): ConfigFactoryProtocol diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/CallModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/CallModule.kt index 8e0c735770..ec32b9625c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/CallModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/CallModule.kt @@ -19,9 +19,4 @@ object CallModule { @Singleton fun provideAudioManagerCompat(@ApplicationContext context: Context) = AudioManagerCompat.create(context) - @Provides - @Singleton - fun provideCallManager(@ApplicationContext context: Context, audioManagerCompat: AudioManagerCompat, storage: StorageProtocol) = - CallManager(context, audioManagerCompat, storage) - } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt index 3351cf7b71..1397c90886 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt @@ -14,21 +14,15 @@ import network.loki.messenger.libsession_util.Curve25519 import network.loki.messenger.libsession_util.GroupInfoConfig import network.loki.messenger.libsession_util.GroupKeysConfig import network.loki.messenger.libsession_util.GroupMembersConfig -import network.loki.messenger.libsession_util.MutableContacts import network.loki.messenger.libsession_util.MutableConversationVolatileConfig import network.loki.messenger.libsession_util.MutableUserGroupsConfig -import network.loki.messenger.libsession_util.MutableUserProfile import network.loki.messenger.libsession_util.UserGroupsConfig import network.loki.messenger.libsession_util.UserProfile import network.loki.messenger.libsession_util.util.BaseCommunityInfo import network.loki.messenger.libsession_util.util.Bytes import network.loki.messenger.libsession_util.util.ConfigPush -import network.loki.messenger.libsession_util.util.Contact -import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.MultiEncrypt -import network.loki.messenger.libsession_util.util.UserPic -import okio.ByteString.Companion.decodeBase64 import org.session.libsession.database.StorageProtocol import org.session.libsession.snode.OwnedSwarmAuth import org.session.libsession.snode.SnodeClock @@ -129,11 +123,9 @@ class ConfigFactory @Inject constructor( val instance = ReentrantReadWriteLock() to UserConfigsImpl( userEd25519SecKey = requiresCurrentUserED25519SecKey(), userAccountId = userAccountId, - threadDb = threadDb, configDatabase = configDatabase, storage = storage.get(), - textSecurePreferences = textSecurePreferences, - usernameUtils = usernameUtils.get() + threadDb = threadDb ) return synchronized(userConfigs) { @@ -659,70 +651,11 @@ private fun MutableConversationVolatileConfig.initFrom(storage: StorageProtocol, } } -private fun MutableUserProfile.initFrom(storage: StorageProtocol, - usernameUtils: UsernameUtils, - textSecurePreferences: TextSecurePreferences -) { - val ownPublicKey = storage.getUserPublicKey() ?: return - val displayName = usernameUtils.getCurrentUsername() ?: return - val picUrl = textSecurePreferences.getProfilePictureURL() - val picKey = textSecurePreferences.getProfileKey()?.decodeBase64()?.toByteArray() - setName(displayName) - if (!picUrl.isNullOrEmpty() && picKey != null && picKey.isNotEmpty()) { - setPic(UserPic(picUrl, picKey)) - } - val ownThreadId = storage.getThreadId(Address.fromSerialized(ownPublicKey)) - setNtsPriority( - if (ownThreadId != null) - if (storage.isPinned(ownThreadId)) ConfigBase.PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE - else ConfigBase.PRIORITY_HIDDEN - ) -} - -private fun MutableContacts.initFrom(storage: StorageProtocol) { - val localUserKey = storage.getUserPublicKey() ?: return - val contactsWithSettings = storage.getAllContacts().filter { recipient -> - recipient.accountID != localUserKey && recipient.accountID.startsWith(IdPrefix.STANDARD.value) - && storage.getThreadId(recipient.accountID) != null - }.map { contact -> - val address = Address.fromSerialized(contact.accountID) - val thread = storage.getThreadId(address) - val isPinned = if (thread != null) { - storage.isPinned(thread) - } else false - - Triple(contact, storage.getRecipientSettings(address)!!, isPinned) - } - for ((contact, settings, isPinned) in contactsWithSettings) { - val url = contact.profilePictureURL - val key = contact.profilePictureEncryptionKey - val userPic = if (url.isNullOrEmpty() || key?.isNotEmpty() != true) { - null - } else { - UserPic(url, key) - } - - val contactInfo = Contact( - id = contact.accountID, - name = contact.name.orEmpty(), - nickname = contact.nickname.orEmpty(), - blocked = settings.isBlocked, - approved = settings.isApproved, - approvedMe = settings.hasApprovedMe(), - profilePicture = userPic ?: UserPic.DEFAULT, - priority = if (isPinned) 1 else 0, - expiryMode = if (settings.expireMessages == 0) ExpiryMode.NONE else ExpiryMode.AfterRead(settings.expireMessages.toLong()) - ) - set(contactInfo) - } -} private class UserConfigsImpl( userEd25519SecKey: ByteArray, private val userAccountId: AccountId, private val configDatabase: ConfigDatabase, - textSecurePreferences: TextSecurePreferences, - usernameUtils: UsernameUtils, storage: StorageProtocol, threadDb: ThreadDatabase, contactsDump: ByteArray? = configDatabase.retrieveConfigAndHashes( @@ -761,18 +694,10 @@ private class UserConfigsImpl( ) init { - if (contactsDump == null) { - contacts.initFrom(storage) - } - if (userGroupsDump == null) { userGroups.initFrom(storage) } - if (userProfileDump == null) { - userProfile.initFrom(storage, usernameUtils, textSecurePreferences) - } - if (convoInfoDump == null) { convoInfoVolatile.initFrom(storage, threadDb) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt index dbbb8bd52f..8df48742f0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt @@ -65,10 +65,6 @@ object DatabaseModule { @Singleton fun provideSmsDatabase(@ApplicationContext context: Context, openHelper: Provider) = SmsDatabase(context, openHelper) - @Provides - @Singleton - fun provideMmsDatabase(@ApplicationContext context: Context, openHelper: Provider) = MmsDatabase(context, openHelper) - @Provides @Singleton fun provideAttachmentDatabase(@ApplicationContext context: Context, diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt index b7108adc4b..dde24bd550 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt @@ -21,6 +21,7 @@ import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.textSizeInBytes import org.thoughtcrime.securesms.database.GroupDatabase +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.util.AvatarUtils @@ -34,6 +35,7 @@ class CreateGroupViewModel @AssistedInject constructor( private val avatarUtils: AvatarUtils, groupDatabase: GroupDatabase, @Assisted createFromLegacyGroupId: String?, + recipientRepository: RecipientRepository, ): ViewModel() { // Child view model to handle contact selection logic //todo we should probably extend this VM instead of instantiating it here @@ -42,8 +44,8 @@ class CreateGroupViewModel @AssistedInject constructor( excludingAccountIDs = emptySet(), applyDefaultFiltering = true, scope = viewModelScope, - appContext = appContext, - avatarUtils = avatarUtils + avatarUtils = avatarUtils, + recipientRepository = recipientRepository, ) // Input: group name diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt index a82c04bf24..0912a9b0ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt @@ -47,7 +47,8 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY import org.session.libsession.utilities.getGroup -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.toUserPic import org.session.libsession.utilities.waitUntilGroupConfigsPushed import org.session.libsignal.protos.SignalServiceProtos.DataMessage import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateDeleteMemberContentMessage @@ -62,6 +63,7 @@ import org.thoughtcrime.securesms.configs.ConfigUploader import org.thoughtcrime.securesms.database.LokiAPIDatabase import org.thoughtcrime.securesms.database.LokiMessageDatabase import org.thoughtcrime.securesms.database.MmsSmsDatabase +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.util.SessionMetaProtocol @@ -78,7 +80,6 @@ class GroupManagerV2Impl @Inject constructor( private val mmsSmsDatabase: MmsSmsDatabase, private val lokiDatabase: LokiMessageDatabase, private val threadDatabase: ThreadDatabase, - private val profileManager: SSKEnvironment.ProfileManagerProtocol, @ApplicationContext val application: Context, private val clock: SnodeClock, private val messageDataProvider: MessageDataProvider, @@ -86,6 +87,7 @@ class GroupManagerV2Impl @Inject constructor( private val configUploader: ConfigUploader, private val scope: GroupScope, private val groupPollerManager: GroupPollerManager, + private val recipientRepository: RecipientRepository, ) : GroupManagerV2 { private val dispatcher = Dispatchers.Default @@ -112,7 +114,7 @@ class GroupManagerV2Impl @Inject constructor( groupName: String, groupDescription: String, members: Set - ): Recipient = withContext(dispatcher) { + ): RecipientV2 = withContext(dispatcher) { val ourAccountId = requireNotNull(storage.getUserPublicKey()) { "Our account ID is not available" } val ourProfile = storage.getUserProfile() @@ -131,8 +133,8 @@ class GroupManagerV2Impl @Inject constructor( val adminKey = checkNotNull(group.adminKey?.data) { "Admin key is null for new group creation." } val groupId = AccountId(group.groupAccountId) - val memberAsRecipients = members.map { - Recipient.from(application, Address.fromSerialized(it.hexString), false) + val memberAsRecipients = members.mapNotNull { + recipientRepository.getRecipient(Address.fromSerialized(it.hexString)) } try { @@ -147,9 +149,7 @@ class GroupManagerV2Impl @Inject constructor( newGroupConfigs.groupMembers.set( newGroupConfigs.groupMembers.getOrConstruct(member.address.toString()).apply { setName(member.name) - setProfilePic(member.profileAvatar?.let { url -> - member.profileKey?.let { key -> UserPic(url, key) } - } ?: UserPic.DEFAULT) + setProfilePic(member.avatar?.toUserPic() ?: UserPic.DEFAULT) } ) } @@ -195,13 +195,7 @@ class GroupManagerV2Impl @Inject constructor( "Failed to create a thread for the group" } - val recipient = - Recipient.from(application, Address.fromSerialized(groupId.hexString), false) - - // Apply various data locally - profileManager.setName(application, recipient, groupName) - storage.setRecipientApprovedMe(recipient, true) - storage.setRecipientApproved(recipient, true) + val recipient = recipientRepository.getRecipient(Address.fromSerialized(groupId.hexString))!! // Invite members JobQueue.shared.add( @@ -813,7 +807,6 @@ class GroupManagerV2Impl @Inject constructor( it.userGroups.set(closedGroupInfo) } - profileManager.setName(application, address, groupName) val groupThreadId = storage.getOrCreateThreadIdFor(address) storage.setRecipientApprovedMe(address, true) storage.setRecipientApproved(address, shouldAutoApprove) @@ -1132,8 +1125,7 @@ class GroupManagerV2Impl @Inject constructor( override fun setExpirationTimer( groupId: AccountId, - mode: ExpiryMode, - expiryChangeTimestampMs: Long + mode: ExpiryMode ) { val adminKey = requireAdminAccess(groupId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/SelectContactsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/SelectContactsViewModel.kt index d59d76609f..8f226e573d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/SelectContactsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/SelectContactsViewModel.kt @@ -1,13 +1,11 @@ package org.thoughtcrime.securesms.groups -import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel -import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -26,8 +24,9 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.AccountId +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.home.search.getSearchName import org.thoughtcrime.securesms.util.AvatarUIData @@ -38,10 +37,10 @@ import org.thoughtcrime.securesms.util.AvatarUtils open class SelectContactsViewModel @AssistedInject constructor( private val configFactory: ConfigFactory, private val avatarUtils: AvatarUtils, - @ApplicationContext private val appContext: Context, @Assisted private val excludingAccountIDs: Set, @Assisted private val applyDefaultFiltering: Boolean, // true by default - If true will filter out blocked and unapproved contacts @Assisted private val scope: CoroutineScope, + private val recipientRepository: RecipientRepository, ) : ViewModel() { // Input: The search query private val mutableSearchQuery = MutableStateFlow("") @@ -91,15 +90,12 @@ open class SelectContactsViewModel @AssistedInject constructor( } else { allContacts.filterNotTo(mutableSetOf()) { it in excludingAccountIDs } }.map { - Recipient.from( - appContext, - Address.fromSerialized(it.hexString), - false - ) + val address = Address.fromSerialized(it.hexString) + recipientRepository.getRecipient(address) ?: RecipientV2.empty(address) } if(applyDefaultFiltering){ - recipientContacts.filter { !it.isBlocked && it.isApproved } // filter out blocked contacts and unapproved contacts + recipientContacts.filter { !it.blocked && it.approved } // filter out blocked contacts and unapproved contacts } else recipientContacts } } @@ -107,7 +103,7 @@ open class SelectContactsViewModel @AssistedInject constructor( private suspend fun filterContacts( - contacts: Collection, + contacts: Collection, query: String, selectedAccountIDs: Set ): List { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt index e5058fd9af..8b1045f1fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt @@ -78,8 +78,8 @@ class ConversationOptionsBottomSheet(private val parentContext: Context) : Botto if (!recipient.isGroupOrCommunityRecipient && !recipient.isLocalNumber) { binding.detailsTextView.visibility = View.VISIBLE - binding.unblockTextView.visibility = if (recipient.isBlocked) View.VISIBLE else View.GONE - binding.blockTextView.visibility = if (recipient.isBlocked) View.GONE else View.VISIBLE + binding.unblockTextView.visibility = if (recipient.blocked) View.VISIBLE else View.GONE + binding.blockTextView.visibility = if (recipient.blocked) View.GONE else View.VISIBLE binding.detailsTextView.setOnClickListener(this) binding.blockTextView.setOnClickListener(this) binding.unblockTextView.setOnClickListener(this) @@ -99,7 +99,7 @@ class ConversationOptionsBottomSheet(private val parentContext: Context) : Botto binding.copyCommunityUrl.setOnClickListener(this) val notificationIconRes = when{ - recipient.isMuted -> R.drawable.ic_volume_off + recipient.isMuted() -> R.drawable.ic_volume_off recipient.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS -> R.drawable.ic_at_sign else -> R.drawable.ic_volume_2 diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index e0776a6244..3cb73b8373 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -19,6 +19,7 @@ import network.loki.messenger.R import network.loki.messenger.databinding.ViewConversationBinding import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_ALL import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_NONE @@ -65,7 +66,7 @@ class ConversationView : LinearLayout { } val unreadCount = thread.unreadCount - if (thread.recipient.isBlocked) { + if (thread.recipient.blocked) { binding.accentView.setBackgroundColor(ThemeUtil.getThemedColor(context, R.attr.danger)) binding.accentView.visibility = View.VISIBLE } else { @@ -98,9 +99,9 @@ class ConversationView : LinearLayout { ) } val recipient = thread.recipient - binding.muteIndicatorImageView.isVisible = recipient.isMuted || recipient.notifyType != NOTIFY_TYPE_ALL + binding.muteIndicatorImageView.isVisible = recipient.isMuted() || recipient.notifyType != NOTIFY_TYPE_ALL - val drawableRes = if (recipient.isMuted || recipient.notifyType == NOTIFY_TYPE_NONE) { + val drawableRes = if (recipient.isMuted() || recipient.notifyType == NOTIFY_TYPE_NONE) { R.drawable.ic_volume_off } else { R.drawable.ic_at_sign @@ -145,7 +146,7 @@ class ConversationView : LinearLayout { fun recycle() { binding.profilePictureView.recycle() } - private fun getTitle(recipient: Recipient): String = when { + private fun getTitle(recipient: RecipientV2): String = when { recipient.isLocalNumber -> context.getString(R.string.noteToSelf) else -> recipient.name // Internally uses the Contact API } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 08a0332f6f..07a5db481e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -567,13 +567,13 @@ class HomeActivity : ScreenLockActionBarActivity(), } bottomSheet.onBlockTapped = { bottomSheet.dismiss() - if (!thread.recipient.isBlocked) { + if (!thread.recipient.blocked) { blockConversation(thread) } } bottomSheet.onUnblockTapped = { bottomSheet.dismiss() - if (thread.recipient.isBlocked) { + if (thread.recipient.blocked) { unblockConversation(thread) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index 3f108bc29f..f14eff22b0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -102,7 +102,7 @@ class HomeViewModel @Inject constructor( // or if the contact is blocked, do not add it if ( thread.recipient.isLocalNumber && hideNoteToSelf || - thread.recipient.isBlocked + thread.recipient.blocked ) { return@mapNotNullTo null } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt index f4ceb84c86..4a88b07b4c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt @@ -19,19 +19,23 @@ import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.FragmentUserDetailsBottomSheetBinding import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.upsertContact +import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.IdPrefix import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 -import org.thoughtcrime.securesms.database.DatabaseContentProviders +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.ThreadDatabase +import org.thoughtcrime.securesms.dependencies.ConfigFactory import javax.inject.Inject @AndroidEntryPoint class UserDetailsBottomSheet: BottomSheetDialogFragment() { @Inject lateinit var threadDb: ThreadDatabase + @Inject lateinit var recipientRepository: RecipientRepository + @Inject lateinit var configFactory: ConfigFactory private lateinit var binding: FragmentUserDetailsBottomSheetBinding @@ -53,8 +57,8 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() { super.onViewCreated(view, savedInstanceState) val publicKey = arguments?.getString(ARGUMENT_PUBLIC_KEY) ?: return dismiss() val threadID = arguments?.getLong(ARGUMENT_THREAD_ID) ?: return dismiss() - val recipient = Recipient.from(requireContext(), Address.fromSerialized(publicKey), false) - val threadRecipient = threadDb.getRecipientForThreadId(threadID) ?: return dismiss() + val threadRecipient = threadDb.getRecipientForThreadId(threadID)?.let(recipientRepository::getRecipientSync) ?: return dismiss() + val recipient = recipientRepository.getRecipientSync(Address.fromSerialized(publicKey)) ?: return dismiss() with(binding) { profilePictureView.publicKey = publicKey profilePictureView.update(recipient) @@ -106,7 +110,7 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() { true } messageButton.setOnClickListener { - val threadId = MessagingModuleConfiguration.shared.storage.getThreadId(recipient) + val threadId = MessagingModuleConfiguration.shared.storage.getThreadId(recipient.address) val intent = Intent( context, ConversationActivityV2::class.java @@ -126,21 +130,25 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() { window.setDimAmount(0.6f) } - fun saveNickName(recipient: Recipient) = with(binding) { + fun saveNickName(recipient: RecipientV2) = with(binding) { nicknameEditText.clearFocus() hideSoftKeyboard() nameTextViewContainer.visibility = View.VISIBLE nameEditTextContainer.visibility = View.INVISIBLE - var newNickName: String? = null + val newNickName: String? if (nicknameEditText.text.isNotEmpty() && nicknameEditText.text.trim().length != 0) { newNickName = nicknameEditText.text.toString() } else { newNickName = previousContactNickname } - val publicKey = recipient.address.toString() - val storage = MessagingModuleConfiguration.shared.storage - val contact = storage.getContactWithAccountID(publicKey) ?: Contact(publicKey) - contact.nickname = newNickName - storage.setContact(contact) + + if (AccountId.fromStringOrNull(recipient.address.address)?.prefix == IdPrefix.STANDARD) { + configFactory.withMutableUserConfigs { configs -> + configs.contacts.upsertContact(recipient.address.address) { + this.nickname = newNickName + } + } + } + nameTextView.text = recipient.name (parentFragment as? UserDetailsBottomSheetCallback) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt index 437751e66b..637f7c2d90 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt @@ -11,8 +11,9 @@ import network.loki.messenger.R import network.loki.messenger.databinding.ViewGlobalSearchHeaderBinding import network.loki.messenger.databinding.ViewGlobalSearchResultBinding import network.loki.messenger.databinding.ViewGlobalSearchSubheaderBinding +import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.utilities.GroupRecord -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.search.model.MessageResult import org.thoughtcrime.securesms.ui.GetString @@ -180,8 +181,10 @@ class GlobalSearchAdapter( groupId = groupRecord.encodedId, title = groupRecord.title, legacyMembersString = if (groupRecord.isLegacyGroup) { - val recipients = groupRecord.members.map { Recipient.from(context, it, false) } - recipients.joinToString(transform = Recipient::getSearchName) + val recipients = groupRecord.members.map { + MessagingModuleConfiguration.shared.recipientRepository.getRecipientSyncOrEmpty(it) + } + recipients.joinToString(transform = RecipientV2::getSearchName) } else { null } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt index b03e255732..b05df3341a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt @@ -7,9 +7,11 @@ import android.text.style.StyleSpan import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import network.loki.messenger.R +import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.ContentView import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.GroupConversation @@ -89,7 +91,9 @@ fun ContentView.bindModel(query: String?, model: GroupConversation) { binding.searchResultProfilePicture.isVisible = true binding.searchResultSubtitle.isVisible = model.isLegacy binding.searchResultTimestamp.isVisible = false - val threadRecipient = Recipient.from(binding.root.context, Address.fromSerialized(model.groupId), false) + val threadRecipient = MessagingModuleConfiguration.shared.recipientRepository.getRecipientSyncOrEmpty( + Address.fromSerialized(model.groupId) + ) binding.searchResultProfilePicture.update(threadRecipient) val nameString = model.title binding.searchResultTitle.text = getHighlight(query, nameString) @@ -104,7 +108,9 @@ fun ContentView.bindModel(query: String?, model: ContactModel) = binding.run { searchResultSubtitle.isVisible = false searchResultTimestamp.isVisible = false searchResultSubtitle.text = null - val recipient = Recipient.from(root.context, Address.fromSerialized(model.contact.hexString), false) + val recipient = MessagingModuleConfiguration.shared.recipientRepository.getRecipientSyncOrEmpty( + Address.fromSerialized(model.contact.hexString) + ) searchResultProfilePicture.update(recipient) val nameString = if (model.isSelf) root.context.getString(R.string.noteToSelf) else model.name @@ -145,7 +151,7 @@ fun ContentView.bindModel(query: String?, model: Message, dateUtils: DateUtils) searchResultSubtitle.isVisible = true } -fun Recipient.getSearchName(): String = +fun RecipientV2.getSearchName(): String = name.takeIf { it.isNotEmpty() && !it.looksLikeAccountId } ?: address.toString().let(::truncateIdForDisplay) diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/search/EmojiSearchRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/search/EmojiSearchRepository.kt index 6d888389aa..106fabf286 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/search/EmojiSearchRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/search/EmojiSearchRepository.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.keyboard.emoji.search import android.content.Context import android.net.Uri +import dagger.hilt.android.qualifiers.ApplicationContext import io.reactivex.Single import io.reactivex.schedulers.Schedulers import org.session.libsession.utilities.TextSecurePreferences @@ -13,6 +14,7 @@ import org.thoughtcrime.securesms.database.EmojiSearchDatabase import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.emoji.EmojiSource import java.util.function.Consumer +import javax.inject.Inject private const val MINIMUM_QUERY_THRESHOLD = 1 private const val MINIMUM_INLINE_QUERY_THRESHOLD = 2 @@ -20,9 +22,9 @@ private const val EMOJI_SEARCH_LIMIT = 20 private val NOT_PUNCTUATION = "[A-Za-z0-9 ]".toRegex() -class EmojiSearchRepository(private val context: Context) { - - private val emojiSearchDatabase: EmojiSearchDatabase = DatabaseComponent.get(context).emojiSearchDatabase() +class EmojiSearchRepository @Inject constructor( + private val emojiSearchDatabase: EmojiSearchDatabase +) { fun submitQuery(query: String, limit: Int = EMOJI_SEARCH_LIMIT): Single> { val result = if (query.length >= MINIMUM_INLINE_QUERY_THRESHOLD && NOT_PUNCTUATION.matches(query.substring(query.lastIndex))) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewViewModel.kt index 3951c44763..8f2d87386d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewViewModel.kt @@ -21,6 +21,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn @@ -33,10 +35,12 @@ import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.MediaDatabase import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.mms.Slide @@ -53,22 +57,20 @@ class MediaOverviewViewModel @AssistedInject constructor( private val application: Application, private val threadDatabase: ThreadDatabase, private val mediaDatabase: MediaDatabase, - private val dateUtils: DateUtils + private val dateUtils: DateUtils, + private val recipientRepository: RecipientRepository, ) : AndroidViewModel(application) { private val timeBuckets by lazy { FixedTimeBuckets() } private val monthTimeBucketFormatter = DateTimeFormatter.ofPattern("MMMM yyyy", Locale.getDefault()) - private val recipient: SharedFlow = application.contentResolver - .observeChanges(DatabaseContentProviders.Attachment.CONTENT_URI) - .onStart { emit(DatabaseContentProviders.Attachment.CONTENT_URI) } - .map { Recipient.from(application, address, false) } - .shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1) + private val recipient = recipientRepository.observeRecipient(address) val mediaListState: StateFlow = recipient - .map { recipient -> + .distinctUntilChanged() + .map { withContext(Dispatchers.Default) { - val threadId = threadDatabase.getOrCreateThreadIdFor(recipient) + val threadId = threadDatabase.getOrCreateThreadIdFor(address) val mediaItems = mediaDatabase.getGalleryMediaForThread(threadId) .use { cursor -> cursor.asSequence() @@ -92,10 +94,11 @@ class MediaOverviewViewModel @AssistedInject constructor( .stateIn(viewModelScope, SharingStarted.Eagerly, null) val conversationName: StateFlow = recipient + .filterNotNull() .map { recipient -> when { recipient.isLocalNumber -> application.getString(R.string.noteToSelf) - else -> recipient.name + else -> recipient.displayName } } .stateIn(viewModelScope, SharingStarted.Eagerly, "") diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java index f04b69e56d..427467df8a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java @@ -25,6 +25,7 @@ import com.squareup.phrase.Phrase; import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.guava.Optional; import org.thoughtcrime.securesms.util.ViewUtilitiesKt; @@ -45,13 +46,9 @@ public class MediaPickerFolderFragment extends Fragment implements MediaPickerFo private Controller controller; private GridLayoutManager layoutManager; - public static @NonNull MediaPickerFolderFragment newInstance(@NonNull Recipient recipient) { - String name = Optional.fromNullable(recipient.getName()) - .or(Optional.fromNullable(recipient.getProfileName())) - .or(recipient.getName()); - + public static @NonNull MediaPickerFolderFragment newInstance(@NonNull RecipientV2 recipient) { Bundle args = new Bundle(); - args.putString(KEY_RECIPIENT_NAME, name); + args.putString(KEY_RECIPIENT_NAME, recipient.getDisplayName()); MediaPickerFolderFragment fragment = new MediaPickerFolderFragment(); fragment.setArguments(args); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt index 7d331b1fb9..9a2f1b3640 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt @@ -30,8 +30,10 @@ import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.Util.isEmpty import org.session.libsession.utilities.concurrent.SimpleTask import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ScreenLockActionBarActivity +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.mediasend.MediaSendViewModel.CountButtonState import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.scribbles.ImageEditorFragment @@ -49,11 +51,13 @@ import org.thoughtcrime.securesms.util.applySafeInsetsPaddings class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragment.Controller, MediaPickerItemFragment.Controller, MediaSendFragment.Controller, ImageEditorFragment.Controller, CameraXFragment.Controller{ - private var recipient: Address? = null + private var recipient: RecipientV2? = null private val viewModel: MediaSendViewModel by viewModels() private lateinit var binding: MediasendActivityBinding + lateinit var recipientRepository: RecipientRepository + override val applyDefaultWindowInsets: Boolean get() = false // we want to handle window insets manually here for fullscreen fragments like the camera screen @@ -75,9 +79,9 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme // Apply windowInsets for our own UI (not the fragment ones because they will want to do their own things) binding.mediasendBottomBar.applySafeInsetsPaddings() - recipient = fromSerialized( + recipient = recipientRepository.getRecipientSync(fromSerialized( intent.getStringExtra(KEY_ADDRESS)!! - ) + )) viewModel.onBodyChanged(intent.getStringExtra(KEY_BODY)!!) @@ -92,7 +96,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme } else if (!isEmpty(media)) { viewModel.onSelectedMediaChanged(this, media!!) - val fragment: Fragment = MediaSendFragment.newInstance(recipient!!) + val fragment: Fragment = MediaSendFragment.newInstance(recipient!!.address) supportFragmentManager.beginTransaction() .replace(R.id.mediasend_fragment_container, fragment, TAG_SEND) .commit() @@ -167,7 +171,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme override fun onMediaSelected(media: Media) { try { viewModel.onSingleMediaSelected(this, media) - navigateToMediaSend(recipient!!) + navigateToMediaSend(recipient!!.address) } catch (e: Exception){ Log.e(TAG, "Error selecting media", e) Toast.makeText(this, R.string.errorUnknown, Toast.LENGTH_LONG).show() @@ -262,7 +266,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme Log.i(TAG, "Camera capture stored: " + media.uri.toString()) viewModel.onImageCaptured(media) - navigateToMediaSend(recipient!!) + navigateToMediaSend(recipient!!.address) }) } @@ -281,7 +285,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme if (buttonState.count > 0) { binding.mediasendCountButton.setOnClickListener { v: View? -> navigateToMediaSend( - recipient!! + recipient!!.address ) } if (buttonState.isVisible) { @@ -329,7 +333,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme } } - private fun navigateToMediaSend(recipient: Recipient) { + private fun navigateToMediaSend(recipient: Address) { val fragment = MediaSendFragment.newInstance(recipient) var backstackTag: String? = null @@ -510,9 +514,9 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme * Get an intent to launch the media send flow starting with the picker. */ @JvmStatic - fun buildGalleryIntent(context: Context, recipient: Recipient, body: String): Intent { + fun buildGalleryIntent(context: Context, recipient: Address, body: String): Intent { val intent = Intent(context, MediaSendActivity::class.java) - intent.putExtra(KEY_ADDRESS, recipient.address.toString()) + intent.putExtra(KEY_ADDRESS, recipient.toString()) intent.putExtra(KEY_BODY, body) return intent } @@ -521,7 +525,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme * Get an intent to launch the media send flow starting with the camera. */ @JvmStatic - fun buildCameraIntent(context: Context, recipient: Recipient): Intent { + fun buildCameraIntent(context: Context, recipient: Address): Intent { val intent = buildGalleryIntent(context, recipient, "") intent.putExtra(KEY_IS_CAMERA, true) return intent @@ -534,7 +538,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme fun buildEditorIntent( context: Context, media: List, - recipient: Recipient, + recipient: Address, body: String ): Intent { val intent = buildGalleryIntent(context, recipient, body) diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt index 7a2397c8ce..3500c25434 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt @@ -12,6 +12,7 @@ import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.database.model.ThreadRecord import com.bumptech.glide.RequestManager +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.util.DateUtils import java.util.Locale import javax.inject.Inject @@ -61,7 +62,7 @@ class MessageRequestView : LinearLayout { binding.profilePictureView.recycle() } - private fun getUserDisplayName(recipient: Recipient): String? { + private fun getUserDisplayName(recipient: RecipientV2): String? { return if (recipient.isLocalNumber) { context.getString(R.string.noteToSelf) } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt index 7e950f0b42..890a2e51d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt @@ -51,6 +51,7 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.hasHidde import org.session.libsession.utilities.TextSecurePreferences.Companion.isNotificationsEnabled import org.session.libsession.utilities.TextSecurePreferences.Companion.removeHasHiddenMessageRequests import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix @@ -59,18 +60,22 @@ import org.session.libsignal.utilities.Util import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.crypto.KeyPairUtilities.getUserED25519KeyPair +import org.thoughtcrime.securesms.database.MmsSmsDatabase import org.thoughtcrime.securesms.database.RecipientDatabase +import org.thoughtcrime.securesms.database.RecipientRepository +import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.ReactionRecord -import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.service.KeyCachingService import org.thoughtcrime.securesms.util.AvatarUtils import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.Companion.WEBRTC_NOTIFICATION import org.thoughtcrime.securesms.util.SessionMetaProtocol.canUserReplyToNotification import org.thoughtcrime.securesms.util.SpanUtil +import javax.inject.Inject +import javax.inject.Singleton /** * Handles posting system notifications for new messages. @@ -78,8 +83,11 @@ import org.thoughtcrime.securesms.util.SpanUtil * * @author Moxie Marlinspike */ -class DefaultMessageNotifier( - val avatarUtils: AvatarUtils +class DefaultMessageNotifier @Inject constructor( + val avatarUtils: AvatarUtils, + private val threadDatabase: ThreadDatabase, + private val recipientRepository: RecipientRepository, + private val mmsSmsDatabase: MmsSmsDatabase, ) : MessageNotifier { override fun setVisibleThread(threadId: Long) { visibleThread = threadId @@ -171,17 +179,16 @@ class DefaultMessageNotifier( override fun updateNotification(context: Context, threadId: Long, signal: Boolean) { val isVisible = visibleThread == threadId - val threads = get(context).threadDatabase() - val recipient = threads.getRecipientForThreadId(threadId) + val recipient = threadDatabase.getRecipientForThreadId(threadId)?.let(recipientRepository::getRecipientSync) - if (recipient != null && !recipient.isGroupOrCommunityRecipient && threads.getMessageCount(threadId) == 1 && - !(recipient.isApproved || threads.getLastSeenAndHasSent(threadId).second()) + if (recipient != null && !recipient.isGroupOrCommunityRecipient && threadDatabase.getMessageCount(threadId) == 1 && + !(recipient.approved || threadDatabase.getLastSeenAndHasSent(threadId).second()) ) { removeHasHiddenMessageRequests(context) } if (!isNotificationsEnabled(context) || - (recipient != null && recipient.isMuted) + (recipient != null && recipient.isMuted()) ) { return } @@ -206,7 +213,7 @@ class DefaultMessageNotifier( var telcoCursor: Cursor? = null try { - telcoCursor = get(context).mmsSmsDatabase().unread // TODO: add a notification specific lighter query here + telcoCursor = mmsSmsDatabase.unread // TODO: add a notification specific lighter query here if ((telcoCursor == null || telcoCursor.isAfterLast) || getLocalNumber(context) == null) { updateBadge(context, 0) @@ -452,13 +459,12 @@ class DefaultMessageNotifier( private fun constructNotificationState(context: Context, cursor: Cursor): NotificationState { val notificationState = NotificationState() - val reader = get(context).mmsSmsDatabase().readerFor(cursor) + val reader = mmsSmsDatabase.readerFor(cursor) if (reader == null) { Log.e(TAG, "No reader for cursor - aborting constructNotificationState") return NotificationState() } - val threadDatabase = get(context).threadDatabase() val cache: MutableMap = HashMap() // CAREFUL: Do not put this loop back as `while ((reader.next.also { record = it }) != null) {` because it breaks with a Null Pointer Exception! @@ -473,15 +479,15 @@ class DefaultMessageNotifier( val conversationRecipient = record.recipient val threadId = record.threadId var body: CharSequence = record.getDisplayBody(context) - var threadRecipients: Recipient? = null + var threadRecipients: RecipientV2? = null var slideDeck: SlideDeck? = null val timestamp = record.timestamp var messageRequest = false if (threadId != -1L) { - threadRecipients = threadDatabase.getRecipientForThreadId(threadId) + threadRecipients = threadDatabase.getRecipientForThreadId(threadId)?.let(recipientRepository::getRecipientSync) messageRequest = threadRecipients != null && !threadRecipients.isGroupOrCommunityRecipient && - !threadRecipients.isApproved && !threadDatabase.getLastSeenAndHasSent(threadId).second() + !threadRecipients.approved && !threadDatabase.getLastSeenAndHasSent(threadId).second() if (messageRequest && (threadDatabase.getMessageCount(threadId) > 1 || !hasHiddenMessageRequests(context))) { continue } @@ -522,7 +528,7 @@ class DefaultMessageNotifier( blindedPublicKey = generateBlindedId(threadId, context) cache[threadId] = blindedPublicKey } - if (threadRecipients == null || !threadRecipients.isMuted) { + if (threadRecipients == null || !threadRecipients.isMuted()) { if(record.isIncomingCall || record.isOutgoingCall){ // do nothing here as we do not want to display a notification for incoming and outgoing calls, // they will instead be handled independently by the pre offer diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt index 16df8fa2db..5331a2a7a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt @@ -73,7 +73,7 @@ class MarkReadReceiver : BroadcastReceiver() { .asSequence() .filter { it.expiryType == ExpiryType.AFTER_READ } .filter { mmsSmsDatabase.getMessageById(it.expirationInfo.id)?.run { - isExpirationTimerUpdate && threadDb.getRecipientForThreadId(threadId)?.isGroupOrCommunityRecipient == true } == false + isExpirationTimerUpdate && threadDb.getRecipientForThreadId(threadId)?.isGroupOrCommunity == true } == false } .forEach { messageExpirationManager.startExpiringNow(it.expirationInfo.id) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java index 8880bdc617..b728cf876d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java @@ -20,7 +20,7 @@ import org.session.libsession.utilities.Address; import org.session.libsession.utilities.ServiceUtil; import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; @@ -107,12 +107,12 @@ public static synchronized void create(@NonNull Context context) { return sound == null ? Uri.EMPTY : sound; } - public static synchronized @Nullable Uri getMessageRingtone(@NonNull Context context, @NonNull Recipient recipient) { + public static synchronized @Nullable Uri getMessageRingtone(@NonNull Context context, @NonNull RecipientV2 recipient) { NotificationManager notificationManager = ServiceUtil.getNotificationManager(context); NotificationChannel channel = notificationManager.getNotificationChannel(recipient.getNotificationChannel()); if (!channelExists(channel)) { - Log.w(TAG, "Recipient had no channel. Returning null."); + Log.w(TAG, "RecipientV2 had no channel. Returning null."); return null; } @@ -151,12 +151,12 @@ public static synchronized void updateMessageVibrate(@NonNull Context context, b public static synchronized void ensureCustomChannelConsistency(@NonNull Context context) { NotificationManager notificationManager = ServiceUtil.getNotificationManager(context); RecipientDatabase db = DatabaseComponent.get(context).recipientDatabase(); - List customRecipients = new ArrayList<>(); + List customRecipients = new ArrayList<>(); Set customChannelIds = new HashSet<>(); Set existingChannelIds = Stream.of(notificationManager.getNotificationChannels()).map(NotificationChannel::getId).collect(Collectors.toSet()); try (RecipientDatabase.RecipientReader reader = db.getRecipientsWithNotificationChannels()) { - Recipient recipient; + RecipientV2 recipient; while ((recipient = reader.getNext()) != null) { customRecipients.add(recipient); customChannelIds.add(recipient.getNotificationChannel()); @@ -171,7 +171,7 @@ public static synchronized void ensureCustomChannelConsistency(@NonNull Context } } - for (Recipient customRecipient : customRecipients) { + for (RecipientV2 customRecipient : customRecipients) { if (!existingChannelIds.contains(customRecipient.getNotificationChannel())) { db.setNotificationChannel(customRecipient, null); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java index 0d57751171..ad8015dc23 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java @@ -10,7 +10,7 @@ import androidx.annotation.Nullable; import androidx.core.app.TaskStackBuilder; -import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import org.thoughtcrime.securesms.mms.SlideDeck; @@ -18,18 +18,18 @@ public class NotificationItem { private final long id; private final boolean mms; - private final @NonNull Recipient conversationRecipient; - private final @NonNull Recipient individualRecipient; - private final @Nullable Recipient threadRecipient; + private final @NonNull RecipientV2 conversationRecipient; + private final @NonNull RecipientV2 individualRecipient; + private final @Nullable RecipientV2 threadRecipient; private final long threadId; private final @Nullable CharSequence text; private final long timestamp; private final @Nullable SlideDeck slideDeck; public NotificationItem(long id, boolean mms, - @NonNull Recipient individualRecipient, - @NonNull Recipient conversationRecipient, - @Nullable Recipient threadRecipient, + @NonNull RecipientV2 individualRecipient, + @NonNull RecipientV2 conversationRecipient, + @Nullable RecipientV2 threadRecipient, long threadId, @Nullable CharSequence text, long timestamp, @Nullable SlideDeck slideDeck) { @@ -44,11 +44,11 @@ public NotificationItem(long id, boolean mms, this.slideDeck = slideDeck; } - public @NonNull Recipient getRecipient() { + public @NonNull RecipientV2 getRecipient() { return threadRecipient == null ? conversationRecipient : threadRecipient; } - public @NonNull Recipient getIndividualRecipient() { + public @NonNull RecipientV2 getIndividualRecipient() { return individualRecipient; } @@ -70,7 +70,7 @@ public long getThreadId() { public PendingIntent getPendingIntent(Context context) { Intent intent = new Intent(context, ConversationActivityV2.class); - Recipient notifyRecipients = threadRecipient != null ? threadRecipient : conversationRecipient; + RecipientV2 notifyRecipients = threadRecipient != null ? threadRecipient : conversationRecipient; if (notifyRecipients != null) intent.putExtra(ConversationActivityV2.ADDRESS, notifyRecipients.getAddress()); intent.putExtra(ConversationActivityV2.THREAD_ID, threadId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java index fcd72e16fa..d3bafba247 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java @@ -12,7 +12,7 @@ import java.util.LinkedList; import java.util.List; -import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; @@ -46,7 +46,7 @@ public void addNotification(NotificationItem item) { public @Nullable Uri getRingtone(@NonNull Context context) { if (!notifications.isEmpty()) { - Recipient recipient = notifications.getFirst().getRecipient(); + RecipientV2 recipient = notifications.getFirst().getRecipient(); return NotificationChannels.getMessageRingtone(context, recipient); } @@ -92,7 +92,7 @@ public PendingIntent getMarkAsReadIntent(Context context, int notificationId) { return PendingIntent.getBroadcast(context, 0, intent, intentFlags); } - public PendingIntent getRemoteReplyIntent(Context context, Recipient recipient, ReplyMethod replyMethod) { + public PendingIntent getRemoteReplyIntent(Context context, RecipientV2 recipient, ReplyMethod replyMethod) { if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications!"); Intent intent = new Intent(RemoteReplyReceiver.REPLY_ACTION); @@ -110,7 +110,7 @@ public PendingIntent getRemoteReplyIntent(Context context, Recipient recipient, return PendingIntent.getBroadcast(context, 0, intent, intentFlags); } - public PendingIntent getAndroidAutoReplyIntent(Context context, Recipient recipient) { + public PendingIntent getAndroidAutoReplyIntent(Context context, RecipientV2 recipient) { if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications!"); Intent intent = new Intent(AndroidAutoReplyReceiver.REPLY_ACTION); @@ -153,7 +153,7 @@ public PendingIntent getAndroidAutoHeardIntent(Context context, int notification return PendingIntent.getBroadcast(context, 0, intent, intentFlags); } - public PendingIntent getQuickReplyIntent(Context context, Recipient recipient) { + public PendingIntent getQuickReplyIntent(Context context, RecipientV2 recipient) { if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications! " + threads.size()); Intent intent = new Intent(context, ConversationActivityV2.class); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index b0036dfd66..66a0f2b4d0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -29,9 +29,11 @@ import org.session.libsession.avatars.ContactPhoto; import org.session.libsession.messaging.MessagingModuleConfiguration; import org.session.libsession.messaging.contacts.Contact; +import org.session.libsession.utilities.Address; import org.session.libsession.utilities.NotificationPrivacyPreference; import org.session.libsession.utilities.Util; import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; import org.thoughtcrime.securesms.mms.Slide; @@ -71,7 +73,7 @@ public SingleRecipientNotificationBuilder( setCategory(NotificationCompat.CATEGORY_MESSAGE); } - public void setThread(@NonNull Recipient recipient) { + public void setThread(@NonNull RecipientV2 recipient) { String channelId = recipient.getNotificationChannel(); setChannelId(channelId != null ? channelId : NotificationChannels.getMessagesChannel(context)); @@ -111,15 +113,15 @@ public void setMessageCount(int messageCount) { setNumber(messageCount); } - public void setPrimaryMessageBody(@NonNull Recipient threadRecipient, - @NonNull Recipient individualRecipient, + public void setPrimaryMessageBody(@NonNull RecipientV2 threadRecipient, + @NonNull RecipientV2 individualRecipient, @NonNull CharSequence message, @Nullable SlideDeck slideDeck) { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); if (privacy.isDisplayContact() && threadRecipient.isGroupOrCommunityRecipient()) { - String displayName = getGroupDisplayName(individualRecipient, threadRecipient.isCommunityRecipient()); + String displayName = getGroupDisplayName(individualRecipient.getAddress(), threadRecipient.isCommunityRecipient()); stringBuilder.append(Util.getBoldedString(displayName + ": ")); } @@ -207,7 +209,7 @@ public void addMessageBody(@NonNull Recipient threadRecipient, SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); if (privacy.isDisplayContact() && threadRecipient.isGroupOrCommunityRecipient()) { - String displayName = getGroupDisplayName(individualRecipient, threadRecipient.isCommunityRecipient()); + String displayName = getGroupDisplayName(individualRecipient.getAddress(), threadRecipient.isCommunityRecipient()); stringBuilder.append(Util.getBoldedString(displayName + ": ")); } @@ -319,9 +321,9 @@ private CharSequence getBigText(List messageBodies) { return content; } - private static Drawable getPlaceholderDrawable(AvatarUtils avatarUtils, Recipient recipient) { + private static Drawable getPlaceholderDrawable(AvatarUtils avatarUtils, RecipientV2 recipient) { String publicKey = recipient.getAddress().toString(); - String displayName = recipient.getName(); + String displayName = recipient.getDisplayName(); return avatarUtils.generateTextBitmap(ICON_SIZE, publicKey, displayName); } @@ -329,9 +331,9 @@ private static Drawable getPlaceholderDrawable(AvatarUtils avatarUtils, Recipien * @param recipient the * individual * recipient for which to get the display name. * @param openGroupRecipient whether in an open group context */ - private String getGroupDisplayName(Recipient recipient, boolean openGroupRecipient) { + private String getGroupDisplayName(Address recipient, boolean openGroupRecipient) { return MessagingModuleConfiguration.getShared().getUsernameUtils().getContactNameWithAccountID( - recipient.getAddress().toString(), + recipient.getAddress(), null, openGroupRecipient ? Contact.ContactContext.OPEN_GROUP : Contact.ContactContext.REGULAR ); diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/manager/CreateAccountManager.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/manager/CreateAccountManager.kt index 9e39c0bfb8..163e3878b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/manager/CreateAccountManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/manager/CreateAccountManager.kt @@ -23,8 +23,6 @@ class CreateAccountManager @Inject constructor( get() = SnodeModule.shared.storage fun createAccount(displayName: String) { - prefs.setProfileName(displayName) - // This is here to resolve a case where the app restarts before a user completes onboarding // which can result in an invalid database state database.clearAllLastMessageHashes() diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameActivity.kt index 8ade5b4e8e..589196c163 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameActivity.kt @@ -8,10 +8,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint +import dagger.hilt.android.lifecycle.withCreationCallback import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.session.libsession.utilities.TextSecurePreferences -import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.home.startHomeActivity import org.thoughtcrime.securesms.onboarding.messagenotifications.startMessageNotificationsActivity @@ -25,15 +25,15 @@ private const val EXTRA_LOAD_FAILED = "extra_load_failed" class PickDisplayNameActivity : BaseActionBarActivity() { @Inject - internal lateinit var viewModelFactory: PickDisplayNameViewModel.AssistedFactory + internal lateinit var viewModelFactory: PickDisplayNameViewModel.Factory @Inject internal lateinit var prefs: TextSecurePreferences - private val loadFailed get() = intent.getBooleanExtra(EXTRA_LOAD_FAILED, false) - - private val viewModel: PickDisplayNameViewModel by viewModels { - viewModelFactory.create(loadFailed) - } + private val viewModel: PickDisplayNameViewModel by viewModels(extrasProducer = { + defaultViewModelCreationExtras.withCreationCallback { + it.create(intent.getBooleanExtra(EXTRA_LOAD_FAILED, false)) + } + }) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameViewModel.kt index dc67a0e735..6d54fa803e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameViewModel.kt @@ -2,10 +2,11 @@ package org.thoughtcrime.securesms.onboarding.pickname import androidx.annotation.StringRes import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -14,14 +15,13 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import network.loki.messenger.R -import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol.Companion.NAME_PADDED_LENGTH +import org.session.libsession.messaging.messages.ProfileUpdateHandler import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.UsernameUtils -internal class PickDisplayNameViewModel( - private val loadFailed: Boolean, +@HiltViewModel(assistedFactory = PickDisplayNameViewModel.Factory::class) +class PickDisplayNameViewModel @AssistedInject constructor( + @Assisted private val loadFailed: Boolean, private val prefs: TextSecurePreferences, - private val usernameUtils: UsernameUtils, ): ViewModel() { private val isCreateAccount = !loadFailed @@ -38,7 +38,7 @@ internal class PickDisplayNameViewModel( when { displayName.isEmpty() -> { _states.update { it.copy(isTextErrorColor = true, error = R.string.displayNameErrorDescription) } } - displayName.toByteArray().size > NAME_PADDED_LENGTH -> { _states.update { it.copy(isTextErrorColor = true, error = R.string.displayNameErrorDescriptionShorter) } } + displayName.toByteArray().size > ProfileUpdateHandler.MAX_PROFILE_NAME_LENGTH -> { _states.update { it.copy(isTextErrorColor = true, error = R.string.displayNameErrorDescriptionShorter) } } else -> { // success - clear the error as we can still see it during the transition to the // next screen. @@ -75,21 +75,9 @@ internal class PickDisplayNameViewModel( _states.update { it.copy(showDialog = false) } } - @dagger.assisted.AssistedFactory - interface AssistedFactory { - fun create(loadFailed: Boolean): Factory - } - - @Suppress("UNCHECKED_CAST") - class Factory @AssistedInject constructor( - @Assisted private val loadFailed: Boolean, - private val prefs: TextSecurePreferences, - private val usernameUtils: UsernameUtils, - ) : ViewModelProvider.Factory { - - override fun create(modelClass: Class): T { - return PickDisplayNameViewModel(loadFailed, prefs, usernameUtils) as T - } + @AssistedFactory + interface Factory { + fun create(loadFailed: Boolean): PickDisplayNameViewModel } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt index e59d86c912..c3d1958a5d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt @@ -10,9 +10,10 @@ import network.loki.messenger.R import network.loki.messenger.databinding.BlockedContactLayoutBinding import org.session.libsession.utilities.recipients.Recipient import com.bumptech.glide.Glide +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.util.adapter.SelectableItem -typealias SelectableRecipient = SelectableItem +typealias SelectableRecipient = SelectableItem class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdapter(RecipientDiffer()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt index e26a69c100..77cfb548c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt @@ -22,7 +22,7 @@ import network.loki.messenger.R import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.database.StorageProtocol -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.util.adapter.SelectableItem import javax.inject.Inject @@ -68,7 +68,7 @@ class BlockedContactsViewModel @Inject constructor(private val storage: StorageP _state.value = state.copy(selectedItems = emptySet()) } - fun select(selectedItem: Recipient, isSelected: Boolean) { + fun select(selectedItem: RecipientV2, isSelected: Boolean) { _state.value = state.run { if (isSelected) copy(selectedItems = selectedItems + selectedItem) else copy(selectedItems = selectedItems - selectedItem) @@ -78,7 +78,7 @@ class BlockedContactsViewModel @Inject constructor(private val storage: StorageP fun getTitle(context: Context): String = context.getString(R.string.blockUnblock) // Method to get the appropriate text to display when unblocking 1, 2, or several contacts - fun getText(context: Context, contactsToUnblock: Set): CharSequence { + fun getText(context: Context, contactsToUnblock: Set): CharSequence { return when (contactsToUnblock.size) { // Note: We do not have to handle 0 because if no contacts are chosen then the unblock button is deactivated 1 -> Phrase.from(context, R.string.blockUnblockName) @@ -99,7 +99,7 @@ class BlockedContactsViewModel @Inject constructor(private val storage: StorageP fun getMessage(context: Context): String = context.getString(R.string.blockUnblock) - fun toggle(selectable: SelectableItem) { + fun toggle(selectable: SelectableItem) { _state.value = state.run { if (selectable.item in selectedItems) copy(selectedItems = selectedItems - selectable.item) else copy(selectedItems = selectedItems + selectable.item) @@ -107,8 +107,8 @@ class BlockedContactsViewModel @Inject constructor(private val storage: StorageP } data class BlockedContactsViewState( - val blockedContacts: List = emptyList(), - val selectedItems: Set = emptySet() + val blockedContacts: List = emptyList(), + val selectedItems: Set = emptySet() ) { val items = blockedContacts.map { SelectableItem(it, it in selectedItems) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt index 3b25f98c44..f923ab5d42 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -71,9 +71,9 @@ import kotlinx.coroutines.launch import network.loki.messenger.BuildConfig import network.loki.messenger.R import network.loki.messenger.databinding.ActivitySettingsBinding +import org.session.libsession.messaging.messages.ProfileUpdateHandler import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.utilities.NonTranslatableStringConstants.NETWORK_NAME -import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol import org.session.libsession.utilities.StringSubstitutionConstants.VERSION_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getColorFromAttr @@ -378,7 +378,7 @@ class SettingsActivity : ScreenLockActionBarActivity() { return false } - if (displayName.toByteArray().size > ProfileManagerProtocol.NAME_PADDED_LENGTH) { + if (displayName.toByteArray().size > ProfileUpdateHandler.MAX_PROFILE_NAME_LENGTH) { Toast.makeText(this, R.string.displayNameErrorDescriptionShorter, Toast.LENGTH_SHORT).show() return false } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt index ac904bde4b..7ef3af4796 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt @@ -9,11 +9,8 @@ import com.canhub.cropper.CropImageView import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -25,12 +22,11 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.ProfileKeyUtil import org.session.libsession.utilities.ProfilePictureUtilities import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.UsernameUtils -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.ExternalStorageUtil.getImageDir import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.NoExternalStorageException import org.session.libsignal.utilities.Util.SECURE_RANDOM +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.preferences.SettingsViewModel.AvatarDialogState.TempAvatar import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints @@ -49,8 +45,8 @@ class SettingsViewModel @Inject constructor( private val prefs: TextSecurePreferences, private val configFactory: ConfigFactory, private val connectivity: NetworkConnectivity, - private val usernameUtils: UsernameUtils, - private val avatarUtils: AvatarUtils + private val avatarUtils: AvatarUtils, + private val recipientRepository: RecipientRepository, ) : ViewModel() { private val TAG = "SettingsViewModel" @@ -59,7 +55,7 @@ class SettingsViewModel @Inject constructor( val hexEncodedPublicKey: String = prefs.getLocalNumber() ?: "" private val userRecipient by lazy { - Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false) + recipientRepository.getRecipientSync(Address.fromSerialized(hexEncodedPublicKey)) } private val _avatarDialogState: MutableStateFlow = MutableStateFlow( diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionDetails.kt b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionDetails.kt index 86c8b83c35..b8ede10641 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionDetails.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionDetails.kt @@ -1,13 +1,13 @@ package org.thoughtcrime.securesms.reactions -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.database.model.MessageId /** * A UI model for a reaction in the [ReactionsDialogFragment] */ data class ReactionDetails( - val sender: Recipient, + val sender: RecipientV2, val baseEmoji: String, val displayEmoji: String, val timestamp: Long, diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDialogFragment.java index e9da05212b..92951b6715 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDialogFragment.java @@ -27,6 +27,7 @@ import java.util.Objects; +import dagger.hilt.android.lifecycle.HiltViewModelExtensions; import network.loki.messenger.R; public final class ReactionsDialogFragment extends BottomSheetDialogFragment implements ReactionViewPagerAdapter.Listener { @@ -143,9 +144,14 @@ private void setUpRecipientsRecyclerView() { } private void setUpViewModel(@NonNull MessageId messageId) { - ReactionsViewModel.Factory factory = new ReactionsViewModel.Factory(messageId); - - ReactionsViewModel viewModel = new ViewModelProvider(this, factory).get(ReactionsViewModel.class); + final ReactionsViewModel viewModel = new ViewModelProvider( + getViewModelStore(), + getDefaultViewModelProviderFactory(), + HiltViewModelExtensions.withCreationCallback( + getDefaultViewModelCreationExtras(), + (ReactionsViewModel.Factory factory) -> factory.create(messageId) + ) + ).get(ReactionsViewModel.class); disposables.add(viewModel.getEmojiCounts().subscribe(emojiCounts -> { if (emojiCounts.size() < 1) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt index b48ab5ad59..a51a804353 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt @@ -6,12 +6,19 @@ import io.reactivex.schedulers.Schedulers import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.components.emoji.EmojiUtil +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.ReactionRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent +import javax.inject.Inject +import javax.inject.Singleton -class ReactionsRepository { +@Singleton +class ReactionsRepository @Inject constructor( + private val recipientRepository: RecipientRepository, +) { fun getReactions(messageId: MessageId): Observable> { return Observable.create { emitter: ObservableEmitter> -> @@ -24,8 +31,9 @@ class ReactionsRepository { val reactions: List = DatabaseComponent.get(context).reactionDatabase().getReactions(messageId) return reactions.map { reaction -> + val authorAddress = Address.fromSerialized(reaction.author) ReactionDetails( - sender = Recipient.from(context, Address.fromSerialized(reaction.author), false), + sender = recipientRepository.getRecipientSync(authorAddress) ?: RecipientV2.empty(authorAddress), baseEmoji = EmojiUtil.getCanonicalRepresentation(reaction.emoji), displayEmoji = reaction.emoji, timestamp = reaction.dateReceived, diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java index 8d345f8d3c..0114886033 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java @@ -6,23 +6,30 @@ import com.annimon.stream.Stream; +import org.thoughtcrime.securesms.database.RecipientRepository; import org.thoughtcrime.securesms.database.model.MessageId; import java.util.Comparator; import java.util.List; import java.util.Map; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.hilt.android.lifecycle.HiltViewModel; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; +@HiltViewModel(assistedFactory = ReactionsViewModel.Factory.class) public class ReactionsViewModel extends ViewModel { private final MessageId messageId; private final ReactionsRepository repository; - public ReactionsViewModel(@NonNull MessageId messageId) { + @AssistedInject + public ReactionsViewModel(@Assisted @NonNull MessageId messageId, final ReactionsRepository repository) { this.messageId = messageId; - this.repository = new ReactionsRepository(); + this.repository = repository; } public @NonNull @@ -65,17 +72,9 @@ private long getLatestTimestamp(List reactions) { return reactions.get(reactions.size() - 1).getDisplayEmoji(); } - static final class Factory implements ViewModelProvider.Factory { + @AssistedFactory + interface Factory { - private final MessageId messageId; - - Factory(@NonNull MessageId messageId) { - this.messageId = messageId; - } - - @Override - public @NonNull T create(@NonNull Class modelClass) { - return modelClass.cast(new ReactionsViewModel(messageId)); - } + ReactionsViewModel create(@NonNull MessageId messageId); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiDialogFragment.java index 2fd547eab3..c8f3a2cfda 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiDialogFragment.java @@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView; import org.thoughtcrime.securesms.util.LifecycleDisposable; +import dagger.hilt.android.lifecycle.HiltViewModelExtensions; import network.loki.messenger.R; public final class ReactWithAnyEmojiDialogFragment extends BottomSheetDialogFragment implements EmojiEventListener, @@ -149,10 +150,15 @@ public void onDismiss(@NonNull DialogInterface dialog) { private void initializeViewModel() { Bundle args = requireArguments(); - ReactWithAnyEmojiRepository repository = new ReactWithAnyEmojiRepository(requireContext()); - ReactWithAnyEmojiViewModel.Factory factory = new ReactWithAnyEmojiViewModel.Factory(repository, args.getLong(ARG_MESSAGE_ID), args.getBoolean(ARG_IS_MMS)); - - viewModel = new ViewModelProvider(this, factory).get(ReactWithAnyEmojiViewModel.class); + final MessageId messageId = new MessageId(args.getLong(ARG_MESSAGE_ID), args.getBoolean(ARG_IS_MMS)); + viewModel = new ViewModelProvider( + getViewModelStore(), + getDefaultViewModelProviderFactory(), + HiltViewModelExtensions.withCreationCallback( + getDefaultViewModelCreationExtras(), + (ReactWithAnyEmojiViewModel.Factory factory) -> factory.create(messageId) + ) + ).get(ReactWithAnyEmojiViewModel.class); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java index bab8591cb6..6c32e9ea10 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java @@ -17,7 +17,7 @@ import network.loki.messenger.R; -final class ReactWithAnyEmojiRepository { +public final class ReactWithAnyEmojiRepository { private static final String TAG = Log.tag(ReactWithAnyEmojiRepository.class); diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiViewModel.java index 0ac6cf18f4..6610c36671 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiViewModel.java @@ -1,13 +1,13 @@ package org.thoughtcrime.securesms.reactions.any; +import android.content.Context; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; import com.annimon.stream.Stream; -import org.session.libsession.messaging.MessagingModuleConfiguration; import org.thoughtcrime.securesms.components.emoji.EmojiPageModel; import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter; import org.thoughtcrime.securesms.database.model.MessageId; @@ -17,10 +17,16 @@ import java.util.List; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.hilt.android.lifecycle.HiltViewModel; +import dagger.hilt.android.qualifiers.ApplicationContext; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.subjects.BehaviorSubject; +@HiltViewModel(assistedFactory = ReactWithAnyEmojiViewModel.Factory.class) public final class ReactWithAnyEmojiViewModel extends ViewModel { private static final int SEARCH_LIMIT = 40; @@ -30,16 +36,18 @@ public final class ReactWithAnyEmojiViewModel extends ViewModel { private final Observable emojiList; private final BehaviorSubject searchResults; - private ReactWithAnyEmojiViewModel(@NonNull ReactWithAnyEmojiRepository repository, - long messageId, - boolean isMms, - @NonNull EmojiSearchRepository emojiSearchRepository) + @AssistedInject + public ReactWithAnyEmojiViewModel( + @Assisted @NonNull MessageId messageId, + @ApplicationContext Context context, + @NonNull EmojiSearchRepository emojiSearchRepository, + @NonNull ReactionsRepository reactionsRepository) { - this.repository = repository; + this.repository = new ReactWithAnyEmojiRepository(context); this.emojiSearchRepository = emojiSearchRepository; this.searchResults = BehaviorSubject.createDefault(new EmojiSearchResult()); - Observable> emojiPages = new ReactionsRepository().getReactions(new MessageId(messageId, isMms)) + Observable> emojiPages = reactionsRepository.getReactions(messageId) .map(thisMessagesReactions -> repository.getEmojiPageModels()); Observable emojiList = emojiPages.map(pages -> { @@ -100,23 +108,9 @@ public EmojiSearchResult() { } } - static class Factory implements ViewModelProvider.Factory { - - private final ReactWithAnyEmojiRepository repository; - private final long messageId; - private final boolean isMms; - - Factory(@NonNull ReactWithAnyEmojiRepository repository, long messageId, boolean isMms) { - this.repository = repository; - this.messageId = messageId; - this.isMms = isMms; - } - - @Override - public @NonNull T create(@NonNull Class modelClass) { - //noinspection ConstantConditions - return modelClass.cast(new ReactWithAnyEmojiViewModel(repository, messageId, isMms, new EmojiSearchRepository(MessagingModuleConfiguration.getShared().getContext()))); - } + @AssistedFactory + interface Factory { + ReactWithAnyEmojiViewModel create(@NonNull MessageId messageId); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index 73f97f9ad9..857d576fe5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -26,7 +26,7 @@ import org.session.libsession.snode.utilities.await import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.DatabaseContentProviders @@ -58,26 +58,26 @@ interface ConversationRepository { fun deleteMessages(messages: Set, threadId: Long) fun deleteAllLocalMessagesInThreadFromSenderOfMessage(messageRecord: MessageRecord) fun setApproved(recipient: Address, isApproved: Boolean) - fun isGroupReadOnly(recipient: Recipient): Boolean + fun isGroupReadOnly(recipient: RecipientV2): Boolean fun getLastSentMessageID(threadId: Long): Flow suspend fun deleteCommunityMessagesRemotely(threadId: Long, messages: Set) suspend fun delete1on1MessagesRemotely( threadId: Long, - recipient: Recipient, + recipient: Address, messages: Set ) suspend fun deleteNoteToSelfMessagesRemotely( threadId: Long, - recipient: Recipient, + recipient: Address, messages: Set ) suspend fun deleteLegacyGroupMessagesRemotely( - recipient: Recipient, + recipient: Address, messages: Set ) - suspend fun deleteGroupV2MessagesRemotely(recipient: Recipient, messages: Set) + suspend fun deleteGroupV2MessagesRemotely(recipient: Address, messages: Set) suspend fun banUser(threadId: Long, recipient: Address): Result suspend fun banAndDeleteAll(threadId: Long, recipient: Address): Result @@ -156,13 +156,12 @@ class DefaultConversationRepository @Inject constructor( } message.openGroupInvitation = openGroupInvitation val expirationConfig = threadDb.getOrCreateThreadIdFor(contact).let(storage::getExpirationConfiguration) - val expiresInMillis = expirationConfig?.expiryMode?.expiryMillis ?: 0 - val expireStartedAt = if (expirationConfig?.expiryMode is ExpiryMode.AfterSend) message.sentTimestamp!! else 0 + val expireStartedAt = if (expirationConfig is ExpiryMode.AfterSend) message.sentTimestamp!! else 0 val outgoingTextMessage = OutgoingTextMessage.fromOpenGroupInvitation( openGroupInvitation, contact, message.sentTimestamp, - expiresInMillis, + expirationConfig.expiryMillis, expireStartedAt ) message.id = smsDb.insertMessageOutbox(-1, outgoingTextMessage, message.sentTimestamp!!, true).orNull() @@ -172,7 +171,7 @@ class DefaultConversationRepository @Inject constructor( } } - override fun isGroupReadOnly(recipient: Recipient): Boolean { + override fun isGroupReadOnly(recipient: RecipientV2): Boolean { // We only care about group v2 recipient if (!recipient.isGroupV2Recipient) { return false @@ -282,11 +281,10 @@ class DefaultConversationRepository @Inject constructor( override suspend fun delete1on1MessagesRemotely( threadId: Long, - recipient: Recipient, + recipient: Address, messages: Set ) { // delete the messages remotely - val publicKey = recipient.address.toString() val userAddress: Address? = textSecurePreferences.getLocalNumber()?.let { Address.fromSerialized(it) } val userAuth = requireNotNull(storage.userAuth) { "User auth is required to delete messages remotely" @@ -296,7 +294,7 @@ class DefaultConversationRepository @Inject constructor( // delete from swarm messageDataProvider.getServerHashForMessage(message.messageId) ?.let { serverHash -> - SnodeAPI.deleteMessage(publicKey, userAuth, listOf(serverHash)) + SnodeAPI.deleteMessage(recipient.address, userAuth, listOf(serverHash)) } // send an UnsendRequest to user's swarm @@ -306,34 +304,32 @@ class DefaultConversationRepository @Inject constructor( // send an UnsendRequest to recipient's swarm buildUnsendRequest(message).let { unsendRequest -> - MessageSender.send(unsendRequest, recipient.address) + MessageSender.send(unsendRequest, recipient) } } } override suspend fun deleteLegacyGroupMessagesRemotely( - recipient: Recipient, + recipient: Address, messages: Set ) { - if (recipient.isLegacyGroupRecipient) { - val publicKey = recipient.address - + if (recipient.isLegacyGroup) { messages.forEach { message -> // send an UnsendRequest to group's swarm buildUnsendRequest(message).let { unsendRequest -> - MessageSender.send(unsendRequest, publicKey) + MessageSender.send(unsendRequest, recipient) } } } } override suspend fun deleteGroupV2MessagesRemotely( - recipient: Recipient, + recipient: Address, messages: Set ) { - require(recipient.isGroupV2Recipient) { "Recipient is not a group v2 recipient" } + require(recipient.isGroupV2) { "Recipient is not a group v2 recipient" } - val groupId = AccountId(recipient.address.toString()) + val groupId = AccountId(recipient.address) val hashes = messages.mapNotNullTo(mutableSetOf()) { msg -> messageDataProvider.getServerHashForMessage(msg.messageId) } @@ -343,11 +339,10 @@ class DefaultConversationRepository @Inject constructor( override suspend fun deleteNoteToSelfMessagesRemotely( threadId: Long, - recipient: Recipient, + recipient: Address, messages: Set ) { // delete the messages remotely - val publicKey = recipient.address.toString() val userAddress: Address? = textSecurePreferences.getLocalNumber()?.let { Address.fromSerialized(it) } val userAuth = requireNotNull(storage.userAuth) { "User auth is required to delete messages remotely" @@ -357,7 +352,7 @@ class DefaultConversationRepository @Inject constructor( // delete from swarm messageDataProvider.getServerHashForMessage(message.messageId) ?.let { serverHash -> - SnodeAPI.deleteMessage(publicKey, userAuth, listOf(serverHash)) + SnodeAPI.deleteMessage(recipient.address, userAuth, listOf(serverHash)) } // send an UnsendRequest to user's swarm diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/SearchModule.kt b/app/src/main/java/org/thoughtcrime/securesms/search/SearchModule.kt deleted file mode 100644 index 65d475336b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/search/SearchModule.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.thoughtcrime.securesms.search - -import android.content.Context -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityComponent -import dagger.hilt.android.components.ViewModelComponent -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.android.scopes.ActivityScoped -import dagger.hilt.android.scopes.ViewModelScoped -import org.session.libsession.utilities.concurrent.SignalExecutors -import org.thoughtcrime.securesms.contacts.ContactAccessor -import org.thoughtcrime.securesms.database.GroupDatabase -import org.thoughtcrime.securesms.database.SearchDatabase -import org.thoughtcrime.securesms.database.SessionContactDatabase -import org.thoughtcrime.securesms.database.ThreadDatabase -import org.thoughtcrime.securesms.dependencies.ConfigFactory - -@Module -@InstallIn(ViewModelComponent::class) -object SearchModule { - - @Provides - @ViewModelScoped - fun provideSearchRepository(@ApplicationContext context: Context, - searchDatabase: SearchDatabase, - threadDatabase: ThreadDatabase, - groupDatabase: GroupDatabase, - contactDatabase: SessionContactDatabase, - configFactory: ConfigFactory) = - SearchRepository( - context, searchDatabase, threadDatabase, groupDatabase, contactDatabase, - ContactAccessor.getInstance(), configFactory, SignalExecutors.SERIAL - ) - - -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt index 18d83a3dc5..d30af29479 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt @@ -3,18 +3,19 @@ package org.thoughtcrime.securesms.search import android.content.Context import android.database.Cursor import android.database.MergeCursor -import com.annimon.stream.Stream +import dagger.hilt.android.qualifiers.ApplicationContext import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.GroupRecord -import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.concurrent.SignalExecutors +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.contacts.ContactAccessor import org.thoughtcrime.securesms.database.CursorList import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.MmsSmsColumns +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.SearchDatabase import org.thoughtcrime.securesms.database.SessionContactDatabase import org.thoughtcrime.securesms.database.ThreadDatabase @@ -22,20 +23,22 @@ import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.search.model.MessageResult import org.thoughtcrime.securesms.search.model.SearchResult import org.thoughtcrime.securesms.util.Stopwatch -import java.util.concurrent.Executor +import javax.inject.Inject +import javax.inject.Singleton // Class to manage data retrieval for search -class SearchRepository( - context: Context, +@Singleton +class SearchRepository @Inject constructor( + @ApplicationContext private val context: Context, private val searchDatabase: SearchDatabase, private val threadDatabase: ThreadDatabase, private val groupDatabase: GroupDatabase, private val contactDatabase: SessionContactDatabase, private val contactAccessor: ContactAccessor, private val configFactory: ConfigFactory, - private val executor: Executor + private val recipientRepository: RecipientRepository, ) { - private val context: Context = context.applicationContext + private val executor = SignalExecutors.SERIAL fun query(query: String, callback: (SearchResult) -> Unit) { // If the sanitized search is empty then abort without search @@ -207,14 +210,14 @@ class SearchRepository( } } - private class MessageModelBuilder(private val context: Context) : CursorList.ModelBuilder { + private inner class MessageModelBuilder(private val context: Context) : CursorList.ModelBuilder { override fun build(cursor: Cursor): MessageResult { val conversationAddress = fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.CONVERSATION_ADDRESS))) val messageAddress = fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.MESSAGE_ADDRESS))) - val conversationRecipient = Recipient.from(context, conversationAddress, false) - val messageRecipient = Recipient.from(context, messageAddress, false) + val conversationRecipient = recipientRepository.getRecipientSync(conversationAddress) ?: RecipientV2.empty(conversationAddress) + val messageRecipient = recipientRepository.getRecipientSync(messageAddress) ?: RecipientV2.empty(messageAddress) val body = cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.SNIPPET)) val sentMs = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_SENT)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java b/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java index 804e47893e..addb65d418 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java +++ b/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java @@ -3,6 +3,7 @@ import androidx.annotation.NonNull; import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import java.util.Objects; @@ -11,14 +12,14 @@ */ public class MessageResult { - public final Recipient conversationRecipient; - public final Recipient messageRecipient; + public final RecipientV2 conversationRecipient; + public final RecipientV2 messageRecipient; public final String bodySnippet; public final long threadId; public final long sentTimestampMs; - public MessageResult(@NonNull Recipient conversationRecipient, - @NonNull Recipient messageRecipient, + public MessageResult(@NonNull RecipientV2 conversationRecipient, + @NonNull RecipientV2 messageRecipient, @NonNull String bodySnippet, long threadId, long sentTimestampMs) @@ -33,7 +34,7 @@ public MessageResult(@NonNull Recipient conversationRecipient, @Override public boolean equals(Object o) { if (!(o instanceof MessageResult that)) return false; - return threadId == that.threadId && sentTimestampMs == that.sentTimestampMs && Objects.equals(conversationRecipient, that.conversationRecipient) && Objects.equals(messageRecipient, that.messageRecipient) && Objects.equals(bodySnippet, that.bodySnippet); + return threadId == that.threadId && sentTimestampMs == that.sentTimestampMs && Objects.equals(conversationRecipient, that.conversationRecipient) && Objects.equals(messageRecipient, that.messageRecipient) && Objects.equals(bodySnippet, that.bodySnippet); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/CallForegroundService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/CallForegroundService.kt index 9ba7051616..8fd9c38dde 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/CallForegroundService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/CallForegroundService.kt @@ -8,35 +8,42 @@ import android.os.Build import android.os.IBinder import androidx.core.app.ServiceCompat import androidx.core.content.IntentCompat +import dagger.hilt.android.AndroidEntryPoint import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.Companion.TYPE_INCOMING_CONNECTING import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.Companion.WEBRTC_NOTIFICATION +import javax.inject.Inject +@AndroidEntryPoint class CallForegroundService : Service() { + @Inject + lateinit var recipientRepository: RecipientRepository + companion object { const val EXTRA_RECIPIENT_ADDRESS = "RECIPIENT_ID" const val EXTRA_TYPE = "CALL_STEP_TYPE" - fun startIntent(context: Context, type: Int, recipient: Recipient?): Intent { + fun startIntent(context: Context, type: Int, recipient: Address?): Intent { return Intent(context, CallForegroundService::class.java) .putExtra(EXTRA_TYPE, type) - .putExtra(EXTRA_RECIPIENT_ADDRESS, recipient?.address) + .putExtra(EXTRA_RECIPIENT_ADDRESS, recipient) } } - private fun getRemoteRecipient(intent: Intent): Recipient? { + private fun getRemoteRecipient(intent: Intent): RecipientV2? { val remoteAddress = IntentCompat.getParcelableExtra(intent, EXTRA_RECIPIENT_ADDRESS, Address::class.java) ?: return null - return Recipient.from(this, remoteAddress, true) + return recipientRepository.getRecipientSync(remoteAddress) } - private fun startForeground(type: Int, recipient: Recipient?) { + private fun startForeground(type: Int, recipient: RecipientV2?) { if (CallNotificationBuilder.areNotificationsEnabled(this)) { try { ServiceCompat.startForeground( diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt index 4355707a0d..8830dc0f8a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt @@ -33,6 +33,7 @@ import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.database.MmsDatabase import org.thoughtcrime.securesms.database.MmsSmsDatabase +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.SmsDatabase import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageId @@ -54,6 +55,7 @@ class ExpiringMessageManager @Inject constructor( private val clock: SnodeClock, private val storage: Lazy, private val preferences: TextSecurePreferences, + private val recipientRepository: RecipientRepository, ) : MessageExpirationManagerProtocol { private val scheduleDeletionChannel: SendChannel @@ -102,10 +104,10 @@ class ExpiringMessageManager @Inject constructor( val expiresInMillis = message.expiryMode.expiryMillis var groupInfo = Optional.absent() val address = fromSerialized(senderPublicKey!!) - var recipient = Recipient.from(context, address, false) + var recipient = recipientRepository.getRecipientSync(address) // if the sender is blocked, we don't display the update, except if it's in a closed group - if (recipient.isBlocked && groupId == null) return null + if (recipient?.blocked == true && groupId == null) return null return try { if (groupId != null) { val groupAddress: Address @@ -120,9 +122,9 @@ class ExpiringMessageManager @Inject constructor( Optional.of(SignalServiceGroup(GroupUtil.getDecodedGroupIDAsData(doubleEncoded), SignalServiceGroup.GroupType.SIGNAL)) } } - recipient = Recipient.from(context, groupAddress, false) + recipient = recipientRepository.getRecipientSync(groupAddress) } - val threadId = storage.get().getThreadId(recipient) ?: return null + val threadId = recipient?.address?.let(storage.get()::getThreadId) ?: return null val mediaMessage = IncomingMediaMessage( address, sentTimestamp!!, -1, expiresInMillis, expireStartedAt, true, @@ -163,11 +165,10 @@ class ExpiringMessageManager @Inject constructor( else -> doubleEncodeGroupID(groupId) } val address = fromSerialized(serializedAddress) - val recipient = Recipient.from(context, address, false) message.threadID = storage.get().getOrCreateThreadIdFor(address) val timerUpdateMessage = OutgoingExpirationUpdateMessage( - recipient, + address, sentTimestamp!!, duration, expireStartedAt, diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt deleted file mode 100644 index 31cf34c738..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package org.thoughtcrime.securesms.sskenvironment - -import android.content.Context -import dagger.Lazy -import network.loki.messenger.libsession_util.util.UserPic -import org.session.libsession.database.StorageProtocol -import org.session.libsession.messaging.contacts.Contact -import org.session.libsession.messaging.jobs.JobQueue -import org.session.libsession.messaging.jobs.RetrieveProfileAvatarJob -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.ConfigFactoryProtocol -import org.session.libsession.utilities.SSKEnvironment -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.upsertContact -import org.session.libsignal.utilities.AccountId -import org.session.libsignal.utilities.IdPrefix -import org.thoughtcrime.securesms.database.RecipientDatabase -import org.thoughtcrime.securesms.database.SessionContactDatabase -import org.thoughtcrime.securesms.database.SessionJobDatabase -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ProfileManager @Inject constructor( - private val configFactory: ConfigFactoryProtocol, - private val storage: Lazy, - private val contactDatabase: SessionContactDatabase, - private val recipientDatabase: RecipientDatabase, - private val jobDatabase: SessionJobDatabase, - private val preferences: TextSecurePreferences, -) : SSKEnvironment.ProfileManagerProtocol { - - override fun setNickname(context: Context, recipient: Address, nickname: String?) { - if (recipient.isLocalNumber) return - val accountID = recipient.address.toString() - var contact = contactDatabase.getContactWithAccountID(accountID) - if (contact == null) contact = Contact(accountID) - contact.threadID = storage.get().getThreadId(recipient.address) - if (contact.nickname != nickname) { - contact.nickname = nickname - contactDatabase.setContact(contact) - } - contactUpdatedInternal(contact) - } - - override fun setName(context: Context, recipient: Address, name: String?) { - // New API - if (recipient.isLocalNumber) return - val accountID = recipient.address.toString() - var contact = contactDatabase.getContactWithAccountID(accountID) - if (contact == null) contact = Contact(accountID) - contact.threadID = storage.get().getThreadId(recipient.address) - if (contact.name != name) { - contact.name = name - contactDatabase.setContact(contact) - } - // Old API - recipientDatabase.setProfileName(recipient, name) - recipient.notifyListeners() - contactUpdatedInternal(contact) - } - - override fun setProfilePicture( - context: Context, - recipient: Address, - profilePictureURL: String?, - profileKey: ByteArray? - ) { - val hasPendingDownload = jobDatabase - .getAllJobs(RetrieveProfileAvatarJob.KEY).any { - (it.value as? RetrieveProfileAvatarJob)?.recipientAddress == recipient.address - } - - recipient.resolve() - - val accountID = recipient.address.toString() - var contact = contactDatabase.getContactWithAccountID(accountID) - if (contact == null) contact = Contact(accountID) - contact.threadID = storage.get().getThreadId(recipient.address) - if (!contact.profilePictureEncryptionKey.contentEquals(profileKey) || contact.profilePictureURL != profilePictureURL) { - contact.profilePictureEncryptionKey = profileKey - contact.profilePictureURL = profilePictureURL - contactDatabase.setContact(contact) - } - contactUpdatedInternal(contact) - if (!hasPendingDownload) { - val job = RetrieveProfileAvatarJob(profilePictureURL, recipient.address, profileKey) - JobQueue.shared.add(job) - } - } - - - override fun contactUpdatedInternal(contact: Contact): String? { - if (contact.accountID == preferences.getLocalNumber()) return null - if (IdPrefix.fromValue(contact.accountID) != IdPrefix.STANDARD) return null // only internally store standard account IDs - return configFactory.withMutableUserConfigs { - val contactConfig = it.contacts - contactConfig.upsertContact(contact.accountID) { - this.name = contact.name.orEmpty() - this.nickname = contact.nickname.orEmpty() - val url = contact.profilePictureURL - val key = contact.profilePictureEncryptionKey - if (!url.isNullOrEmpty() && key != null && key.size == 32) { - this.profilePicture = UserPic(url, key) - } else if (url.isNullOrEmpty() && key == null) { - this.profilePicture = UserPic.DEFAULT - } - } - contactConfig.get(contact.accountID)?.hashCode()?.toString() - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt index e6eb68c84c..426ffd6a22 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt @@ -30,6 +30,7 @@ import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Snode import org.thoughtcrime.securesms.dependencies.DatabaseComponent @@ -259,7 +260,7 @@ class TokenPageViewModel @Inject constructor( // Grab the database and reader details we need to count the conversations / groups val threadDatabase = DatabaseComponent.get(context).threadDatabase() val cursor = threadDatabase.approvedConversationList - val result = mutableSetOf() + val result = mutableSetOf() // Look through the database to build up our conversation & group counts (still on Dispatchers.IO not the main thread) threadDatabase.readerFor(cursor).use { reader -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtilsImpl.kt b/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtilsImpl.kt deleted file mode 100644 index c5a81da8a8..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtilsImpl.kt +++ /dev/null @@ -1,54 +0,0 @@ -package org.thoughtcrime.securesms.util - -import network.loki.messenger.libsession_util.getOrNull -import org.session.libsession.messaging.contacts.Contact -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.UsernameUtils -import org.session.libsession.utilities.truncateIdForDisplay -import org.session.libsignal.utilities.AccountId -import org.thoughtcrime.securesms.database.SessionContactDatabase -import org.thoughtcrime.securesms.dependencies.ConfigFactory - -class UsernameUtilsImpl( - private val prefs: TextSecurePreferences, - private val configFactory: ConfigFactory, - private val sessionContactDatabase: SessionContactDatabase, -): UsernameUtils { - override fun getCurrentUsernameWithAccountIdFallback(): String = prefs.getProfileName() - ?: truncateIdForDisplay( prefs.getLocalNumber() ?: "") - - override fun getCurrentUsername(): String? = prefs.getProfileName() - - override fun saveCurrentUserName(name: String) { - configFactory.withMutableUserConfigs { - it.userProfile.setName(name) - } - } - - override fun getContactNameWithAccountID( - accountID: String, - groupId: AccountId?, - contactContext: Contact.ContactContext - ): String { - val contact = sessionContactDatabase.getContactWithAccountID(accountID) - return getContactNameWithAccountID(contact, accountID, groupId, contactContext) - } - - override fun getContactNameWithAccountID( - contact: Contact?, - accountID: String, - groupId: AccountId?, - contactContext: Contact.ContactContext) - : String { - // first attempt to get the name from the contact - val userName: String? = contact?.displayName(contactContext) - ?: if(groupId != null){ - configFactory.withGroupConfigs(groupId) { it.groupMembers.getOrNull(accountID)?.name } - } else null - - // if the username is actually set to the user's accountId, truncate it - val validatedUsername = if(userName == accountID) truncateIdForDisplay(accountID) else userName - - return if(validatedUsername.isNullOrEmpty()) truncateIdForDisplay(accountID) else validatedUsername - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt index 094bd615d9..227bc117e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.pm.PackageManager import android.telephony.TelephonyManager import androidx.core.content.ContextCompat +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow @@ -61,13 +62,16 @@ import org.webrtc.SurfaceViewRenderer import java.nio.ByteBuffer import java.util.ArrayDeque import java.util.UUID +import javax.inject.Inject +import javax.inject.Singleton import kotlin.math.abs import org.thoughtcrime.securesms.webrtc.data.State as CallState -class CallManager( - private val context: Context, +@Singleton +class CallManager @Inject constructor( + @ApplicationContext private val context: Context, audioManager: AudioManagerCompat, - private val storage: StorageProtocol + private val storage: StorageProtocol, ): PeerConnection.Observer, SignalAudioManager.EventListener, CameraEventListener, DataChannel.Observer { @@ -76,7 +80,7 @@ class CallManager( data class VideoEnabled(val isEnabled: Boolean): StateEvent() data class CallStateUpdate(val state: CallState): StateEvent() data class AudioDeviceUpdate(val selectedDevice: AudioDevice, val audioDevices: Set): StateEvent() - data class RecipientUpdate(val recipient: Recipient?): StateEvent() { + data class RecipientUpdate(val recipient: Address?): StateEvent() { companion object { val UNKNOWN = RecipientUpdate(recipient = null) } @@ -92,6 +96,8 @@ class CallManager( private const val DATA_CHANNEL_NAME = "signaling" } + private val Address.isLocalNumber: Boolean get() = address.equals(storage.getUserPublicKey(), ignoreCase = true) + private val signalAudioManager: SignalAudioManager = SignalAudioManager(context, this, audioManager) private val peerConnectionObservers = mutableSetOf() @@ -148,7 +154,7 @@ class CallManager( var pendingOfferTime: Long = -1 var preOfferCallData: PreOffer? = null var callId: UUID? = null - var recipient: Recipient? = null + var recipient: Address? = null set(value) { field = value _recipientEvents.value = RecipientUpdate(value) @@ -310,7 +316,7 @@ class CallManager( queueOutgoingIce(expectedCallId, expectedRecipient) } - private fun queueOutgoingIce(expectedCallId: UUID, expectedRecipient: Recipient) { + private fun queueOutgoingIce(expectedCallId: UUID, expectedRecipient: Address) { postViewModelState(CallViewModel.State.CALL_SENDING_ICE) outgoingIceDebouncer.publish { val currentCallId = this.callId ?: return@publish @@ -330,7 +336,7 @@ class CallManager( currentCallId ) .applyExpiryMode(thread) - .also { MessageSender.sendNonDurably(it, currentRecipient.address, isSyncMessage = currentRecipient.isLocalNumber) } + .also { MessageSender.sendNonDurably(it, currentRecipient, isSyncMessage = currentRecipient.isLocalNumber) } } } @@ -442,7 +448,7 @@ class CallManager( handleMirroring() } - fun onPreOffer(callId: UUID, recipient: Recipient, onSuccess: () -> Unit) { + fun onPreOffer(callId: UUID, recipient: Address, onSuccess: () -> Unit) { stateProcessor.processEvent(Event.ReceivePreOffer) { if (preOfferCallData != null) { Log.d(TAG, "Received new pre-offer when we are already expecting one") @@ -454,7 +460,7 @@ class CallManager( } } - fun onNewOffer(offer: String, callId: UUID, recipient: Recipient): Promise { + fun onNewOffer(offer: String, callId: UUID, recipient: Address): Promise { if (callId != this.callId) return Promise.ofFail(NullPointerException("No callId")) if (recipient != this.recipient) return Promise.ofFail(NullPointerException("No recipient")) val thread = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient) @@ -474,13 +480,13 @@ class CallManager( pendingIncomingIceUpdates.clear() val answerMessage = CallMessage.answer(answer.description, callId).applyExpiryMode(thread) Log.i("Loki", "Posting new answer") - MessageSender.sendNonDurably(answerMessage, recipient.address, isSyncMessage = recipient.isLocalNumber) + MessageSender.sendNonDurably(answerMessage, recipient, isSyncMessage = recipient.isLocalNumber) } else { Promise.ofFail(Exception("Couldn't reconnect from current state")) } } - fun onIncomingRing(offer: String, callId: UUID, recipient: Recipient, callTime: Long, onSuccess: () -> Unit) { + fun onIncomingRing(offer: String, callId: UUID, recipient: Address, callTime: Long, onSuccess: () -> Unit) { postConnectionEvent(Event.ReceiveOffer) { this.callId = callId this.recipient = recipient @@ -526,9 +532,9 @@ class CallManager( val sendAnswerMessage = MessageSender.sendNonDurably(CallMessage.answer( answer.description, callId - ).applyExpiryMode(thread), recipient.address, isSyncMessage = recipient.isLocalNumber) + ).applyExpiryMode(thread), recipient, isSyncMessage = recipient.isLocalNumber) - insertCallMessage(recipient.address.toString(), CallMessageType.CALL_INCOMING, false) + insertCallMessage(recipient.toString(), CallMessageType.CALL_INCOMING, false) while (pendingIncomingIceUpdates.isNotEmpty()) { val candidate = pendingIncomingIceUpdates.pop() ?: break @@ -579,14 +585,14 @@ class CallManager( val thread = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient) return MessageSender.sendNonDurably(CallMessage.preOffer( callId - ).applyExpiryMode(thread), recipient.address, isSyncMessage = recipient.isLocalNumber).bind { + ).applyExpiryMode(thread), recipient, isSyncMessage = recipient.isLocalNumber).bind { Log.d("Loki", "Sent pre-offer") Log.d("Loki", "Sending offer") postViewModelState(CallViewModel.State.CALL_OFFER_OUTGOING) MessageSender.sendNonDurably(CallMessage.offer( offer.description, callId - ).applyExpiryMode(thread), recipient.address, isSyncMessage = recipient.isLocalNumber).success { + ).applyExpiryMode(thread), recipient, isSyncMessage = recipient.isLocalNumber).success { Log.d("Loki", "Sent offer") }.fail { Log.e("Loki", "Failed to send offer", it) @@ -602,8 +608,8 @@ class CallManager( stateProcessor.processEvent(Event.DeclineCall) { val thread = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient) MessageSender.sendNonDurably(CallMessage.endCall(callId).applyExpiryMode(thread), Address.fromSerialized(userAddress), isSyncMessage = true) - MessageSender.sendNonDurably(CallMessage.endCall(callId).applyExpiryMode(thread), recipient.address, isSyncMessage = recipient.isLocalNumber) - insertCallMessage(recipient.address.toString(), CallMessageType.CALL_INCOMING) + MessageSender.sendNonDurably(CallMessage.endCall(callId).applyExpiryMode(thread), recipient, isSyncMessage = recipient.isLocalNumber) + insertCallMessage(recipient.toString(), CallMessageType.CALL_INCOMING) } } @@ -612,7 +618,7 @@ class CallManager( stateProcessor.processEvent(Event.IgnoreCall) } - fun handleLocalHangup(intentRecipient: Recipient?) { + fun handleLocalHangup(intentRecipient: Address?) { val recipient = recipient ?: return val callId = callId ?: return @@ -627,7 +633,7 @@ class CallManager( } val thread = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient) - MessageSender.sendNonDurably(CallMessage.endCall(callId).applyExpiryMode(thread), recipient.address, isSyncMessage = recipient.isLocalNumber) + MessageSender.sendNonDurably(CallMessage.endCall(callId).applyExpiryMode(thread), recipient, isSyncMessage = recipient.isLocalNumber) } } @@ -786,7 +792,7 @@ class CallManager( } } - fun handleResponseMessage(recipient: Recipient, callId: UUID, answer: SessionDescription) { + fun handleResponseMessage(recipient: Address, callId: UUID, answer: SessionDescription) { if (recipient != this.recipient || callId != this.callId) { Log.w(TAG,"Got answer for recipient and call ID we're not currently dialing") return @@ -859,7 +865,7 @@ class CallManager( }) connection.setLocalDescription(offer) val thread = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient) - MessageSender.sendNonDurably(CallMessage.offer(offer.description, callId).applyExpiryMode(thread), recipient.address, isSyncMessage = recipient.isLocalNumber) + MessageSender.sendNonDurably(CallMessage.offer(offer.description, callId).applyExpiryMode(thread), recipient, isSyncMessage = recipient.isLocalNumber) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt index 11acf5a7a5..9a9ec294ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt @@ -22,6 +22,7 @@ import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.OFFER import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.PRE_OFFER import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.PROVISIONAL_ANSWER import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.permissions.Permissions import org.webrtc.IceCandidate import javax.inject.Inject @@ -32,7 +33,8 @@ class CallMessageProcessor @Inject constructor( @ApplicationContext private val context: Context, private val textSecurePreferences: TextSecurePreferences, private val storage: StorageProtocol, - private val webRtcBridge: WebRtcCallBridge + private val webRtcBridge: WebRtcCallBridge, + private val recipientRepository: RecipientRepository, ) { companion object { @@ -46,7 +48,7 @@ class CallMessageProcessor @Inject constructor( val nextMessage = WebRtcUtils.SIGNAL_QUEUE.receive() Log.d("Loki", nextMessage.type?.name ?: "CALL MESSAGE RECEIVED") val sender = nextMessage.sender ?: continue - val approvedContact = Recipient.from(context, Address.fromSerialized(sender), false).isApproved + val approvedContact = recipientRepository.getRecipient(Address.fromSerialized(sender))?.approved == true Log.i("Loki", "Contact is approved?: $approvedContact") if (!approvedContact && storage.getUserPublicKey() != sender) continue diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.kt index 6738814846..a0d6777327 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.kt @@ -14,6 +14,7 @@ import com.squareup.phrase.Phrase import network.loki.messenger.R import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.notifications.NotificationChannels import org.thoughtcrime.securesms.webrtc.WebRtcCallBridge.Companion.ACTION_DENY_CALL import org.thoughtcrime.securesms.webrtc.WebRtcCallBridge.Companion.ACTION_IGNORE_CALL @@ -36,7 +37,7 @@ class CallNotificationBuilder { } @JvmStatic - fun getCallInProgressNotification(context: Context, type: Int, recipient: Recipient?): Notification { + fun getCallInProgressNotification(context: Context, type: Int, recipient: RecipientV2?): Notification { val contentIntent = WebRtcCallActivity.getCallActivityIntent(context) val pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) @@ -48,7 +49,7 @@ class CallNotificationBuilder { .setOngoing(true) var recipName = "Unknown" - recipient?.name?.let { name -> + recipient?.displayName?.let { name -> recipName = name } diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt index 88659c4dfc..1c45c0d141 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt @@ -17,6 +17,7 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.ViewUtil +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_ANSWER_INCOMING import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_ANSWER_OUTGOING import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_CONNECTED @@ -38,8 +39,8 @@ class CallViewModel @Inject constructor( @ApplicationContext private val context: Context, private val callManager: CallManager, private val rtcCallBridge: WebRtcCallBridge, - private val usernameUtils: UsernameUtils - + private val usernameUtils: UsernameUtils, + private val recipientRepository: RecipientRepository, ): ViewModel() { //todo PHONE Can we eventually remove this state and instead use the StateMachine.kt State? @@ -196,7 +197,7 @@ class CallViewModel @Inject constructor( fun denyCall() = rtcCallBridge.handleDenyCall() fun createCall(recipientAddress: Address) = - rtcCallBridge.handleOutgoingCall(Recipient.from(context, recipientAddress, true)) + rtcCallBridge.handleOutgoingCall(recipientAddress) fun hangUp() = rtcCallBridge.handleLocalHangup(null) diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/PreOffer.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PreOffer.kt index dfc2dc6fb3..f07599b721 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/PreOffer.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PreOffer.kt @@ -1,6 +1,6 @@ package org.thoughtcrime.securesms.webrtc -import org.session.libsession.utilities.recipients.Recipient -import java.util.* +import org.session.libsession.utilities.Address +import java.util.UUID -data class PreOffer(val callId: UUID, val recipient: Recipient) \ No newline at end of file +data class PreOffer(val callId: UUID, val recipient: Address) \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallBridge.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallBridge.kt index d65b5f0f22..5d7876ee03 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallBridge.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallBridge.kt @@ -19,11 +19,13 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import network.loki.messenger.BuildConfig +import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.utilities.Address import org.session.libsession.utilities.FutureTaskListener -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.database.RecipientRepository +import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.notifications.BackgroundPollWorker import org.thoughtcrime.securesms.service.CallForegroundService import org.thoughtcrime.securesms.util.NetworkConnectivity @@ -63,7 +65,9 @@ import org.thoughtcrime.securesms.webrtc.data.State as CallState class WebRtcCallBridge @Inject constructor( @ApplicationContext private val context: Context, private val callManager: CallManager, - private val networkConnectivity: NetworkConnectivity + private val networkConnectivity: NetworkConnectivity, + private val recipientRepository: RecipientRepository, + private val storage: StorageProtocol, ): CallManager.WebRtcListener { companion object { @@ -108,6 +112,9 @@ class WebRtcCallBridge @Inject constructor( } } + private val Address.isLocalNumber: Boolean + get() = address.equals(storage.getUserPublicKey(), ignoreCase = true) + @Synchronized private fun terminate() { @@ -149,14 +156,12 @@ class WebRtcCallBridge @Inject constructor( } private fun handleBusyCall(address: Address) { - val recipient = getRecipientFromAddress(address) - insertMissedCall(recipient, false) + insertMissedCall(address, false) } private fun handleNewOffer(address: Address, sdp: String, callId: UUID) { Log.d(TAG, "Handle new offer") - val recipient = getRecipientFromAddress(address) - callManager.onNewOffer(sdp, callId, recipient).fail { + callManager.onNewOffer(sdp, callId, address).fail { Log.e("Loki", "Error handling new offer", it) callManager.postConnectionError() terminate() @@ -187,17 +192,15 @@ class WebRtcCallBridge @Inject constructor( return@execute } - val recipient = getRecipientFromAddress(address) - if (isIncomingMessageExpired(callTime)) { Log.d(TAG, "Pre offer expired - message timestamp was deemed expired: ${System.currentTimeMillis() - callTime}s") - insertMissedCall(recipient, true) + insertMissedCall(address, true) terminate() return@execute } - callManager.onPreOffer(callId, recipient) { - setCallNotification(TYPE_INCOMING_PRE_OFFER, recipient) + callManager.onPreOffer(callId, address) { + setCallNotification(TYPE_INCOMING_PRE_OFFER, address) callManager.postViewModelState(CallViewModel.State.CALL_PRE_OFFER_INCOMING) callManager.initializeAudioForCall() callManager.startIncomingRinger() @@ -213,16 +216,15 @@ class WebRtcCallBridge @Inject constructor( private fun handleIncomingPreOffer(address: Address, sdp: String, callId: UUID, callTime: Long) { serviceExecutor.execute { - val recipient = getRecipientFromAddress(address) val preOffer = callManager.preOfferCallData - if (callManager.isPreOffer() && (preOffer == null || preOffer.callId != callId || preOffer.recipient.address != recipient.address)) { + if (callManager.isPreOffer() && (preOffer == null || preOffer.callId != callId || preOffer.recipient != address)) { Log.d(TAG, "Incoming ring from non-matching pre-offer") return@execute } - callManager.onIncomingRing(sdp, callId, recipient, callTime) { + callManager.onIncomingRing(sdp, callId, address, callTime) { if (_hasAcceptedCall.value) { - setCallNotification(TYPE_INCOMING_CONNECTING, recipient) + setCallNotification(TYPE_INCOMING_CONNECTING, address) } else { //No need to do anything here as this case is already taken care of from the pre offer that came before } @@ -238,7 +240,7 @@ class WebRtcCallBridge @Inject constructor( } } - fun handleOutgoingCall(recipient: Recipient) { + fun handleOutgoingCall(recipient: Address) { serviceExecutor.execute { if (!callManager.isIdle()) return@execute @@ -390,7 +392,7 @@ class WebRtcCallBridge @Inject constructor( } } - fun handleLocalHangup(recipient: Recipient?) { + fun handleLocalHangup(recipient: Address?) { serviceExecutor.execute { callManager.handleLocalHangup(recipient) terminate() @@ -419,16 +421,15 @@ class WebRtcCallBridge @Inject constructor( fun handleAnswerIncoming(address: Address, sdp: String, callId: UUID) { serviceExecutor.execute { try { - val recipient = getRecipientFromAddress(address) - if (recipient.isLocalNumber && callManager.currentConnectionState in CallState.CAN_DECLINE_STATES) { - handleLocalHangup(recipient) + if (address.isLocalNumber && callManager.currentConnectionState in CallState.CAN_DECLINE_STATES) { + handleLocalHangup(address) return@execute } callManager.postViewModelState(CallViewModel.State.CALL_ANSWER_OUTGOING) callManager.handleResponseMessage( - recipient, + address, callId, SessionDescription(SessionDescription.Type.ANSWER, sdp) ) @@ -523,7 +524,7 @@ class WebRtcCallBridge @Inject constructor( * - Directly sent by the notification manager * - Displayed as part of a foreground Service */ - private fun setCallNotification(type: Int, recipient: Recipient?) { + private fun setCallNotification(type: Int, recipient: Address?) { // send appropriate notification if we have permission if ( ActivityCompat.checkSelfPermission( @@ -553,10 +554,10 @@ class WebRtcCallBridge @Inject constructor( } @SuppressLint("MissingPermission") - private fun sendNotification(type: Int, recipient: Recipient?){ + private fun sendNotification(type: Int, recipient: Address?){ NotificationManagerCompat.from(context).notify( WEBRTC_NOTIFICATION, - CallNotificationBuilder.getCallInProgressNotification(context, type, recipient) + CallNotificationBuilder.getCallInProgressNotification(context, type, recipient?.let(recipientRepository::getRecipientSync)) ) } @@ -564,7 +565,7 @@ class WebRtcCallBridge @Inject constructor( * This will attempt to start a service with an attached notification, * if the service fails to start a manual notification will be sent */ - private fun startServiceOrShowNotification(type: Int, recipient: Recipient?){ + private fun startServiceOrShowNotification(type: Int, recipient: Address?){ try { ContextCompat.startForegroundService(context, CallForegroundService.startIntent(context, type, recipient)) } catch (e: Exception) { @@ -573,9 +574,7 @@ class WebRtcCallBridge @Inject constructor( } } - private fun getRecipientFromAddress(address: Address): Recipient = Recipient.from(context, address, true) - - private fun insertMissedCall(recipient: Recipient, signal: Boolean) { + private fun insertMissedCall(recipient: Address, signal: Boolean) { callManager.insertCallMessage( threadPublicKey = recipient.address.toString(), callMessageType = CallMessageType.CALL_MISSED, From 994c629d74ba9a8399af290e44eb308613814c06 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Thu, 26 Jun 2025 16:25:17 +1000 Subject: [PATCH 09/52] WIP --- .../libsession/database/StorageProtocol.kt | 3 +- .../messaging/MessagingModuleConfiguration.kt | 2 - .../messaging/groups/GroupInviteException.kt | 9 +- .../messaging/jobs/InviteContactsJob.kt | 2 +- .../utilities/UpdateMessageBuilder.kt | 28 +- .../session/libsession/utilities/Address.kt | 3 + .../utilities/ConfigFactoryProtocol.kt | 6 + .../utilities/recipients/RecipientV2.kt | 164 +++++-- .../securesms/ApplicationContext.kt | 4 - .../securesms/MediaPreviewActivity.kt | 21 +- .../components/ProfilePictureView.kt | 9 +- .../contacts/ShareContactListLoader.kt | 2 +- .../securesms/contacts/UserView.kt | 12 +- .../conversation/v2/ConversationActivityV2.kt | 14 +- .../conversation/v2/ConversationViewModel.kt | 9 +- .../v2/MessageDetailsViewModel.kt | 2 +- .../conversation/v2/dialogs/DownloadDialog.kt | 2 +- .../v2/messages/ControlMessageView.kt | 8 +- .../v2/messages/VisibleMessageView.kt | 9 +- .../settings/ConversationSettingsViewModel.kt | 36 +- .../securesms/database/RecipientRepository.kt | 278 ++++++----- .../securesms/database/Storage.kt | 53 +-- .../securesms/dependencies/ConfigFactory.kt | 2 - .../dependencies/SessionUtilModule.kt | 16 - .../groups/BaseGroupMembersViewModel.kt | 13 +- .../securesms/groups/EditGroupViewModel.kt | 12 +- .../securesms/groups/GroupManagerV2Impl.kt | 5 +- .../securesms/groups/GroupMembersViewModel.kt | 9 +- .../securesms/home/ConversationView.kt | 3 +- .../securesms/home/HomeActivity.kt | 11 +- .../securesms/home/HomeViewModel.kt | 5 +- .../securesms/home/UserDetailsBottomSheet.kt | 9 +- .../home/search/GlobalSearchAdapterUtils.kt | 5 +- .../messagerequests/MessageRequestView.kt | 2 +- .../MessageRequestsActivity.kt | 2 +- .../AbstractNotificationBuilder.java | 9 +- .../notifications/DefaultMessageNotifier.kt | 25 +- .../notifications/MarkReadReceiver.kt | 16 +- .../MultipleRecipientNotificationBuilder.kt | 37 +- .../securesms/notifications/ReplyMethod.java | 5 +- .../SingleRecipientNotificationBuilder.java | 25 +- .../manager/CreateAccountManager.kt | 8 +- .../pickname/PickDisplayNameViewModel.kt | 8 +- .../preferences/BlockedContactsAdapter.kt | 2 +- .../preferences/BlockedContactsViewModel.kt | 8 +- .../preferences/SettingsViewModel.kt | 12 +- .../securesms/util/AvatarUtils.kt | 7 +- .../securesms/util/MockDataGenerator.kt | 441 ------------------ .../securesms/util/SessionMetaProtocol.kt | 6 +- .../securesms/webrtc/CallViewModel.kt | 13 +- 50 files changed, 492 insertions(+), 900 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 749bfcd6b0..70c5ee20d2 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -48,8 +48,7 @@ interface StorageProtocol { fun getUserBlindedAccountId(serverPublicKey: String): AccountId? fun getUserProfile(): Profile fun setBlocksCommunityMessageRequests(recipient: Address, blocksMessageRequests: Boolean) - fun setUserProfilePicture(newProfilePicture: String?, newProfileKey: ByteArray?) - fun clearUserPic(clearConfig: Boolean = true) + // Signal fun getOrGenerateRegistrationID(): Int diff --git a/app/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt b/app/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt index 4df6f724f9..f9602c0921 100644 --- a/app/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt +++ b/app/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt @@ -11,7 +11,6 @@ import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.Device import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.Toaster -import org.session.libsession.utilities.UsernameUtils import org.thoughtcrime.securesms.database.RecipientRepository class MessagingModuleConfiguration( @@ -26,7 +25,6 @@ class MessagingModuleConfiguration( val clock: SnodeClock, val preferences: TextSecurePreferences, val deprecationManager: LegacyGroupDeprecationManager, - val usernameUtils: UsernameUtils, val recipientRepository: RecipientRepository, ) { diff --git a/app/src/main/java/org/session/libsession/messaging/groups/GroupInviteException.kt b/app/src/main/java/org/session/libsession/messaging/groups/GroupInviteException.kt index 415a4a6f51..402c65dc5f 100644 --- a/app/src/main/java/org/session/libsession/messaging/groups/GroupInviteException.kt +++ b/app/src/main/java/org/session/libsession/messaging/groups/GroupInviteException.kt @@ -3,13 +3,12 @@ package org.session.libsession.messaging.groups import android.content.Context import com.squareup.phrase.Phrase import network.loki.messenger.R -import org.session.libsession.database.StorageProtocol +import org.session.libsession.utilities.Address import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.OTHER_NAME_KEY -import org.session.libsession.utilities.UsernameUtils -import org.session.libsession.utilities.truncateIdForDisplay +import org.thoughtcrime.securesms.database.RecipientRepository /** * Exception that occurs during a group invite. @@ -31,9 +30,9 @@ class GroupInviteException( } } - fun format(context: Context, usernameUtils: UsernameUtils): CharSequence { + fun format(context: Context, recipientRepository: RecipientRepository): CharSequence { val getInviteeName = { accountId: String -> - usernameUtils.getContactNameWithAccountID(accountId) + recipientRepository.getRecipientDisplayNameSync(Address.fromSerialized(accountId)) } val first = inviteeAccountIds.first().let(getInviteeName) diff --git a/app/src/main/java/org/session/libsession/messaging/jobs/InviteContactsJob.kt b/app/src/main/java/org/session/libsession/messaging/jobs/InviteContactsJob.kt index 707883cbc4..33418f9a67 100644 --- a/app/src/main/java/org/session/libsession/messaging/jobs/InviteContactsJob.kt +++ b/app/src/main/java/org/session/libsession/messaging/jobs/InviteContactsJob.kt @@ -127,7 +127,7 @@ class InviteContactsJob(val groupSessionId: String, val memberSessionIds: Array< groupName = groupName.orEmpty(), underlying = firstError, ).format(MessagingModuleConfiguration.shared.context, - MessagingModuleConfiguration.shared.usernameUtils).let { + MessagingModuleConfiguration.shared.recipientRepository).let { withContext(Dispatchers.Main) { toaster.toast(it, Toast.LENGTH_LONG) } diff --git a/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt b/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt index 796dd77bed..69155b4f93 100644 --- a/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt +++ b/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -2,41 +2,51 @@ package org.session.libsession.messaging.utilities import android.content.Context import com.squareup.phrase.Phrase -import network.loki.messenger.libsession_util.getOrNull import network.loki.messenger.R +import network.loki.messenger.libsession_util.getOrNull import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.calls.CallMessageType.CALL_FIRST_MISSED import org.session.libsession.messaging.calls.CallMessageType.CALL_INCOMING import org.session.libsession.messaging.calls.CallMessageType.CALL_MISSED import org.session.libsession.messaging.calls.CallMessageType.CALL_OUTGOING -import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage.Kind.SCREENSHOT +import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ExpirationUtil -import org.session.libsession.utilities.getExpirationTypeDisplayValue -import org.session.libsession.utilities.truncateIdForDisplay -import org.session.libsignal.utilities.Log import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY import org.session.libsession.utilities.StringSubstitutionConstants.DISAPPEARING_MESSAGES_TYPE_KEY import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.OTHER_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY +import org.session.libsession.utilities.getExpirationTypeDisplayValue import org.session.libsession.utilities.getGroup import org.session.libsignal.utilities.AccountId +import org.session.libsignal.utilities.Log object UpdateMessageBuilder { const val TAG = "UpdateMessageBuilder" val storage = MessagingModuleConfiguration.shared.storage - val usernameUtils = MessagingModuleConfiguration.shared.usernameUtils + val recipientRepository = MessagingModuleConfiguration.shared.recipientRepository - private fun getGroupMemberName(memberId: String, groupId: AccountId? = null) = - usernameUtils.getContactNameWithAccountID(memberId, groupId) + private fun getGroupMemberName(senderAddress: String, groupV2Id: AccountId? = null): String { + return recipientRepository.getRecipientDisplayNameSync( + address = Address.fromSerialized(senderAddress), + // There's additional way to getting a group member's name via config system. + fallbackName = { + groupV2Id?.let { gid -> + MessagingModuleConfiguration.shared.configFactory.withGroupConfigs(gid) { + it.groupMembers.getOrNull(senderAddress)?.name + } + } + } + ) + } @JvmStatic fun buildGroupUpdateMessage( @@ -402,7 +412,7 @@ object UpdateMessageBuilder { } fun buildCallMessage(context: Context, type: CallMessageType, senderId: String): String { - val senderName = usernameUtils.getContactNameWithAccountID(senderId) + val senderName = recipientRepository.getRecipientDisplayNameSync(Address.fromSerialized(senderId)) return when (type) { CALL_INCOMING -> Phrase.from(context, R.string.callsCalledYou).put(NAME_KEY, senderName) diff --git a/app/src/main/java/org/session/libsession/utilities/Address.kt b/app/src/main/java/org/session/libsession/utilities/Address.kt index 8c501ce23a..9ddba07a26 100644 --- a/app/src/main/java/org/session/libsession/utilities/Address.kt +++ b/app/src/main/java/org/session/libsession/utilities/Address.kt @@ -42,6 +42,9 @@ data class Address private constructor(val address: String) : Parcelable, Compar override fun compareTo(other: Address): Int = address.compareTo(other.address) + val debugString: String + get() = "Address(address=${address.substring(0, address.length.coerceAtMost(5))}...)" + companion object { val UNKNOWN = Address("Unknown") diff --git a/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt b/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt index 85c06628a5..b1607ee800 100644 --- a/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt +++ b/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt @@ -24,6 +24,7 @@ import network.loki.messenger.libsession_util.ReadableUserGroupsConfig import network.loki.messenger.libsession_util.ReadableUserProfile import network.loki.messenger.libsession_util.util.ConfigPush import network.loki.messenger.libsession_util.util.GroupInfo +import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.snode.SwarmAuth import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.AccountId @@ -112,6 +113,11 @@ enum class UserConfigType(val namespace: Int) { USER_GROUPS(Namespace.USER_GROUPS()), } +val ConfigFactoryProtocol.currentUserName: String get() = withUserConfigs { it.userProfile.getName().orEmpty() } +val ConfigFactoryProtocol.currentUserProfile: UserPic? get() = withUserConfigs { configs -> + configs.userProfile.getPic().takeIf { it.url.isNotBlank() } +} + /** * Shortcut to get the group info for a closed group. Equivalent to: `withUserConfigs { it.userGroups.getClosedGroup(groupId) }` */ diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt index da76b95992..02cc2fa8ed 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt @@ -4,87 +4,171 @@ import network.loki.messenger.libsession_util.util.Bytes import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.RecipientAvatar.EncryptedRemotePic -import org.session.libsession.utilities.recipients.RecipientAvatar.Inline +import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.model.NotifyType import java.time.ZonedDateTime data class RecipientV2( - val isLocalNumber: Boolean, - val address: Address, - val nickname: String?, - val name: String, - val approved: Boolean, - val approvedMe: Boolean, - val blocked: Boolean, + val basic: BasicRecipient, val mutedUntil: ZonedDateTime?, val autoDownloadAttachments: Boolean?, @get:NotifyType val notifyType: Int, - val avatar: RecipientAvatar?, - val expiryMode: ExpiryMode, val acceptsCommunityMessageRequests: Boolean, + val notificationChannel: String? = null, ) { - val isGroupOrCommunityRecipient: Boolean get() = address.isGroupOrCommunity - val isCommunityRecipient: Boolean get() = address.isCommunity - val isCommunityInboxRecipient: Boolean get() = address.isCommunityInbox - val isCommunityOutboxRecipient: Boolean get() = address.isCommunityOutbox - val isGroupV2Recipient: Boolean get() = address.isGroupV2 - val isLegacyGroupRecipient: Boolean get() = address.isLegacyGroup - val isContactRecipient: Boolean get() = address.isContact - val is1on1: Boolean get() = !isLocalNumber && address.isContact - val isGroupRecipient: Boolean get() = address.isGroup + val isLocalNumber: Boolean get() = basic.isLocalNumber + val address: Address get() = basic.address + val avatar: RecipientAvatar? get() = basic.avatar - val displayName: String - get() = nickname?.takeIf { it.isNotBlank() } ?: name + val isGroupOrCommunityRecipient: Boolean get() = basic.isGroupOrCommunityRecipient + val isCommunityRecipient: Boolean get() = basic.isCommunityRecipient + val isCommunityInboxRecipient: Boolean get() = basic.isCommunityInboxRecipient + val isCommunityOutboxRecipient: Boolean get() = basic.isCommunityOutboxRecipient + val isGroupV2Recipient: Boolean get() = basic.isGroupV2Recipient + val isLegacyGroupRecipient: Boolean get() = basic.isLegacyGroupRecipient + val isContactRecipient: Boolean get() = basic.isContactRecipient + val is1on1: Boolean get() = basic.is1on1 + val isGroupRecipient: Boolean get() = basic.isGroupRecipient + + val displayName: String get() = basic.displayName + val expiryMode: ExpiryMode get() = when (basic) { + is BasicRecipient.Self -> basic.expiryMode + is BasicRecipient.Contact -> basic.expiryMode + is BasicRecipient.Group -> basic.expiryMode + else -> ExpiryMode.NONE + } + + val approved: Boolean get() = (basic as? BasicRecipient.Contact)?.approved ?: true + val approvedMe: Boolean get() = (basic as? BasicRecipient.Contact)?.approvedMe ?: true + val blocked: Boolean get() = when (basic) { + is BasicRecipient.Generic -> basic.blocked + is BasicRecipient.Contact -> basic.blocked + else -> false + } fun isMuted(now: ZonedDateTime = ZonedDateTime.now()): Boolean { return mutedUntil?.isAfter(now) == true } val showCallMenu: Boolean - get() = !isGroupOrCommunityRecipient && approvedMe && approved; + get() = !isGroupOrCommunityRecipient && approvedMe && approved val mutedUntilMills: Long? get() = mutedUntil?.toInstant()?.toEpochMilli() @Deprecated("Use `avatar` property instead", ReplaceWith("avatar")) val profileAvatar: String? - get() = (avatar as? EncryptedRemotePic)?.url + get() = (avatar as? RecipientAvatar.EncryptedRemotePic)?.url @Deprecated("Use `avatar` property instead", ReplaceWith("avatar?.toUserPic()")) val profileKey: ByteArray? - get() = (avatar as? EncryptedRemotePic)?.key?.data + get() = (avatar as? RecipientAvatar.EncryptedRemotePic)?.key?.data companion object { fun empty(address: Address): RecipientV2 { return RecipientV2( - isLocalNumber = false, - address = address, - nickname = null, - name = "", - approved = false, - approvedMe = false, - blocked = false, + basic = BasicRecipient.Generic(address), mutedUntil = null, autoDownloadAttachments = true, notifyType = RecipientDatabase.NOTIFY_TYPE_ALL, - avatar = null, - expiryMode = ExpiryMode.NONE, acceptsCommunityMessageRequests = false, ) } } } +sealed interface BasicRecipient { + val address: Address + val isLocalNumber: Boolean + val displayName: String + val avatar: RecipientAvatar? + + /** + * A recipient that is backed by the config system. + */ + sealed interface ConfigBasedRecipient : BasicRecipient + + data class Generic( + override val address: Address, + override val displayName: String = "", + override val avatar: RecipientAvatar? = null, + override val isLocalNumber: Boolean = false, + val blocked: Boolean = false, + ) : BasicRecipient + + /** + * Yourself. + */ + data class Self( + val name: String, + override val address: Address, + override val avatar: RecipientAvatar.EncryptedRemotePic?, + val expiryMode: ExpiryMode, + val acceptsCommunityMessageRequests: Boolean, + ) : ConfigBasedRecipient { + override val displayName: String + get() = name + + override val isLocalNumber: Boolean + get() = true + } + + /** + * A recipient that is your **real** contact. + */ + data class Contact( + override val address: Address, + val name: String, + val nickname: String?, + override val avatar: RecipientAvatar.EncryptedRemotePic?, + val approved: Boolean, + val approvedMe: Boolean, + val blocked: Boolean, + val expiryMode: ExpiryMode + ) : ConfigBasedRecipient { + override val displayName: String + get() = nickname?.takeIf { it.isNotBlank() } ?: name + + override val isLocalNumber: Boolean + get() = false + } + + /** + * A recipient that is a groupv2. + */ + data class Group( + override val address: Address, + val name: String, + override val avatar: RecipientAvatar.EncryptedRemotePic?, + val expiryMode: ExpiryMode, + ) : ConfigBasedRecipient { + override val displayName: String + get() = name + + override val isLocalNumber: Boolean + get() = false + } +} + +val BasicRecipient.isGroupOrCommunityRecipient: Boolean get() = address.isGroupOrCommunity +val BasicRecipient.isCommunityRecipient: Boolean get() = address.isCommunity +val BasicRecipient.isCommunityInboxRecipient: Boolean get() = address.isCommunityInbox +val BasicRecipient.isCommunityOutboxRecipient: Boolean get() = address.isCommunityOutbox +val BasicRecipient.isGroupV2Recipient: Boolean get() = address.isGroupV2 +val BasicRecipient.isLegacyGroupRecipient: Boolean get() = address.isLegacyGroup +val BasicRecipient.isContactRecipient: Boolean get() = address.isContact +val BasicRecipient.is1on1: Boolean get() = !isLocalNumber && address.isContact +val BasicRecipient.isGroupRecipient: Boolean get() = address.isGroup + sealed interface RecipientAvatar { data class EncryptedRemotePic(val url: String, val key: Bytes) : RecipientAvatar data class Inline(val bytes: Bytes) : RecipientAvatar companion object { - fun UserPic.toRecipientAvatar(): RecipientAvatar? { + fun UserPic.toRecipientAvatar(): RecipientAvatar.EncryptedRemotePic? { return when { url.isBlank() -> null else -> EncryptedRemotePic( @@ -114,7 +198,13 @@ sealed interface RecipientAvatar { fun RecipientAvatar.toUserPic(): UserPic? { return when (this) { - is EncryptedRemotePic -> UserPic(url, key) - is Inline -> null + is RecipientAvatar.EncryptedRemotePic -> UserPic(url, key) + is RecipientAvatar.Inline -> null } +} + +inline fun RecipientV2?.displayNameOrFallback(fallbackName: () -> String? = { null }, address: String): String { + return this?.displayName + ?: fallbackName()?.takeIf { it.isNotBlank() } + ?: truncateIdForDisplay(address) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt index d9314014e3..50717a46a9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.kt @@ -61,7 +61,6 @@ import org.session.libsession.utilities.SSKEnvironment.Companion.configure import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.pushSuffix import org.session.libsession.utilities.Toaster -import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.WindowDebouncer import org.session.libsignal.utilities.HTTP.isConnectedToNetwork import org.session.libsignal.utilities.JsonUtil @@ -186,7 +185,6 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, @Inject lateinit var webRtcCallBridge: Lazy @Inject lateinit var legacyGroupDeprecationManager: Lazy @Inject lateinit var cleanupInvitationHandler: Lazy - @Inject lateinit var usernameUtils: Lazy @Inject lateinit var pollerManager: Lazy @Inject lateinit var recipientRepository: Lazy @@ -289,7 +287,6 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, clock = snodeClock.get(), preferences = textSecurePreferences.get(), deprecationManager = legacyGroupDeprecationManager.get(), - usernameUtils = usernameUtils.get(), recipientRepository = recipientRepository.get(), ) @@ -379,7 +376,6 @@ class ApplicationContext : Application(), DefaultLifecycleObserver, pollerManager.get() legacyGroupDeprecationManager.get() cleanupInvitationHandler.get() - usernameUtils.get() backgroundPollManager.get() appVisibilityManager.get() groupPollerManager.get() diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt index 88731b3e23..1ea55d4034 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt @@ -65,16 +65,14 @@ import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAt import org.session.libsession.snode.SnodeAPI.nowWithOffset import org.session.libsession.utilities.Address import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY -import org.session.libsession.utilities.Util.runOnMain import org.session.libsession.utilities.getColorFromAttr -import org.session.libsession.utilities.recipients.Recipient -import org.session.libsession.utilities.recipients.RecipientModifiedListener import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ShareActivity import org.thoughtcrime.securesms.components.MediaView import org.thoughtcrime.securesms.components.dialogs.DeleteMediaPreviewDialog import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.media.MediaOverviewActivity @@ -121,6 +119,9 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), @Inject lateinit var dateUtils: DateUtils + @Inject + lateinit var recipientRepository: RecipientRepository + override val applyDefaultWindowInsets: Boolean get() = false @@ -274,7 +275,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), if (mediaItem.outgoing) supportActionBar?.title = getString(R.string.you) else if (mediaItem.recipient != null) supportActionBar?.title = - mediaItem.recipient.name + mediaItem.recipient.displayName else supportActionBar?.title = "" supportActionBar?.subtitle = relativeTimeSpan @@ -643,7 +644,6 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), try { val item = adapter!!.getMediaItemFor(position) - if (item.recipient != null) item.recipient.addListener(this@MediaPreviewActivity) viewModel.setActiveAlbumRailItem(this@MediaPreviewActivity, position) updateActionBar() } catch (e: Exception){ @@ -657,7 +657,6 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), try { val item = adapter!!.getMediaItemFor(position) - if (item.recipient != null) item.recipient.removeListener(this@MediaPreviewActivity) } catch (e: CursorIndexOutOfBoundsException) { throw RuntimeException("position = $position leftIsRecent = $leftIsRecent", e) } catch (e: Exception){ @@ -679,8 +678,9 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), } } - private class CursorPagerAdapter( - context: Context, private val glideRequests: RequestManager, + private inner class CursorPagerAdapter( + context: Context, + private val glideRequests: RequestManager, private val window: Window, private val cursor: Cursor, private var autoPlayPosition: Int, private val leftIsRecent: Boolean ) : MediaItemAdapter() { @@ -742,7 +742,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), if (mediaRecord.attachment.dataUri == null) throw AssertionError() return MediaItem( - if (address != null) Recipient.from(context, address, true) else null, + address?.let(recipientRepository::getRecipientSync), mediaRecord.attachment, mediaRecord.attachment.dataUri!!, mediaRecord.contentType, @@ -809,7 +809,8 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), fun getPreviewIntent(context: Context?, args: MediaPreviewArgs): Intent? { return getPreviewIntent( context, args.slide, - args.mmsRecord, args.thread.address + args.mmsRecord, + args.thread ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index 2321a3f39e..13eededd8c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -21,8 +21,6 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.utilities.Address import org.session.libsession.utilities.AppTextSecurePreferences import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.UsernameUtils -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientAvatar import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.truncateIdForDisplay @@ -56,9 +54,6 @@ class ProfilePictureView @JvmOverloads constructor( @Inject lateinit var storage: StorageProtocol - @Inject - lateinit var usernameUtils: UsernameUtils - @Inject lateinit var avatarUtils: AvatarUtils @@ -105,8 +100,8 @@ class ProfilePictureView @JvmOverloads constructor( address: Address, profileViewDataType: ProfileViewDataType = ProfileViewDataType.OneOnOne ) { - fun getUserDisplayName(publicKey: String): String = prefs.takeIf { userPublicKey == publicKey }?.getProfileName() - ?: usernameUtils.getContactNameWithAccountID(publicKey) + fun getUserDisplayName(publicKey: String): String = recipientRepository.getRecipientDisplayNameSync( + Address.fromSerialized(publicKey)) // group avatar if (profileViewDataType is ProfileViewDataType.GroupvV2 || profileViewDataType is ProfileViewDataType.LegacyGroup) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt index 35786f3b68..e1d435f794 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt @@ -30,7 +30,7 @@ class ShareContactListLoader( return@filter openGroup.canWrite } if (filter.isNullOrEmpty()) return@filter true - it.first.name.contains(filter.trim(), true) || it.first.address.toString().contains(filter.trim(), true) + it.first.displayName.contains(filter.trim(), true) || it.first.address.toString().contains(filter.trim(), true) }.sortedWith( compareBy> { !it.first.isLocalNumber } // NTS come first .thenByDescending { it.second } // then order by last message time diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt index 71f756d340..d2c6124373 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt @@ -8,17 +8,12 @@ import android.widget.LinearLayout import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ViewUserBinding -import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.recipients.RecipientV2 -import javax.inject.Inject @AndroidEntryPoint class UserView : LinearLayout { private lateinit var binding: ViewUserBinding - @Inject - lateinit var usernameUtils: UsernameUtils - enum class ActionIndicator { None, Menu, @@ -51,18 +46,17 @@ class UserView : LinearLayout { fun bind(user: RecipientV2, actionIndicator: ActionIndicator, isSelected: Boolean = false, showCurrentUserAsNoteToSelf: Boolean = false) { val isLocalUser = user.isLocalNumber - fun getUserDisplayName(publicKey: String): String { + fun getUserDisplayName(): String { return when { isLocalUser && showCurrentUserAsNoteToSelf -> context.getString(R.string.noteToSelf) isLocalUser && !showCurrentUserAsNoteToSelf -> context.getString(R.string.you) - else -> usernameUtils.getContactNameWithAccountID(publicKey) + else -> user.displayName } } - val address = user.address.toString() binding.profilePictureView.update(user) binding.actionIndicatorImageView.setImageResource(R.drawable.ic_radio_unselected) - binding.nameTextView.text = if (user.isGroupOrCommunityRecipient) user.name else getUserDisplayName(address) + binding.nameTextView.text = if (user.isGroupOrCommunityRecipient) user.displayName else getUserDisplayName() when (actionIndicator) { ActionIndicator.None -> { binding.actionIndicatorImageView.visibility = View.GONE diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 0e7651e371..f3edbf097c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -990,7 +990,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, if (shouldShowLegacy) { val txt = Phrase.from(this, R.string.disappearingMessagesLegacy) - .put(NAME_KEY, legacyRecipient!!.name) + .put(NAME_KEY, legacyRecipient!!.displayName) .format() binding.conversationHeader.outdatedDisappearingBannerTextView.text = txt } @@ -1365,12 +1365,12 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, binding.conversationRecyclerView.isVisible = false binding.placeholderText.text = when (groupThreadStatus) { GroupThreadStatus.Kicked -> Phrase.from(this, R.string.groupRemovedYou) - .put(GROUP_NAME_KEY, recipient.name) + .put(GROUP_NAME_KEY, recipient.displayName) .format() .toString() GroupThreadStatus.Destroyed -> Phrase.from(this, R.string.groupDeletedMemberDescription) - .put(GROUP_NAME_KEY, recipient.name) + .put(GROUP_NAME_KEY, recipient.displayName) .format() .toString() @@ -1394,7 +1394,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, // If we're trying to message someone who has blocked community message requests blindedRecipient?.acceptsCommunityMessageRequests == false -> { Phrase.from(applicationContext, R.string.messageRequestsTurnedOff) - .put(NAME_KEY, recipient.name) + .put(NAME_KEY, recipient.displayName) .format() } @@ -1402,7 +1402,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, recipient.isCommunityInboxRecipient || recipient.isCommunityOutboxRecipient || recipient.is1on1 || recipient.isGroupOrCommunityRecipient -> { Phrase.from(applicationContext, R.string.groupNoMessages) - .put(GROUP_NAME_KEY, recipient.name) + .put(GROUP_NAME_KEY, recipient.displayName) .format() } @@ -1490,7 +1490,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, val name = if (recipient.isGroupV2Recipient && invitingAdmin != null) { invitingAdmin.getSearchName() } else { - recipient.name + recipient.displayName } showSessionDialog { @@ -1527,7 +1527,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, title(R.string.blockUnblock) text( Phrase.from(context, R.string.blockUnblockName) - .put(NAME_KEY, recipient.name) + .put(NAME_KEY, recipient.displayName) .format() ) dangerButton(R.string.blockUnblock, R.string.AccessibilityId_unblockConfirm) { viewModel.unblock() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 3bdc4cfb79..53af01fc11 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -46,10 +46,10 @@ import org.session.libsession.utilities.ExpirationUtil import org.session.libsession.utilities.StringSubstitutionConstants.DATE_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.recipients.MessageType import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.displayNameOrFallback import org.session.libsession.utilities.recipients.getType import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Hex @@ -104,7 +104,6 @@ class ConversationViewModel @AssistedInject constructor( val legacyGroupDeprecationManager: LegacyGroupDeprecationManager, val dateUtils: DateUtils, private val expiredGroupManager: ExpiredGroupManager, - private val usernameUtils: UsernameUtils, private val avatarUtils: AvatarUtils, private val recipientChangeSource: RecipientChangeSource, private val openGroupManager: OpenGroupManager, @@ -484,7 +483,7 @@ class ConversationViewModel @AssistedInject constructor( // calculate the main app bar data val avatarData = avatarUtils.getUIDataFromRecipient(conversation) _appBarData.value = ConversationAppBarData( - title = conversation.takeUnless { it?.isLocalNumber == true }?.name ?: application.getString(R.string.noteToSelf), + title = conversation.takeUnless { it?.isLocalNumber == true }?.displayName ?: application.getString(R.string.noteToSelf), pagerData = pagerData, showCall = conversation?.showCallMenu ?: false, showAvatar = showOptionsMenu, @@ -1259,7 +1258,9 @@ class ConversationViewModel @AssistedInject constructor( } } - fun getUsername(accountId: String) = usernameUtils.getContactNameWithAccountID(accountId) + fun getUsername(accountId: String) = recipientRepository + .getRecipientSync(fromSerialized(accountId)) + .displayNameOrFallback(address = accountId) fun onSearchOpened(){ _appBarData.update { _appBarData.value.copy(showSearch = true) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index 301d5592c2..d4ae52d657 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -169,7 +169,7 @@ class MessageDetailsViewModel @AssistedInject constructor( status = status, senderInfo = sender.run { TitledText( - if(messageRecord.isOutgoing) context.getString(R.string.you) else name, + if(messageRecord.isOutgoing) context.getString(R.string.you) else displayName, address.toString() ) }, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt index 361738169b..d556995e0b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt @@ -30,7 +30,7 @@ class AutoDownloadDialog(private val threadRecipient: RecipientV2, title(getString(R.string.attachmentsAutoDownloadModalTitle)) val explanation = Phrase.from(context, R.string.attachmentsAutoDownloadModalDescription) - .put(CONVERSATION_NAME_KEY, threadRecipient.name) + .put(CONVERSATION_NAME_KEY, threadRecipient.displayName) .format() text(explanation) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index 45535937ac..304bfbc7ce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -190,13 +190,13 @@ class ControlMessageView : LinearLayout { context.showSessionDialog { val titleTxt = context.getSubbedString( R.string.callsMissedCallFrom, - NAME_KEY to message.individualRecipient.name + NAME_KEY to message.individualRecipient.displayName ) title(titleTxt) val bodyTxt = context.getSubbedCharSequence( R.string.callsYouMissedCallPermissions, - NAME_KEY to message.individualRecipient.name + NAME_KEY to message.individualRecipient.displayName ) text(bodyTxt) @@ -219,13 +219,13 @@ class ControlMessageView : LinearLayout { context.showSessionDialog { val titleTxt = context.getSubbedString( R.string.callsMissedCallFrom, - NAME_KEY to message.individualRecipient.name + NAME_KEY to message.individualRecipient.displayName ) title(titleTxt) val bodyTxt = context.getSubbedCharSequence( R.string.callsMicrophonePermissionsRequired, - NAME_KEY to message.individualRecipient.name + NAME_KEY to message.individualRecipient.displayName ) text(bodyTxt) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index fb1503bfc6..edbc5602da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -41,7 +41,6 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ThemeUtil.getThemedColor -import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.ViewUtil import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.modifyLayoutParams @@ -87,7 +86,6 @@ class VisibleMessageView : FrameLayout { @Inject lateinit var mmsDb: MmsDatabase @Inject lateinit var dateUtils: DateUtils @Inject lateinit var configFactory: ConfigFactoryProtocol - @Inject lateinit var usernameUtils: UsernameUtils @Inject lateinit var openGroupManager: OpenGroupManager @Inject lateinit var recipientRepository: RecipientRepository @@ -257,12 +255,7 @@ class VisibleMessageView : FrameLayout { binding.senderNameTextView.isVisible = !message.isOutgoing && (isStartOfMessageCluster && (isGroupThread || snIsSelected)) val contactContext = if (thread.isCommunityRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR - binding.senderNameTextView.text = usernameUtils.getContactNameWithAccountID( - contact = contact, - accountID = senderAccountID, - contactContext = contactContext, - groupId = groupId - ) + binding.senderNameTextView.text = recipientRepository.getRecipientDisplayNameSync(Address.fromSerialized(senderAccountID)) // Unread marker val shouldShowUnreadMarker = lastSeen != -1L && message.timestamp > lastSeen && (previous == null || previous.timestamp <= lastSeen) && !message.isOutgoing diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt index 0bf54cb4a6..67ac8f0287 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt @@ -34,7 +34,6 @@ import network.loki.messenger.libsession_util.util.BlindKeyAPI import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.GroupInfo import org.session.libsession.database.StorageProtocol -import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.utilities.Address.Companion.fromSerialized @@ -227,10 +226,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( // name val name = when { conversation.isLocalNumber -> context.getString(R.string.noteToSelf) - - conversation.isGroupV2Recipient -> getGroupName() - - else -> conversation.name + else -> conversation.displayName } // account ID @@ -552,7 +548,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( showSimpleDialog = Dialog( title = context.getString(R.string.block), message = Phrase.from(context, R.string.blockDescription) - .put(NAME_KEY, recipient?.name ?: "") + .put(NAME_KEY, recipient?.displayName ?: "") .format(), positiveText = context.getString(R.string.block), negativeText = context.getString(R.string.cancel), @@ -571,7 +567,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( showSimpleDialog = Dialog( title = context.getString(R.string.blockUnblock), message = Phrase.from(context, R.string.blockUnblockName) - .put(NAME_KEY, recipient?.name ?: "") + .put(NAME_KEY, recipient?.displayName ?: "") .format(), positiveText = context.getString(R.string.blockUnblock), negativeText = context.getString(R.string.cancel), @@ -667,8 +663,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( showSimpleDialog = Dialog( title = context.getString(R.string.contactDelete), message = Phrase.from(context, R.string.deleteContactDescription) - .put(NAME_KEY, recipient?.name ?: "") - .put(NAME_KEY, recipient?.name ?: "") + .put(NAME_KEY, recipient?.displayName ?: "") .format(), positiveText = context.getString(R.string.delete), negativeText = context.getString(R.string.cancel), @@ -700,7 +695,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( showSimpleDialog = Dialog( title = context.getString(R.string.conversationsDelete), message = Phrase.from(context, R.string.deleteConversationDescription) - .put(NAME_KEY, recipient?.name ?: "") + .put(NAME_KEY, recipient?.displayName ?: "") .format(), positiveText = context.getString(R.string.delete), negativeText = context.getString(R.string.cancel), @@ -731,7 +726,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( showSimpleDialog = Dialog( title = context.getString(R.string.communityLeave), message = Phrase.from(context, R.string.groupLeaveDescription) - .put(GROUP_NAME_KEY, recipient?.name ?: "") + .put(GROUP_NAME_KEY, recipient?.displayName ?: "") .format(), positiveText = context.getString(R.string.leave), negativeText = context.getString(R.string.cancel), @@ -764,26 +759,26 @@ class ConversationSettingsViewModel @AssistedInject constructor( // default to 1on1 var message: CharSequence = Phrase.from(context, R.string.clearMessagesChatDescriptionUpdated) - .put(NAME_KEY,conversation.name) + .put(NAME_KEY,conversation.displayName) .format() when{ conversation.isGroupV2Recipient -> { if(groupV2?.hasAdminKey() == true){ // group admin clearing messages have a dedicated custom dialog - _dialogState.update { it.copy(groupAdminClearMessagesDialog = GroupAdminClearMessageDialog(getGroupName())) } + _dialogState.update { it.copy(groupAdminClearMessagesDialog = GroupAdminClearMessageDialog(conversation.displayName)) } return } else { message = Phrase.from(context, R.string.clearMessagesGroupDescriptionUpdated) - .put(GROUP_NAME_KEY, getGroupName()) + .put(GROUP_NAME_KEY, conversation.displayName) .format() } } conversation.isCommunityRecipient -> { message = Phrase.from(context, R.string.clearMessagesCommunityUpdated) - .put(COMMUNITY_NAME_KEY, conversation.name) + .put(COMMUNITY_NAME_KEY, conversation.displayName) .format() } @@ -836,15 +831,6 @@ class ConversationSettingsViewModel @AssistedInject constructor( } } - - private fun getGroupName(): String { - val conversation = recipient ?: return "" - val accountId = AccountId(conversation.address.toString()) - return configFactory.withGroupConfigs(accountId) { - it.groupInfo.getName() - } ?: groupV2?.name ?: "" - } - private fun confirmLeaveGroup(){ val groupData = groupV2 ?: return _dialogState.update { state -> @@ -883,7 +869,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( hideLoading() val txt = Phrase.from(context, R.string.groupLeaveErrorFailed) - .put(GROUP_NAME_KEY, getGroupName()) + .put(GROUP_NAME_KEY, conversation.displayName) .format().toString() Toast.makeText(context, txt, Toast.LENGTH_LONG).show() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index 246010d142..c8f4f83146 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.database +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.Flow @@ -14,21 +15,19 @@ import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import network.loki.messenger.libsession_util.ReadableGroupInfoConfig -import network.loki.messenger.libsession_util.ReadableUserProfile -import network.loki.messenger.libsession_util.util.Contact import network.loki.messenger.libsession_util.util.ExpiryMode -import network.loki.messenger.libsession_util.util.GroupInfo -import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigUpdateNotification import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getGroup +import org.session.libsession.utilities.recipients.BasicRecipient import org.session.libsession.utilities.recipients.Recipient.RecipientSettings import org.session.libsession.utilities.recipients.RecipientAvatar import org.session.libsession.utilities.recipients.RecipientAvatar.Companion.toRecipientAvatar import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.displayNameOrFallback import org.session.libsession.utilities.userConfigsChanged import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.IdPrefix @@ -87,80 +86,72 @@ class RecipientRepository @Inject constructor( } private suspend fun fetchRecipient(address: Address): Pair> { - val myAddress by lazy { preferences.getLocalNumber()?.let(Address::fromSerialized) } - - // Short-circuit for our own address. - if (address.isContact && address == myAddress) { - return configFactory.withUserConfigs { - createLocalRecipient(address, it.userProfile) - } to configFactory.userConfigsChanged() - } + val basicRecipient = getBasicRecipientFast(address) val changeSource: Flow<*> val value: RecipientV2? - // We'll always try to fetch the recipient settings from the database first - val settings: RecipientSettings? = withContext(Dispatchers.Default) { - recipientDatabase.getRecipientSettings(address).orNull() - } + when (basicRecipient) { + is BasicRecipient.Self -> { + value = createLocalRecipient(basicRecipient) + changeSource = configFactory.userConfigsChanged() + } + + is BasicRecipient.Contact -> { + value = createContactRecipient( + basic = basicRecipient, + fallbackSettings = withContext(Dispatchers.Default) { + recipientDatabase.getRecipientSettings(address).orNull() + } + ) - when { - // Address is a legit 05 person (not necessarily a "real" contact) - address.isContact && AccountId.fromStringOrNull(address.address)?.prefix == IdPrefix.STANDARD -> { - // We know this address can be looked up in the contacts config or the recipient database. changeSource = merge( configFactory.userConfigsChanged(), recipientDatabase.updateNotifications.filter { it == address } ) - - val contactInConfig = configFactory.withUserConfigs { configs -> - configs.contacts.get(address.address) - } - - value = if (contactInConfig != null) { - createContactRecipient(address, contactInConfig, settings) - } else if (settings != null) { - createGenericRecipient(address, settings) - } else { - null - } } - address.isGroupV2 -> { - val groupId = AccountId(address.address) - val group = configFactory.getGroup(groupId) - - if (group == null) { - value = null - changeSource = configFactory.userConfigsChanged() - } else { - value = configFactory.withGroupConfigs(groupId) { configs -> - createGroupV2Recipient(address, group, configs.groupInfo, settings) + is BasicRecipient.Group -> { + value = createGroupV2Recipient( + basic = basicRecipient, + settings = withContext(Dispatchers.Default) { + recipientDatabase.getRecipientSettings(address).orNull() } + ) - changeSource = merge( - configFactory.userConfigsChanged(), - configFactory.configUpdateNotifications - .filterIsInstance() - .filter { it.groupId.hexString == address.address }, - recipientDatabase.updateNotifications.filter { it == address } - ) - } - } - - address.isCommunity || address.isLegacyGroup -> { changeSource = merge( - groupDatabase.updateNotification, + configFactory.userConfigsChanged(), + configFactory.configUpdateNotifications + .filterIsInstance() + .filter { it.groupId.hexString == address.address }, recipientDatabase.updateNotifications.filter { it == address } ) - - val group: GroupRecord? = groupDatabase.getGroup(address.toGroupString()).orNull() - value = group?.let { createCommunityOrLegacyGroupRecipient(address, it, settings) } } - else -> { - changeSource = recipientDatabase.updateNotifications.filter { it == address } - value = settings?.let { createGenericRecipient(address, it) } + null -> { + // Given address is not backed by the config system so we'll get them from + // local database. + + val settings = withContext(Dispatchers.Default) { + recipientDatabase.getRecipientSettings(address).orNull() + } + + when { + address.isLegacyGroup || address.isCommunity -> { + changeSource = merge( + groupDatabase.updateNotification, + recipientDatabase.updateNotifications.filter { it == address } + ) + + val group: GroupRecord? = groupDatabase.getGroup(address.toGroupString()).orNull() + value = group?.let { createCommunityOrLegacyGroupRecipient(address, it, settings) } + } + + else -> { + value = createGenericRecipient(address, settings) + changeSource = recipientDatabase.updateNotifications.filter { it == address } + } + } } } @@ -189,6 +180,89 @@ class RecipientRepository @Inject constructor( return runBlocking { flow.first() } } + /** + * Returns the recipient name for the given address. This will try to get the information + * as efficiently as possible, but if it fails to do so a blocking call to the database + * might be made. + * + * If you know the recipient is backed by the config system, it's better to use + * [getBasicRecipientFast] instead. + */ + @DelicateCoroutinesApi + inline fun getRecipientDisplayNameSync(address: Address, fallbackName: () -> String? = { null }): String { + val basic = getBasicRecipientFast(address) + if (basic != null) { + return basic.displayName + } + + return getRecipientSync(address).displayNameOrFallback( + fallbackName = fallbackName, + address = address.address, + ) + } + + /** + * Returns a [BasicRecipient] for the given address, without going into the database. + * If it's impossible, this will return null. When it does, it doesn't mean the recipient + * doesn't exist, it just means we don't have a fast way to get it. You will need to call + * [RecipientRepository.getRecipient] to get the full recipient data. + */ + fun getBasicRecipientFast(address: Address): BasicRecipient.ConfigBasedRecipient? { + return when { + // Is this our own address? + address.address.equals(preferences.getLocalNumber(), ignoreCase = true) -> { + configFactory.withUserConfigs { configs -> + BasicRecipient.Self( + address = address, + name = configs.userProfile.getName().orEmpty(), + avatar = configs.userProfile.getPic().toRecipientAvatar(), + expiryMode = configs.userProfile.getNtsExpiry(), + acceptsCommunityMessageRequests = configs.userProfile.getCommunityMessageRequests(), + ) + } + } + + // Is this in our contact? + !address.isGroupOrCommunity && + AccountId.fromStringOrNull(address.address)?.prefix == IdPrefix.STANDARD -> { + configFactory.withUserConfigs { configs -> + configs.contacts.get(address.address) + }?.let { contact -> + BasicRecipient.Contact( + address = address, + name = contact.name, + nickname = contact.nickname.takeIf { it.isNotBlank() }, + avatar = contact.profilePicture.toRecipientAvatar(), + approved = contact.approved, + approvedMe = contact.approvedMe, + blocked = contact.blocked, + expiryMode = contact.expiryMode + ) + } + } + + // Is this a group? + address.isGroupV2 -> { + val groupId = AccountId(address.address) + val groupInfo = configFactory.getGroup(groupId) ?: return null + configFactory.withGroupConfigs(groupId) { configs -> + BasicRecipient.Group( + address = address, + avatar = configs.groupInfo.getProfilePic().toRecipientAvatar(), + expiryMode = configs.groupInfo.expiryMode, + name = configs.groupInfo.getName() ?: groupInfo.name + ) + } + } + + // Otherwise, there's no fast way to get a basic recipient + else -> { + Log.w(TAG, "No fast way to get a basic recipient for address: ${address.debugString}") + null + } + } + } + /** * Returns a recipient for the given address, or an empty recipient if not found. * This is useful to avoid null checks in the UI. @@ -204,21 +278,13 @@ class RecipientRepository @Inject constructor( companion object { private const val TAG = "RecipientRepository" - private fun createLocalRecipient(address: Address, config: ReadableUserProfile): RecipientV2 { + private fun createLocalRecipient(basic: BasicRecipient.Self): RecipientV2 { return RecipientV2( - isLocalNumber = true, - address = address, - name = config.getName().orEmpty(), - approved = true, - approvedMe = true, - blocked = false, + basic = basic, mutedUntil = null, autoDownloadAttachments = true, notifyType = RecipientDatabase.NOTIFY_TYPE_ALL, - avatar = config.getPic().toRecipientAvatar(), - nickname = null, - expiryMode = config.getNtsExpiry(), - acceptsCommunityMessageRequests = config.getCommunityMessageRequests(), + acceptsCommunityMessageRequests = basic.acceptsCommunityMessageRequests, ) } @@ -239,24 +305,14 @@ class RecipientRepository @Inject constructor( } private fun createGroupV2Recipient( - address: Address, - group: GroupInfo.ClosedGroupInfo, - groupInfo: ReadableGroupInfoConfig, + basic: BasicRecipient.Group, settings: RecipientSettings? ): RecipientV2 { return RecipientV2( - name = groupInfo.getName() ?: group.name, - address = address, - isLocalNumber = false, - nickname = null, - approvedMe = true, - approved = !group.invited, - avatar = groupInfo.getProfilePic().toRecipientAvatar(), - blocked = false, + basic = basic, mutedUntil = settings?.muteUntilDate, notifyType = settings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, autoDownloadAttachments = settings?.autoDownloadAttachments, - expiryMode = groupInfo.expiryMode, acceptsCommunityMessageRequests = false, ) @@ -266,24 +322,14 @@ class RecipientRepository @Inject constructor( * Creates a RecipientV2 instance from the provided Contact config and optional fallback settings. */ private fun createContactRecipient( - address: Address, - contactInConfig: Contact, // Config data + basic: BasicRecipient.Contact, fallbackSettings: RecipientSettings?, // Local db data ): RecipientV2 { return RecipientV2( - isLocalNumber = false, - address = address, - name = contactInConfig.name, - nickname = contactInConfig.nickname.takeIf { it.isNotBlank() }, - avatar = contactInConfig.profilePicture.toRecipientAvatar(), - approved = contactInConfig.approved, - approvedMe = contactInConfig.approvedMe, - blocked = contactInConfig.blocked, + basic = basic, mutedUntil = fallbackSettings?.muteUntilDate, autoDownloadAttachments = fallbackSettings?.autoDownloadAttachments, - notifyType = fallbackSettings?.notifyType - ?: RecipientDatabase.NOTIFY_TYPE_ALL, - expiryMode = contactInConfig.expiryMode, + notifyType = fallbackSettings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, acceptsCommunityMessageRequests = fallbackSettings?.blocksCommunityMessageRequests != true ) } @@ -294,18 +340,14 @@ class RecipientRepository @Inject constructor( settings: RecipientSettings?, // Local db data ): RecipientV2 { return RecipientV2( - isLocalNumber = false, - address = address, - name = group.title, - nickname = null, - avatar = RecipientAvatar.fromBytes(group.avatar), - approved = true, - approvedMe = true, - blocked = false, + basic = BasicRecipient.Generic( + address = address, + displayName = group.title, + avatar = group.avatar?.let { RecipientAvatar.fromBytes(it) } + ), mutedUntil = settings?.muteUntilDate, autoDownloadAttachments = settings?.autoDownloadAttachments, notifyType = settings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, - expiryMode = ExpiryMode.NONE, acceptsCommunityMessageRequests = false, ) } @@ -316,37 +358,27 @@ class RecipientRepository @Inject constructor( */ private fun createGenericRecipient(address: Address, settings: RecipientSettings): RecipientV2 { return RecipientV2( - isLocalNumber = false, - address = address, - name = settings.profileName.orEmpty(), - nickname = settings.systemDisplayName, - avatar = settings.profileAvatar?.let { RecipientAvatar.from(it, settings.profileKey) }, - approved = settings.isApproved, - approvedMe = settings.hasApprovedMe(), - blocked = settings.isBlocked, + basic = BasicRecipient.Generic( + address = address, + displayName = settings.systemDisplayName?.takeIf { it.isNotBlank() } ?: settings.profileName.orEmpty(), + avatar = settings.profileAvatar?.let { RecipientAvatar.from(it, settings.profileKey) }, + isLocalNumber = false, + blocked = settings.isBlocked + ), mutedUntil = settings.muteUntil.takeIf { it > 0 } ?.let { ZonedDateTime.from(Instant.ofEpochMilli(it)) }, autoDownloadAttachments = settings.autoDownloadAttachments, notifyType = settings.notifyType, - expiryMode = ExpiryMode.NONE, // A generic recipient does not have an expiry mode acceptsCommunityMessageRequests = !settings.blocksCommunityMessageRequests ) } fun empty(address: Address): RecipientV2 { return RecipientV2( - isLocalNumber = false, - address = address, - name = "", - nickname = null, - approved = false, - approvedMe = false, - blocked = false, + basic = BasicRecipient.Generic(address = address), mutedUntil = null, autoDownloadAttachments = null, notifyType = RecipientDatabase.NOTIFY_TYPE_ALL, - avatar = null, - expiryMode = ExpiryMode.NONE, acceptsCommunityMessageRequests = false, ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 224a5db8d8..c083729df1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -59,7 +59,6 @@ import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.ProfileKeyUtil import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 @@ -122,7 +121,6 @@ open class Storage @Inject constructor( private val messageExpirationManager: SSKEnvironment.MessageExpirationManagerProtocol, private val clock: SnodeClock, private val preferences: TextSecurePreferences, - private val usernameUtils: UsernameUtils, private val openGroupManager: Lazy, private val recipientRepository: RecipientRepository, private val profileUpdateHandler: ProfileUpdateHandler, @@ -210,10 +208,14 @@ open class Storage @Inject constructor( } override fun getUserProfile(): Profile { - val displayName = usernameUtils.getCurrentUsername() - val profileKey = ProfileKeyUtil.getProfileKey(context) - val profilePictureUrl = TextSecurePreferences.getProfilePictureURL(context) - return Profile(displayName, profileKey, profilePictureUrl) + return configFactory.withUserConfigs { configs -> + val pic = configs.userProfile.getPic() + Profile( + displayName = configs.userProfile.getName(), + profilePictureURL = pic.url.takeIf { it.isNotBlank() }, + profileKey = pic.key.data.takeIf { pic.url.isNotBlank() }, + ) + } } override fun setBlocksCommunityMessageRequests(recipient: Address, blocksMessageRequests: Boolean) { @@ -221,16 +223,6 @@ open class Storage @Inject constructor( db.setBlocksCommunityMessageRequests(recipient, blocksMessageRequests) } - override fun setUserProfilePicture(newProfilePicture: String?, newProfileKey: ByteArray?) { - val ourRecipient = fromSerialized(getUserPublicKey()!!) - preferences.setProfileKey(newProfileKey?.let { Base64.encodeBytes(it) }) - preferences.setProfilePictureURL(newProfilePicture) - - if (newProfileKey != null) { - JobQueue.shared.add(RetrieveProfileAvatarJob(newProfilePicture, ourRecipient, newProfileKey)) - } - } - override fun getOrGenerateRegistrationID(): Int { var registrationID = TextSecurePreferences.getLocalRegistrationId(context) if (registrationID == 0) { @@ -308,8 +300,8 @@ open class Storage @Inject constructor( val config = configs.convoInfoVolatile val convo = when { // recipient closed group - recipient.isLegacyGroup -> config.getOrConstructLegacyGroup(GroupUtil.doubleDecodeGroupId(recipient.address.toString())) - recipient.isGroupV2 -> config.getOrConstructClosedGroup(recipient.address.toString()) + recipient.isLegacyGroup -> config.getOrConstructLegacyGroup(GroupUtil.doubleDecodeGroupId(recipient.toString())) + recipient.isGroupV2 -> config.getOrConstructClosedGroup(recipient.toString()) // recipient is open group recipient.isCommunity -> { val openGroupJoinUrl = getOpenGroup(threadId)?.joinURL ?: return@withMutableUserConfigs @@ -320,10 +312,10 @@ open class Storage @Inject constructor( // otherwise recipient is one to one recipient.isContact -> { // don't process non-standard account IDs though - if (IdPrefix.fromValue(recipient.address.toString()) != IdPrefix.STANDARD) return@withMutableUserConfigs - config.getOrConstructOneToOne(recipient.address.toString()) + if (IdPrefix.fromValue(recipient.toString()) != IdPrefix.STANDARD) return@withMutableUserConfigs + config.getOrConstructOneToOne(recipient.toString()) } - else -> throw NullPointerException("Weren't expecting to have a convo with address ${recipient.address.toString()}") + else -> throw NullPointerException("Weren't expecting to have a convo with address ${recipient.toString()}") } convo.lastRead = lastSeenTime if (convo.unread) { @@ -528,25 +520,6 @@ open class Storage @Inject constructor( return configFactory.withUserConfigs { it.userProfile.getCommunityMessageRequests() } } - override fun clearUserPic(clearConfig: Boolean) { - val userPublicKey = getUserPublicKey() ?: return Log.w(TAG, "No user public key when trying to clear user pic") - val recipient = fromSerialized(userPublicKey) - - // Clear details related to the user's profile picture - preferences.setProfileKey(null) - ProfileKeyUtil.setEncodedProfileKey(context, null) - recipientDatabase.setProfileAvatar(recipient, null) - preferences.setProfileAvatarId(0) - preferences.setProfilePictureURL(null) - - Recipient.removeCached(fromSerialized(userPublicKey)) // ACL HERE?!?!?! - if (clearConfig) { - configFactory.withMutableUserConfigs { - it.userProfile.setPic(UserPic.DEFAULT) - } - } - } - override fun setAuthToken(room: String, server: String, newValue: String) { val id = "$server.$room" lokiAPIDatabase.setAuthToken(id, newValue) diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt index 1397c90886..972e02ae6f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt @@ -39,7 +39,6 @@ import org.session.libsession.utilities.MutableUserConfigs import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.UserConfigType import org.session.libsession.utilities.UserConfigs -import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.getGroup import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.session.libsignal.utilities.AccountId @@ -69,7 +68,6 @@ class ConfigFactory @Inject constructor( private val textSecurePreferences: TextSecurePreferences, private val clock: SnodeClock, private val configToDatabaseSync: Lazy, - private val usernameUtils: Lazy ) : ConfigFactoryProtocol { companion object { // This is a buffer period within which we will process messages which would result in a diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/SessionUtilModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/SessionUtilModule.kt index 03fbb56c24..9c94e15a4a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/SessionUtilModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/SessionUtilModule.kt @@ -8,15 +8,11 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.GlobalScope import org.session.libsession.messaging.groups.GroupScope import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.UsernameUtils -import org.thoughtcrime.securesms.database.SessionContactDatabase -import org.thoughtcrime.securesms.util.UsernameUtilsImpl import javax.inject.Named import javax.inject.Singleton @@ -31,7 +27,6 @@ object SessionUtilModule { @Named(POLLER_SCOPE) fun providePollerScope(): CoroutineScope = GlobalScope - @OptIn(ExperimentalCoroutinesApi::class) @Provides @Named(POLLER_SCOPE) fun provideExecutor(): CoroutineDispatcher = Dispatchers.IO.limitedParallelism(1) @@ -51,15 +46,4 @@ object SessionUtilModule { return LegacyGroupDeprecationManager(prefs) } - @Provides - @Singleton - fun provideUsernameUtils( - prefs: TextSecurePreferences, - configFactory: ConfigFactory, - sessionContactDatabase: SessionContactDatabase, - ): UsernameUtils = UsernameUtilsImpl( - prefs = prefs, - configFactory = configFactory, - sessionContactDatabase = sessionContactDatabase, - ) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt index 3e1fe9d78b..94130b445a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt @@ -21,22 +21,24 @@ import network.loki.messenger.R import network.loki.messenger.libsession_util.allWithStatus import network.loki.messenger.libsession_util.util.GroupMember import org.session.libsession.database.StorageProtocol +import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigUpdateNotification import org.session.libsession.utilities.GroupDisplayInfo -import org.session.libsession.utilities.UsernameUtils +import org.session.libsession.utilities.recipients.displayNameOrFallback import org.session.libsignal.utilities.AccountId +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.util.AvatarUIData import org.thoughtcrime.securesms.util.AvatarUtils import java.util.EnumSet -abstract class BaseGroupMembersViewModel ( +abstract class BaseGroupMembersViewModel( private val groupId: AccountId, @ApplicationContext private val context: Context, private val storage: StorageProtocol, - private val usernameUtils: UsernameUtils, private val configFactory: ConfigFactoryProtocol, - private val avatarUtils: AvatarUtils + private val avatarUtils: AvatarUtils, + private val recipientRepository: RecipientRepository, ) : ViewModel() { // Output: the source-of-truth group information. Other states are derived from this. protected val groupInfo: StateFlow>?> = @@ -100,7 +102,8 @@ abstract class BaseGroupMembersViewModel ( val name = if (isMyself) { context.getString(R.string.you) } else { - usernameUtils.getContactNameWithAccountID(memberAccountId.hexString, groupId) + recipientRepository.getRecipient(Address.fromSerialized(memberAccountId.hexString)) + .displayNameOrFallback(fallbackName = { member.name }, address = memberAccountId.hexString) } val highlightStatus = status in EnumSet.of( diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/EditGroupViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/EditGroupViewModel.kt index 6718bed70e..5be4b98480 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/EditGroupViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/EditGroupViewModel.kt @@ -21,8 +21,8 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupInviteException import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.utilities.ConfigFactoryProtocol -import org.session.libsession.utilities.UsernameUtils import org.session.libsignal.utilities.AccountId +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.util.AvatarUtils @@ -34,9 +34,9 @@ class EditGroupViewModel @AssistedInject constructor( storage: StorageProtocol, private val configFactory: ConfigFactoryProtocol, private val groupManager: GroupManagerV2, - private val usernameUtils: UsernameUtils, - private val avatarUtils: AvatarUtils -) : BaseGroupMembersViewModel(groupId, context, storage, usernameUtils, configFactory, avatarUtils) { + avatarUtils: AvatarUtils, + private val recipientRepository: RecipientRepository, +) : BaseGroupMembersViewModel(groupId, context, storage, configFactory, avatarUtils, recipientRepository) { // Output: The name of the group. This is the current name of the group, not the name being edited. val groupName: StateFlow = groupInfo @@ -69,7 +69,7 @@ class EditGroupViewModel @AssistedInject constructor( showLoading = false, errorMessage = { err -> if (err is GroupInviteException) { - err.format(context, usernameUtils).toString() + err.format(context, recipientRepository).toString() } else { null } @@ -89,7 +89,7 @@ class EditGroupViewModel @AssistedInject constructor( showLoading = false, errorMessage = { err -> if (err is GroupInviteException) { - err.format(context, usernameUtils).toString() + err.format(context, recipientRepository).toString() } else { null } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt index 0912a9b0ad..9b63c2a5b4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt @@ -47,6 +47,7 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY import org.session.libsession.utilities.getGroup +import org.session.libsession.utilities.recipients.BasicRecipient import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.recipients.toUserPic import org.session.libsession.utilities.waitUntilGroupConfigsPushed @@ -148,7 +149,9 @@ class GroupManagerV2Impl @Inject constructor( for (member in memberAsRecipients) { newGroupConfigs.groupMembers.set( newGroupConfigs.groupMembers.getOrConstruct(member.address.toString()).apply { - setName(member.name) + // Must use the contact's original name because we are setting this info + // for other gorup members to see. + setName((member.basic as? BasicRecipient.Contact)?.name.orEmpty()) setProfilePic(member.avatar?.toUserPic() ?: UserPic.DEFAULT) } ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMembersViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMembersViewModel.kt index abd8296c6b..04d2b845ef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMembersViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMembersViewModel.kt @@ -12,13 +12,12 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.session.libsession.database.StorageProtocol import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigFactoryProtocol -import org.session.libsession.utilities.UsernameUtils import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.util.AvatarUtils @@ -28,9 +27,9 @@ class GroupMembersViewModel @AssistedInject constructor( @ApplicationContext private val context: Context, private val storage: StorageProtocol, configFactory: ConfigFactoryProtocol, - usernameUtils: UsernameUtils, - avatarUtils: AvatarUtils -) : BaseGroupMembersViewModel(groupId, context, storage, usernameUtils, configFactory, avatarUtils) { + avatarUtils: AvatarUtils, + recipientRepository: RecipientRepository, +) : BaseGroupMembersViewModel(groupId, context, storage, configFactory, avatarUtils, recipientRepository) { private val _navigationActions = Channel() val navigationActions get() = _navigationActions.receiveAsFlow() diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index 3cb73b8373..ae9d38afe7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -18,7 +18,6 @@ import javax.inject.Inject import network.loki.messenger.R import network.loki.messenger.databinding.ViewConversationBinding import org.session.libsession.utilities.ThemeUtil -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_ALL @@ -148,7 +147,7 @@ class ConversationView : LinearLayout { private fun getTitle(recipient: RecipientV2): String = when { recipient.isLocalNumber -> context.getString(R.string.noteToSelf) - else -> recipient.name // Internally uses the Contact API + else -> recipient.displayName // Internally uses the Contact API } // endregion } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 07a5db481e..b9fc2a6ab7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -612,7 +612,7 @@ class HomeActivity : ScreenLockActionBarActivity(), showSessionDialog { title(R.string.block) text(Phrase.from(context, R.string.blockDescription) - .put(NAME_KEY, thread.recipient.name) + .put(NAME_KEY, thread.recipient.displayName) .format()) dangerButton(R.string.block, R.string.AccessibilityId_blockConfirm) { lifecycleScope.launch(Dispatchers.Default) { @@ -623,7 +623,7 @@ class HomeActivity : ScreenLockActionBarActivity(), } } // Block confirmation toast added as per SS-64 - val txt = Phrase.from(context, R.string.blockBlockedUser).put(NAME_KEY, thread.recipient.name).format().toString() + val txt = Phrase.from(context, R.string.blockBlockedUser).put(NAME_KEY, thread.recipient.displayName).format().toString() Toast.makeText(context, txt, Toast.LENGTH_LONG).show() } cancelButton() @@ -633,7 +633,7 @@ class HomeActivity : ScreenLockActionBarActivity(), private fun unblockConversation(thread: ThreadRecord) { showSessionDialog { title(R.string.blockUnblock) - text(Phrase.from(context, R.string.blockUnblockName).put(NAME_KEY, thread.recipient.name).format()) + text(Phrase.from(context, R.string.blockUnblockName).put(NAME_KEY, thread.recipient.displayName).format()) dangerButton(R.string.blockUnblock, R.string.AccessibilityId_unblockConfirm) { lifecycleScope.launch(Dispatchers.Default) { storage.setBlocked(listOf(thread.recipient.address), false) @@ -651,8 +651,7 @@ class HomeActivity : ScreenLockActionBarActivity(), title(R.string.contactDelete) text( Phrase.from(context, R.string.deleteContactDescription) - .put(NAME_KEY, thread.recipient?.name ?: "") - .put(NAME_KEY, thread.recipient?.name ?: "") + .put(NAME_KEY, thread.recipient?.displayName ?: "") .format() ) dangerButton(R.string.delete, R.string.qa_conversation_settings_dialog_delete_contact_confirm) { @@ -768,7 +767,7 @@ class HomeActivity : ScreenLockActionBarActivity(), else { // If this is a 1-on-1 conversation title = getString(R.string.conversationsDelete) message = Phrase.from(this, R.string.conversationsDeleteDescription) - .put(NAME_KEY, recipient.name) + .put(NAME_KEY, recipient.displayName) .format() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index f14eff22b0..d9a615a768 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -37,7 +37,7 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigUpdateNotification import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Log -import org.session.libsession.utilities.UsernameUtils +import org.session.libsession.utilities.currentUserName import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.ThreadDatabase @@ -59,7 +59,6 @@ class HomeViewModel @Inject constructor( private val typingStatusRepository: TypingStatusRepository, private val configFactory: ConfigFactory, private val callManager: CallManager, - private val usernameUtils: UsernameUtils, private val storage: StorageProtocol, private val groupManager: GroupManagerV2 ) : ViewModel() { @@ -212,7 +211,7 @@ class HomeViewModel @Inject constructor( } } - fun getCurrentUsername() = usernameUtils.getCurrentUsernameWithAccountIdFallback() + fun getCurrentUsername() = configFactory.currentUserName fun blockContact(accountId: String) { viewModelScope.launch(Dispatchers.Default) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt index 4a88b07b4c..8c7c0f965e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt @@ -20,6 +20,7 @@ import network.loki.messenger.R import network.loki.messenger.databinding.FragmentUserDetailsBottomSheetBinding import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.utilities.Address +import org.session.libsession.utilities.recipients.BasicRecipient import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.upsertContact import org.session.libsignal.utilities.AccountId @@ -89,11 +90,9 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() { else -> return@setOnEditorActionListener false } } - nameTextView.text = recipient.name + nameTextView.text = recipient.displayName - nameEditIcon.isVisible = recipient.isContactRecipient - && !threadRecipient.isCommunityInboxRecipient - && !threadRecipient.isCommunityOutboxRecipient + nameEditIcon.isVisible = recipient.basic is BasicRecipient.Contact publicKeyTextView.isVisible = !threadRecipient.isCommunityRecipient && !threadRecipient.isCommunityInboxRecipient @@ -149,7 +148,7 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() { } } - nameTextView.text = recipient.name + nameTextView.text = newNickName (parentFragment as? UserDetailsBottomSheetCallback) ?: (requireActivity() as? UserDetailsBottomSheetCallback)?.onNicknameSaved() diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt index b05df3341a..d7b5a7ab34 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt @@ -10,7 +10,6 @@ import network.loki.messenger.R import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.ContentView @@ -138,7 +137,7 @@ fun ContentView.bindModel(query: String?, model: Message, dateUtils: DateUtils) val textSpannable = SpannableStringBuilder() if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) { // group chat, bind - val text = "${model.messageResult.messageRecipient.name}: " + val text = "${model.messageResult.messageRecipient.displayName}: " textSpannable.append(text) } textSpannable.append(getHighlight( @@ -152,7 +151,7 @@ fun ContentView.bindModel(query: String?, model: Message, dateUtils: DateUtils) } fun RecipientV2.getSearchName(): String = - name.takeIf { it.isNotEmpty() && !it.looksLikeAccountId } + displayName.takeIf { it.isNotEmpty() && !it.looksLikeAccountId } ?: address.toString().let(::truncateIdForDisplay) fun Contact.getSearchName(): String = diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt index 3500c25434..95563e33b4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt @@ -66,7 +66,7 @@ class MessageRequestView : LinearLayout { return if (recipient.isLocalNumber) { context.getString(R.string.noteToSelf) } else { - recipient.name // Internally uses the Contact API + recipient.displayName // Internally uses the Contact API } } // endregion diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt index 3bbd148e22..d8b0fc16cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt @@ -97,7 +97,7 @@ class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClick showSessionDialog { title(R.string.block) text(Phrase.from(context, R.string.blockDescription) - .put(NAME_KEY, thread.recipient.name) + .put(NAME_KEY, thread.recipient.displayName) .format()) dangerButton(R.string.block, R.string.AccessibilityId_blockConfirm) { doBlock() diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java index f9e3cdb330..92c5beabcd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java @@ -14,7 +14,8 @@ import org.session.libsession.utilities.NotificationPrivacyPreference; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.RecipientV2; import network.loki.messenger.R; @@ -39,9 +40,9 @@ public AbstractNotificationBuilder(Context context, NotificationPrivacyPreferenc setLed(); } - protected CharSequence getStyledMessage(@NonNull Recipient recipient, @Nullable CharSequence message) { + protected CharSequence getStyledMessage(@NonNull RecipientV2 recipient, @Nullable CharSequence message) { SpannableStringBuilder builder = new SpannableStringBuilder(); - builder.append(Util.getBoldedString(recipient.getName())); + builder.append(Util.getBoldedString(recipient.getDisplayName())); builder.append(": "); builder.append(message == null ? "" : message); @@ -61,7 +62,7 @@ private void setLed() { setLights(ledColor, 500,2000); } - public void setTicker(@NonNull Recipient recipient, @Nullable CharSequence message) { + public void setTicker(@NonNull RecipientV2 recipient, @Nullable CharSequence message) { if (privacy.isDisplayMessage()) { setTicker(getStyledMessage(recipient, trimToDisplayLength(message))); } else if (privacy.isDisplayContact()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt index 890a2e51d2..eab46645db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt @@ -32,11 +32,6 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import com.annimon.stream.Stream import com.squareup.phrase.Phrase -import java.util.concurrent.Executor -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.concurrent.Volatile import me.leolin.shortcutbadger.ShortcutBadger import network.loki.messenger.R import network.loki.messenger.libsession_util.util.BlindKeyAPI @@ -60,6 +55,7 @@ import org.session.libsignal.utilities.Util import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.crypto.KeyPairUtilities.getUserED25519KeyPair +import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.database.MmsSmsDatabase import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.RecipientRepository @@ -71,11 +67,15 @@ import org.thoughtcrime.securesms.database.model.ReactionRecord import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.service.KeyCachingService import org.thoughtcrime.securesms.util.AvatarUtils -import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.Companion.WEBRTC_NOTIFICATION import org.thoughtcrime.securesms.util.SessionMetaProtocol.canUserReplyToNotification import org.thoughtcrime.securesms.util.SpanUtil +import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.Companion.WEBRTC_NOTIFICATION +import java.util.concurrent.Executor +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject -import javax.inject.Singleton +import kotlin.concurrent.Volatile /** * Handles posting system notifications for new messages. @@ -88,6 +88,7 @@ class DefaultMessageNotifier @Inject constructor( private val threadDatabase: ThreadDatabase, private val recipientRepository: RecipientRepository, private val mmsSmsDatabase: MmsSmsDatabase, + private val lokiThreadDatabase: LokiThreadDatabase, ) : MessageNotifier { override fun setVisibleThread(threadId: Long) { visibleThread = threadId @@ -385,7 +386,7 @@ class DefaultMessageNotifier @Inject constructor( val notifications = notificationState.notifications builder.setMessageCount(notificationState.notificationCount, notificationState.threadCount) - builder.setMostRecentSender(notifications[0].individualRecipient, notifications[0].recipient) + builder.setMostRecentSender(notifications[0].individualRecipient) builder.setGroup(NOTIFICATION_GROUP) builder.setDeleteIntent(notificationState.getDeleteIntent(context)) builder.setOnlyAlertOnce(!signal) @@ -410,8 +411,7 @@ class DefaultMessageNotifier @Inject constructor( while (iterator.hasPrevious()) { val item = iterator.previous() builder.addMessageBody( - item.individualRecipient, item.recipient, - highlightMentions( + item.individualRecipient, highlightMentions( (if (item.text != null) item.text else "")!!, false, false, @@ -560,9 +560,9 @@ class DefaultMessageNotifier @Inject constructor( if (lastReact.isPresent) { if (threadRecipients != null && !threadRecipients.isGroupOrCommunityRecipient) { val reaction = lastReact.get() - val reactor = Recipient.from(context, fromSerialized(reaction.author), false) + val reactor = recipientRepository.getRecipientSyncOrEmpty(fromSerialized(reaction.author)) val emoji = Phrase.from(context, R.string.emojiReactsNotification).put(EMOJI_KEY, reaction.emoji).format().toString() - notificationState.addNotification(NotificationItem(id, mms, reactor, reactor, threadRecipients, threadId, emoji, reaction.dateSent, slideDeck)) + notificationState.addNotification(NotificationItem(id, mms,reactor, reactor, threadRecipients, threadId, emoji, reaction.dateSent, slideDeck)) } } } @@ -573,7 +573,6 @@ class DefaultMessageNotifier @Inject constructor( } private fun generateBlindedId(threadId: Long, context: Context): String? { - val lokiThreadDatabase = get(context).lokiThreadDatabase() val openGroup = lokiThreadDatabase.getOpenGroupChat(threadId) val edKeyPair = getUserED25519KeyPair(context) if (openGroup != null && edKeyPair != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt index 5331a2a7a6..71ad8bef6f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt @@ -10,6 +10,7 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.session.libsession.database.userAuth +import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration.Companion.shared import org.session.libsession.messaging.messages.control.ReadReceipt import org.session.libsession.messaging.sending_receiving.MessageSender.send @@ -19,14 +20,14 @@ import org.session.libsession.snode.utilities.await import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled import org.session.libsession.utilities.associateByNotNull -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.BasicRecipient +import org.session.libsession.utilities.recipients.isGroupOrCommunityRecipient import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.conversation.disappearingmessages.ExpiryType import org.thoughtcrime.securesms.database.ExpirationInfo import org.thoughtcrime.securesms.database.MarkedMessageInfo import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.util.SessionMetaProtocol.shouldSendReadReceipt @AndroidEntryPoint class MarkReadReceiver : BroadcastReceiver() { @@ -118,14 +119,23 @@ class MarkReadReceiver : BroadcastReceiver() { } } + private val BasicRecipient.shouldSendReadReceipt: Boolean + get() = when (this) { + is BasicRecipient.Contact -> approved && !blocked + is BasicRecipient.Generic -> !isGroupOrCommunityRecipient && !blocked + else -> false + } + private fun sendReadReceipts( context: Context, markedReadMessages: List ) { if (!isReadReceiptsEnabled(context)) return + val recipientRepository = MessagingModuleConfiguration.shared.recipientRepository + markedReadMessages.map { it.syncMessageId } - .filter { shouldSendReadReceipt(Recipient.from(context, it.address, false)) } + .filter { recipientRepository.getBasicRecipientFast(it.address)?.shouldSendReadReceipt == true } .groupBy { it.address } .forEach { (address, messages) -> messages.map { it.timetamp } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.kt index 9ab380a3c5..65527eb24e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.kt @@ -8,14 +8,12 @@ import android.text.SpannableStringBuilder import androidx.core.app.NotificationCompat import com.squareup.phrase.Phrase import network.loki.messenger.R -import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.NotificationPrivacyPreference import org.session.libsession.utilities.StringSubstitutionConstants.CONVERSATION_COUNT_KEY import org.session.libsession.utilities.StringSubstitutionConstants.MESSAGE_COUNT_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.Util.getBoldedString -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.home.HomeActivity import org.thoughtcrime.securesms.ui.getSubbedString import java.util.LinkedList @@ -38,21 +36,15 @@ class MultipleRecipientNotificationBuilder(context: Context, privacy: Notificati setNumber(messageCount) } - fun setMostRecentSender(recipient: Recipient, threadRecipient: Recipient) { - var displayName = recipient.name - if (threadRecipient.isGroupOrCommunityRecipient) { - displayName = getGroupDisplayName(recipient, threadRecipient.isCommunityRecipient) - } + fun setMostRecentSender(recipient: RecipientV2) { if (privacy.isDisplayContact) { val txt = Phrase.from(context, R.string.notificationsMostRecent) - .put(NAME_KEY, displayName) + .put(NAME_KEY, recipient.displayName) .format().toString() setContentText(txt) } - if (recipient.notificationChannel != null) { - setChannelId(recipient.notificationChannel!!) - } + recipient.notificationChannel?.let(this::setChannelId) } fun addActions(markAsReadIntent: PendingIntent?) { @@ -67,19 +59,15 @@ class MultipleRecipientNotificationBuilder(context: Context, privacy: Notificati fun putStringExtra(key: String?, value: String?) { extras.putString(key, value) } - fun addMessageBody(sender: Recipient, threadRecipient: Recipient, body: CharSequence?) { - var displayName = sender.name - if (threadRecipient.isGroupOrCommunityRecipient) { - displayName = getGroupDisplayName(sender, threadRecipient.isCommunityRecipient) - } + fun addMessageBody(sender: RecipientV2, body: CharSequence?) { if (privacy.isDisplayMessage) { val builder = SpannableStringBuilder() - builder.append(getBoldedString(displayName)) + builder.append(getBoldedString(sender.displayName)) builder.append(": ") builder.append(body ?: "") messageBodies.add(builder) } else if (privacy.isDisplayContact) { - messageBodies.add(getBoldedString(displayName)) + messageBodies.add(getBoldedString(sender.displayName)) } } @@ -91,15 +79,4 @@ class MultipleRecipientNotificationBuilder(context: Context, privacy: Notificati } return super.build() } - - /** - * @param recipient the * individual * recipient for which to get the display name. - * @param openGroupRecipient whether in an open group context - */ - private fun getGroupDisplayName(recipient: Recipient, openGroupRecipient: Boolean): String { - return MessagingModuleConfiguration.shared.usernameUtils.getContactNameWithAccountID( - accountID = recipient.address.toString(), - contactContext = if (openGroupRecipient) Contact.ContactContext.OPEN_GROUP else Contact.ContactContext.REGULAR - ) - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/ReplyMethod.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/ReplyMethod.java index 43b8b1d55f..7416006f7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/ReplyMethod.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/ReplyMethod.java @@ -1,16 +1,17 @@ package org.thoughtcrime.securesms.notifications; import android.content.Context; + import androidx.annotation.NonNull; -import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; public enum ReplyMethod { GroupMessage, SecureMessage; - public static @NonNull ReplyMethod forRecipient(Context context, Recipient recipient) { + public static @NonNull ReplyMethod forRecipient(Context context, RecipientV2 recipient) { if (recipient.isGroupOrCommunityRecipient()) { return ReplyMethod.GroupMessage; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index 66a0f2b4d0..0d05678ab2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -32,7 +32,6 @@ import org.session.libsession.utilities.Address; import org.session.libsession.utilities.NotificationPrivacyPreference; import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; @@ -78,7 +77,7 @@ public void setThread(@NonNull RecipientV2 recipient) { setChannelId(channelId != null ? channelId : NotificationChannels.getMessagesChannel(context)); if (privacy.isDisplayContact()) { - setContentTitle(recipient.getName()); + setContentTitle(recipient.getDisplayName()); ContactPhoto contactPhoto = recipient.getContactPhoto(); if (contactPhoto != null) { @@ -121,8 +120,7 @@ public void setPrimaryMessageBody(@NonNull RecipientV2 threadRecipient, SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); if (privacy.isDisplayContact() && threadRecipient.isGroupOrCommunityRecipient()) { - String displayName = getGroupDisplayName(individualRecipient.getAddress(), threadRecipient.isCommunityRecipient()); - stringBuilder.append(Util.getBoldedString(displayName + ": ")); + stringBuilder.append(Util.getBoldedString(individualRecipient.getDisplayName() + ": ")); } if (privacy.isDisplayMessage()) { @@ -202,15 +200,14 @@ public void putStringExtra(String key, String value) { extras.putString(key,value); } - public void addMessageBody(@NonNull Recipient threadRecipient, - @NonNull Recipient individualRecipient, + public void addMessageBody(@NonNull RecipientV2 threadRecipient, + @NonNull RecipientV2 individualRecipient, @Nullable CharSequence messageBody) { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); if (privacy.isDisplayContact() && threadRecipient.isGroupOrCommunityRecipient()) { - String displayName = getGroupDisplayName(individualRecipient.getAddress(), threadRecipient.isCommunityRecipient()); - stringBuilder.append(Util.getBoldedString(displayName + ": ")); + stringBuilder.append(Util.getBoldedString(individualRecipient.getDisplayName() + ": ")); } if (privacy.isDisplayMessage()) { @@ -326,16 +323,4 @@ private static Drawable getPlaceholderDrawable(AvatarUtils avatarUtils, Recipien String displayName = recipient.getDisplayName(); return avatarUtils.generateTextBitmap(ICON_SIZE, publicKey, displayName); } - - /** - * @param recipient the * individual * recipient for which to get the display name. - * @param openGroupRecipient whether in an open group context - */ - private String getGroupDisplayName(Address recipient, boolean openGroupRecipient) { - return MessagingModuleConfiguration.getShared().getUsernameUtils().getContactNameWithAccountID( - recipient.getAddress(), - null, - openGroupRecipient ? Contact.ContactContext.OPEN_GROUP : Contact.ContactContext.REGULAR - ); - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/manager/CreateAccountManager.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/manager/CreateAccountManager.kt index 163e3878b5..96020e531f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/manager/CreateAccountManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/manager/CreateAccountManager.kt @@ -2,8 +2,8 @@ package org.thoughtcrime.securesms.onboarding.manager import android.app.Application import org.session.libsession.snode.SnodeModule +import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.UsernameUtils import org.session.libsignal.database.LokiAPIDatabaseProtocol import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.hexEncodedPublicKey @@ -16,8 +16,8 @@ import javax.inject.Singleton class CreateAccountManager @Inject constructor( private val application: Application, private val prefs: TextSecurePreferences, - private val usernameUtils: UsernameUtils, - private val versionDataFetcher: VersionDataFetcher + private val versionDataFetcher: VersionDataFetcher, + private val configFactory: ConfigFactoryProtocol ) { private val database: LokiAPIDatabaseProtocol get() = SnodeModule.shared.storage @@ -40,7 +40,7 @@ class CreateAccountManager @Inject constructor( prefs.setLocalNumber(userHexEncodedPublicKey) prefs.setRestorationTime(0) - usernameUtils.saveCurrentUserName(displayName) + configFactory.withMutableUserConfigs { it.userProfile.setName(displayName) } versionDataFetcher.startTimedVersionCheck() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameViewModel.kt index 6d54fa803e..2d8274f511 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameViewModel.kt @@ -16,12 +16,14 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import network.loki.messenger.R import org.session.libsession.messaging.messages.ProfileUpdateHandler +import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.TextSecurePreferences @HiltViewModel(assistedFactory = PickDisplayNameViewModel.Factory::class) class PickDisplayNameViewModel @AssistedInject constructor( @Assisted private val loadFailed: Boolean, private val prefs: TextSecurePreferences, + private val configFactory: ConfigFactoryProtocol, ): ViewModel() { private val isCreateAccount = !loadFailed @@ -46,8 +48,10 @@ class PickDisplayNameViewModel @AssistedInject constructor( viewModelScope.launch(Dispatchers.IO) { if (loadFailed) { - prefs.setProfileName(displayName) - usernameUtils.saveCurrentUserName(displayName) + configFactory.withMutableUserConfigs { + it.userProfile.setName(displayName) + } + _events.emit(Event.LoadAccountComplete) } else _events.emit(Event.CreateAccount(displayName)) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt index c3d1958a5d..33b1597a4a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt @@ -48,7 +48,7 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap val binding = BlockedContactLayoutBinding.bind(itemView) fun bind(selectable: SelectableRecipient, toggle: (SelectableRecipient) -> Unit) { - binding.recipientName.text = selectable.item.name + binding.recipientName.text = selectable.item.displayName with (binding.profilePictureView) { update(selectable.item) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt index 77cfb548c3..93c4ce6066 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt @@ -53,7 +53,7 @@ class BlockedContactsViewModel @Inject constructor(private val storage: StorageP executor.launch(IO) { for (update in listUpdateChannel) { val blockedContactState = state.copy( - blockedContacts = storage.blockedContacts().sortedBy { it.name } + blockedContacts = storage.blockedContacts().sortedBy { it.displayName } ) withContext(Main) { _state.value = blockedContactState @@ -82,15 +82,15 @@ class BlockedContactsViewModel @Inject constructor(private val storage: StorageP return when (contactsToUnblock.size) { // Note: We do not have to handle 0 because if no contacts are chosen then the unblock button is deactivated 1 -> Phrase.from(context, R.string.blockUnblockName) - .put(NAME_KEY, contactsToUnblock.elementAt(0).name) + .put(NAME_KEY, contactsToUnblock.elementAt(0).displayName) .format() 2 -> Phrase.from(context, R.string.blockUnblockNameTwo) - .put(NAME_KEY, contactsToUnblock.elementAt(0).name) + .put(NAME_KEY, contactsToUnblock.elementAt(0).displayName) .format() else -> { val othersCount = contactsToUnblock.size - 1 Phrase.from(context, R.string.blockUnblockNameMultiple) - .put(NAME_KEY, contactsToUnblock.elementAt(0).name) + .put(NAME_KEY, contactsToUnblock.elementAt(0).displayName) .put(COUNT_KEY, othersCount) .format() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt index 7ef3af4796..28a12fb46d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt @@ -22,6 +22,7 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.ProfileKeyUtil import org.session.libsession.utilities.ProfilePictureUtilities import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.currentUserName import org.session.libsignal.utilities.ExternalStorageUtil.getImageDir import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.NoExternalStorageException @@ -93,9 +94,9 @@ class SettingsViewModel @Inject constructor( } } - fun getDisplayName(): String = usernameUtils.getCurrentUsernameWithAccountIdFallback() + fun getDisplayName(): String = configFactory.currentUserName - fun hasAvatar() = prefs.getProfileAvatarId() != 0 + fun hasAvatar() = configFactory.withUserConfigs { it.userProfile.getPic().url.isNotBlank() } fun createTempFile(): File? { try { @@ -210,12 +211,13 @@ class SettingsViewModel @Inject constructor( // When removing the profile picture the supplied ByteArray is empty so we'll clear the local data if (profilePicture.isEmpty()) { - MessagingModuleConfiguration.shared.storage.clearUserPic() + configFactory.withMutableUserConfigs { + it.userProfile.setPic(UserPic.DEFAULT) + } // update dialog state _avatarDialogState.value = AvatarDialogState.NoAvatar } else { - prefs.setProfileAvatarId(SECURE_RANDOM.nextInt()) ProfileKeyUtil.setEncodedProfileKey(context, encodedProfileKey) // Attempt to grab the details we require to update the profile picture @@ -247,7 +249,7 @@ class SettingsViewModel @Inject constructor( } fun updateName(displayName: String) { - usernameUtils.saveCurrentUserName(displayName) + configFactory.withMutableUserConfigs { it.userProfile.setName(displayName) } } fun permanentlyHidePassword() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt index b794388972..967791a1d0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt @@ -25,7 +25,6 @@ import org.session.libsession.avatars.ContactPhoto import org.session.libsession.avatars.ProfileContactPhoto import org.session.libsession.database.StorageProtocol import org.session.libsession.utilities.Address -import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.IdPrefix @@ -40,7 +39,6 @@ import javax.inject.Singleton @Singleton class AvatarUtils @Inject constructor( @ApplicationContext private val context: Context, - private val usernameUtils: UsernameUtils, private val groupDatabase: GroupDatabase, // for legacy groups private val storage: Lazy, private val recipientRepository: RecipientRepository, @@ -132,10 +130,9 @@ class AvatarUtils @Inject constructor( } } - private fun getUIElementForRecipient(recipient: Recipient): AvatarUIElement { + private fun getUIElementForRecipient(recipient: RecipientV2): AvatarUIElement { // name - val name = if(recipient.isLocalNumber) usernameUtils.getCurrentUsernameWithAccountIdFallback() - else recipient.name + val name = recipient.displayName val defaultColor = Color(getColorFromKey(recipient.address.toString())) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt deleted file mode 100644 index 95878aec98..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt +++ /dev/null @@ -1,441 +0,0 @@ -package org.thoughtcrime.securesms.util - -import android.content.Context -import network.loki.messenger.libsession_util.Curve25519 -import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.contacts.Contact -import org.session.libsession.messaging.messages.signal.IncomingTextMessage -import org.session.libsession.messaging.messages.signal.OutgoingTextMessage -import org.session.libsession.messaging.open_groups.OpenGroup -import org.session.libsession.messaging.open_groups.OpenGroupApi -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.recipients.Recipient -import org.session.libsignal.crypto.ecc.Curve -import org.session.libsignal.crypto.ecc.DjbECPrivateKey -import org.session.libsignal.crypto.ecc.DjbECPublicKey -import org.session.libsignal.crypto.ecc.ECKeyPair -import org.session.libsignal.messages.SignalServiceGroup -import org.session.libsignal.utilities.Log -import org.session.libsignal.utilities.guava.Optional -import org.session.libsignal.utilities.hexEncodedPublicKey -import org.thoughtcrime.securesms.crypto.KeyPairUtilities -import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.groups.GroupManager -import java.security.SecureRandom -import kotlin.random.asKotlinRandom - -object MockDataGenerator { - private var printProgress = true - private var hasStartedGenerationThisRun = false - - // FIXME: Update this to run in a transaction instead of individual db writes (should drastically speed it up) - fun generateMockData(context: Context) { - // Don't re-generate the mock data if it already exists - val mockDataExistsRecipient = Recipient.from(context, Address.fromSerialized("MockDatabaseThread"), false) - val storage = MessagingModuleConfiguration.shared.storage - val threadDb = DatabaseComponent.get(context).threadDatabase() - val lokiThreadDB = DatabaseComponent.get(context).lokiThreadDatabase() - val contactDb = DatabaseComponent.get(context).sessionContactDatabase() - val recipientDb = DatabaseComponent.get(context).recipientDatabase() - val smsDb = DatabaseComponent.get(context).smsDatabase() - - if (hasStartedGenerationThisRun || threadDb.getThreadIdIfExistsFor(mockDataExistsRecipient) != -1L) { - hasStartedGenerationThisRun = true - return - } - - /// The mock data generation is quite slow, there are 3 parts which take a decent amount of time (deleting the account afterwards will - /// also take a long time): - /// Generating the threads & content - ~3m per 100 - val dmThreadCount: Int = 1000 - val closedGroupThreadCount: Int = 50 - val openGroupThreadCount: Int = 20 - val messageRangePerThread: List = listOf(0..500) - val dmRandomSeed: String = "1111" - val cgRandomSeed: String = "2222" - val ogRandomSeed: String = "3333" - val chunkSize: Int = 1000 // Chunk up the thread writing to prevent memory issues - val stringContent: List = "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 ".map { it.toString() } - val wordContent: List = listOf("alias", "consequatur", "aut", "perferendis", "sit", "voluptatem", "accusantium", "doloremque", "aperiam", "eaque", "ipsa", "quae", "ab", "illo", "inventore", "veritatis", "et", "quasi", "architecto", "beatae", "vitae", "dicta", "sunt", "explicabo", "aspernatur", "aut", "odit", "aut", "fugit", "sed", "quia", "consequuntur", "magni", "dolores", "eos", "qui", "ratione", "voluptatem", "sequi", "nesciunt", "neque", "dolorem", "ipsum", "quia", "dolor", "sit", "amet", "consectetur", "adipisci", "velit", "sed", "quia", "non", "numquam", "eius", "modi", "tempora", "incidunt", "ut", "labore", "et", "dolore", "magnam", "aliquam", "quaerat", "voluptatem", "ut", "enim", "ad", "minima", "veniam", "quis", "nostrum", "exercitationem", "ullam", "corporis", "nemo", "enim", "ipsam", "voluptatem", "quia", "voluptas", "sit", "suscipit", "laboriosam", "nisi", "ut", "aliquid", "ex", "ea", "commodi", "consequatur", "quis", "autem", "vel", "eum", "iure", "reprehenderit", "qui", "in", "ea", "voluptate", "velit", "esse", "quam", "nihil", "molestiae", "et", "iusto", "odio", "dignissimos", "ducimus", "qui", "blanditiis", "praesentium", "laudantium", "totam", "rem", "voluptatum", "deleniti", "atque", "corrupti", "quos", "dolores", "et", "quas", "molestias", "excepturi", "sint", "occaecati", "cupiditate", "non", "provident", "sed", "ut", "perspiciatis", "unde", "omnis", "iste", "natus", "error", "similique", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollitia", "animi", "id", "est", "laborum", "et", "dolorum", "fuga", "et", "harum", "quidem", "rerum", "facilis", "est", "et", "expedita", "distinctio", "nam", "libero", "tempore", "cum", "soluta", "nobis", "est", "eligendi", "optio", "cumque", "nihil", "impedit", "quo", "porro", "quisquam", "est", "qui", "minus", "id", "quod", "maxime", "placeat", "facere", "possimus", "omnis", "voluptas", "assumenda", "est", "omnis", "dolor", "repellendus", "temporibus", "autem", "quibusdam", "et", "aut", "consequatur", "vel", "illum", "qui", "dolorem", "eum", "fugiat", "quo", "voluptas", "nulla", "pariatur", "at", "vero", "eos", "et", "accusamus", "officiis", "debitis", "aut", "rerum", "necessitatibus", "saepe", "eveniet", "ut", "et", "voluptates", "repudiandae", "sint", "et", "molestiae", "non", "recusandae", "itaque", "earum", "rerum", "hic", "tenetur", "a", "sapiente", "delectus", "ut", "aut", "reiciendis", "voluptatibus", "maiores", "doloribus", "asperiores", "repellat") - val timestampNow: Long = System.currentTimeMillis() - val userAccountId: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - val logProgress: ((String, String) -> Unit) = logProgress@{ title, event -> - if (!printProgress) { return@logProgress } - - Log.i("[MockDataGenerator]", "${System.currentTimeMillis()} $title - $event") - } - - hasStartedGenerationThisRun = true - - // FIXME: Make sure this data doesn't go off device somehow? - logProgress("", "Start") - - // First create the thread used to indicate that the mock data has been generated - threadDb.getOrCreateThreadIdFor(mockDataExistsRecipient) - - // -- DM Thread - val dmThreadRandomGenerator: SecureRandom = SecureRandom(dmRandomSeed.toByteArray()) - var dmThreadIndex: Int = 0 - logProgress("DM Threads", "Start Generating $dmThreadCount threads") - - while (dmThreadIndex < dmThreadCount) { - val remainingThreads: Int = (dmThreadCount - dmThreadIndex) - - (0 until Math.min(chunkSize, remainingThreads)).forEach { index -> - val threadIndex: Int = (dmThreadIndex + index) - - logProgress("DM Thread $threadIndex", "Start") - - val dataBytes = (0 until 16).map { dmThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } - val randomAccountId: String = KeyPairUtilities.generate(dataBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey - val isMessageRequest: Boolean = dmThreadRandomGenerator.nextBoolean() - val contactNameLength: Int = (5 + dmThreadRandomGenerator.nextInt(15)) - - val numMessages: Int = ( - messageRangePerThread[threadIndex % messageRangePerThread.count()].first + - dmThreadRandomGenerator.nextInt(messageRangePerThread[threadIndex % messageRangePerThread.count()].last()) - ) - - // Generate the thread - val recipient = Recipient.from(context, Address.fromSerialized(randomAccountId), false) - val contact = Contact(randomAccountId) - val threadId = threadDb.getOrCreateThreadIdFor(recipient) - - // Generate the contact - val contactIsApproved: Boolean = (!isMessageRequest || dmThreadRandomGenerator.nextBoolean()) - contactDb.setContact(contact) - contactDb.setContactIsTrusted(contact, true, threadId) - recipientDb.setApproved(recipient, contactIsApproved) - recipientDb.setApprovedMe(recipient, (!isMessageRequest && (dmThreadRandomGenerator.nextInt(10) < 8))) // 80% approved the current user - - contact.name = (0 until dmThreadRandomGenerator.nextInt(contactNameLength)) - .map { stringContent.random(dmThreadRandomGenerator.asKotlinRandom()) } - .joinToString() - recipientDb.setProfileName(recipient, contact.name) - contactDb.setContact(contact) - - // Generate the message history (Note: Unapproved message requests will only include incoming messages) - logProgress("DM Thread $threadIndex", "Generate $numMessages Messages") - (0 until numMessages).forEach { index -> - val isIncoming: Boolean = ( - dmThreadRandomGenerator.nextBoolean() && - (!isMessageRequest || contactIsApproved) - ) - val messageWords: Int = (1 + dmThreadRandomGenerator.nextInt(19)) - - if (isIncoming) { - smsDb.insertMessageInbox( - IncomingTextMessage( - recipient.address, - 1, - (timestampNow - (index * 5000)), - (0 until messageWords) - .map { wordContent.random(dmThreadRandomGenerator.asKotlinRandom()) } - .joinToString(), - Optional.absent(), - 0, - 0, - false, - -1, - false - ), - (timestampNow - (index * 5000)), - false - ) - } - else { - smsDb.insertMessageOutbox( - threadId, - OutgoingTextMessage( - recipient, - (0 until messageWords) - .map { wordContent.random(dmThreadRandomGenerator.asKotlinRandom()) } - .joinToString(), - 0, - 0, - -1, - (timestampNow - (index * 5000)) - ), - (timestampNow - (index * 5000)), - false - ) - } - } - - logProgress("DM Thread $threadIndex", "Done") - } - logProgress("DM Threads", "Done") - - dmThreadIndex += chunkSize - } - logProgress("DM Threads", "Done") - - // -- Closed Group - - val cgThreadRandomGenerator: SecureRandom = SecureRandom(cgRandomSeed.toByteArray()) - var cgThreadIndex: Int = 0 - logProgress("Closed Group Threads", "Start Generating $closedGroupThreadCount threads") - - while (cgThreadIndex < closedGroupThreadCount) { - val remainingThreads: Int = (closedGroupThreadCount - cgThreadIndex) - - (0 until Math.min(chunkSize, remainingThreads)).forEach { index -> - val threadIndex: Int = (cgThreadIndex + index) - - logProgress("Closed Group Thread $threadIndex", "Start") - - val dataBytes = (0 until 16).map { cgThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } - val randomGroupPublicKey: String = KeyPairUtilities.generate(dataBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey - val groupNameLength: Int = (5 + cgThreadRandomGenerator.nextInt(15)) - val groupName: String = (0 until groupNameLength) - .map { stringContent.random(cgThreadRandomGenerator.asKotlinRandom()) } - .joinToString() - val numGroupMembers: Int = cgThreadRandomGenerator.nextInt (10) - val numMessages: Int = ( - messageRangePerThread[threadIndex % messageRangePerThread.count()].first + - cgThreadRandomGenerator.nextInt(messageRangePerThread[threadIndex % messageRangePerThread.count()].last()) - ) - - // Generate the Contacts in the group - val members: MutableList = mutableListOf(userAccountId) - logProgress("Closed Group Thread $threadIndex", "Generate $numGroupMembers Contacts") - - (0 until numGroupMembers).forEach { - val contactBytes = (0 until 16).map { cgThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } - val randomAccountId: String = KeyPairUtilities.generate(contactBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey - val contactNameLength: Int = (5 + cgThreadRandomGenerator.nextInt(15)) - - val recipient = Recipient.from(context, Address.fromSerialized(randomAccountId), false) - val contact = Contact(randomAccountId) - contactDb.setContact(contact) - recipientDb.setApproved(recipient, true) - recipientDb.setApprovedMe(recipient, true) - - contact.name = (0 until cgThreadRandomGenerator.nextInt(contactNameLength)) - .map { stringContent.random(cgThreadRandomGenerator.asKotlinRandom()) } - .joinToString() - recipientDb.setProfileName(recipient, contact.name) - contactDb.setContact(contact) - members.add(randomAccountId) - } - - val groupId = GroupUtil.doubleEncodeGroupID(randomGroupPublicKey) - val threadId = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupId)) - val adminUserId = members.random(cgThreadRandomGenerator.asKotlinRandom()) - storage.createGroup( - groupId, - groupName, - members.map { Address.fromSerialized(it) }, - null, - null, - listOf(Address.fromSerialized(adminUserId)), - timestampNow - ) - storage.addClosedGroupPublicKey(randomGroupPublicKey) - - // Add the group to the user's set of public keys to poll for and store the key pair - val encryptionKeyPair = Curve25519.generateKeyPair().let { - ECKeyPair( - DjbECPublicKey(it.pubKey.data), - DjbECPrivateKey(it.secretKey.data) - ) - } - storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, randomGroupPublicKey, System.currentTimeMillis()) - storage.createInitialConfigGroup(randomGroupPublicKey, groupName, GroupUtil.createConfigMemberMap(members, setOf(adminUserId)), System.currentTimeMillis(), encryptionKeyPair, 0) - - // Add the group created message - if (userAccountId == adminUserId) { - storage.insertOutgoingInfoMessage(context, groupId, SignalServiceGroup.Type.CREATION, groupName, members, listOf(adminUserId), threadId, (timestampNow - (numMessages * 5000))) - } else { - storage.insertIncomingInfoMessage(context, adminUserId, groupId, SignalServiceGroup.Type.CREATION, groupName, members, listOf(adminUserId), (timestampNow - (numMessages * 5000))) - } - - // Generate the message history (Note: Unapproved message requests will only include incoming messages) - logProgress("Closed Group Thread $threadIndex", "Generate $numMessages Messages") - - (0 until numGroupMembers).forEach { - val messageWords: Int = (1 + cgThreadRandomGenerator.nextInt(19)) - val senderId: String = members.random(cgThreadRandomGenerator.asKotlinRandom()) - - if (senderId != userAccountId) { - smsDb.insertMessageInbox( - IncomingTextMessage( - Address.fromSerialized(senderId), - 1, - (timestampNow - (index * 5000)), - (0 until messageWords) - .map { wordContent.random(cgThreadRandomGenerator.asKotlinRandom()) } - .joinToString(), - Optional.absent(), - 0, - 0, - false, - -1, - false - ), - (timestampNow - (index * 5000)), - false - ) - } - else { - smsDb.insertMessageOutbox( - threadId, - OutgoingTextMessage( - threadDb.getRecipientForThreadId(threadId), - (0 until messageWords) - .map { wordContent.random(cgThreadRandomGenerator.asKotlinRandom()) } - .joinToString(), - 0, - 0, - -1, - (timestampNow - (index * 5000)) - ), - (timestampNow - (index * 5000)), - false - ) - } - } - - logProgress("Closed Group Thread $threadIndex", "Done") - } - - cgThreadIndex += chunkSize - } - logProgress("Closed Group Threads", "Done") - - // --Open Group - - val ogThreadRandomGenerator: SecureRandom = SecureRandom(cgRandomSeed.toByteArray()) - var ogThreadIndex: Int = 0 - logProgress("Open Group Threads", "Start Generating $openGroupThreadCount threads") - - while (ogThreadIndex < openGroupThreadCount) { - val remainingThreads: Int = (openGroupThreadCount - ogThreadIndex) - - (0 until Math.min(chunkSize, remainingThreads)).forEach { index -> - val threadIndex: Int = (ogThreadIndex + index) - - logProgress("Open Group Thread $threadIndex", "Start") - - val dataBytes = (0 until 32).map { ogThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } - val randomGroupPublicKey: String = KeyPairUtilities.generate(dataBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey - val serverNameLength: Int = (5 + ogThreadRandomGenerator.nextInt(15)) - val roomNameLength: Int = (5 + ogThreadRandomGenerator.nextInt(15)) - val roomDescriptionLength: Int = (10 + ogThreadRandomGenerator.nextInt(40)) - val serverName: String = (0 until serverNameLength) - .map { stringContent.random(ogThreadRandomGenerator.asKotlinRandom()) } - .joinToString() - val roomName: String = (0 until roomNameLength) - .map { stringContent.random(ogThreadRandomGenerator.asKotlinRandom()) } - .joinToString() - val roomDescription: String = (0 until roomDescriptionLength) - .map { stringContent.random(ogThreadRandomGenerator.asKotlinRandom()) } - .joinToString() - val numGroupMembers: Int = ogThreadRandomGenerator.nextInt(250) - val numMessages: Int = ( - messageRangePerThread[threadIndex % messageRangePerThread.count()].first + - ogThreadRandomGenerator.nextInt(messageRangePerThread[threadIndex % messageRangePerThread.count()].last()) - ) - - // Generate the Contacts in the group - val members: MutableList = mutableListOf(userAccountId) - logProgress("Open Group Thread $threadIndex", "Generate $numGroupMembers Contacts") - - (0 until numGroupMembers).forEach { - val contactBytes = (0 until 16).map { ogThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } - val randomAccountId: String = KeyPairUtilities.generate(contactBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey - val contactNameLength: Int = (5 + ogThreadRandomGenerator.nextInt(15)) - - val recipient = Recipient.from(context, Address.fromSerialized(randomAccountId), false) - val contact = Contact(randomAccountId) - contactDb.setContact(contact) - recipientDb.setApproved(recipient, true) - recipientDb.setApprovedMe(recipient, true) - - contact.name = (0 until ogThreadRandomGenerator.nextInt(contactNameLength)) - .map { stringContent.random(cgThreadRandomGenerator.asKotlinRandom()) } - .joinToString() - recipientDb.setProfileName(recipient, contact.name) - contactDb.setContact(contact) - members.add(randomAccountId) - } - - // Create the open group model and the thread - val openGroupId = "$serverName.$roomName" - val threadId = GroupManager.createOpenGroup(openGroupId, context, null, roomName).threadId - val hasBlinding: Boolean = ogThreadRandomGenerator.nextBoolean() - - // Generate the capabilities and other data - storage.setOpenGroupPublicKey(serverName, randomGroupPublicKey) - storage.setServerCapabilities( - serverName, - ( - listOf(OpenGroupApi.Capability.SOGS.name.lowercase()) + - if (hasBlinding) { listOf(OpenGroupApi.Capability.BLIND.name.lowercase()) } else { emptyList() } - ) - ) - storage.setUserCount(roomName, serverName, numGroupMembers) - lokiThreadDB.setOpenGroupChat( - OpenGroup( - server = serverName, room = roomName, publicKey = randomGroupPublicKey, - name = roomName, imageId = null, canWrite = true, infoUpdates = 0, - description = null - ), threadId - ) - - // Generate the message history (Note: Unapproved message requests will only include incoming messages) - logProgress("Open Group Thread $threadIndex", "Generate $numMessages Messages") - - (0 until numMessages).forEach { index -> - val messageWords: Int = (1 + ogThreadRandomGenerator.nextInt(19)) - val senderId: String = members.random(ogThreadRandomGenerator.asKotlinRandom()) - - if (senderId != userAccountId) { - smsDb.insertMessageInbox( - IncomingTextMessage( - Address.fromSerialized(senderId), - 1, - (timestampNow - (index * 5000)), - (0 until messageWords) - .map { wordContent.random(ogThreadRandomGenerator.asKotlinRandom()) } - .joinToString(), - Optional.absent(), - 0, - 0, - false, - -1, - false - ), - (timestampNow - (index * 5000)), - false - ) - } else { - smsDb.insertMessageOutbox( - threadId, - OutgoingTextMessage( - threadDb.getRecipientForThreadId(threadId), - (0 until messageWords) - .map { wordContent.random(ogThreadRandomGenerator.asKotlinRandom()) } - .joinToString(), - 0, - 0, - -1, - (timestampNow - (index * 5000)) - ), - (timestampNow - (index * 5000)), - false - ) - } - } - - logProgress("Open Group Thread $threadIndex", "Done") - } - - ogThreadIndex += chunkSize - } - - logProgress("Open Group Threads", "Done") - logProgress("", "Complete") - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt index 86ebe9eb1a..55101dc835 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt @@ -34,7 +34,7 @@ object SessionMetaProtocol { } @JvmStatic - fun canUserReplyToNotification(recipient: Recipient): Boolean { + fun canUserReplyToNotification(recipient: RecipientV2): Boolean { // TODO return !recipient.address.isRSSFeed return true } @@ -48,10 +48,6 @@ object SessionMetaProtocol { return hasBody || hasAttachment || hasLinkPreview } - @JvmStatic - fun shouldSendReadReceipt(recipient: RecipientV2): Boolean { - return !recipient.isGroupOrCommunityRecipient && recipient.approved && !recipient.blocked - } @JvmStatic fun shouldSendTypingIndicator(recipient: RecipientV2): Boolean { diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt index 1c45c0d141..4ef89f27b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt @@ -14,8 +14,9 @@ import kotlinx.coroutines.flow.scan import kotlinx.coroutines.flow.stateIn import network.loki.messenger.R import org.session.libsession.utilities.Address -import org.session.libsession.utilities.UsernameUtils -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.ConfigFactoryProtocol +import org.session.libsession.utilities.currentUserName +import org.session.libsession.utilities.recipients.displayNameOrFallback import org.thoughtcrime.securesms.conversation.v2.ViewUtil import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_ANSWER_INCOMING @@ -39,8 +40,8 @@ class CallViewModel @Inject constructor( @ApplicationContext private val context: Context, private val callManager: CallManager, private val rtcCallBridge: WebRtcCallBridge, - private val usernameUtils: UsernameUtils, private val recipientRepository: RecipientRepository, + private val configFactory: ConfigFactoryProtocol, ): ViewModel() { //todo PHONE Can we eventually remove this state and instead use the StateMachine.kt State? @@ -201,9 +202,11 @@ class CallViewModel @Inject constructor( fun hangUp() = rtcCallBridge.handleLocalHangup(null) - fun getContactName(accountID: String) = usernameUtils.getContactNameWithAccountID(accountID) + fun getContactName(accountID: String) = + recipientRepository.getRecipientSync(Address.fromSerialized(accountID)) + .displayNameOrFallback(address = accountID) - fun getCurrentUsername() = usernameUtils.getCurrentUsernameWithAccountIdFallback() + fun getCurrentUsername() = configFactory.currentUserName data class CallState( val callLabelTitle: String?, From 6c7a81309d9c2a60dd892b1185ad59d5dab1654e Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Thu, 26 Jun 2025 16:40:09 +1000 Subject: [PATCH 10/52] WIP --- .../messaging/jobs/RetrieveProfileAvatarJob.kt | 4 ++-- .../securesms/components/ProfilePictureView.kt | 5 +++-- .../securesms/database/RecipientRepository.kt | 4 ++++ .../org/thoughtcrime/securesms/util/AvatarUtils.kt | 12 +++--------- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt b/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt index 98c810acee..bdcee4e042 100644 --- a/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt +++ b/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt @@ -72,7 +72,7 @@ class RetrieveProfileAvatarJob( } AvatarHelper.delete(context, recipientAddress) - storage.setProfilePicture(recipientAddress, null, null) +// storage.setProfilePicture(recipientAddress, null, null) return } @@ -95,7 +95,7 @@ class RetrieveProfileAvatarJob( setProfilePictureURL(context, profileAvatar) } - storage.setProfilePicture(recipientAddress, profileAvatar, profileKey) +// storage.setProfilePicture(recipientAddress, profileAvatar, profileKey) } catch (e: NonRetryableException){ Log.e("Loki", "Failed to download profile avatar from non-retryable error", e) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index 13eededd8c..b33221bb00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -14,6 +14,7 @@ import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ViewProfilePictureBinding import org.session.libsession.avatars.ContactColors +import org.session.libsession.avatars.ContactPhoto import org.session.libsession.avatars.PlaceholderAvatarPhoto import org.session.libsession.avatars.ProfileContactPhoto import org.session.libsession.avatars.ResourceContactPhoto @@ -196,7 +197,7 @@ class ProfilePictureView @JvmOverloads constructor( if (profilePicturesCache[imageView] == recipient) return // recipient is mutable so without cloning it the line above always returns true as the changes to the underlying recipient happens on both shared instances profilePicturesCache[imageView] = recipient - val signalProfilePicture = recipient.contactPhoto + val signalProfilePicture: ContactPhoto? = null val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject glide.clear(imageView) @@ -218,7 +219,7 @@ class ProfilePictureView @JvmOverloads constructor( glide.load(createUnknownRecipientDrawable(publicKey)) .centerCrop() .into(imageView) - } else if (recipient.isCommunityRecipient && recipient.groupAvatarId == null) { + } else if (recipient.isCommunityRecipient && avatar == null) { glide.load(unknownOpenGroupDrawable) .centerCrop() .into(imageView) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index c8f4f83146..326305f3ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -275,6 +275,10 @@ class RecipientRepository @Inject constructor( return getRecipientSync(address) ?: empty(address) } + suspend fun getRecipientOrEmpty(address: Address): RecipientV2 { + return getRecipient(address) ?: empty(address) + } + companion object { private const val TAG = "RecipientRepository" diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt index 967791a1d0..4d7206c1cb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt @@ -94,11 +94,7 @@ class AvatarUtils @Inject constructor( // and the second should be the unknown icon with a colour based on the group id elements.add( getUIElementForRecipient( - Recipient.from( - context, Address.fromSerialized( - members[0].toString() - ), false - ) + recipientRepository.getRecipientOrEmpty(Address.fromSerialized(members[0].toString())) ) ) @@ -112,9 +108,7 @@ class AvatarUtils @Inject constructor( else -> { members.forEach { elements.add( - getUIElementForRecipient( - Recipient.from(context, it, false) - ) + getUIElementForRecipient(recipientRepository.getRecipientOrEmpty(it)) ) } } @@ -139,7 +133,7 @@ class AvatarUtils @Inject constructor( // custom image val (contactPhoto, customIcon, color) = when { // use custom image if there is one - hasAvatar(recipient.contactPhoto) -> Triple(recipient.contactPhoto, null, defaultColor) + hasAvatar(recipient.avatar as? ContactPhoto) -> Triple(recipient.avatar as? ContactPhoto, null, defaultColor) // communities without a custom image should use a default image recipient.isCommunityRecipient -> Triple(null, R.drawable.session_logo, null) From 1675762ae8e093ae711dc217cc3258cc282c1aad Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Fri, 27 Jun 2025 11:03:23 +1000 Subject: [PATCH 11/52] WIP --- .../libsession/database/StorageProtocol.kt | 3 +- .../libsession/messaging/contacts/Contact.kt | 1 - .../signal/OutgoingGroupMediaMessage.java | 1 - .../messages/signal/OutgoingMediaMessage.java | 1 - .../signal/OutgoingSecureMediaMessage.java | 1 - .../messages/signal/OutgoingTextMessage.java | 1 - .../notifications/MessageNotifier.kt | 2 - .../utilities/ConfigFactoryProtocol.kt | 36 - .../utilities/ProfilePictureModifiedEvent.kt | 5 - .../utilities/recipients/Recipient.java | 697 ------------------ .../recipients/RecipientExporter.java | 44 -- .../recipients/RecipientModifiedListener.java | 6 - .../recipients/RecipientProvider.java | 210 ------ .../utilities/recipients/RecipientV2.kt | 33 + .../thoughtcrime/securesms/ShareActivity.kt | 1 - .../securesms/ShortcutLauncherActivity.kt | 1 - .../securesms/components/FromTextView.java | 14 +- .../components/TypingStatusSender.java | 1 - .../contacts/ContactsCursorLoader.java | 2 +- .../conversation/v2/ConversationActivityV2.kt | 23 +- .../conversation/v2/ConversationAdapter.kt | 1 - .../v2/ConversationReactionOverlay.kt | 1 - .../v2/components/AlbumThumbnailView.kt | 2 - .../TypingIndicatorViewContainer.kt | 4 +- .../conversation/v2/dialogs/BlockedDialog.kt | 1 - .../conversation/v2/input_bar/InputBar.kt | 2 - .../v2/messages/AttachmentControlView.kt | 1 - .../conversation/v2/messages/QuoteView.kt | 1 - .../v2/messages/VisibleMessageContentView.kt | 2 - .../NotificationSettingsViewModel.kt | 4 - .../v2/utilities/AttachmentManager.java | 1 - .../securesms/database/GroupDatabase.java | 59 +- .../securesms/database/MmsDatabase.kt | 4 +- .../securesms/database/RecipientDatabase.java | 101 +-- .../securesms/database/RecipientRepository.kt | 30 +- .../securesms/database/SmsDatabase.java | 48 +- .../securesms/database/Storage.kt | 24 +- .../securesms/database/ThreadDatabase.java | 4 - .../database/loaders/PagingMediaLoader.java | 1 - .../database/model/DisplayRecord.java | 1 - .../database/model/MediaMmsMessageRecord.java | 1 - .../database/model/MessageRecord.java | 1 - .../database/model/SmsMessageRecord.java | 6 +- .../database/model/ThreadRecord.java | 6 +- .../securesms/dependencies/DatabaseModule.kt | 4 - .../securesms/groups/GroupManager.java | 18 +- .../home/ConversationOptionsBottomSheet.kt | 3 - .../securesms/home/HomeActivity.kt | 13 - .../securesms/media/MediaOverviewViewModel.kt | 3 - .../mediasend/MediaPickerFolderFragment.java | 1 - .../securesms/mediasend/MediaSendActivity.kt | 1 - .../securesms/mediasend/MediaSendFragment.kt | 1 - .../messagerequests/MessageRequestView.kt | 1 - .../AndroidAutoReplyReceiver.java | 17 +- .../notifications/DefaultMessageNotifier.kt | 5 - .../notifications/NotificationChannels.java | 63 -- .../OptimizedMessageNotifier.java | 12 +- .../notifications/RemoteReplyReceiver.java | 14 +- .../preferences/BlockedContactsAdapter.kt | 1 - .../securesms/preferences/QRCodeActivity.kt | 1 - .../reactions/ReactionRecipientsAdapter.java | 2 +- .../reactions/ReactionsRepository.kt | 1 - .../securesms/search/model/MessageResult.java | 1 - .../securesms/service/DirectShareService.java | 23 +- .../service/ExpiringMessageManager.kt | 1 - .../TypingStatusRepository.java | 30 +- .../securesms/tokenpage/TokenPageViewModel.kt | 1 - .../securesms/util/AvatarUtils.kt | 1 - .../securesms/util/SessionMetaProtocol.kt | 1 - .../securesms/webrtc/CallManager.kt | 1 - .../securesms/webrtc/CallMessageProcessor.kt | 1 - .../webrtc/CallNotificationBuilder.kt | 1 - .../DisappearingMessagesViewModelTest.kt | 1 - .../v2/ConversationViewModelTest.kt | 1 - .../conversation/v2/MentionViewModelTest.kt | 1 - .../recipients/RecipientExporterTest.java | 37 - 76 files changed, 184 insertions(+), 1467 deletions(-) delete mode 100644 app/src/main/java/org/session/libsession/utilities/ProfilePictureModifiedEvent.kt delete mode 100644 app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java delete mode 100644 app/src/main/java/org/session/libsession/utilities/recipients/RecipientExporter.java delete mode 100644 app/src/main/java/org/session/libsession/utilities/recipients/RecipientModifiedListener.java delete mode 100644 app/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java delete mode 100644 app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientExporterTest.java diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 70c5ee20d2..10f3ba45d1 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -27,7 +27,7 @@ import org.session.libsession.messaging.utilities.UpdateMessageData import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupDisplayInfo import org.session.libsession.utilities.GroupRecord -import org.session.libsession.utilities.recipients.Recipient.RecipientSettings +import org.session.libsession.utilities.recipients.RecipientSettings import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceAttachmentPointer @@ -129,7 +129,6 @@ interface StorageProtocol { fun createInitialConfigGroup(groupPublicKey: String, name: String, members: Map, formationTimestamp: Long, encryptionKeyPair: ECKeyPair, expirationTimer: Int) fun isGroupActive(groupPublicKey: String): Boolean fun setActive(groupID: String, value: Boolean) - fun getZombieMembers(groupID: String): Set fun removeMember(groupID: String, member: Address) fun updateMembers(groupID: String, members: List
) fun getAllLegacyGroupPublicKeys(): Set diff --git a/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt b/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt index 9b41df6f08..5a1c313aa5 100644 --- a/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt +++ b/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt @@ -2,7 +2,6 @@ package org.session.libsession.messaging.contacts import android.os.Parcelable import kotlinx.parcelize.Parcelize -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.truncateIdForDisplay diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java index 10e2e93c2a..4c1d7ead1b 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java @@ -9,7 +9,6 @@ import org.session.libsession.utilities.Contact; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; -import org.session.libsession.utilities.recipients.Recipient; import java.util.LinkedList; import java.util.List; diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java index 9a53a2c574..85ae7a0a0d 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java @@ -12,7 +12,6 @@ import org.session.libsession.utilities.DistributionTypes; import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.NetworkFailure; -import org.session.libsession.utilities.recipients.Recipient; import java.util.Collections; import java.util.LinkedList; diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java index 3a897091a5..9d8b5e9521 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java @@ -8,7 +8,6 @@ import org.session.libsession.utilities.Contact; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; -import org.session.libsession.utilities.recipients.Recipient; import java.util.Collections; import java.util.List; diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java index 41cb1bf6d2..c79a68102f 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java @@ -3,7 +3,6 @@ import org.session.libsession.messaging.messages.visible.OpenGroupInvitation; import org.session.libsession.messaging.messages.visible.VisibleMessage; import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.messaging.utilities.UpdateMessageData; public class OutgoingTextMessage { diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/MessageNotifier.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/MessageNotifier.kt index 8de01ca53e..0573561c9f 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/MessageNotifier.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/MessageNotifier.kt @@ -1,13 +1,11 @@ package org.session.libsession.messaging.sending_receiving.notifications import android.content.Context -import org.session.libsession.utilities.recipients.Recipient interface MessageNotifier { fun setHomeScreenVisible(isVisible: Boolean) fun setVisibleThread(threadId: Long) fun setLastDesktopActivityTimestamp(timestamp: Long) - fun notifyMessageDeliveryFailed(context: Context?, recipient: Recipient?, threadId: Long) fun cancelDelayedNotifications() fun updateNotification(context: Context) fun updateNotification(context: Context, threadId: Long) diff --git a/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt b/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt index b1607ee800..77479d1b96 100644 --- a/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt +++ b/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt @@ -26,7 +26,6 @@ import network.loki.messenger.libsession_util.util.ConfigPush import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.snode.SwarmAuth -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.AccountId interface ConfigFactoryProtocol { @@ -125,41 +124,6 @@ fun ConfigFactoryProtocol.getGroup(groupId: AccountId): GroupInfo.ClosedGroupInf return withUserConfigs { it.userGroups.getClosedGroup(groupId.hexString) } } -/** - * Shortcut to check if the current user was kicked from a given group V2 (as a Recipient) - */ -fun ConfigFactoryProtocol.wasKickedFromGroupV2(group: Recipient) = - group.isGroupV2Recipient && getGroup(AccountId(group.address.toString()))?.kicked == true - -/** - * Shortcut to check if the a given group is destroyed - */ -fun ConfigFactoryProtocol.isGroupDestroyed(group: Recipient) = - group.isGroupV2Recipient && getGroup(AccountId(group.address.toString()))?.destroyed == true - -/** - * Wait until all user configs are pushed to the server. - * - * This function is not essential to the pushing of the configs, the config push will schedule - * itself upon changes, so this function is purely observatory. - * - * This function will check the user configs immediately, if nothing needs to be pushed, it will return immediately. - * - * @return True if all user configs are pushed, false if the timeout is reached. - */ -suspend fun ConfigFactoryProtocol.waitUntilUserConfigsPushed(timeoutMills: Long = 10_000L): Boolean { - fun needsPush() = withUserConfigs { configs -> - UserConfigType.entries.any { configs.getConfig(it).needsPush() } - } - - return withTimeoutOrNull(timeoutMills){ - configUpdateNotifications - .onStart { emit(ConfigUpdateNotification.UserConfigsModified) } // Trigger the filtering immediately - .filter { it == ConfigUpdateNotification.UserConfigsModified && !needsPush() } - .first() - } != null -} - /** * Flow that emits when the user configs are modified or merged. */ diff --git a/app/src/main/java/org/session/libsession/utilities/ProfilePictureModifiedEvent.kt b/app/src/main/java/org/session/libsession/utilities/ProfilePictureModifiedEvent.kt deleted file mode 100644 index f06ca2ec0f..0000000000 --- a/app/src/main/java/org/session/libsession/utilities/ProfilePictureModifiedEvent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.session.libsession.utilities - -import org.session.libsession.utilities.recipients.Recipient - -data class ProfilePictureModifiedEvent(val recipient: Recipient) \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java deleted file mode 100644 index cb68ba5714..0000000000 --- a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java +++ /dev/null @@ -1,697 +0,0 @@ -/* - * Copyright (C) 2011 Whisper Systems - * Copyright (C) 2013 - 2017 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.session.libsession.utilities.recipients; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.annimon.stream.function.Consumer; - -import org.greenrobot.eventbus.EventBus; -import org.session.libsession.avatars.ContactColors; -import org.session.libsession.avatars.ContactPhoto; -import org.session.libsession.avatars.GroupRecordContactPhoto; -import org.session.libsession.avatars.ProfileContactPhoto; -import org.session.libsession.avatars.TransparentContactPhoto; -import org.session.libsession.messaging.MessagingModuleConfiguration; -import org.session.libsession.messaging.contacts.Contact; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.FutureTaskListener; -import org.session.libsession.utilities.GroupRecord; -import org.session.libsession.utilities.GroupUtil; -import org.session.libsession.utilities.ListenableFutureTask; -import org.session.libsession.utilities.MaterialColor; -import org.session.libsession.utilities.ProfilePictureModifiedEvent; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.UsernameUtils; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.RecipientProvider.RecipientDetails; -import org.session.libsignal.utilities.Log; -import org.session.libsignal.utilities.guava.Optional; -import org.thoughtcrime.securesms.database.model.NotifyType; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.WeakHashMap; -import java.util.concurrent.ExecutionException; - -import kotlinx.coroutines.flow.Flow; - -public class Recipient implements RecipientModifiedListener, Cloneable { - - private static final String TAG = Recipient.class.getSimpleName(); - private static final RecipientProvider provider = new RecipientProvider(); - - private final Set listeners = Collections.newSetFromMap(new WeakHashMap()); - - private final @NonNull Address address; - private final @NonNull List participants = new LinkedList<>(); - - private final Context context; - private @Nullable String name; - private boolean resolving; - private boolean isLocalNumber; - - private @Nullable Long groupAvatarId; - public long mutedUntil = 0; - @NotifyType - public int notifyType = 0; - private boolean autoDownloadAttachments = false; - private boolean blocked = false; - private boolean approved = false; - private boolean approvedMe = false; - - private @Nullable byte[] profileKey; - private @Nullable String profileName; - private @Nullable String profileAvatar; - private String notificationChannel; - private boolean blocksCommunityMessageRequests; - - @SuppressWarnings("ConstantConditions") - public static @NonNull Flow from(@NonNull Context context, @NonNull Address address, boolean asynchronous) { - if (address == null) throw new AssertionError(address); - throw new UnsupportedOperationException(); - } - - @SuppressWarnings("ConstantConditions") - public static @NonNull Flow from(@NonNull Context context, @NonNull Address address, @NonNull Optional settings, @NonNull Optional groupRecord, boolean asynchronous) { - if (address == null) throw new AssertionError(address); - throw new UnsupportedOperationException(); - } - - public static void applyCached(@NonNull Address address, Consumer consumer) { - Optional recipient = provider.getCached(address); - if (recipient.isPresent()) consumer.accept(recipient.get()); - } - - public static boolean removeCached(@NonNull Address address) { - return provider.removeCached(address); - } - - Recipient(@NonNull Context context, - @NonNull Address address, - @Nullable Recipient stale, - @NonNull Optional details, - @NonNull ListenableFutureTask future) - { - this.context = context.getApplicationContext(); - this.address = address; - this.resolving = true; - - if (stale != null) { - this.name = stale.name; - this.groupAvatarId = stale.groupAvatarId; - this.isLocalNumber = stale.isLocalNumber; - this.mutedUntil = stale.mutedUntil; - this.blocked = stale.blocked; - this.approved = stale.approved; - this.approvedMe = stale.approvedMe; - this.notificationChannel = stale.notificationChannel; - this.profileKey = stale.profileKey; - this.profileName = stale.profileName; - this.profileAvatar = stale.profileAvatar; - this.notifyType = stale.notifyType; - this.autoDownloadAttachments = stale.autoDownloadAttachments; - - this.participants.clear(); - this.participants.addAll(stale.participants); - } - - if (details.isPresent()) { - this.name = details.get().name; - this.groupAvatarId = details.get().groupAvatarId; - this.isLocalNumber = details.get().isLocalNumber; - this.mutedUntil = details.get().mutedUntil; - this.blocked = details.get().blocked; - this.approved = details.get().approved; - this.approvedMe = details.get().approvedMe; - this.notificationChannel = details.get().notificationChannel; - this.profileKey = details.get().profileKey; - this.profileName = details.get().profileName; - this.profileAvatar = details.get().profileAvatar; - this.notifyType = details.get().notifyType; - this.autoDownloadAttachments = details.get().autoDownloadAttachments; - this.blocksCommunityMessageRequests = details.get().blocksCommunityMessageRequests; - - this.participants.clear(); - this.participants.addAll(details.get().participants); - } - - future.addListener(new FutureTaskListener() { - @Override - public void onSuccess(RecipientDetails result) { - if (result != null) { - synchronized (Recipient.this) { - Recipient.this.name = result.name; - Recipient.this.groupAvatarId = result.groupAvatarId; - Recipient.this.isLocalNumber = result.isLocalNumber; - Recipient.this.mutedUntil = result.mutedUntil; - Recipient.this.blocked = result.blocked; - Recipient.this.approved = result.approved; - Recipient.this.approvedMe = result.approvedMe; - Recipient.this.notificationChannel = result.notificationChannel; - Recipient.this.profileKey = result.profileKey; - Recipient.this.profileName = result.profileName; - Recipient.this.profileAvatar = result.profileAvatar; - Recipient.this.notifyType = result.notifyType; - Recipient.this.autoDownloadAttachments = result.autoDownloadAttachments; - Recipient.this.blocksCommunityMessageRequests = result.blocksCommunityMessageRequests; - - - Recipient.this.participants.clear(); - Recipient.this.participants.addAll(result.participants); - Recipient.this.resolving = false; - - if (!listeners.isEmpty()) { - for (Recipient recipient : participants) recipient.addListener(Recipient.this); - } - - Recipient.this.notifyAll(); - } - - notifyListeners(); - } - } - - @Override - public void onFailure(ExecutionException error) { - Log.w(TAG, error); - } - }); - } - - Recipient(@NonNull Context context, @NonNull Address address, @NonNull RecipientDetails details) { - this.context = context.getApplicationContext(); - this.address = address; - this.name = details.name; - this.groupAvatarId = details.groupAvatarId; - this.isLocalNumber = details.isLocalNumber; - this.mutedUntil = details.mutedUntil; - this.notifyType = details.notifyType; - this.autoDownloadAttachments = details.autoDownloadAttachments; - this.blocked = details.blocked; - this.approved = details.approved; - this.approvedMe = details.approvedMe; - this.notificationChannel = details.notificationChannel; - this.profileKey = details.profileKey; - this.profileName = details.profileName; - this.profileAvatar = details.profileAvatar; - this.blocksCommunityMessageRequests = details.blocksCommunityMessageRequests; - - this.participants.addAll(details.participants); - this.resolving = false; - } - - public boolean isLocalNumber() { - return isLocalNumber; - } - - public synchronized @NonNull String getName() { - UsernameUtils usernameUtils = MessagingModuleConfiguration.getShared().getUsernameUtils(); - String accountID = this.address.toString(); - if (isGroupOrCommunityRecipient()) { - if (this.name == null) { - List names = new LinkedList<>(); - for (Recipient recipient : participants) { - names.add(recipient.name); - } - return Util.join(names, ", "); - } else { - return this.name; - } - } else if (isCommunityInboxRecipient()){ - String inboxID = GroupUtil.getDecodedOpenGroupInboxAccountId(accountID); - return usernameUtils.getContactNameWithAccountID(inboxID, null, Contact.ContactContext.OPEN_GROUP); - } else { - return usernameUtils.getContactNameWithAccountID(accountID, null, Contact.ContactContext.REGULAR); - } - } - - public void setName(@Nullable String name) { - boolean notify = false; - - synchronized (this) { - if (!Util.equals(this.name, name)) { - this.name = name; - notify = true; - } - } - - if (notify) notifyListeners(); - } - - public boolean getBlocksCommunityMessageRequests() { - return blocksCommunityMessageRequests; - } - - public void setBlocksCommunityMessageRequests(boolean blocksCommunityMessageRequests) { - synchronized (this) { - this.blocksCommunityMessageRequests = blocksCommunityMessageRequests; - } - - notifyListeners(); - } - - public synchronized @NonNull MaterialColor getColor() { - if (isGroupOrCommunityRecipient()) return MaterialColor.GROUP; - else if (name != null) return ContactColors.generateFor(name); - else return ContactColors.UNKNOWN_COLOR; - } - - public @NonNull Address getAddress() { - return address; - } - - public synchronized @Nullable String getProfileName() { - return profileName; - } - - public void setProfileName(@Nullable String profileName) { - synchronized (this) { - this.profileName = profileName; - } - - notifyListeners(); - } - - public synchronized @Nullable String getProfileAvatar() { - return profileAvatar; - } - - public void setProfileAvatar(@Nullable String profileAvatar) { - synchronized (this) { - this.profileAvatar = profileAvatar; - } - - notifyListeners(); - EventBus.getDefault().post(new ProfilePictureModifiedEvent(this)); - } - - public boolean isGroupOrCommunityRecipient() { - return address.isGroupOrCommunity(); - } - - public boolean isContactRecipient() { - return address.isContact(); - } - public boolean is1on1() { return address.isContact() && !isLocalNumber; } - - public boolean isCommunityRecipient() { - return address.isCommunity(); - } - - public boolean isCommunityOutboxRecipient() { - return address.isCommunityOutbox(); - } - - public boolean isCommunityInboxRecipient() { - return address.isCommunityInbox(); - } - - public boolean isLegacyGroupRecipient() { - return address.isLegacyGroup(); - } - - public boolean isGroupRecipient() { - return address.isGroup(); - } - - public boolean isGroupV2Recipient() { - return address.isGroupV2(); - } - - - @Deprecated - public boolean isPushGroupRecipient() { - return address.isGroupOrCommunity(); - } - - public @NonNull synchronized List getParticipants() { - return new LinkedList<>(participants); - } - - public void setParticipants(@NonNull List participants) { - synchronized (this) { - this.participants.clear(); - this.participants.addAll(participants); - } - - notifyListeners(); - } - - public synchronized void addListener(RecipientModifiedListener listener) { - if (listeners.isEmpty()) { - for (Recipient recipient : participants) recipient.addListener(this); - } - listeners.add(listener); - } - - public synchronized void removeListener(RecipientModifiedListener listener) { - listeners.remove(listener); - - if (listeners.isEmpty()) { - for (Recipient recipient : participants) recipient.removeListener(this); - } - } - - public synchronized @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted) { - return (new TransparentContactPhoto()).asDrawable(context, getColor().toAvatarColor(context), inverted); - } - - public synchronized @Nullable ContactPhoto getContactPhoto() { - if (isLocalNumber) return new ProfileContactPhoto(address, String.valueOf(TextSecurePreferences.getProfileAvatarId(context))); - else if (isGroupOrCommunityRecipient() && groupAvatarId != null) return new GroupRecordContactPhoto(address, groupAvatarId); - else if (profileAvatar != null) return new ProfileContactPhoto(address, profileAvatar); - else return null; - } - - public void setGroupAvatarId(@Nullable Long groupAvatarId) { - boolean notify = false; - - synchronized (this) { - if (!Util.equals(this.groupAvatarId, groupAvatarId)) { - this.groupAvatarId = groupAvatarId; - notify = true; - } - } - - if (notify) notifyListeners(); - } - - @Nullable - public synchronized Long getGroupAvatarId() { - return groupAvatarId; - } - - public synchronized boolean isMuted() { - return System.currentTimeMillis() <= mutedUntil; - } - - public void setMuted(long mutedUntil) { - synchronized (this) { - this.mutedUntil = mutedUntil; - } - - notifyListeners(); - } - - public void setNotifyType(@NotifyType int notifyType) { - synchronized (this) { - this.notifyType = notifyType; - } - - notifyListeners(); - } - - public boolean getAutoDownloadAttachments() { - return autoDownloadAttachments; - } - - public void setAutoDownloadAttachments(boolean autoDownloadAttachments) { - synchronized (this) { - this.autoDownloadAttachments = autoDownloadAttachments; - } - - notifyListeners(); - } - - public synchronized boolean isBlocked() { - return blocked; - } - - public void setBlocked(boolean blocked) { - synchronized (this) { - this.blocked = blocked; - } - - notifyListeners(); - } - - public synchronized boolean isApproved() { - return approved; - } - - public void setApproved(boolean approved) { - synchronized (this) { - this.approved = approved; - } - - notifyListeners(); - } - - public synchronized boolean hasApprovedMe() { - return approvedMe; - } - - public void setHasApprovedMe(boolean approvedMe) { - synchronized (this) { - this.approvedMe = approvedMe; - } - - notifyListeners(); - } - - public synchronized @Nullable String getNotificationChannel() { - return notificationChannel; - } - - public void setNotificationChannel(@Nullable String value) { - boolean notify = false; - - synchronized (this) { - if (!Util.equals(this.notificationChannel, value)) { - this.notificationChannel = value; - notify = true; - } - } - - if (notify) notifyListeners(); - } - - public synchronized @Nullable byte[] getProfileKey() { - return profileKey; - } - - public void setProfileKey(@Nullable byte[] profileKey) { - synchronized (this) { - this.profileKey = profileKey; - } - - notifyListeners(); - } - - public synchronized Recipient resolve() { - while (resolving) Util.wait(this, 0); - return this; - } - - public synchronized boolean showCallMenu() { - return !isGroupOrCommunityRecipient() && hasApprovedMe() && isApproved(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Recipient recipient = (Recipient) o; - return resolving == recipient.resolving - && mutedUntil == recipient.mutedUntil - && notifyType == recipient.notifyType - && blocked == recipient.blocked - && approved == recipient.approved - && approvedMe == recipient.approvedMe - && address.equals(recipient.address) - && Objects.equals(name, recipient.name) - && Objects.equals(groupAvatarId, recipient.groupAvatarId) - && Arrays.equals(profileKey, recipient.profileKey) - && Objects.equals(profileName, recipient.profileName) - && Objects.equals(profileAvatar, recipient.profileAvatar) - && blocksCommunityMessageRequests == recipient.blocksCommunityMessageRequests; - } - - @Override - public int hashCode() { - int result = Objects.hash( - address, - name, - resolving, - groupAvatarId, - mutedUntil, - notifyType, - blocked, - approved, - approvedMe, - profileName, - profileAvatar, - blocksCommunityMessageRequests - ); - result = 31 * result + Arrays.hashCode(profileKey); - return result; - } - - public void notifyListeners() { - Set localListeners; - - synchronized (this) { - localListeners = new HashSet<>(listeners); - } - - for (RecipientModifiedListener listener : localListeners) - listener.onModified(this); - } - - @Override - public void onModified(Recipient recipient) { - notifyListeners(); - } - - public synchronized boolean isResolving() { - return resolving; - } - - public enum DisappearingState { - LEGACY(0), UPDATED(1); - - private final int id; - - DisappearingState(int id) { - this.id = id; - } - - public int getId() { - return id; - } - - public static DisappearingState fromId(int id) { - return values()[id]; - } - } - - public static class RecipientSettings { - private final boolean blocked; - private final boolean approved; - private final boolean approvedMe; - private final long muteUntil; - private final int notifyType; - @Nullable - private final Boolean autoDownloadAttachments; - private final int expireMessages; - private final byte[] profileKey; - private final String systemDisplayName; - private final String signalProfileName; - private final String signalProfileAvatar; - private final String notificationChannel; - private final boolean blocksCommunityMessageRequests; - - public RecipientSettings(boolean blocked, boolean approved, boolean approvedMe, long muteUntil, - int notifyType, - @Nullable Boolean autoDownloadAttachments, - int expireMessages, - @Nullable byte[] profileKey, - @Nullable String systemDisplayName, - @Nullable String signalProfileName, - @Nullable String signalProfileAvatar, - @Nullable String notificationChannel, - boolean blocksCommunityMessageRequests - ) - { - this.blocked = blocked; - this.approved = approved; - this.approvedMe = approvedMe; - this.muteUntil = muteUntil; - this.notifyType = notifyType; - this.autoDownloadAttachments = autoDownloadAttachments; - this.expireMessages = expireMessages; - this.profileKey = profileKey; - this.systemDisplayName = systemDisplayName; - this.signalProfileName = signalProfileName; - this.signalProfileAvatar = signalProfileAvatar; - this.notificationChannel = notificationChannel; - this.blocksCommunityMessageRequests = blocksCommunityMessageRequests; - } - - public boolean isBlocked() { - return blocked; - } - - public boolean isApproved() { - return approved; - } - - public boolean hasApprovedMe() { - return approvedMe; - } - - public long getMuteUntil() { - return muteUntil; - } - - @NotifyType - public int getNotifyType() { - return notifyType; - } - - public boolean getAutoDownloadAttachments() { - return autoDownloadAttachments; - } - - public int getExpireMessages() { - return expireMessages; - } - - public @Nullable byte[] getProfileKey() { - return profileKey; - } - - public @Nullable String getSystemDisplayName() { - return systemDisplayName; - } - - public @Nullable String getProfileName() { - return signalProfileName; - } - - public @Nullable String getProfileAvatar() { - return signalProfileAvatar; - } - - public @Nullable String getNotificationChannel() { - return notificationChannel; - } - - public boolean getBlocksCommunityMessageRequests() { - return blocksCommunityMessageRequests; - } - - } - - @NonNull - @Override - public Recipient clone() throws CloneNotSupportedException { - return (Recipient) super.clone(); - } -} diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientExporter.java b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientExporter.java deleted file mode 100644 index f01a8174ae..0000000000 --- a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientExporter.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.session.libsession.utilities.recipients; - -import android.content.Intent; -import android.provider.ContactsContract; -import android.text.TextUtils; - -import org.session.libsession.utilities.Address; - -import static android.content.Intent.ACTION_INSERT_OR_EDIT; - -public final class RecipientExporter { - - public static RecipientExporter export(Recipient recipient) { - return new RecipientExporter(recipient); - } - - private final Recipient recipient; - - private RecipientExporter(Recipient recipient) { - this.recipient = recipient; - } - - public Intent asAddContactIntent() { - Intent intent = new Intent(ACTION_INSERT_OR_EDIT); - intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); - addNameToIntent(intent, recipient.getProfileName()); - addAddressToIntent(intent, recipient.getAddress()); - return intent; - } - - private static void addNameToIntent(Intent intent, String profileName) { - if (!TextUtils.isEmpty(profileName)) { - intent.putExtra(ContactsContract.Intents.Insert.NAME, profileName); - } - } - - private static void addAddressToIntent(Intent intent, Address address) { - if (address.isContact()) { - intent.putExtra(ContactsContract.Intents.Insert.PHONE, address.toString()); - } else { - throw new RuntimeException("Cannot export Recipient with neither phone nor email"); - } - } -} diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientModifiedListener.java b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientModifiedListener.java deleted file mode 100644 index a537f8fdcb..0000000000 --- a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientModifiedListener.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.session.libsession.utilities.recipients; - - -public interface RecipientModifiedListener { - public void onModified(Recipient recipient); -} diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java deleted file mode 100644 index 292be9479b..0000000000 --- a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.session.libsession.utilities.recipients; - -import android.content.Context; -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.session.libsession.messaging.MessagingModuleConfiguration; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.GroupRecord; -import org.session.libsession.utilities.ListenableFutureTask; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.Recipient.RecipientSettings; -import org.session.libsignal.utilities.guava.Optional; - -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; - -import network.loki.messenger.R; - -class RecipientProvider { - - @SuppressWarnings("unused") - private static final String TAG = RecipientProvider.class.getSimpleName(); - - private static final RecipientCache recipientCache = new RecipientCache(); - private static final ExecutorService asyncRecipientResolver = Util.newSingleThreadedLifoExecutor(); - - @NonNull Recipient getRecipient(@NonNull Context context, @NonNull Address address, @NonNull Optional settings, @NonNull Optional groupRecord, boolean asynchronous) { - Recipient cachedRecipient = recipientCache.get(address); - - if (cachedRecipient != null && (asynchronous || !cachedRecipient.isResolving()) && ((!groupRecord.isPresent() && !settings.isPresent()) || !cachedRecipient.isResolving())) { - return cachedRecipient; - } - - Optional prefetchedRecipientDetails = createPrefetchedRecipientDetails(context, address, settings, groupRecord); - - if (asynchronous) { - cachedRecipient = new Recipient(context, address, cachedRecipient, prefetchedRecipientDetails, getRecipientDetailsAsync(context, address, settings, groupRecord)); - } else { - cachedRecipient = new Recipient(context, address, getRecipientDetailsSync(context, address, settings, groupRecord, false)); - } - - recipientCache.set(address, cachedRecipient); - return cachedRecipient; - } - - @NonNull Optional getCached(@NonNull Address address) { - return Optional.fromNullable(recipientCache.get(address)); - } - - boolean removeCached(@NonNull Address address) { - return recipientCache.remove(address); - } - - private @NonNull Optional createPrefetchedRecipientDetails(@NonNull Context context, @NonNull Address address, - @NonNull Optional settings, - @NonNull Optional groupRecord) - { - if (address.isGroupOrCommunity() && settings.isPresent() && groupRecord.isPresent()) { - return Optional.of(getGroupRecipientDetails(context, address, groupRecord, settings, true)); - } else if (!address.isGroupOrCommunity() && settings.isPresent()) { - boolean isLocalNumber = address.toString().equals(TextSecurePreferences.getLocalNumber(context)); - return Optional.of(new RecipientDetails(null, null, !TextUtils.isEmpty(settings.get().getSystemDisplayName()), isLocalNumber, settings.get(), null)); - } - - return Optional.absent(); - } - - private @NonNull ListenableFutureTask getRecipientDetailsAsync(final Context context, final @NonNull Address address, final @NonNull Optional settings, final @NonNull Optional groupRecord) - { - Callable task = () -> getRecipientDetailsSync(context, address, settings, groupRecord, true); - - ListenableFutureTask future = new ListenableFutureTask<>(task); - asyncRecipientResolver.submit(future); - return future; - } - - private @NonNull RecipientDetails getRecipientDetailsSync(Context context, @NonNull Address address, Optional settings, Optional groupRecord, boolean nestedAsynchronous) { - if (address.isLegacyGroup() || address.isCommunity()) return getGroupRecipientDetails(context, address, groupRecord, settings, nestedAsynchronous); - else return getIndividualRecipientDetails(context, address, settings); - } - - private @NonNull RecipientDetails getIndividualRecipientDetails(Context context, @NonNull Address address, Optional settings) { - if (!settings.isPresent()) { - settings = Optional.fromNullable(MessagingModuleConfiguration.getShared().getStorage().getRecipientSettings(address)); - } - - boolean systemContact = settings.isPresent() && !TextUtils.isEmpty(settings.get().getSystemDisplayName()); - boolean isLocalNumber = address.toString().equals(TextSecurePreferences.getLocalNumber(context)); - return new RecipientDetails(null, null, systemContact, isLocalNumber, settings.orNull(), null); - } - - private @NonNull RecipientDetails getGroupRecipientDetails(Context context, Address groupId, Optional groupRecord, Optional settings, boolean asynchronous) { - - if (!groupRecord.isPresent()) { - groupRecord = Optional.fromNullable(MessagingModuleConfiguration.getShared().getStorage().getGroup(groupId.toGroupString())); - } - - if (!settings.isPresent()) { - - settings = Optional.fromNullable(MessagingModuleConfiguration.getShared().getStorage().getRecipientSettings(groupId)); - } - - if (groupRecord.isPresent()) { - String title = groupRecord.get().getTitle(); - List
memberAddresses = groupRecord.get().getMembers(); - List members = new LinkedList<>(); - Long avatarId = null; - - for (Address memberAddress : memberAddresses) { - members.add(getRecipient(context, memberAddress, Optional.absent(), Optional.absent(), asynchronous)); - } - - if (groupRecord.get().getAvatar() != null && groupRecord.get().getAvatar().length > 0) { - avatarId = groupRecord.get().getAvatarId(); - } - - return new RecipientDetails(title, avatarId, false, false, settings.orNull(), members); - } - - return new RecipientDetails(context.getString(R.string.groupUnknown), null, false, false, settings.orNull(), null); - } - - static class RecipientDetails { - @Nullable final String name; - @Nullable final Long groupAvatarId; - final long mutedUntil; - final int notifyType; - final boolean autoDownloadAttachments; - final boolean blocked; - final boolean approved; - final boolean approvedMe; - final int expireMessages; - @NonNull final List participants; - @Nullable final String profileName; - @Nullable final byte[] profileKey; - @Nullable final String profileAvatar; - final boolean systemContact; - final boolean isLocalNumber; - @Nullable final String notificationChannel; - final boolean blocksCommunityMessageRequests; - - RecipientDetails(@Nullable String name, @Nullable Long groupAvatarId, - boolean systemContact, boolean isLocalNumber, @Nullable RecipientSettings settings, - @Nullable List participants) - { - this.groupAvatarId = groupAvatarId; - this.mutedUntil = settings != null ? settings.getMuteUntil() : 0; - this.notifyType = settings != null ? settings.getNotifyType() : 0; - this.autoDownloadAttachments = settings != null && settings.getAutoDownloadAttachments(); - this.blocked = settings != null && settings.isBlocked(); - this.approved = settings != null && settings.isApproved(); - this.approvedMe = settings != null && settings.hasApprovedMe(); - this.expireMessages = settings != null ? settings.getExpireMessages() : 0; - this.participants = participants == null ? new LinkedList<>() : participants; - this.profileName = settings != null ? settings.getProfileName() : null; - this.profileKey = settings != null ? settings.getProfileKey() : null; - this.profileAvatar = settings != null ? settings.getProfileAvatar() : null; - this.systemContact = systemContact; - this.isLocalNumber = isLocalNumber; - this.notificationChannel = settings != null ? settings.getNotificationChannel() : null; - this.blocksCommunityMessageRequests = settings != null && settings.getBlocksCommunityMessageRequests(); - - if (name == null && settings != null) this.name = settings.getSystemDisplayName(); - else this.name = name; - } - } - - private static class RecipientCache { - - private final Map cache = new ConcurrentHashMap<>(1000); - - public Recipient get(Address address) { - return cache.get(address); - } - - public void set(Address address, Recipient recipient) { - cache.put(address, recipient); - } - - public boolean remove(Address address) { - return cache.remove(address) != null; - } - - } - -} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt index 02cc2fa8ed..286cbfdef0 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt @@ -1,8 +1,13 @@ package org.session.libsession.utilities.recipients +import android.content.Context +import android.graphics.drawable.Drawable import network.loki.messenger.libsession_util.util.Bytes import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.UserPic +import org.session.libsession.avatars.ContactPhoto +import org.session.libsession.avatars.ProfileContactPhoto +import org.session.libsession.avatars.TransparentContactPhoto import org.session.libsession.utilities.Address import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.database.RecipientDatabase @@ -48,6 +53,7 @@ data class RecipientV2( else -> false } + @JvmOverloads fun isMuted(now: ZonedDateTime = ZonedDateTime.now()): Boolean { return mutedUntil?.isAfter(now) == true } @@ -58,6 +64,15 @@ data class RecipientV2( val mutedUntilMills: Long? get() = mutedUntil?.toInstant()?.toEpochMilli() + @Deprecated("use avatar instead") + //TODO: Not working + val contactPhoto: ContactPhoto? + get() = when (val a = avatar) { + is RecipientAvatar.EncryptedRemotePic -> ProfileContactPhoto(address, a.url) + is RecipientAvatar.Inline -> null + else -> null + } + @Deprecated("Use `avatar` property instead", ReplaceWith("avatar")) val profileAvatar: String? get() = (avatar as? RecipientAvatar.EncryptedRemotePic)?.url @@ -196,6 +211,24 @@ sealed interface RecipientAvatar { } } +/** + * Represents local database data for a recipient. + */ +class RecipientSettings( + val blocked: Boolean, + val approved: Boolean, + val approvedMe: Boolean, + val muteUntil: Long, + val notifyType: Int, + val autoDownloadAttachments: Boolean?, + val expireMessages: Int, + val profileKey: ByteArray?, + val systemDisplayName: String?, + val profileName: String?, + val profileAvatar: String?, + val blocksCommunityMessagesRequests: Boolean +) + fun RecipientAvatar.toUserPic(): UserPic? { return when (this) { is RecipientAvatar.EncryptedRemotePic -> UserPic(url, key) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt index 278a042521..af578a8ff6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt @@ -35,7 +35,6 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.DistributionTypes import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.ViewUtil -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.components.SearchToolbar import org.thoughtcrime.securesms.components.SearchToolbar.SearchListener diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.kt index d2c4142d65..67499e5a0c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.kt @@ -13,7 +13,6 @@ import kotlinx.coroutines.launch import network.loki.messenger.R import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized -import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.home.HomeActivity diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java b/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java index 6726ebf61d..35cf68c47b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java @@ -15,7 +15,7 @@ import network.loki.messenger.R; -import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import org.thoughtcrime.securesms.components.emoji.EmojiTextView; import org.thoughtcrime.securesms.util.ResUtil; import org.session.libsession.utilities.CenterAlignedRelativeSizeSpan; @@ -32,12 +32,12 @@ public FromTextView(Context context, AttributeSet attrs) { super(context, attrs); } - public void setText(Recipient recipient) { + public void setText(RecipientV2 recipient) { setText(recipient, true); } - public void setText(Recipient recipient, boolean read) { - String fromString = recipient.getName(); + public void setText(RecipientV2 recipient, boolean read) { + String fromString = recipient.getDisplayName(); int typeface; @@ -56,8 +56,8 @@ public void setText(Recipient recipient, boolean read) { if (recipient.isLocalNumber()) { builder.append(getContext().getString(R.string.noteToSelf)); - } else if (!TextUtils.isEmpty(recipient.getProfileName())) { - SpannableString profileName = new SpannableString(" (~" + recipient.getProfileName() + ") "); + } else if (!TextUtils.isEmpty(recipient.getDisplayName())) { + SpannableString profileName = new SpannableString(" (~" + recipient.getDisplayName() + ") "); profileName.setSpan(new CenterAlignedRelativeSizeSpan(0.75f), 0, profileName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); profileName.setSpan(new TypefaceSpan("sans-serif-light"), 0, profileName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); profileName.setSpan(new ForegroundColorSpan(ResUtil.getColor(getContext(), R.attr.conversation_list_item_subject_color)), 0, profileName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); @@ -75,7 +75,7 @@ public void setText(Recipient recipient, boolean read) { setText(builder); - if (recipient.isBlocked()) setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_user_round_x, 0, 0, 0); + if (recipient.getBlocked()) setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_user_round_x, 0, 0, 0); else if (recipient.isMuted()) setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_volume_off, 0, 0, 0); else setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java b/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java index 138c3830b1..81f2a9a5ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java @@ -8,7 +8,6 @@ import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.RecipientV2; import org.thoughtcrime.securesms.database.RecipientRepository; import org.thoughtcrime.securesms.database.ThreadDatabase; diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java index 801bee2376..62dfed2abc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java @@ -176,7 +176,7 @@ private Cursor getRecentConversationsCursor() { ThreadDatabase.Reader reader = threadDatabase.readerFor(rawConversations); ThreadRecord threadRecord; while ((threadRecord = reader.getNext()) != null) { - recentConversations.addRow(new Object[] { threadRecord.getRecipient().getName(), + recentConversations.addRow(new Object[] { threadRecord.getRecipient().getDisplayName(), threadRecord.getRecipient().getAddress().toString(), ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE, "", diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index f3edbf097c..2ce656a313 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -44,7 +44,6 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.fragment.app.DialogFragment -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.Lifecycle import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider @@ -106,8 +105,6 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED import org.session.libsession.utilities.concurrent.SimpleTask import org.session.libsession.utilities.getColorFromAttr -import org.session.libsession.utilities.recipients.Recipient -import org.session.libsession.utilities.recipients.RecipientModifiedListener import org.session.libsignal.crypto.MnemonicCodec import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.IdPrefix @@ -126,7 +123,6 @@ import org.thoughtcrime.securesms.conversation.disappearingmessages.Disappearing import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnActionSelectedListener import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnReactionSelectedListener import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.ShowOpenUrlDialog -import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.MESSAGE_ID import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_COPY import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_DELETE import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_REPLY @@ -239,7 +235,7 @@ private const val TAG = "ConversationActivityV2" @AndroidEntryPoint class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher, - ConversationActionModeCallbackDelegate, VisibleMessageViewDelegate, RecipientModifiedListener, + ConversationActionModeCallbackDelegate, VisibleMessageViewDelegate, SearchBottomBar.EventListener, LoaderManager.LoaderCallbacks, OnReactionSelectedListener, ReactWithAnyEmojiDialogFragment.Callback, ReactionsDialogFragment.Callback, UserDetailsBottomSheet.UserDetailsBottomSheetCallback { @@ -1166,14 +1162,15 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, // endregion // region Animation & Updating - override fun onModified(recipient: Recipient) { - viewModel.updateRecipient() - - runOnUiThread { - invalidateOptionsMenu() - updateSendAfterApprovalText() - } - } + //TODO test recipient update +// override fun onModified(recipient: Recipient) { +// viewModel.updateRecipient() +// +// runOnUiThread { +// invalidateOptionsMenu() +// updateSendAfterApprovalText() +// } +// } private fun updateSendAfterApprovalText() { binding.textSendAfterApproval.isVisible = viewModel.showSendAfterApprovalText diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 7c880a065a..f0fa09b7ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt index ece56f3f2d..969ffff2d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt @@ -45,7 +45,6 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.getColorFromAttr -import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.components.emoji.EmojiImageView import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel import org.thoughtcrime.securesms.components.menu.ActionItem diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt index 5e3114a92d..a1d04a445e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt @@ -18,8 +18,6 @@ import network.loki.messenger.databinding.AlbumThumbnailViewBinding import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.Address import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY -import org.session.libsession.utilities.recipients.Recipient -import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.components.CornerMask import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorViewContainer.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorViewContainer.kt index 3077d227e3..f9a10c602d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorViewContainer.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorViewContainer.kt @@ -5,7 +5,7 @@ import android.util.AttributeSet import android.view.LayoutInflater import android.widget.LinearLayout import network.loki.messenger.databinding.ViewConversationTypingContainerBinding -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.Address class TypingIndicatorViewContainer : LinearLayout { private lateinit var binding: ViewConversationTypingContainerBinding @@ -18,7 +18,7 @@ class TypingIndicatorViewContainer : LinearLayout { binding = ViewConversationTypingContainerBinding.inflate(LayoutInflater.from(context), this, true) } - fun setTypists(typists: List) { + fun setTypists(typists: List
) { if (typists.isEmpty()) { binding.typingIndicator.root.stopAnimation(); return } binding.typingIndicator.root.startAnimation() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt index f58961656c..451dcc3031 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt @@ -11,7 +11,6 @@ import network.loki.messenger.R import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.utilities.Address import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY -import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.createSessionDialog import org.thoughtcrime.securesms.ui.getSubbedCharSequence diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index 4a8b7de0a4..244986ae58 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -19,9 +19,7 @@ import com.bumptech.glide.RequestManager import network.loki.messenger.R import network.loki.messenger.databinding.ViewInputBarBinding import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview -import org.session.libsession.utilities.Address import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.conversation.v2.InputBarContentState import org.thoughtcrime.securesms.conversation.v2.InputBarState diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt index b76d43030f..2a25bfef2c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt @@ -16,7 +16,6 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.StringSubstitutionConstants.FILE_TYPE_KEY -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.conversation.v2.ViewUtil import org.thoughtcrime.securesms.conversation.v2.dialogs.AutoDownloadDialog diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt index b43c95bbe0..5326972419 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt @@ -16,7 +16,6 @@ import network.loki.messenger.databinding.ViewQuoteBinding import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getColorFromAttr -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index ba48dd1144..1ba7271565 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -30,11 +30,9 @@ import network.loki.messenger.databinding.ViewVisibleMessageContentBinding import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment -import org.session.libsession.utilities.Address import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.modifyLayoutParams -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.messages.AttachmentControlView.AttachmentType.AUDIO diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt index ad598284e5..79ce003e9a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt @@ -22,7 +22,6 @@ import network.loki.messenger.R import org.session.libsession.LocalisedTimeUtil import org.session.libsession.utilities.StringSubstitutionConstants.DATE_TIME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_LARGE_KEY -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_ALL @@ -35,9 +34,6 @@ import org.thoughtcrime.securesms.ui.OptionsCardData import org.thoughtcrime.securesms.ui.RadioOption import org.thoughtcrime.securesms.ui.getSubbedString import org.thoughtcrime.securesms.util.DateUtils -import java.time.Instant -import java.time.ZoneId -import java.time.format.DateTimeFormatter import java.util.concurrent.TimeUnit import kotlin.time.Duration.Companion.milliseconds diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java index b68b398c49..becc4c1e73 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java @@ -43,7 +43,6 @@ import network.loki.messenger.R; import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.ListenableFuture; import org.session.libsignal.utilities.Log; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index 5ff235a4da..f371a79271 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -19,7 +19,6 @@ import org.session.libsession.utilities.GroupRecord; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.database.LokiOpenGroupDatabaseProtocol; import org.session.libsignal.messages.SignalServiceAttachmentPointer; import org.session.libsignal.utilities.guava.Optional; @@ -27,6 +26,7 @@ import org.thoughtcrime.securesms.util.BitmapUtil; import java.io.Closeable; +import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -165,20 +165,20 @@ public List getAllGroups(boolean includeInactive) { return groups; } - public @NonNull List getGroupMembers(String groupId, boolean includeSelf) { + public @NonNull List
getGroupMembers(String groupId, boolean includeSelf) { List
members = getCurrentMembers(groupId, false); - List recipients = new LinkedList<>(); + List
filtered = new ArrayList<>(); for (Address member : members) { if (!includeSelf && Util.isOwnNumber(context, member.toString())) continue; if (member.isContact()) { - recipients.add(Recipient.from(context, member, false)); + filtered.add(member); } } - return recipients; + return filtered; } public @NonNull List
getGroupMemberAddresses(String groupId, boolean includeSelf) { @@ -195,17 +195,6 @@ public List getAllGroups(boolean includeInactive) { return members; } - public @NonNull List getGroupZombieMembers(String groupId) { - List
members = getCurrentZombieMembers(groupId); - List recipients = new LinkedList<>(); - - for (Address member : members) { - recipients.add(Recipient.from(context, member, false)); - } - - return recipients; - } - public long create(@NonNull String groupId, @Nullable String title, @NonNull List
members, @Nullable SignalServiceAttachmentPointer avatar, @Nullable String relay, @Nullable List
admins, @NonNull Long formationTimestamp) { @@ -235,12 +224,6 @@ public long create(@NonNull String groupId, @Nullable String title, @NonNull Lis long threadId = getWritableDatabase().insert(TABLE_NAME, null, contentValues); - Recipient.applyCached(Address.fromSerialized(groupId), recipient -> { - recipient.setName(title); - recipient.setGroupAvatarId(avatar != null ? avatar.getId() : null); - recipient.setParticipants(Stream.of(members).map(memberAddress -> Recipient.from(context, memberAddress, true)).toList()); - }); - notifyConversationListeners(threadId); notifyConversationListListeners(); @@ -253,7 +236,6 @@ public boolean delete(@NonNull String groupId) { int result = getWritableDatabase().delete(TABLE_NAME, GROUP_ID + " = ?", new String[]{groupId}); if (result > 0) { - Recipient.removeCached(Address.fromSerialized(groupId)); notifyConversationListListeners(); updateNotification.tryEmit(groupId); return true; @@ -278,13 +260,7 @@ public void update(String groupId, String title, SignalServiceAttachmentPointer GROUP_ID + " = ?", new String[] {groupId}); - Recipient.applyCached(Address.fromSerialized(groupId), recipient -> { - recipient.setName(title); - recipient.setGroupAvatarId(avatar != null ? avatar.getId() : null); - }); - updateNotification.tryEmit(groupId); - notifyConversationListListeners(); } @@ -295,15 +271,8 @@ public void updateTitle(String groupID, String newValue) { getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", new String[] {groupID}); - Recipient recipient = Recipient.from(context, Address.fromSerialized(groupID), false); - final boolean nameChanged = !newValue.equals(recipient.getName()); - recipient.setName(newValue); - - if (nameChanged) { - notifyConversationListListeners(); - - updateNotification.tryEmit(groupId); - } + updateNotification.tryEmit(groupID); + notifyConversationListListeners(); } public void updateProfilePicture(String groupID, Bitmap newValue) { @@ -325,7 +294,6 @@ public void updateProfilePicture(String groupID, byte[] newValue) { getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", new String[] {groupID}); - Recipient.applyCached(Address.fromSerialized(groupID), recipient -> recipient.setGroupAvatarId(avatarId == 0 ? null : avatarId)); notifyConversationListListeners(); updateNotification.tryEmit(groupID); } @@ -345,7 +313,6 @@ public void removeProfilePicture(String groupID) { GROUP_ID + " = ?", new String[] {groupID}); - Recipient.applyCached(Address.fromSerialized(groupID), recipient -> recipient.setGroupAvatarId(null)); notifyConversationListListeners(); updateNotification.tryEmit(groupID); } @@ -373,10 +340,6 @@ public void updateMembers(String groupId, List
members) { getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", new String[] {groupId}); - Recipient.applyCached(Address.fromSerialized(groupId), recipient -> { - recipient.setParticipants(Stream.of(members).map(a -> Recipient.from(context, a, false)).toList()); - }); - updateNotification.tryEmit(groupId); } @@ -407,14 +370,6 @@ public void removeMember(String groupId, Address source) { getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", new String[] {groupId}); - Recipient.applyCached(Address.fromSerialized(groupId), recipient -> { - List current = recipient.getParticipants(); - Recipient removal = Recipient.from(context, source, false); - - current.remove(removal); - recipient.setParticipants(current); - }); - updateNotification.tryEmit(groupId); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index c3b30f5674..1803524034 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -45,7 +45,6 @@ import org.session.libsession.utilities.IdentityKeyMismatchList import org.session.libsession.utilities.NetworkFailure import org.session.libsession.utilities.NetworkFailureList import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log @@ -779,8 +778,7 @@ class MmsDatabase @Inject constructor( val members = get(context).groupDatabase() .getGroupMembers(message.recipient.toGroupString(), false) val receiptDatabase = get(context).groupReceiptDatabase() - receiptDatabase.insert(Stream.of(members).map { obj: Recipient -> obj.address } - .toList(), + receiptDatabase.insert(members, messageId, GroupReceiptDatabase.STATUS_UNDELIVERED, message.sentTimeMillis ) for (address in earlyDeliveryReceipts.keys) receiptDatabase.update( diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index d53aac9e22..7e574b543f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -8,17 +8,15 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.annimon.stream.Stream; -import com.esotericsoftware.kryo.util.Null; import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.Recipient.RecipientSettings; +import org.session.libsession.utilities.recipients.RecipientSettings; import org.session.libsignal.utilities.Base64; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.guava.Optional; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; -import java.io.Closeable; + import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -82,6 +80,7 @@ public class RecipientDatabase extends Database { private static final String CALL_RINGTONE = "call_ringtone"; @Deprecated(forRemoval = true) private static final String CALL_VIBRATE = "call_vibrate"; + @Deprecated(forRemoval = true) private static final String NOTIFICATION_CHANNEL = "notification_channel"; @Deprecated(forRemoval = true) private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode"; @@ -207,15 +206,8 @@ public SharedFlow
getUpdateNotifications() { return updateNotifications; } - public RecipientReader getRecipientsWithNotificationChannels() { - SQLiteDatabase database = getReadableDatabase(); - Cursor cursor = database.query(TABLE_NAME, new String[] {ID, ADDRESS}, NOTIFICATION_CHANNEL + " NOT NULL", - null, null, null, null, null); - - return new RecipientReader(context, cursor); - } - - public Optional getRecipientSettings(@NonNull Address address) { + @Nullable + public RecipientSettings getRecipientSettings(@NonNull Address address) { SQLiteDatabase database = getReadableDatabase(); try (Cursor cursor = database.query(TABLE_NAME, null, ADDRESS + " = ?", new String[]{address.toString()}, null, null, null)) { @@ -224,11 +216,12 @@ public Optional getRecipientSettings(@NonNull Address address return getRecipientSettings(cursor); } - return Optional.absent(); + return null; } } - Optional getRecipientSettings(@NonNull Cursor cursor) { + @NonNull + RecipientSettings getRecipientSettings(@NonNull Cursor cursor) { boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCK)) == 1; boolean approved = cursor.getInt(cursor.getColumnIndexOrThrow(APPROVED)) == 1; boolean approvedMe = cursor.getInt(cursor.getColumnIndexOrThrow(APPROVED_ME)) == 1; @@ -245,7 +238,6 @@ Optional getRecipientSettings(@NonNull Cursor cursor) { String systemDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_DISPLAY_NAME)); String signalProfileName = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_NAME)); String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SESSION_PROFILE_AVATAR)); - String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL)); boolean blocksCommunityMessageRequests = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCKS_COMMUNITY_MESSAGE_REQUESTS)) == 1; byte[] profileKey = null; @@ -259,13 +251,12 @@ Optional getRecipientSettings(@NonNull Cursor cursor) { } } - return Optional.of(new RecipientSettings(blocked, approved, approvedMe, muteUntil, + return new RecipientSettings(blocked, approved, approvedMe, muteUntil, notifyType, autoDownloadAttachments, expireMessages, profileKey, systemDisplayName, signalProfileName, signalProfileAvatar, - notificationChannel, - blocksCommunityMessageRequests)); + blocksCommunityMessageRequests); } public boolean getApproved(@NonNull Address address) { @@ -360,30 +351,6 @@ public void setNotifyType(@NonNull Address recipient, int notifyType) { updateNotifications.tryEmit(recipient); } - public void setProfileKey(@NonNull Address recipient, @Nullable byte[] profileKey) { - ContentValues values = new ContentValues(1); - values.put(PROFILE_KEY, profileKey == null ? null : Base64.encodeBytes(profileKey)); - updateOrInsert(recipient, values); - notifyRecipientListeners(); - updateNotifications.tryEmit(recipient); - } - - public void setProfileAvatar(@NonNull Address recipient, @Nullable String profileAvatar) { - ContentValues contentValues = new ContentValues(1); - contentValues.put(SESSION_PROFILE_AVATAR, profileAvatar); - updateOrInsert(recipient, contentValues); - notifyRecipientListeners(); - updateNotifications.tryEmit(recipient); - } - - public void setProfileName(@NonNull Address recipient, @Nullable String profileName) { - ContentValues contentValues = new ContentValues(1); - contentValues.put(SYSTEM_DISPLAY_NAME, profileName); - updateOrInsert(recipient, contentValues); - notifyRecipientListeners(); - updateNotifications.tryEmit(recipient); - } - public void updateProfile(@NonNull Address recipient, @Nullable String newName, @Nullable UserPic profilePic, @@ -461,49 +428,17 @@ public List
getBlockedContacts() { * * @return A list of all recipients */ - public List getAllRecipients() { + public List
getAllRecipients() { SQLiteDatabase database = getReadableDatabase(); - Cursor cursor = database.query(TABLE_NAME, new String[] {ID, ADDRESS}, null, - null, null, null, null, null); - - RecipientReader reader = new RecipientReader(context, cursor); - List returnList = new ArrayList<>(); - Recipient current; - - while ((current = reader.getNext()) != null) { - returnList.add(current); - } - - reader.close(); - return returnList; - } - - public static class RecipientReader implements Closeable { - - private final Context context; - private final Cursor cursor; - - RecipientReader(Context context, Cursor cursor) { - this.context = context; - this.cursor = cursor; - } - - public @NonNull Recipient getCurrent() { - String serialized = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)); - return Recipient.from(context, Address.fromSerialized(serialized), false); - } - - public @Nullable Recipient getNext() { - if (cursor != null && !cursor.moveToNext()) { - return null; + try(final Cursor cursor = database.query(TABLE_NAME, new String[] {ADDRESS}, null, + null, null, null, null, null)) { + final List
recipients = new ArrayList<>(cursor.getCount()); + while (cursor.moveToNext()) { + String serialized = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)); + recipients.add(Address.fromSerialized(serialized)); } - - return getCurrent(); - } - - public void close() { - cursor.close(); + return recipients; } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index 326305f3ca..78f361fd65 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -23,9 +23,9 @@ import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.recipients.BasicRecipient -import org.session.libsession.utilities.recipients.Recipient.RecipientSettings import org.session.libsession.utilities.recipients.RecipientAvatar import org.session.libsession.utilities.recipients.RecipientAvatar.Companion.toRecipientAvatar +import org.session.libsession.utilities.recipients.RecipientSettings import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.recipients.displayNameOrFallback import org.session.libsession.utilities.userConfigsChanged @@ -73,7 +73,12 @@ class RecipientRepository @Inject constructor( private fun createRecipientFlow(address: Address): SharedFlow { return flow { while (true) { - val (value, changeSource) = fetchRecipient(address) + val (value, changeSource) = fetchRecipient(address) ?: run { + // If we don't have a recipient for this address, emit null and terminate the flow. + emit(null) + return@flow + } + emit(value) changeSource.first() Log.d(TAG, "Recipient changed for ${address.address.substring(0..10)}") @@ -85,7 +90,7 @@ class RecipientRepository @Inject constructor( SharingStarted.WhileSubscribed(replayExpirationMillis = 0L), replay = 1) } - private suspend fun fetchRecipient(address: Address): Pair> { + private suspend fun fetchRecipient(address: Address): Pair>? { val basicRecipient = getBasicRecipientFast(address) val changeSource: Flow<*> @@ -101,7 +106,7 @@ class RecipientRepository @Inject constructor( value = createContactRecipient( basic = basicRecipient, fallbackSettings = withContext(Dispatchers.Default) { - recipientDatabase.getRecipientSettings(address).orNull() + recipientDatabase.getRecipientSettings(address) } ) @@ -115,7 +120,7 @@ class RecipientRepository @Inject constructor( value = createGroupV2Recipient( basic = basicRecipient, settings = withContext(Dispatchers.Default) { - recipientDatabase.getRecipientSettings(address).orNull() + recipientDatabase.getRecipientSettings(address) } ) @@ -133,7 +138,7 @@ class RecipientRepository @Inject constructor( // local database. val settings = withContext(Dispatchers.Default) { - recipientDatabase.getRecipientSettings(address).orNull() + recipientDatabase.getRecipientSettings(address) } when { @@ -147,10 +152,15 @@ class RecipientRepository @Inject constructor( value = group?.let { createCommunityOrLegacyGroupRecipient(address, it, settings) } } - else -> { + settings != null -> { value = createGenericRecipient(address, settings) changeSource = recipientDatabase.updateNotifications.filter { it == address } } + + else -> { + Log.w(TAG, "No recipient found for address: ${address.debugString}") + return null + } } } } @@ -334,7 +344,7 @@ class RecipientRepository @Inject constructor( mutedUntil = fallbackSettings?.muteUntilDate, autoDownloadAttachments = fallbackSettings?.autoDownloadAttachments, notifyType = fallbackSettings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, - acceptsCommunityMessageRequests = fallbackSettings?.blocksCommunityMessageRequests != true + acceptsCommunityMessageRequests = fallbackSettings?.blocksCommunityMessagesRequests == false, ) } @@ -367,13 +377,13 @@ class RecipientRepository @Inject constructor( displayName = settings.systemDisplayName?.takeIf { it.isNotBlank() } ?: settings.profileName.orEmpty(), avatar = settings.profileAvatar?.let { RecipientAvatar.from(it, settings.profileKey) }, isLocalNumber = false, - blocked = settings.isBlocked + blocked = settings.blocked ), mutedUntil = settings.muteUntil.takeIf { it > 0 } ?.let { ZonedDateTime.from(Instant.ofEpochMilli(it)) }, autoDownloadAttachments = settings.autoDownloadAttachments, notifyType = settings.notifyType, - acceptsCommunityMessageRequests = !settings.blocksCommunityMessageRequests + acceptsCommunityMessageRequests = !settings.blocksCommunityMessagesRequests ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 062e97d026..8158de907e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -17,7 +17,6 @@ */ package org.thoughtcrime.securesms.database; -import static org.session.libsignal.utilities.Util.SECURE_RANDOM; import static org.thoughtcrime.securesms.database.MmsSmsColumns.Types.GROUP_UPDATE_MESSAGE_BIT; import android.content.ContentValues; @@ -41,7 +40,7 @@ import org.session.libsession.utilities.IdentityKeyMismatchList; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.JsonUtil; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.guava.Optional; @@ -54,19 +53,21 @@ import java.io.Closeable; import java.io.IOException; import java.util.Arrays; -import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import javax.inject.Inject; import javax.inject.Provider; +import javax.inject.Singleton; /** * Database for storage of SMS messages. * * @author Moxie Marlinspike */ +@Singleton public class SmsDatabase extends MessagingDatabase { private static final String TAG = SmsDatabase.class.getSimpleName(); @@ -150,8 +151,12 @@ public class SmsDatabase extends MessagingDatabase { private static final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache(); private static final EarlyReceiptCache earlyReadReceiptCache = new EarlyReceiptCache(); - public SmsDatabase(Context context, Provider databaseHelper) { + private final RecipientRepository recipientRepository; + + @Inject + public SmsDatabase(Context context, Provider databaseHelper, RecipientRepository recipientRepository) { super(context, databaseHelper); + this.recipientRepository = recipientRepository; } protected String getTableName() { @@ -443,14 +448,14 @@ private Pair updateMessageBodyAndType(long messageId, String body, l } protected Optional insertMessageInbox(IncomingTextMessage message, long type, long serverTimestamp, boolean runThreadUpdate) { - Recipient recipient = Recipient.from(context, message.getSender(), true); + Address recipient = message.getSender(); - Recipient groupRecipient; + Address groupRecipient; if (message.getGroupId() == null) { groupRecipient = null; } else { - groupRecipient = Recipient.from(context, message.getGroupId(), true); + groupRecipient = message.getGroupId(); } boolean unread = (message.isSecureMessage() || message.isGroup() || message.isUnreadCallMessage()); @@ -782,33 +787,6 @@ public Reader readerFor(Cursor cursor) { return new Reader(cursor); } - public OutgoingMessageReader readerFor(OutgoingTextMessage message, long threadId) { - return new OutgoingMessageReader(message, threadId); - } - - public class OutgoingMessageReader { - - private final OutgoingTextMessage message; - private final long id; - private final long threadId; - - public OutgoingMessageReader(OutgoingTextMessage message, long threadId) { - this.message = message; - this.threadId = threadId; - this.id = SECURE_RANDOM.nextLong(); - } - - public MessageRecord getCurrent() { - return new SmsMessageRecord(id, message.getMessageBody(), - message.getRecipient(), message.getRecipient(), - SnodeAPI.getNowWithOffset(), SnodeAPI.getNowWithOffset(), - 0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(), - threadId, 0, new LinkedList(), - message.getExpiresIn(), - SnodeAPI.getNowWithOffset(), 0, Collections.emptyList(), false); - } - } - public class Reader implements Closeable { private final Cursor cursor; @@ -852,7 +830,7 @@ public SmsMessageRecord getCurrent() { } List mismatches = getMismatches(mismatchDocument); - Recipient recipient = Recipient.from(context, address, true); + RecipientV2 recipient = recipientRepository.getRecipientSyncOrEmpty(address); List reactions = DatabaseComponent.get(context).reactionDatabase().getReactions(cursor); return new SmsMessageRecord(messageId, body, recipient, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index c083729df1..b1c6f772b6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -13,7 +13,6 @@ import network.loki.messenger.libsession_util.util.Bytes import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.KeyPair -import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.BlindedIdMapping @@ -24,7 +23,6 @@ import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageReceiveJob import org.session.libsession.messaging.jobs.MessageSendJob -import org.session.libsession.messaging.jobs.RetrieveProfileAvatarJob import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.ProfileUpdateHandler import org.session.libsession.messaging.messages.control.GroupUpdated @@ -56,11 +54,10 @@ import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.GroupDisplayInfo import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.ProfileKeyUtil import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getGroup -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientSettings import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsession.utilities.upsertContact import org.session.libsignal.crypto.ecc.DjbECPublicKey @@ -68,7 +65,6 @@ import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.utilities.AccountId -import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.KeyHelper @@ -750,10 +746,6 @@ open class Storage @Inject constructor( groupDatabase.setActive(groupID, value) } - override fun getZombieMembers(groupID: String): Set { - return groupDatabase.getGroupZombieMembers(groupID).map { it.address.toString() }.toHashSet() - } - override fun removeMember(groupID: String, member: Address) { groupDatabase.removeMember(groupID, member) } @@ -1086,8 +1078,8 @@ open class Storage @Inject constructor( return threadDatabase.getRecipientForThreadId(threadId) } - override fun getRecipientSettings(address: Address): Recipient.RecipientSettings? { - return recipientDatabase.getRecipientSettings(address).orNull() + override fun getRecipientSettings(address: Address): RecipientSettings? { + return recipientDatabase.getRecipientSettings(address) } override fun syncLibSessionContacts(contacts: List, timestamp: Long?) { @@ -1117,13 +1109,13 @@ open class Storage @Inject constructor( // which in the case of contacts we are messaging for the first time and who haven't yet approved us, it won't be the case // But that person is saved in the Recipient db. We might need to investigate how to clean the relationship between Recipients, Contacts and config Contacts. val removedContacts = recipientDatabase.allRecipients.filter { localContact -> - IdPrefix.fromValue(localContact.address.toString()) == IdPrefix.STANDARD && // only want standard address - localContact.is1on1 && // only for conversations - localContact.address.toString() != currentUserKey && // we don't want to remove ourselves (ie, our Note to Self) - moreContacts.none { it.id == localContact.address.toString() } // we don't want to remove contacts that are present in the config + IdPrefix.fromValue(localContact.toString()) == IdPrefix.STANDARD && // only want standard address + localContact.isContact && // only for conversations + localContact.address != currentUserKey && // we don't want to remove ourselves (ie, our Note to Self) + moreContacts.none { it.id == localContact.address } // we don't want to remove contacts that are present in the config } removedContacts.forEach { - deleteContact(it.address.toString()) + deleteContact(it.address) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index cffd38827e..c947962883 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -41,17 +41,13 @@ import org.session.libsession.utilities.ConfigFactoryProtocolKt; import org.session.libsession.utilities.DelimiterUtil; import org.session.libsession.utilities.DistributionTypes; -import org.session.libsession.utilities.GroupRecord; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.Recipient.RecipientSettings; import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.AccountId; import org.session.libsignal.utilities.IdPrefix; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.Pair; -import org.session.libsignal.utilities.guava.Optional; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.model.GroupThreadStatus; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java index 3855886e2f..50188e100d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/PagingMediaLoader.java @@ -11,7 +11,6 @@ import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId; import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import org.thoughtcrime.securesms.mms.PartAuthority; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java index 7e946b8469..8a80db17a7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -20,7 +20,6 @@ import androidx.annotation.NonNull; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.RecipientV2; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java index c506d263e2..ff574c1f01 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java @@ -25,7 +25,6 @@ import org.session.libsession.utilities.Contact; import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.NetworkFailure; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.RecipientV2; import org.thoughtcrime.securesms.database.SmsDatabase.Status; import org.thoughtcrime.securesms.mms.SlideDeck; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index c9c08a41f8..67287a4f47 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -36,7 +36,6 @@ import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.NetworkFailure; import org.session.libsession.utilities.ThemeUtil; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.AccountId; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java index 4a746d23a2..470f2c4472 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java @@ -21,7 +21,7 @@ import androidx.annotation.NonNull; import org.session.libsession.utilities.IdentityKeyMismatch; -import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; import java.util.LinkedList; import java.util.List; @@ -35,8 +35,8 @@ public class SmsMessageRecord extends MessageRecord { public SmsMessageRecord(long id, - String body, Recipient recipient, - Recipient individualRecipient, + String body, RecipientV2 recipient, + RecipientV2 individualRecipient, long dateSent, long dateReceived, int deliveryReceiptCount, long type, long threadId, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 50bbc615aa..4b751e5dae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -34,7 +34,6 @@ import network.loki.messenger.R; import org.session.libsession.messaging.utilities.UpdateMessageData; import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.RecipientV2; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; @@ -90,10 +89,9 @@ public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri, } private String getName() { - return getRecipient().getName(); + return getRecipient().getDisplayName(); } - @Override public CharSequence getDisplayBody(@NonNull Context context) { if (groupThreadStatus == GroupThreadStatus.Kicked) { @@ -209,7 +207,7 @@ public CharSequence getNonControlMessageDisplayBody(@NonNull Context context) { prefix = context.getString(R.string.you); } else if(lastMessage != null){ - prefix = lastMessage.getIndividualRecipient().getName(); + prefix = lastMessage.getIndividualRecipient().getDisplayName(); } return Phrase.from(context.getString(R.string.messageSnippetGroup)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt index 8df48742f0..3f4f09558b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt @@ -61,10 +61,6 @@ object DatabaseModule { return manager.openHelper } - @Provides - @Singleton - fun provideSmsDatabase(@ApplicationContext context: Context, openHelper: Provider) = SmsDatabase(context, openHelper) - @Provides @Singleton fun provideAttachmentDatabase(@ApplicationContext context: Context, diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java index b1a0c5d9fa..3e294f572f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java @@ -6,25 +6,20 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.session.libsession.messaging.MessagingModuleConfiguration; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.DistributionTypes; import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import org.thoughtcrime.securesms.util.BitmapUtil; -import java.io.IOException; import java.util.HashSet; import java.util.LinkedList; import java.util.Objects; import java.util.Set; -import network.loki.messenger.libsession_util.UserGroupsConfig; - public class GroupManager { public static long getOpenGroupThreadID(String id, @NonNull Context context) { @@ -33,8 +28,7 @@ public static long getOpenGroupThreadID(String id, @NonNull Context context) { } public static long getThreadIDFromGroupID(String groupID, @NonNull Context context) { - final Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupID), true); - return DatabaseComponent.get(context).threadDatabase().getThreadIdIfExistsFor(groupRecipient); + return DatabaseComponent.get(context).threadDatabase().getThreadIdIfExistsFor(Address.fromSerialized(groupID)); } public static @NonNull GroupActionResult createOpenGroup(@NonNull String id, @@ -53,7 +47,6 @@ public static long getThreadIDFromGroupID(String groupID, @NonNull Context cont { final byte[] avatarBytes = BitmapUtil.toByteArray(avatar); final GroupDatabase groupDatabase = DatabaseComponent.get(context).groupDatabase(); - final Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), false); final Set
memberAddresses = new HashSet<>(); memberAddresses.add(Address.fromSerialized(Objects.requireNonNull(TextSecurePreferences.getLocalNumber(context)))); @@ -61,6 +54,7 @@ public static long getThreadIDFromGroupID(String groupID, @NonNull Context cont groupDatabase.updateProfilePicture(groupId, avatarBytes); + Address groupRecipient = Address.fromSerialized(groupId); long threadID = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor( groupRecipient, DistributionTypes.CONVERSATION); return new GroupActionResult(groupRecipient, threadID); @@ -71,7 +65,7 @@ public static boolean deleteGroup(@NonNull String groupId, { final GroupDatabase groupDatabase = DatabaseComponent.get(context).groupDatabase(); final ThreadDatabase threadDatabase = DatabaseComponent.get(context).threadDatabase(); - final Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), false); + final Address groupRecipient = Address.fromSerialized(groupId); long threadId = threadDatabase.getThreadIdIfExistsFor(groupRecipient); if (threadId != -1L) { @@ -82,15 +76,15 @@ public static boolean deleteGroup(@NonNull String groupId, } public static class GroupActionResult { - private Recipient groupRecipient; + private Address groupRecipient; private long threadId; - public GroupActionResult(Recipient groupRecipient, long threadId) { + public GroupActionResult(Address groupRecipient, long threadId) { this.groupRecipient = groupRecipient; this.threadId = threadId; } - public Recipient getGroupRecipient() { + public Address getGroupRecipient() { return groupRecipient; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt index 8b1045f1fa..064530bf3a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt @@ -13,9 +13,6 @@ import network.loki.messenger.R import network.loki.messenger.databinding.FragmentConversationBottomSheetBinding import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager import org.session.libsession.utilities.GroupRecord -import org.session.libsession.utilities.getGroup -import org.session.libsession.utilities.isGroupDestroyed -import org.session.libsession.utilities.wasKickedFromGroupV2 import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index b9fc2a6ab7..8a00781a10 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -35,8 +35,6 @@ import network.loki.messenger.BuildConfig import network.loki.messenger.R import network.loki.messenger.databinding.ActivityHomeBinding import org.greenrobot.eventbus.EventBus -import org.greenrobot.eventbus.Subscribe -import org.greenrobot.eventbus.ThreadMode import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager @@ -44,11 +42,9 @@ import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.Address -import org.session.libsession.utilities.ProfilePictureModifiedEvent import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ApplicationContext @@ -497,15 +493,6 @@ class HomeActivity : ScreenLockActionBarActivity(), // endregion // region Updating - @Subscribe(threadMode = ThreadMode.MAIN) - fun onUpdateProfileEvent(event: ProfilePictureModifiedEvent) { - if (event.recipient.isLocalNumber) { - updateProfileButton() - } else { - homeViewModel.tryReload() - } - } - private fun updateProfileButton() { binding.profileButton.publicKey = publicKey binding.profileButton.displayName = homeViewModel.getCurrentUsername() diff --git a/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewViewModel.kt index 8f2d87386d..b4de5ec712 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewViewModel.kt @@ -34,8 +34,6 @@ import org.session.libsession.messaging.messages.control.DataExtractionNotificat import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.Recipient -import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.MediaDatabase @@ -49,7 +47,6 @@ import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.SaveAttachmentTask import org.thoughtcrime.securesms.util.asSequence -import org.thoughtcrime.securesms.util.observeChanges @HiltViewModel(assistedFactory = MediaOverviewViewModel.Factory::class) class MediaOverviewViewModel @AssistedInject constructor( diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java index 427467df8a..fd3bc134a7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java @@ -24,7 +24,6 @@ import com.bumptech.glide.Glide; import com.squareup.phrase.Phrase; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.guava.Optional; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt index 9a2f1b3640..3429d624bd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt @@ -29,7 +29,6 @@ import org.session.libsession.utilities.MediaTypes import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.Util.isEmpty import org.session.libsession.utilities.concurrent.SimpleTask -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ScreenLockActionBarActivity diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt index 0302dc8bed..538e6337a8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt @@ -34,7 +34,6 @@ import network.loki.messenger.databinding.MediasendFragmentBinding import org.session.libsession.utilities.Address import org.session.libsession.utilities.MediaTypes import org.session.libsession.utilities.TextSecurePreferences.Companion.isEnterSendsEnabled -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardHiddenListener import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt index 95563e33b4..1dd3076bea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt @@ -8,7 +8,6 @@ import android.widget.LinearLayout import androidx.recyclerview.widget.RecyclerView import network.loki.messenger.R import network.loki.messenger.databinding.ViewMessageRequestBinding -import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.database.model.ThreadRecord import com.bumptech.glide.RequestManager diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java index b7051357e1..ff05598929 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java @@ -33,7 +33,6 @@ import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.snode.SnodeAPI; import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.database.MarkedMessageInfo; @@ -69,7 +68,6 @@ public void onReceive(final Context context, Intent intent) final Address address = intent.getParcelableExtra(ADDRESS_EXTRA); final long threadId = intent.getLongExtra(THREAD_ID_EXTRA, -1); final CharSequence responseText = getMessageText(intent); - final Recipient recipient = Recipient.from(context, address, false); if (responseText != null) { new AsyncTask() { @@ -79,7 +77,7 @@ protected Void doInBackground(Void... params) { long replyThreadId; if (threadId == -1) { - replyThreadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient); + replyThreadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(address); } else { replyThreadId = threadId; } @@ -87,15 +85,14 @@ protected Void doInBackground(Void... params) { VisibleMessage message = new VisibleMessage(); message.setText(responseText.toString()); message.setSentTimestamp(SnodeAPI.getNowWithOffset()); - MessageSender.send(message, recipient.getAddress()); - ExpirationConfiguration config = DatabaseComponent.get(context).storage().getExpirationConfiguration(threadId); - ExpiryMode expiryMode = config == null ? null : config.getExpiryMode(); - long expiresInMillis = expiryMode == null ? 0 : expiryMode.getExpiryMillis(); + MessageSender.send(message, address); + ExpiryMode expiryMode = DatabaseComponent.get(context).storage().getExpirationConfiguration(threadId); + long expiresInMillis = expiryMode.getExpiryMillis(); long expireStartedAt = expiryMode instanceof ExpiryMode.AfterSend ? message.getSentTimestamp() : 0L; - if (recipient.isGroupOrCommunityRecipient()) { + if (address.isGroupOrCommunity()) { Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message"); - OutgoingMediaMessage reply = OutgoingMediaMessage.from(message, recipient, Collections.emptyList(), null, null, expiresInMillis, 0); + OutgoingMediaMessage reply = OutgoingMediaMessage.from(message, address, Collections.emptyList(), null, null, expiresInMillis, 0); try { DatabaseComponent.get(context).mmsDatabase().insertMessageOutbox(reply, replyThreadId, false, true); } catch (MmsException e) { @@ -103,7 +100,7 @@ protected Void doInBackground(Void... params) { } } else { Log.w("AndroidAutoReplyReceiver", "Sending regular message "); - OutgoingTextMessage reply = OutgoingTextMessage.from(message, recipient, expiresInMillis, expireStartedAt); + OutgoingTextMessage reply = OutgoingTextMessage.from(message, address, expiresInMillis, expireStartedAt); DatabaseComponent.get(context).smsDatabase().insertMessageOutbox(replyThreadId, reply, false, SnodeAPI.getNowWithOffset(), true); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt index eab46645db..e93abfcc14 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt @@ -45,7 +45,6 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.getRepea import org.session.libsession.utilities.TextSecurePreferences.Companion.hasHiddenMessageRequests import org.session.libsession.utilities.TextSecurePreferences.Companion.isNotificationsEnabled import org.session.libsession.utilities.TextSecurePreferences.Companion.removeHasHiddenMessageRequests -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Hex @@ -102,10 +101,6 @@ class DefaultMessageNotifier @Inject constructor( lastDesktopActivityTimestamp = timestamp } - override fun notifyMessageDeliveryFailed(context: Context?, recipient: Recipient?, threadId: Long) { - // We do not provide notifications for message delivery failure. - } - override fun cancelDelayedNotifications() { executor.cancel() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java index b728cf876d..15f9d3ef64 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java @@ -6,30 +6,18 @@ import android.content.Context; import android.media.AudioAttributes; import android.net.Uri; -import android.os.AsyncTask; import android.provider.Settings; -import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; - -import com.annimon.stream.Collectors; -import com.annimon.stream.Stream; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.ServiceUtil; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.database.RecipientDatabase; -import org.thoughtcrime.securesms.dependencies.DatabaseComponent; -import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; import network.loki.messenger.BuildConfig; import network.loki.messenger.R; @@ -68,10 +56,6 @@ public static synchronized void create(@NonNull Context context) { } onCreate(context, notificationManager); - - AsyncTask.SERIAL_EXECUTOR.execute(() -> { - ensureCustomChannelConsistency(context); - }); } /** @@ -81,23 +65,6 @@ public static synchronized void create(@NonNull Context context) { return getMessagesChannelId(TextSecurePreferences.getNotificationMessagesChannelVersion(context)); } - /** - * @return A name suitable to be displayed as the notification channel title. - */ - public static @NonNull String getChannelDisplayNameFor(@NonNull Context context, @Nullable String systemName, @Nullable String profileName, @NonNull Address address) { - if (!TextUtils.isEmpty(systemName)) { - return systemName; - } else if (!TextUtils.isEmpty(profileName)) { - return profileName; - } else if (!TextUtils.isEmpty(address.toString())) { - return address.toString(); - } else { - return context.getString(R.string.unknown); - } - } - - - /** * @return The message ringtone set for the default message channel. @@ -147,36 +114,6 @@ public static synchronized void updateMessageVibrate(@NonNull Context context, b updateMessageChannel(context, channel -> channel.enableVibration(enabled)); } - @WorkerThread - public static synchronized void ensureCustomChannelConsistency(@NonNull Context context) { - NotificationManager notificationManager = ServiceUtil.getNotificationManager(context); - RecipientDatabase db = DatabaseComponent.get(context).recipientDatabase(); - List customRecipients = new ArrayList<>(); - Set customChannelIds = new HashSet<>(); - Set existingChannelIds = Stream.of(notificationManager.getNotificationChannels()).map(NotificationChannel::getId).collect(Collectors.toSet()); - - try (RecipientDatabase.RecipientReader reader = db.getRecipientsWithNotificationChannels()) { - RecipientV2 recipient; - while ((recipient = reader.getNext()) != null) { - customRecipients.add(recipient); - customChannelIds.add(recipient.getNotificationChannel()); - } - } - - for (NotificationChannel existingChannel : notificationManager.getNotificationChannels()) { - if (existingChannel.getId().startsWith(CONTACT_PREFIX) && !customChannelIds.contains(existingChannel.getId())) { - notificationManager.deleteNotificationChannel(existingChannel.getId()); - } else if (existingChannel.getId().startsWith(MESSAGES_PREFIX) && !existingChannel.getId().equals(getMessagesChannel(context))) { - notificationManager.deleteNotificationChannel(existingChannel.getId()); - } - } - - for (RecipientV2 customRecipient : customRecipients) { - if (!existingChannelIds.contains(customRecipient.getNotificationChannel())) { - db.setNotificationChannel(customRecipient, null); - } - } - } private static void onCreate(@NonNull Context context, @NonNull NotificationManager notificationManager) { NotificationChannelGroup messagesGroup = new NotificationChannelGroup(CATEGORY_MESSAGES, context.getResources().getString(R.string.messages)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java index cdd87d155c..532d78451d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java @@ -10,7 +10,6 @@ import org.session.libsession.messaging.sending_receiving.pollers.Poller; import org.session.libsession.messaging.sending_receiving.pollers.PollerManager; import org.session.libsession.utilities.Debouncer; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.ThreadUtils; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.util.AvatarUtils; @@ -32,8 +31,11 @@ public class OptimizedMessageNotifier implements MessageNotifier { private final PollerManager pollerManager; @Inject - public OptimizedMessageNotifier(AvatarUtils avatarUtils, OpenGroupPollerManager openGroupPollerManager, PollerManager pollerManager) { - this.wrapped = new DefaultMessageNotifier(avatarUtils); + public OptimizedMessageNotifier(AvatarUtils avatarUtils, + OpenGroupPollerManager openGroupPollerManager, + PollerManager pollerManager, + DefaultMessageNotifier defaultMessageNotifier) { + this.wrapped = defaultMessageNotifier; this.openGroupPollerManager = openGroupPollerManager; this.debouncer = new Debouncer(TimeUnit.SECONDS.toMillis(2)); this.pollerManager = pollerManager; @@ -50,10 +52,6 @@ public void setHomeScreenVisible(boolean isVisible) { @Override public void setLastDesktopActivityTimestamp(long timestamp) { wrapped.setLastDesktopActivityTimestamp(timestamp);} - @Override - public void notifyMessageDeliveryFailed(Context context, Recipient recipient, long threadId) { - wrapped.notifyMessageDeliveryFailed(context, recipient, threadId); - } @Override public void cancelDelayedNotifications() { wrapped.cancelDelayedNotifications(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java index 3144bd33f6..422f86cac1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java @@ -26,7 +26,6 @@ import androidx.core.app.RemoteInput; -import org.session.libsession.messaging.messages.ExpirationConfiguration; import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; import org.session.libsession.messaging.messages.visible.VisibleMessage; @@ -34,7 +33,6 @@ import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.snode.SnodeClock; import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.database.MarkedMessageInfo; import org.thoughtcrime.securesms.database.MmsDatabase; @@ -96,19 +94,17 @@ public void onReceive(final Context context, Intent intent) { new AsyncTask() { @Override protected Void doInBackground(Void... params) { - Recipient recipient = Recipient.from(context, address, false); - long threadId = threadDatabase.getOrCreateThreadIdFor(recipient); + long threadId = threadDatabase.getOrCreateThreadIdFor(address); VisibleMessage message = new VisibleMessage(); message.setSentTimestamp(clock.currentTimeMills()); message.setText(responseText.toString()); - ExpirationConfiguration config = storage.getExpirationConfiguration(threadId); - ExpiryMode expiryMode = config == null ? null : config.getExpiryMode(); + ExpiryMode expiryMode = storage.getExpirationConfiguration(threadId); - long expiresInMillis = expiryMode == null ? 0 : expiryMode.getExpiryMillis(); + long expiresInMillis = expiryMode.getExpiryMillis(); long expireStartedAt = expiryMode instanceof ExpiryMode.AfterSend ? message.getSentTimestamp() : 0L; switch (replyMethod) { case GroupMessage: { - OutgoingMediaMessage reply = OutgoingMediaMessage.from(message, recipient, Collections.emptyList(), null, null, expiresInMillis, 0); + OutgoingMediaMessage reply = OutgoingMediaMessage.from(message, address, Collections.emptyList(), null, null, expiresInMillis, 0); try { message.setId(new MessageId(mmsDatabase.insertMessageOutbox(reply, threadId, false, true), true)); MessageSender.send(message, address); @@ -118,7 +114,7 @@ protected Void doInBackground(Void... params) { break; } case SecureMessage: { - OutgoingTextMessage reply = OutgoingTextMessage.from(message, recipient, expiresInMillis, expireStartedAt); + OutgoingTextMessage reply = OutgoingTextMessage.from(message, address, expiresInMillis, expireStartedAt); message.setId(new MessageId(smsDatabase.insertMessageOutbox(threadId, reply, false, System.currentTimeMillis(), true), false)); MessageSender.send(message, address); break; diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt index 33b1597a4a..ae72514fdf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt @@ -8,7 +8,6 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import network.loki.messenger.R import network.loki.messenger.databinding.BlockedContactLayoutBinding -import org.session.libsession.utilities.recipients.Recipient import com.bumptech.glide.Glide import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.util.adapter.SelectableItem diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt index 3ed441cf42..5dc0dbc716 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt @@ -23,7 +23,6 @@ import kotlinx.coroutines.flow.asSharedFlow import network.loki.messenger.R import org.session.libsession.utilities.Address import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.PublicKeyValidation import org.thoughtcrime.securesms.ScreenLockActionBarActivity import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java index 4607246897..1cc7a84a20 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java @@ -161,7 +161,7 @@ void bind(@NonNull ReactionDetails reaction) { this.recipient.setText(R.string.you); this.remove.setVisibility(canRemove ? View.VISIBLE : View.GONE); } else { - String name = reaction.getSender().getName(); + String name = reaction.getSender().getDisplayName(); this.recipient.setText(name); this.remove.setVisibility(View.GONE); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt index a51a804353..71c707982a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt @@ -5,7 +5,6 @@ import io.reactivex.ObservableEmitter import io.reactivex.schedulers.Schedulers import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.components.emoji.EmojiUtil import org.thoughtcrime.securesms.database.RecipientRepository diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java b/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java index addb65d418..b8ebcfa760 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java +++ b/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java @@ -2,7 +2,6 @@ import androidx.annotation.NonNull; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.RecipientV2; import java.util.Objects; diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java index 8a6ed84f3d..34869a152e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java @@ -5,6 +5,8 @@ import android.content.IntentFilter; import android.database.Cursor; import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Parcel; @@ -13,9 +15,11 @@ import androidx.annotation.NonNull; -import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.RecipientV2Kt; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.ShareActivity; +import org.thoughtcrime.securesms.database.RecipientRepository; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; @@ -26,7 +30,15 @@ import java.util.List; import java.util.concurrent.ExecutionException; +import javax.inject.Inject; + +import dagger.hilt.android.AndroidEntryPoint; + +@AndroidEntryPoint public class DirectShareService extends ChooserTargetService { + @Inject + RecipientRepository recipientRepository; + private static final String TAG = DirectShareService.class.getSimpleName(); @@ -43,8 +55,8 @@ public List onGetChooserTargets(ComponentName targetActivityName, ThreadRecord record; while ((record = reader.getNext()) != null && results.size() < 10) { - Recipient recipient = Recipient.from(this, record.getRecipient().getAddress(), false); - String name = recipient.getName(); + RecipientV2 recipient = record.getRecipient(); + String name = recipient.getDisplayName(); Bitmap avatar; @@ -83,8 +95,9 @@ public List onGetChooserTargets(ComponentName targetActivityName, } } - private Bitmap getFallbackDrawable(@NonNull Recipient recipient) { - return BitmapUtil.createFromDrawable(recipient.getFallbackContactPhotoDrawable(this, false), + private Bitmap getFallbackDrawable(@NonNull RecipientV2 recipient) { + //TODO: Use proper color + return BitmapUtil.createFromDrawable(new ColorDrawable(Color.RED), getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width), getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt index 8830dc0f8a..3b5e6356a9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt @@ -25,7 +25,6 @@ import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil.doubleEncodeGroupID import org.session.libsession.utilities.SSKEnvironment.MessageExpirationManagerProtocol import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/TypingStatusRepository.java b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/TypingStatusRepository.java index 0ab62d8c21..9e36d78bbd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/TypingStatusRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/TypingStatusRepository.java @@ -15,8 +15,8 @@ import org.session.libsession.utilities.SSKEnvironment; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; +import org.thoughtcrime.securesms.database.RecipientRepository; import java.util.ArrayList; import java.util.Collections; @@ -43,9 +43,11 @@ public class TypingStatusRepository implements SSKEnvironment.TypingIndicatorsPr private final Map> notifiers; private final MutableLiveData> threadsNotifier; private final TextSecurePreferences preferences; + private final RecipientRepository recipientRepository; @Inject - public TypingStatusRepository(TextSecurePreferences preferences) { + public TypingStatusRepository(TextSecurePreferences preferences, RecipientRepository recipientRepository) { + this.recipientRepository = recipientRepository; this.typistMap = new HashMap<>(); this.timers = new HashMap<>(); this.notifiers = new HashMap<>(); @@ -59,12 +61,12 @@ public synchronized void didReceiveTypingStartedMessage(@NotNull Context context return; } - if (Recipient.from(context, author, false).isBlocked()) { + if (recipientRepository.getRecipientSyncOrEmpty(author).getBlocked()) { return; } Set typists = Util.getOrDefault(typistMap, threadId, new LinkedHashSet<>()); - Typist typist = new Typist(Recipient.from(context, author, false), device, threadId); + Typist typist = new Typist(author, device, threadId); if (!typists.contains(typist)) { typists.add(typist); @@ -88,12 +90,12 @@ public synchronized void didReceiveTypingStoppedMessage(@NotNull Context context return; } - if (Recipient.from(context, author, false).isBlocked()) { + if (recipientRepository.getRecipientSyncOrEmpty(author).getBlocked()) { return; } Set typists = Util.getOrDefault(typistMap, threadId, new LinkedHashSet<>()); - Typist typist = new Typist(Recipient.from(context, author, false), device, threadId); + Typist typist = new Typist(author, device, threadId); if (typists.contains(typist)) { typists.remove(typist); @@ -145,7 +147,7 @@ private void notifyThread(long threadId, @NonNull Set typists, boolean i MutableLiveData notifier = Util.getOrDefault(notifiers, threadId, new MutableLiveData<>()); notifiers.put(threadId, notifier); - Set uniqueTypists = new LinkedHashSet<>(); + Set
uniqueTypists = new LinkedHashSet<>(); for (Typist typist : typists) { uniqueTypists.add(typist.getAuthor()); } @@ -157,15 +159,15 @@ private void notifyThread(long threadId, @NonNull Set typists, boolean i } public static class TypingState { - private final List typists; + private final List
typists; private final boolean replacedByIncomingMessage; - public TypingState(List typists, boolean replacedByIncomingMessage) { + public TypingState(List
typists, boolean replacedByIncomingMessage) { this.typists = typists; this.replacedByIncomingMessage = replacedByIncomingMessage; } - public List getTypists() { + public List
getTypists() { return typists; } @@ -175,17 +177,17 @@ public boolean isReplacedByIncomingMessage() { } private static class Typist { - private final Recipient author; + private final Address author; private final int device; private final long threadId; - private Typist(@NonNull Recipient author, int device, long threadId) { + private Typist(@NonNull Address author, int device, long threadId) { this.author = author; this.device = device; this.threadId = threadId; } - public Recipient getAuthor() { + public Address getAuthor() { return author; } @@ -206,7 +208,7 @@ public boolean equals(Object o) { if (device != typist.device) return false; if (threadId != typist.threadId) return false; - return author.getAddress().equals(typist.author.getAddress()); + return author.equals(typist.author); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt index 426ffd6a22..0d6c6d8d45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt @@ -29,7 +29,6 @@ import org.session.libsession.utilities.StringSubstitutionConstants.RELATIVE_TIM import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Snode diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt index 4d7206c1cb..8d46bbef00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt @@ -25,7 +25,6 @@ import org.session.libsession.avatars.ContactPhoto import org.session.libsession.avatars.ProfileContactPhoto import org.session.libsession.database.StorageProtocol import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.utilities.IdPrefix import org.thoughtcrime.securesms.database.GroupDatabase diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt index 55101dc835..7ad58d1dee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.util import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.session.libsignal.messages.SignalServiceDataMessage diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt index 227bc117e6..c6ad55b7fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -26,7 +26,6 @@ import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.Debouncer import org.session.libsession.utilities.Util -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.ICE_CANDIDATES import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.dependencies.DatabaseComponent diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt index 9a9ec294ad..0b4ec3ca71 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt @@ -14,7 +14,6 @@ import org.session.libsession.messaging.utilities.WebRtcUtils import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.ANSWER import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.END_CALL import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.ICE_CANDIDATES diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.kt index a0d6777327..68ade010e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.kt @@ -13,7 +13,6 @@ import androidx.core.app.NotificationManagerCompat import com.squareup.phrase.Phrase import network.loki.messenger.R import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientV2 import org.thoughtcrime.securesms.notifications.NotificationChannels import org.thoughtcrime.securesms.webrtc.WebRtcCallBridge.Companion.ACTION_DENY_CALL diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt index b5f687d72d..3340bd7959 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt @@ -20,7 +20,6 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.BaseViewModelTest import org.thoughtcrime.securesms.MainCoroutineRule diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt index 8ee7184cbe..db9cf04f90 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt @@ -23,7 +23,6 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager -import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.BaseViewModelTest import org.thoughtcrime.securesms.MainCoroutineRule import org.thoughtcrime.securesms.database.Storage diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/MentionViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/MentionViewModelTest.kt index ffd2bb318e..afd9cce6a5 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/MentionViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/MentionViewModelTest.kt @@ -20,7 +20,6 @@ import org.robolectric.RobolectricTestRunner import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.open_groups.GroupMemberRole import org.session.libsession.messaging.open_groups.OpenGroup -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.BaseViewModelTest import org.thoughtcrime.securesms.MainCoroutineRule diff --git a/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientExporterTest.java b/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientExporterTest.java deleted file mode 100644 index c20ce39fb1..0000000000 --- a/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientExporterTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.thoughtcrime.securesms.recipients; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import junit.framework.TestCase; - -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.RecipientExporter; - -//FIXME AC: This test group is outdated. -@Ignore("This test group uses outdated instrumentation and needs a migration to modern tools.") -@RunWith(MockitoJUnitRunner.class) -public final class RecipientExporterTest extends TestCase { - - @Test - public void asAddContactIntent_with_neither_email_nor_phone() { - RecipientExporter exporter = RecipientExporter.export(givenRecipient("Bob", mock(Address.class))); - - assertThatThrownBy(exporter::asAddContactIntent).isExactlyInstanceOf(RuntimeException.class) - .hasMessage("Cannot export Recipient with neither phone nor email"); - } - - private Recipient givenRecipient(String profileName, Address address) { - Recipient recipient = mock(Recipient.class); - when(recipient.getProfileName()).thenReturn(profileName); - when(recipient.getAddress()).thenReturn(address); - return recipient; - } - -} From 143e957332e6a2c1b4b272d3d406a2d4642fdbc2 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Fri, 27 Jun 2025 11:10:01 +1000 Subject: [PATCH 12/52] WIP --- app/build.gradle.kts | 1 - .../messaging/messages/ProfileUpdateHandler.kt | 5 +++-- .../securesms/contacts/ContactAccessor.java | 17 +++++++++++------ .../conversation/v2/ConversationActivityV2.kt | 1 - .../securesms/database/SmsDatabase.java | 6 +++++- .../pickname/PickDisplayNameActivity.kt | 2 -- .../securesms/reactions/ReactionsViewModel.java | 2 +- .../any/ReactWithAnyEmojiViewModel.java | 2 +- gradle/libs.versions.toml | 2 -- 9 files changed, 21 insertions(+), 17 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0ce57b2704..b838982854 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -269,7 +269,6 @@ dependencies { implementation(libs.photoview) implementation(libs.glide) implementation(libs.compose) - implementation(libs.eventbus) implementation(libs.android.image.cropper) implementation(libs.subsampling.scale.image.view) { exclude(group = "com.android.support", module = "support-annotations") diff --git a/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt b/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt index 96fd3a6c33..de39ee3c97 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt @@ -1,5 +1,6 @@ package org.session.libsession.messaging.messages +import dagger.Lazy import network.loki.messenger.BuildConfig import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.database.StorageProtocol @@ -28,7 +29,7 @@ class ProfileUpdateHandler @Inject constructor( private val recipientDatabase: RecipientDatabase, private val blindedIdMappingDatabase: BlindedIdMappingDatabase, private val prefs: TextSecurePreferences, - private val storage: StorageProtocol, + private val storage: Lazy, ) { fun handleProfileUpdate(sender: Address, updates: Updates, communityServerPubKey: String?) { @@ -43,7 +44,7 @@ class ProfileUpdateHandler @Inject constructor( fun handleProfileUpdate(sender: AccountId, updates: Updates, communityServerPubKey: String?) { if (sender.hexString == prefs.getLocalNumber() || - (communityServerPubKey != null && storage.getUserBlindedAccountId(communityServerPubKey) == sender) + (communityServerPubKey != null && storage.get().getUserBlindedAccountId(communityServerPubKey) == sender) ) { Log.w(TAG, "Ignoring profile update for local number") return diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java index 8b0dd77064..bd3c7c1cc6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java @@ -29,6 +29,9 @@ import java.util.LinkedList; import java.util.List; +import javax.inject.Inject; +import javax.inject.Singleton; + import network.loki.messenger.R; /** @@ -43,19 +46,21 @@ * @author Moxie Marlinspike */ +@Singleton public class ContactAccessor { + private final GroupDatabase groupDatabase; - private static final ContactAccessor instance = new ContactAccessor(); - - public static synchronized ContactAccessor getInstance() { - return instance; + @Inject + public ContactAccessor(GroupDatabase groupDatabase) { + this.groupDatabase = groupDatabase; } - public List getNumbersForThreadSearchFilter(Context context, String constraint) { + + public List getNumbersForThreadSearchFilter(Context context, String constraint) { LinkedList numberList = new LinkedList<>(); GroupRecord record; - try (GroupDatabase.Reader reader = DatabaseComponent.get(context).groupDatabase().getGroupsFilteredByTitle(constraint)) { + try (GroupDatabase.Reader reader = groupDatabase.getGroupsFilteredByTitle(constraint)) { while ((record = reader.getNext()) != null) { numberList.add(record.getEncodedId()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 2ce656a313..ff7d0a7e80 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -253,7 +253,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, @Inject lateinit var lokiMessageDb: LokiMessageDatabase @Inject lateinit var storage: StorageProtocol @Inject lateinit var reactionDb: ReactionDatabase - @Inject lateinit var viewModelFactory: ConversationViewModel.Factory @Inject lateinit var mentionViewModelFactory: MentionViewModel.AssistedFactory @Inject lateinit var dateUtils: DateUtils @Inject lateinit var configFactory: ConfigFactory diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 8158de907e..2ad66ddf38 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -62,6 +62,8 @@ import javax.inject.Provider; import javax.inject.Singleton; +import dagger.hilt.android.qualifiers.ApplicationContext; + /** * Database for storage of SMS messages. * @@ -154,7 +156,9 @@ public class SmsDatabase extends MessagingDatabase { private final RecipientRepository recipientRepository; @Inject - public SmsDatabase(Context context, Provider databaseHelper, RecipientRepository recipientRepository) { + public SmsDatabase(@ApplicationContext Context context, + Provider databaseHelper, + RecipientRepository recipientRepository) { super(context, databaseHelper); this.recipientRepository = recipientRepository; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameActivity.kt index 589196c163..b8018ab9e4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameActivity.kt @@ -24,8 +24,6 @@ private const val EXTRA_LOAD_FAILED = "extra_load_failed" @AndroidEntryPoint class PickDisplayNameActivity : BaseActionBarActivity() { - @Inject - internal lateinit var viewModelFactory: PickDisplayNameViewModel.Factory @Inject internal lateinit var prefs: TextSecurePreferences diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java index 0114886033..0c5c601f77 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java @@ -73,7 +73,7 @@ private long getLatestTimestamp(List reactions) { } @AssistedFactory - interface Factory { + public interface Factory { ReactionsViewModel create(@NonNull MessageId messageId); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiViewModel.java index 6610c36671..ad86278da5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiViewModel.java @@ -109,7 +109,7 @@ public EmojiSearchResult() { } @AssistedFactory - interface Factory { + public interface Factory { ReactWithAnyEmojiViewModel create(@NonNull MessageId messageId); } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e74104cdfa..8961ffa538 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,6 @@ constraintlayoutVersion = "2.2.1" copperFlowVersion = "1.0.0" coreTestingVersion = "2.2.0" espressoCoreVersion = "3.5.1" -eventbusVersion = "3.0.0" exifinterfaceVersion = "1.3.4" firebaseMessagingVersion = "24.0.0" flexboxVersion = "3.0.0" @@ -144,7 +143,6 @@ androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtxVersion" } compose = { module = "com.github.bumptech.glide:compose", version.ref = "composeVersion" } conscrypt-android = { module = "org.conscrypt:conscrypt-android", version.ref = "conscryptAndroidVersion" } -eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbusVersion" } firebase-messaging = { module = "com.google.firebase:firebase-messaging", version.ref = "firebaseMessagingVersion" } flexbox = { module = "com.google.android.flexbox:flexbox", version.ref = "flexboxVersion" } glide = { module = "com.github.bumptech.glide:glide", version.ref = "glideVersion" } From 92873c64eda19fa528de0f4f62c2b0290d884139 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Fri, 27 Jun 2025 14:57:14 +1000 Subject: [PATCH 13/52] Recipient optimising --- .../libsession/database/StorageProtocol.kt | 3 - .../utilities/recipients/RecipientV2.kt | 9 ++- .../securesms/configs/ConfigToDatabaseSync.kt | 2 - .../securesms/database/RecipientDatabase.java | 62 +++++++++++++++---- .../securesms/database/RecipientRepository.kt | 21 +++---- .../securesms/database/Storage.kt | 59 ++++++------------ .../securesms/groups/GroupManagerV2Impl.kt | 6 +- .../securesms/home/HomeActivity.kt | 7 --- .../repository/ConversationRepository.kt | 14 ++++- 9 files changed, 99 insertions(+), 84 deletions(-) diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 10f3ba45d1..64dd442db3 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -213,9 +213,6 @@ interface StorageProtocol { fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) fun insertMessageRequestResponseFromContact(response: MessageRequestResponse) fun insertMessageRequestResponseFromYou(threadId: Long) - fun setRecipientApproved(recipient: Address, approved: Boolean) - fun getRecipientApproved(address: Address): Boolean - fun setRecipientApprovedMe(recipient: Address, approvedMe: Boolean) fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long) fun conversationHasOutgoing(userPublicKey: String): Boolean fun deleteMessagesByHash(threadId: Long, hashes: List) diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt index 286cbfdef0..70e2e33796 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt @@ -227,7 +227,14 @@ class RecipientSettings( val profileName: String?, val profileAvatar: String?, val blocksCommunityMessagesRequests: Boolean -) +) { + val profilePic: UserPic? get() = + if (!profileAvatar.isNullOrBlank() && profileKey != null) { + UserPic(profileAvatar, profileKey) + } else { + null + } +} fun RecipientAvatar.toUserPic(): UserPic? { return when (this) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt index d3c37fa0ef..a04b654b68 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt @@ -287,8 +287,6 @@ class ConfigToDatabaseSync @Inject constructor( for (closedGroup in userGroups.closedGroupInfo) { val address = fromSerialized(closedGroup.groupAccountId) - storage.setRecipientApprovedMe(address, true) - storage.setRecipientApproved(address, !closedGroup.invited) val threadId = storage.getOrCreateThreadIdFor(address) // If we don't already have a date and the config has a date, use it diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 7e574b543f..cb1e186d5a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -7,6 +7,8 @@ import android.database.Cursor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.collection.LruCache; + import com.annimon.stream.Stream; import net.zetetic.database.sqlcipher.SQLiteDatabase; @@ -20,6 +22,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import javax.inject.Provider; @@ -195,8 +198,12 @@ public static String getAddBlocksCommunityMessageRequests() { public static final int NOTIFY_TYPE_MENTIONS = 1; public static final int NOTIFY_TYPE_NONE = 2; + @NonNull private final MutableSharedFlow
updateNotifications = SharedFlowKt.MutableSharedFlow(0, 256, BufferOverflow.DROP_OLDEST); + @NonNull + private final LruCache recipientSettingsCache = new LruCache<>(512); + public RecipientDatabase(Context context, Provider databaseHelper) { super(context, databaseHelper); } @@ -208,12 +215,19 @@ public SharedFlow
getUpdateNotifications() { @Nullable public RecipientSettings getRecipientSettings(@NonNull Address address) { + final RecipientSettings cachedSettings = recipientSettingsCache.get(address); + if (cachedSettings != null) { + return cachedSettings; + } + SQLiteDatabase database = getReadableDatabase(); try (Cursor cursor = database.query(TABLE_NAME, null, ADDRESS + " = ?", new String[]{address.toString()}, null, null, null)) { if (cursor != null && cursor.moveToNext()) { - return getRecipientSettings(cursor); + RecipientSettings settings = getRecipientSettings(cursor); + recipientSettingsCache.put(address, settings); + return settings; } return null; @@ -221,7 +235,7 @@ public RecipientSettings getRecipientSettings(@NonNull Address address) { } @NonNull - RecipientSettings getRecipientSettings(@NonNull Cursor cursor) { + private RecipientSettings getRecipientSettings(@NonNull Cursor cursor) { boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCK)) == 1; boolean approved = cursor.getInt(cursor.getColumnIndexOrThrow(APPROVED)) == 1; boolean approvedMe = cursor.getInt(cursor.getColumnIndexOrThrow(APPROVED_ME)) == 1; @@ -259,14 +273,8 @@ RecipientSettings getRecipientSettings(@NonNull Cursor cursor) { blocksCommunityMessageRequests); } - public boolean getApproved(@NonNull Address address) { - SQLiteDatabase db = getReadableDatabase(); - try (Cursor cursor = db.query(TABLE_NAME, new String[]{APPROVED}, ADDRESS + " = ?", new String[]{address.toString()}, null, null, null)) { - if (cursor != null && cursor.moveToNext()) { - return cursor.getInt(cursor.getColumnIndexOrThrow(APPROVED)) == 1; - } - } - return false; + private void invalidateCache(@NonNull Address recipient) { + recipientSettingsCache.remove(recipient); } public void setApproved(@NonNull Address recipient, boolean approved) { @@ -275,6 +283,7 @@ public void setApproved(@NonNull Address recipient, boolean approved) { updateOrInsert(recipient, values); notifyRecipientListeners(); + invalidateCache(recipient); updateNotifications.tryEmit(recipient); } @@ -284,6 +293,7 @@ public void setApprovedMe(@NonNull Address recipient, boolean approvedMe) { updateOrInsert(recipient, values); notifyRecipientListeners(); + invalidateCache(recipient); updateNotifications.tryEmit(recipient); } @@ -300,18 +310,25 @@ public void setBlocked(@NonNull Iterable
recipients, boolean blocked) { } finally { db.endTransaction(); } - notifyRecipientListeners(); - recipients.forEach(updateNotifications::tryEmit); + for (Address recipient : recipients) { + invalidateCache(recipient); + updateNotifications.tryEmit(recipient); + } + + notifyRecipientListeners(); } // Delete a recipient with the given address from the database public void deleteRecipient(@NonNull String recipientAddress) { + Address address = Address.fromSerialized(recipientAddress); SQLiteDatabase db = getWritableDatabase(); int rowCount = db.delete(TABLE_NAME, ADDRESS + " = ?", new String[] { recipientAddress }); if (rowCount == 0) { Log.w(TAG, "Could not find to delete recipient with address: " + recipientAddress); } + + invalidateCache(address); notifyRecipientListeners(); - updateNotifications.tryEmit(Address.fromSerialized(recipientAddress)); + updateNotifications.tryEmit(address); } public void setAutoDownloadAttachments(@NonNull Address recipient, boolean shouldAutoDownloadAttachments) { @@ -325,6 +342,8 @@ public void setAutoDownloadAttachments(@NonNull Address recipient, boolean shoul } finally { db.endTransaction(); } + + invalidateCache(recipient); notifyRecipientListeners(); updateNotifications.tryEmit(recipient); } @@ -346,6 +365,8 @@ public void setNotifyType(@NonNull Address recipient, int notifyType) { ContentValues values = new ContentValues(); values.put(NOTIFY_TYPE, notifyType); updateOrInsert(recipient, values); + + invalidateCache(recipient); notifyConversationListListeners(); notifyRecipientListeners(); updateNotifications.tryEmit(recipient); @@ -359,6 +380,18 @@ public void updateProfile(@NonNull Address recipient, return; // nothing to update } + // This call could be called with a lot of same data so it's worth checking if we + // actually need to update the database. + final RecipientSettings cached = recipientSettingsCache.get(recipient); + if (cached != null && + Objects.equals(cached.getSystemDisplayName(), newName) && + Objects.equals(cached.getProfilePic(), profilePic) && + (acceptsCommunityRequests == null || !cached.getBlocksCommunityMessagesRequests() == acceptsCommunityRequests) + ) { + Log.w(TAG, "No changes to update for recipient: " + recipient); + return; + } + ContentValues contentValues = new ContentValues(4); if (newName != null) { contentValues.put(SYSTEM_DISPLAY_NAME, newName); @@ -373,6 +406,7 @@ public void updateProfile(@NonNull Address recipient, } updateOrInsert(recipient, contentValues); + invalidateCache(recipient); updateNotifications.tryEmit(recipient); } @@ -380,6 +414,7 @@ public void setNotificationChannel(@NonNull Address recipient, @Nullable String ContentValues contentValues = new ContentValues(1); contentValues.put(NOTIFICATION_CHANNEL, notificationChannel); updateOrInsert(recipient, contentValues); + invalidateCache(recipient); notifyRecipientListeners(); updateNotifications.tryEmit(recipient); } @@ -388,6 +423,7 @@ public void setBlocksCommunityMessageRequests(@NonNull Address recipient, boolea ContentValues contentValues = new ContentValues(1); contentValues.put(BLOCKS_COMMUNITY_MESSAGE_REQUESTS, isBlocked ? 1 : 0); updateOrInsert(recipient, contentValues); + invalidateCache(recipient); notifyRecipientListeners(); updateNotifications.tryEmit(recipient); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index 78f361fd65..a292686bc1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -73,7 +73,9 @@ class RecipientRepository @Inject constructor( private fun createRecipientFlow(address: Address): SharedFlow { return flow { while (true) { - val (value, changeSource) = fetchRecipient(address) ?: run { + val (value, changeSource) = fetchRecipient(address) { + withContext(Dispatchers.Default) { recipientDatabase.getRecipientSettings(it) } + } ?: run { // If we don't have a recipient for this address, emit null and terminate the flow. emit(null) return@flow @@ -90,7 +92,7 @@ class RecipientRepository @Inject constructor( SharingStarted.WhileSubscribed(replayExpirationMillis = 0L), replay = 1) } - private suspend fun fetchRecipient(address: Address): Pair>? { + private inline fun fetchRecipient(address: Address, settingsFetcher: (address: Address) -> RecipientSettings?): Pair>? { val basicRecipient = getBasicRecipientFast(address) val changeSource: Flow<*> @@ -105,9 +107,7 @@ class RecipientRepository @Inject constructor( is BasicRecipient.Contact -> { value = createContactRecipient( basic = basicRecipient, - fallbackSettings = withContext(Dispatchers.Default) { - recipientDatabase.getRecipientSettings(address) - } + fallbackSettings = settingsFetcher(address) ) changeSource = merge( @@ -119,9 +119,7 @@ class RecipientRepository @Inject constructor( is BasicRecipient.Group -> { value = createGroupV2Recipient( basic = basicRecipient, - settings = withContext(Dispatchers.Default) { - recipientDatabase.getRecipientSettings(address) - } + settings = settingsFetcher(address) ) changeSource = merge( @@ -137,9 +135,7 @@ class RecipientRepository @Inject constructor( // Given address is not backed by the config system so we'll get them from // local database. - val settings = withContext(Dispatchers.Default) { - recipientDatabase.getRecipientSettings(address) - } + val settings = settingsFetcher(address) when { address.isLegacyGroup || address.isCommunity -> { @@ -187,7 +183,8 @@ class RecipientRepository @Inject constructor( } } - return runBlocking { flow.first() } + // Otherwise, we might have to go to the database to get the recipient.. + return fetchRecipient(address, recipientDatabase::getRecipientSettings)?.first } /** diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index b1c6f772b6..5215064ac3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -128,7 +128,8 @@ open class Storage @Inject constructor( override fun threadCreated(address: Address, threadId: Long) { val localUserAddress = getUserPublicKey() ?: return - if (!getRecipientApproved(address) && localUserAddress != address.toString()) return // don't store unapproved / message requests + val approved = recipientRepository.getRecipientSyncOrEmpty(address).approved + if (!approved && localUserAddress != address.toString()) return // don't store unapproved / message requests when { address.isLegacyGroup -> { @@ -373,11 +374,15 @@ open class Storage @Inject constructor( } else { senderAddress } - if (!targetAddress.isGroupOrCommunity) { - if (isUserSender || isUserBlindedSender) { - setRecipientApproved(targetAddress, true) - } else { - setRecipientApprovedMe(targetAddress, true) + if (!targetAddress.isGroupOrCommunity && IdPrefix.fromValue(targetAddress.address) == IdPrefix.STANDARD) { + configFactory.withMutableUserConfigs { configs -> + configs.contacts.upsertContact(targetAddress.address) { + if (isUserSender || isUserBlindedSender) { + approved = true + } else { + approvedMe = true + } + } } } if (message.threadID == null && !targetAddress.isCommunity) { @@ -1394,13 +1399,15 @@ open class Storage @Inject constructor( deleteConversation(blindedThreadId) } - var alreadyApprovedMe: Boolean = false - configFactory.withUserConfigs { - // check is the person had not yet approvedMe - alreadyApprovedMe = it.contacts.get(sender.toString())?.approvedMe ?: false - } + var alreadyApprovedMe = false - setRecipientApprovedMe(sender, true) + // Update the contact's approval status + configFactory.withMutableUserConfigs { configs -> + configs.contacts.upsertContact(sender.toString()) { + alreadyApprovedMe = approvedMe + approvedMe = true + } + } // only show the message if wasn't already approvedMe before if(!alreadyApprovedMe) { @@ -1456,34 +1463,6 @@ open class Storage @Inject constructor( mmsDatabase.insertSecureDecryptedMessageInbox(message, threadId, runThreadUpdate = false) } - override fun getRecipientApproved(address: Address): Boolean { - return address.isGroupV2 || recipientDatabase.getApproved(address) - } - - override fun setRecipientApproved(address: Address, approved: Boolean) { - recipientDatabase.setApproved(address, approved) - if (!address.isContact || address.toString() == getUserPublicKey()) return - configFactory.withMutableUserConfigs { - it.contacts.upsertContact(address.toString()) { - // if the contact wasn't approved before but is approved now, make sure it's visible - if(approved && !this.approved) this.priority = PRIORITY_VISIBLE - - // update approval - this.approved = approved - } - } - } - - override fun setRecipientApprovedMe(address: Address, approvedMe: Boolean) { - recipientDatabase.setApprovedMe(address, approvedMe) - if (!address.isContact || address.toString() == getUserPublicKey()) return - configFactory.withMutableUserConfigs { - it.contacts.upsertContact(address.toString()) { - this.approvedMe = approvedMe - } - } - } - override fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long) { val address = fromSerialized(senderPublicKey) val recipient = recipientRepository.getRecipientSync(address) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt index 9b63c2a5b4..9ea05e5d13 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt @@ -791,9 +791,9 @@ class GroupManagerV2Impl @Inject constructor( inviteMessageHash: String, ) { val address = Address.fromSerialized(groupId.hexString) + val inviterRecipient = recipientRepository.getBasicRecipientFast(Address.fromSerialized(inviter.hexString)) - val shouldAutoApprove = - storage.getRecipientApproved(Address.fromSerialized(inviter.hexString)) + val shouldAutoApprove = (inviterRecipient as? BasicRecipient.Contact)?.approved == true val closedGroupInfo = GroupInfo.ClosedGroupInfo( groupAccountId = groupId.hexString, adminKey = authDataOrAdminSeed.takeIf { fromPromotion }?.let { GroupInfo.ClosedGroupInfo.adminKeyFromSeed(it) }?.toBytes(), @@ -811,8 +811,6 @@ class GroupManagerV2Impl @Inject constructor( } val groupThreadId = storage.getOrCreateThreadIdFor(address) - storage.setRecipientApprovedMe(address, true) - storage.setRecipientApproved(address, shouldAutoApprove) if (shouldAutoApprove) { approveGroupInvite(closedGroupInfo, inviteMessageHash) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 8a00781a10..12e11582db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -34,7 +34,6 @@ import kotlinx.coroutines.withContext import network.loki.messenger.BuildConfig import network.loki.messenger.R import network.loki.messenger.databinding.ActivityHomeBinding -import org.greenrobot.eventbus.EventBus import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager @@ -319,7 +318,6 @@ class HomeActivity : ScreenLockActionBarActivity(), }.collectLatest(globalSearchAdapter::setNewData) } } - EventBus.getDefault().register(this@HomeActivity) if (isFromOnboarding) { if (Build.VERSION.SDK_INT >= 33 && (getSystemService(NOTIFICATION_SERVICE) as NotificationManager).areNotificationsEnabled().not()) { @@ -481,11 +479,6 @@ class HomeActivity : ScreenLockActionBarActivity(), ApplicationContext.getInstance(this).messageNotifier.setHomeScreenVisible(false) } - override fun onDestroy() { - super.onDestroy() - EventBus.getDefault().unregister(this) - } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults) diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index 857d576fe5..9d98245492 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -27,7 +27,9 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.upsertContact import org.session.libsignal.utilities.AccountId +import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.DraftDatabase @@ -263,7 +265,15 @@ class DefaultConversationRepository @Inject constructor( } override fun setApproved(recipient: Address, isApproved: Boolean) { - storage.setRecipientApproved(recipient, isApproved) + if (IdPrefix.fromValue(recipient.address) == IdPrefix.STANDARD) { + configFactory.withMutableUserConfigs { configs -> + configs.contacts.upsertContact(recipient.address) { + approved = isApproved + } + } + } else { + // Can not approve anything that is not a standard contact + } } override suspend fun deleteCommunityMessagesRemotely( @@ -422,7 +432,7 @@ class DefaultConversationRepository @Inject constructor( override suspend fun acceptMessageRequest(threadId: Long, recipient: Address) = runCatching { withContext(Dispatchers.Default) { - storage.setRecipientApproved(recipient, true) + setApproved(recipient, true) if (recipient.isGroupV2) { groupManager.respondToInvitation( AccountId(recipient.toString()), From 7514740d9fff31a2b5dfbfcc061aec01cdba8aae Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:32:41 +1000 Subject: [PATCH 14/52] Fixes crash on disappearing messages --- .../disappearingmessages/DisappearingMessages.kt | 5 ++++- .../securesms/database/GroupDatabase.java | 11 +++++++---- .../securesms/database/RecipientRepository.kt | 9 +++++++-- .../org/thoughtcrime/securesms/database/Storage.kt | 2 +- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt index 2b68303ba8..40314e45a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt @@ -7,6 +7,7 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.sending_receiving.MessageSender +import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.Address import org.session.libsession.utilities.ExpirationUtil import org.session.libsession.utilities.SSKEnvironment.MessageExpirationManagerProtocol @@ -25,7 +26,8 @@ class DisappearingMessages @Inject constructor( private val textSecurePreferences: TextSecurePreferences, private val messageExpirationManager: MessageExpirationManagerProtocol, private val storage: StorageProtocol, - private val groupManagerV2: GroupManagerV2 + private val groupManagerV2: GroupManagerV2, + private val clock: SnodeClock, ) { fun set(threadId: Long, address: Address, mode: ExpiryMode, isGroup: Boolean) { storage.setExpirationConfiguration(threadId, mode) @@ -38,6 +40,7 @@ class DisappearingMessages @Inject constructor( sender = textSecurePreferences.getLocalNumber() isSenderSelf = true recipient = address.toString() + sentTimestamp = clock.currentTimeMills() } messageExpirationManager.insertExpirationTimerMessage(message) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index f371a79271..60f5cd6abd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -268,11 +268,14 @@ public void update(String groupId, String title, SignalServiceAttachmentPointer public void updateTitle(String groupID, String newValue) { ContentValues contentValues = new ContentValues(); contentValues.put(TITLE, newValue); - getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", - new String[] {groupID}); - updateNotification.tryEmit(groupID); - notifyConversationListListeners(); + // Only notify if the title is actually changed. This is more a performance optimization rather + // than functional. + if (getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ? AND " + TITLE + " != ?", + new String[] {groupID, newValue}) > 0) { + updateNotification.tryEmit(groupID); + notifyConversationListListeners(); + } } public void updateProfilePicture(String groupID, Bitmap newValue) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index a292686bc1..ee1968297c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -2,10 +2,12 @@ package org.thoughtcrime.securesms.database import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first @@ -70,6 +72,7 @@ class RecipientRepository @Inject constructor( // This function creates a flow that emits the recipient information for the given address, // the function itself must be fast, not directly access db and lock free, as it is called from a locked context. + @OptIn(FlowPreview::class) private fun createRecipientFlow(address: Address): SharedFlow { return flow { while (true) { @@ -82,8 +85,10 @@ class RecipientRepository @Inject constructor( } emit(value) - changeSource.first() - Log.d(TAG, "Recipient changed for ${address.address.substring(0..10)}") + val evt = changeSource + .debounce(1000) // Debounce to avoid too frequent updates + .first() + Log.d(TAG, "Recipient changed for ${address.debugString}, triggering event: $evt") } }.shareIn(GlobalScope, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 5215064ac3..b7a3881238 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1665,7 +1665,7 @@ open class Storage @Inject constructor( .mapTo(allBlockedContacts) { Address.fromSerialized(it.id) } // Source data from the local database. This might contain something that is not synced - // to the config system. + // to the config system.e allBlockedContacts.addAll(recipientDatabase.blockedContacts) return allBlockedContacts.map { From 47bbe62c91639d3df2d013755288227fe5444a9b Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:43:54 +1000 Subject: [PATCH 15/52] Renamed --- .../libsession/database/StorageProtocol.kt | 4 +- .../libsession/messaging/contacts/Contact.kt | 4 +- .../messaging/groups/GroupManagerV2.kt | 4 +- .../jobs/RetrieveProfileAvatarJob.kt | 5 +- .../OutgoingExpirationUpdateMessage.java | 1 - .../ReceivedMessageHandler.kt | 4 +- .../utilities/recipients/MessageType.kt | 2 +- .../{RecipientV2.kt => Recipient.kt} | 19 ++++--- .../securesms/MediaPreviewActivity.kt | 5 +- .../securesms/components/FromTextView.java | 6 +-- .../components/ProfilePictureView.kt | 12 ++--- .../components/TypingStatusSender.java | 5 +- .../contacts/ContactSelectionListAdapter.kt | 12 ++--- .../contacts/ShareContactListFragment.kt | 8 +-- .../contacts/ShareContactListLoader.kt | 8 +-- .../securesms/contacts/UserView.kt | 4 +- .../conversation/v2/ConversationAdapter.kt | 4 +- .../conversation/v2/ConversationViewModel.kt | 27 +++++----- .../v2/MessageDetailsViewModel.kt | 6 +-- .../conversation/v2/dialogs/DownloadDialog.kt | 6 +-- .../conversation/v2/input_bar/InputBar.kt | 5 +- .../v2/messages/AttachmentControlView.kt | 4 +- .../conversation/v2/messages/QuoteView.kt | 8 +-- .../v2/messages/VisibleMessageContentView.kt | 7 ++- .../settings/ConversationSettingsViewModel.kt | 6 +-- .../NotificationSettingsViewModel.kt | 4 +- .../v2/utilities/AttachmentManager.java | 1 - .../securesms/database/MmsDatabase.kt | 6 +-- .../securesms/database/RecipientRepository.kt | 50 +++++++++---------- .../securesms/database/SmsDatabase.java | 4 +- .../securesms/database/Storage.kt | 6 +-- .../securesms/database/ThreadDatabase.java | 8 ++- .../database/model/DisplayRecord.java | 12 ++--- .../database/model/MediaMmsMessageRecord.java | 6 +-- .../database/model/MessageRecord.java | 22 ++++---- .../database/model/MmsMessageRecord.java | 18 +++---- .../database/model/SmsMessageRecord.java | 18 +++---- .../database/model/ThreadRecord.java | 6 +-- .../securesms/groups/GroupManagerV2Impl.kt | 5 +- .../groups/SelectContactsViewModel.kt | 6 +-- .../securesms/home/ConversationView.kt | 4 +- .../securesms/home/UserDetailsBottomSheet.kt | 4 +- .../home/search/GlobalSearchAdapter.kt | 4 +- .../home/search/GlobalSearchAdapterUtils.kt | 4 +- .../mediasend/MediaPickerFolderFragment.java | 6 +-- .../securesms/mediasend/MediaSendActivity.kt | 4 +- .../messagerequests/MessageRequestView.kt | 6 +-- .../AbstractNotificationBuilder.java | 7 ++- .../notifications/DefaultMessageNotifier.kt | 4 +- .../MultipleRecipientNotificationBuilder.kt | 6 +-- .../notifications/NotificationChannels.java | 4 +- .../notifications/NotificationItem.java | 20 ++++---- .../notifications/NotificationState.java | 10 ++-- .../securesms/notifications/ReplyMethod.java | 4 +- .../SingleRecipientNotificationBuilder.java | 17 +++---- .../preferences/BlockedContactsAdapter.kt | 4 +- .../preferences/BlockedContactsViewModel.kt | 12 ++--- .../securesms/reactions/ReactionDetails.kt | 16 +++--- .../reactions/ReactionsRepository.kt | 4 +- .../repository/ConversationRepository.kt | 6 +-- .../securesms/search/SearchRepository.kt | 6 +-- .../securesms/search/model/MessageResult.java | 10 ++-- .../service/CallForegroundService.kt | 6 +-- .../securesms/service/DirectShareService.java | 7 ++- .../securesms/tokenpage/TokenPageViewModel.kt | 7 +-- .../securesms/util/AvatarUtils.kt | 6 +-- .../securesms/util/ContactUtilities.kt | 6 +-- .../securesms/util/SessionMetaProtocol.kt | 6 +-- .../webrtc/CallNotificationBuilder.kt | 5 +- 69 files changed, 266 insertions(+), 287 deletions(-) rename app/src/main/java/org/session/libsession/utilities/recipients/{RecipientV2.kt => Recipient.kt} (94%) diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 64dd442db3..af65ee9815 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -28,7 +28,7 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupDisplayInfo import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.recipients.RecipientSettings -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceGroup @@ -252,7 +252,7 @@ interface StorageProtocol { fun deleteReactions(messageId: MessageId) fun deleteReactions(messageIds: List, mms: Boolean) fun setBlocked(recipients: Iterable
, isBlocked: Boolean, fromConfigUpdate: Boolean = false) - fun blockedContacts(): List + fun blockedContacts(): List fun getExpirationConfiguration(threadId: Long): ExpiryMode fun setExpirationConfiguration(threadId: Long, expiryMode: ExpiryMode) fun getExpiringMessages(messageIds: List = emptyList()): List> diff --git a/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt b/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt index 5a1c313aa5..6d899da758 100644 --- a/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt +++ b/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt @@ -2,7 +2,7 @@ package org.session.libsession.messaging.contacts import android.os.Parcelable import kotlinx.parcelize.Parcelize -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.truncateIdForDisplay @Parcelize @@ -69,7 +69,7 @@ class Contact( } companion object { - fun contextForRecipient(recipient: RecipientV2): ContactContext { + fun contextForRecipient(recipient: Recipient): ContactContext { return if (recipient.isCommunityRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR } } diff --git a/app/src/main/java/org/session/libsession/messaging/groups/GroupManagerV2.kt b/app/src/main/java/org/session/libsession/messaging/groups/GroupManagerV2.kt index 2d2a2dc37d..b8ea9990ec 100644 --- a/app/src/main/java/org/session/libsession/messaging/groups/GroupManagerV2.kt +++ b/app/src/main/java/org/session/libsession/messaging/groups/GroupManagerV2.kt @@ -3,7 +3,7 @@ package org.session.libsession.messaging.groups import androidx.annotation.StringRes import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.messaging.messages.control.GroupUpdated -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateDeleteMemberContentMessage import org.session.libsignal.utilities.AccountId @@ -16,7 +16,7 @@ interface GroupManagerV2 { groupName: String, groupDescription: String, members: Set - ): RecipientV2 + ): Recipient suspend fun inviteMembers( group: AccountId, diff --git a/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt b/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt index bdcee4e042..d0542e6fc1 100644 --- a/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt +++ b/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt @@ -1,6 +1,5 @@ package org.session.libsession.messaging.jobs -import kotlinx.coroutines.flow.first import org.session.libsession.avatars.AvatarHelper import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.utilities.Data @@ -10,7 +9,7 @@ import org.session.libsession.utilities.DownloadUtilities.downloadFromFileServer import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfileAvatarId import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfilePictureURL import org.session.libsession.utilities.Util.equals -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.exceptions.NonRetryableException import org.session.libsignal.utilities.HTTP import org.session.libsignal.utilities.Log @@ -46,7 +45,7 @@ class RetrieveProfileAvatarJob( val context = MessagingModuleConfiguration.shared.context val storage = MessagingModuleConfiguration.shared.storage val recipient = MessagingModuleConfiguration.shared.recipientRepository.getRecipient(recipientAddress) - ?: RecipientV2.empty(recipientAddress) + ?: Recipient.empty(recipientAddress) if (profileKey == null || (profileKey.size != 32 && profileKey.size != 16)) { return delegate.handleJobFailedPermanently(this, dispatcherName, Exception("Recipient profile key is gone!")) diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java index 078729ab29..cd411a3580 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java @@ -3,7 +3,6 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.DistributionTypes; -import org.session.libsession.utilities.recipients.RecipientV2; import java.util.Collections; import java.util.LinkedList; diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 673b553de7..15fa2434ed 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -46,7 +46,7 @@ import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.GroupUtil.doubleEncodeGroupID import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.recipients.MessageType -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.getType import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.AccountId @@ -308,7 +308,7 @@ class VisibleMessageHandlerContext( storage.getUserPublicKey() } - val threadRecipient: RecipientV2? by lazy { + val threadRecipient: Recipient? by lazy { storage.getRecipientForThread(threadId)?.let(recipientRepository::getRecipientSync) } } diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/MessageType.kt b/app/src/main/java/org/session/libsession/utilities/recipients/MessageType.kt index afbd55d142..591300cd12 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/MessageType.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/MessageType.kt @@ -4,7 +4,7 @@ enum class MessageType { ONE_ON_ONE, LEGACY_GROUP, GROUPS_V2, NOTE_TO_SELF, COMMUNITY } -fun RecipientV2.getType(): MessageType = +fun Recipient.getType(): MessageType = when{ isCommunityRecipient -> MessageType.COMMUNITY isLocalNumber -> MessageType.NOTE_TO_SELF diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt similarity index 94% rename from app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt rename to app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt index 70e2e33796..3a46dc1ebc 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientV2.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt @@ -1,20 +1,17 @@ package org.session.libsession.utilities.recipients -import android.content.Context -import android.graphics.drawable.Drawable import network.loki.messenger.libsession_util.util.Bytes import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.avatars.ContactPhoto import org.session.libsession.avatars.ProfileContactPhoto -import org.session.libsession.avatars.TransparentContactPhoto import org.session.libsession.utilities.Address import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.model.NotifyType import java.time.ZonedDateTime -data class RecipientV2( +data class Recipient( val basic: BasicRecipient, val mutedUntil: ZonedDateTime?, val autoDownloadAttachments: Boolean?, @@ -45,7 +42,12 @@ data class RecipientV2( else -> ExpiryMode.NONE } - val approved: Boolean get() = (basic as? BasicRecipient.Contact)?.approved ?: true + val approved: Boolean get() = when (basic) { + is BasicRecipient.Contact -> basic.approved + is BasicRecipient.Group -> basic.approved + else -> true + } + val approvedMe: Boolean get() = (basic as? BasicRecipient.Contact)?.approvedMe ?: true val blocked: Boolean get() = when (basic) { is BasicRecipient.Generic -> basic.blocked @@ -82,8 +84,8 @@ data class RecipientV2( get() = (avatar as? RecipientAvatar.EncryptedRemotePic)?.key?.data companion object { - fun empty(address: Address): RecipientV2 { - return RecipientV2( + fun empty(address: Address): Recipient { + return Recipient( basic = BasicRecipient.Generic(address), mutedUntil = null, autoDownloadAttachments = true, @@ -158,6 +160,7 @@ sealed interface BasicRecipient { val name: String, override val avatar: RecipientAvatar.EncryptedRemotePic?, val expiryMode: ExpiryMode, + val approved: Boolean, ) : ConfigBasedRecipient { override val displayName: String get() = name @@ -243,7 +246,7 @@ fun RecipientAvatar.toUserPic(): UserPic? { } } -inline fun RecipientV2?.displayNameOrFallback(fallbackName: () -> String? = { null }, address: String): String { +inline fun Recipient?.displayNameOrFallback(fallbackName: () -> String? = { null }, address: String): String { return this?.displayName ?: fallbackName()?.takeIf { it.isNotBlank() } ?: truncateIdForDisplay(address) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt index 1ea55d4034..c650b09b7a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt @@ -66,9 +66,8 @@ import org.session.libsession.snode.SnodeAPI.nowWithOffset import org.session.libsession.utilities.Address import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.getColorFromAttr -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.ShareActivity import org.thoughtcrime.securesms.components.MediaView import org.thoughtcrime.securesms.components.dialogs.DeleteMediaPreviewDialog import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord @@ -780,7 +779,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), } class MediaItem( - val recipient: RecipientV2?, + val recipient: Recipient?, val attachment: DatabaseAttachment?, val uri: Uri, val mimeType: String, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java b/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java index 35cf68c47b..5faa91a56f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java @@ -15,7 +15,7 @@ import network.loki.messenger.R; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.components.emoji.EmojiTextView; import org.thoughtcrime.securesms.util.ResUtil; import org.session.libsession.utilities.CenterAlignedRelativeSizeSpan; @@ -32,11 +32,11 @@ public FromTextView(Context context, AttributeSet attrs) { super(context, attrs); } - public void setText(RecipientV2 recipient) { + public void setText(Recipient recipient) { setText(recipient, true); } - public void setText(RecipientV2 recipient, boolean read) { + public void setText(Recipient recipient, boolean read) { String fromString = recipient.getDisplayName(); int typeface; diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index b33221bb00..2ae3d18a8b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -23,7 +23,7 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.AppTextSecurePreferences import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.recipients.RecipientAvatar -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.truncateIdForDisplay import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.GroupDatabase @@ -47,7 +47,7 @@ class ProfilePictureView @JvmOverloads constructor( var displayName: String? = null var additionalPublicKey: String? = null var additionalDisplayName: String? = null - var recipient: RecipientV2? = null + var recipient: Recipient? = null @Inject lateinit var groupDatabase: GroupDatabase @@ -61,14 +61,14 @@ class ProfilePictureView @JvmOverloads constructor( @Inject lateinit var recipientRepository: RecipientRepository - private val profilePicturesCache = mutableMapOf() + private val profilePicturesCache = mutableMapOf() private val resourcePadding by lazy { context.resources.getDimensionPixelSize(R.dimen.normal_padding).toFloat() } private val unknownOpenGroupDrawable by lazy { ResourceContactPhoto(R.drawable.ic_notification) .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false, resourcePadding) } - constructor(context: Context, sender: RecipientV2): this(context) { + constructor(context: Context, sender: Recipient): this(context) { update(sender) } @@ -79,7 +79,7 @@ class ProfilePictureView @JvmOverloads constructor( .asDrawable(context, color, false, resourcePadding) } - fun update(recipient: RecipientV2) { + fun update(recipient: Recipient) { this.recipient = recipient recipient.run { update( @@ -190,7 +190,7 @@ class ProfilePictureView @JvmOverloads constructor( } else { val address = Address.fromSerialized(publicKey) - this.recipient = recipientRepository.getRecipientSync(address) ?: RecipientV2.empty(address) + this.recipient = recipientRepository.getRecipientSync(address) ?: Recipient.empty(address) this.recipient!! } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java b/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java index 81f2a9a5ad..285bfb8ae4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java @@ -1,14 +1,13 @@ package org.thoughtcrime.securesms.components; import android.annotation.SuppressLint; -import android.content.Context; import org.session.libsession.messaging.messages.control.TypingIndicator; import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.RecipientRepository; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.util.SessionMetaProtocol; @@ -86,7 +85,7 @@ private synchronized void onTypingStopped(long threadId, boolean notify) { private void sendTyping(long threadId, boolean typingStarted) { Address address = threadDatabase.getRecipientForThreadId(threadId); if (address == null) { return; } - RecipientV2 recipient = recipientRepository.getRecipientSync(address); + Recipient recipient = recipientRepository.getRecipientSync(address); if (recipient == null) { return; } if (!SessionMetaProtocol.shouldSendTypingIndicator(recipient)) { return; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.kt index 44b2c3dfb2..229e7b6cba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.kt @@ -4,11 +4,11 @@ import android.content.Context import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.RequestManager -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient class ContactSelectionListAdapter(private val context: Context, private val multiSelect: Boolean) : RecyclerView.Adapter() { lateinit var glide: RequestManager - val selectedContacts = mutableSetOf() + val selectedContacts = mutableSetOf() var items = listOf() set(value) { field = value; notifyDataSetChanged() } var contactClickListener: ContactClickListener? = null @@ -53,7 +53,7 @@ class ContactSelectionListAdapter(private val context: Context, private val mult } } - fun onContactClick(recipient: RecipientV2) { + fun onContactClick(recipient: Recipient) { if (selectedContacts.contains(recipient)) { selectedContacts.remove(recipient) contactClickListener?.onContactDeselected(recipient) @@ -71,7 +71,7 @@ class ContactSelectionListAdapter(private val context: Context, private val mult } interface ContactClickListener { - fun onContactClick(contact: RecipientV2) - fun onContactSelected(contact: RecipientV2) - fun onContactDeselected(contact: RecipientV2) + fun onContactClick(contact: Recipient) + fun onContactSelected(contact: Recipient) + fun onContactDeselected(contact: Recipient) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt index 4df9ca4c28..982547cdcd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt @@ -15,7 +15,7 @@ import com.bumptech.glide.Glide import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.databinding.ShareContactListFragmentBinding import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import javax.inject.Inject @@ -118,15 +118,15 @@ class ShareContactListFragment : Fragment(), LoaderManager.LoaderCallbacks> { !it.first.isLocalNumber } // NTS come first + compareBy> { !it.first.isLocalNumber } // NTS come first .thenByDescending { it.second } // then order by last message time ) .map { it.first }.toList() @@ -40,7 +40,7 @@ class ShareContactListLoader( return getItems(contacts) } - private fun getItems(contacts: List): List { + private fun getItems(contacts: List): List { val items = contacts.map { ContactSelectionListItem.Contact(it) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt index d2c6124373..c8328ecc31 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt @@ -8,7 +8,7 @@ import android.widget.LinearLayout import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ViewUserBinding -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient @AndroidEntryPoint class UserView : LinearLayout { @@ -43,7 +43,7 @@ class UserView : LinearLayout { // endregion // region Updating - fun bind(user: RecipientV2, actionIndicator: ActionIndicator, isSelected: Boolean = false, showCurrentUserAsNoteToSelf: Boolean = false) { + fun bind(user: Recipient, actionIndicator: ActionIndicator, isSelected: Boolean = false, showCurrentUserAsNoteToSelf: Boolean = false) { val isLocalUser = user.isLocalNumber fun getUserDisplayName(): String { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index f0fa09b7ac..4c4b6311fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -22,7 +22,7 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView @@ -35,7 +35,7 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent class ConversationAdapter( context: Context, cursor: Cursor, - conversation: RecipientV2?, + conversation: Recipient?, originalLastSeen: Long, private val isReversed: Boolean, private val onItemPress: (MessageRecord, Int, VisibleMessageView, MotionEvent) -> Unit, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 53af01fc11..f19cf1bcab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -32,7 +32,6 @@ import kotlinx.coroutines.withContext import network.loki.messenger.R import network.loki.messenger.libsession_util.util.BlindKeyAPI import network.loki.messenger.libsession_util.util.ExpiryMode -import network.loki.messenger.libsession_util.util.KeyPair import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupManagerV2 @@ -48,7 +47,7 @@ import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.recipients.MessageType -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.displayNameOrFallback import org.session.libsession.utilities.recipients.getType import org.session.libsignal.utilities.AccountId @@ -144,7 +143,7 @@ class ConversationViewModel @AssistedInject constructor( )) val appBarData: StateFlow = _appBarData - private var _recipient: RetrieveOnce = RetrieveOnce { + private var _recipient: RetrieveOnce = RetrieveOnce { val conversation = repository.maybeGetRecipientForThreadId(threadId)?.let(recipientRepository::getRecipientSync) // set admin from current conversation @@ -179,17 +178,17 @@ class ConversationViewModel @AssistedInject constructor( conversation } - val recipient: RecipientV2? + val recipient: Recipient? get() = _recipient.value - val blindedRecipient: RecipientV2? + val blindedRecipient: Recipient? get() = _recipient.value?.let { recipient -> getBlindedRecipient(recipient) } private var currentAppBarNotificationState: String? = null - private fun getBlindedRecipient(recipient: RecipientV2?): RecipientV2? = + private fun getBlindedRecipient(recipient: Recipient?): Recipient? = when { recipient?.isCommunityOutboxRecipient == true -> recipient recipient?.isCommunityInboxRecipient == true -> @@ -203,7 +202,7 @@ class ConversationViewModel @AssistedInject constructor( * * null if this convo is not a group(v2) conversation, or error getting the info */ - val invitingAdmin: RecipientV2? + val invitingAdmin: Recipient? get() { val recipient = recipient ?: return null if (!recipient.isGroupV2Recipient) return null @@ -378,7 +377,7 @@ class ConversationViewModel @AssistedInject constructor( } private fun getInputBarState( - recipient: RecipientV2?, + recipient: Recipient?, community: OpenGroup?, deprecationState: LegacyGroupDeprecationManager.DeprecationState ): InputBarState { @@ -417,7 +416,7 @@ class ConversationViewModel @AssistedInject constructor( } } - private fun updateAppBarData(conversation: RecipientV2?) { + private fun updateAppBarData(conversation: Recipient?) { viewModelScope.launch { // sort out the pager data, if any val pagerData: MutableList = mutableListOf() @@ -500,7 +499,7 @@ class ConversationViewModel @AssistedInject constructor( } } - private fun getNotificationStatusTitle(conversation: RecipientV2): String{ + private fun getNotificationStatusTitle(conversation: Recipient): String{ return if(conversation.isMuted()) application.getString(R.string.notificationsHeaderMute) else application.getString(R.string.notificationsHeaderMentionsOnly) } @@ -512,7 +511,7 @@ class ConversationViewModel @AssistedInject constructor( * 1. First time we send message to a person. * Since we haven't been approved by them, we can't send them any media, only text */ - private fun shouldEnableInputMediaControls(recipient: RecipientV2?): Boolean { + private fun shouldEnableInputMediaControls(recipient: Recipient?): Boolean { // Specifically disallow multimedia if we don't have a recipient to send anything to if (recipient == null) { @@ -555,7 +554,7 @@ class ConversationViewModel @AssistedInject constructor( * 3. The legacy group is deprecated, OR * 4. Blinded recipient who have disabled message request from community members */ - private fun shouldShowInput(recipient: RecipientV2?, + private fun shouldShowInput(recipient: Recipient?, community: OpenGroup?, deprecationState: LegacyGroupDeprecationManager.DeprecationState ): Boolean { @@ -570,7 +569,7 @@ class ConversationViewModel @AssistedInject constructor( } } - private fun buildMessageRequestState(recipient: RecipientV2?): MessageRequestUiState { + private fun buildMessageRequestState(recipient: Recipient?): MessageRequestUiState { // The basic requirement of showing a message request is: // 1. The other party has not been approved by us, AND // 2. We haven't sent a message to them before (if we do, we would be the one requesting permission), AND @@ -1131,7 +1130,7 @@ class ConversationViewModel @AssistedInject constructor( updateAppBarData(recipient) } - fun legacyBannerRecipient(context: Context): RecipientV2? = recipient?.run { + fun legacyBannerRecipient(context: Context): Recipient? = recipient?.run { storage.getLastLegacyRecipient(address.toString())?.let { recipientRepository.getRecipientSync(fromSerialized(it)) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index d4ae52d657..37460b9578 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -26,7 +26,7 @@ import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.Address import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.MediaPreviewArgs import org.thoughtcrime.securesms.database.AttachmentDatabase @@ -116,7 +116,7 @@ class MessageDetailsViewModel @AssistedInject constructor( val slides = mmsRecord?.slideDeck?.slides ?: emptyList() val conversationAddress = threadDb.getRecipientForThreadId(threadId)!! - val conversation = recipientRepository.getRecipient(conversationAddress) ?: RecipientV2.empty(conversationAddress) + val conversation = recipientRepository.getRecipient(conversationAddress) ?: Recipient.empty(conversationAddress) val isDeprecatedLegacyGroup = conversationAddress.isLegacyGroup && deprecationManager.isDeprecated @@ -253,7 +253,7 @@ data class MessageDetailsState( val status: MessageStatus? = null, val senderInfo: TitledText? = null, val senderAvatarData: AvatarUIData? = null, - val thread: RecipientV2? = null, + val thread: Recipient? = null, val readOnly: Boolean = false, ) { val fromTitle = GetString(R.string.from) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt index d556995e0b..2bcfa785d5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt @@ -10,7 +10,7 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.StringSubstitutionConstants.CONVERSATION_NAME_KEY -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.createSessionDialog import org.thoughtcrime.securesms.database.SessionContactDatabase import org.thoughtcrime.securesms.util.createAndStartAttachmentDownload @@ -19,8 +19,8 @@ import javax.inject.Inject /** Shown when receiving media from a contact for the first time, to confirm that * they are to be trusted and files sent by them are to be downloaded. */ @AndroidEntryPoint -class AutoDownloadDialog(private val threadRecipient: RecipientV2, - private val databaseAttachment: DatabaseAttachment +class AutoDownloadDialog(private val threadRecipient: Recipient, + private val databaseAttachment: DatabaseAttachment ) : DialogFragment() { @Inject lateinit var storage: StorageProtocol diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index 244986ae58..c2fa87466f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -20,7 +20,7 @@ import network.loki.messenger.R import network.loki.messenger.databinding.ViewInputBarBinding import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.InputBarContentState import org.thoughtcrime.securesms.conversation.v2.InputBarState import org.thoughtcrime.securesms.conversation.v2.components.LinkPreviewDraftView @@ -31,7 +31,6 @@ import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.util.addTextChangedListener import org.thoughtcrime.securesms.util.contains -import org.thoughtcrime.securesms.util.setSafeOnClickListener // TODO: A lot of the logic regarding voice messages is currently performed in the ConversationActivity // TODO: and here - it would likely be best to move this into the CA's ViewModel. @@ -190,7 +189,7 @@ class InputBar @JvmOverloads constructor( delegate?.startRecordingVoiceMessage() } - fun draftQuote(thread: RecipientV2, message: MessageRecord, glide: RequestManager) { + fun draftQuote(thread: Recipient, message: MessageRecord, glide: RequestManager) { quoteView?.let(binding.inputBarAdditionalContentContainer::removeView) quote = message diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt index 2a25bfef2c..bf8547af17 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/AttachmentControlView.kt @@ -16,7 +16,7 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.StringSubstitutionConstants.FILE_TYPE_KEY -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.ViewUtil import org.thoughtcrime.securesms.conversation.v2.dialogs.AutoDownloadDialog import org.thoughtcrime.securesms.mms.Slide @@ -144,7 +144,7 @@ class AttachmentControlView: LinearLayout { // endregion // region Interaction - fun showDownloadDialog(threadRecipient: RecipientV2, attachment: DatabaseAttachment) { + fun showDownloadDialog(threadRecipient: Recipient, attachment: DatabaseAttachment) { if (threadRecipient.autoDownloadAttachments != true) { // just download (context.findActivity() as? ActivityDispatcher)?.showDialog(AutoDownloadDialog(threadRecipient, attachment)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt index 5326972419..4c9ee9bda6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt @@ -16,7 +16,7 @@ import network.loki.messenger.databinding.ViewQuoteBinding import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getColorFromAttr -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities import org.thoughtcrime.securesms.database.SessionContactDatabase @@ -66,9 +66,9 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? // endregion // region Updating - fun bind(authorPublicKey: String, body: String?, attachments: SlideDeck?, thread: RecipientV2, - isOutgoingMessage: Boolean, isOpenGroupInvitation: Boolean, threadID: Long, - isOriginalMissing: Boolean, glide: RequestManager) { + fun bind(authorPublicKey: String, body: String?, attachments: SlideDeck?, thread: Recipient, + isOutgoingMessage: Boolean, isOpenGroupInvitation: Boolean, threadID: Long, + isOriginalMissing: Boolean, glide: RequestManager) { // Author val author = contactDb.getContactWithAccountID(authorPublicKey) val localNumber = TextSecurePreferences.getLocalNumber(context) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index 1ba7271565..02e8322784 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -4,7 +4,6 @@ import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.content.res.ColorStateList -import android.graphics.Color import android.graphics.Rect import android.text.Spannable import android.text.style.BackgroundColorSpan @@ -33,7 +32,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAt import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.modifyLayoutParams -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.messages.AttachmentControlView.AttachmentType.AUDIO import org.thoughtcrime.securesms.conversation.v2.messages.AttachmentControlView.AttachmentType.DOCUMENT @@ -70,7 +69,7 @@ class VisibleMessageContentView : ConstraintLayout { isStartOfMessageCluster: Boolean = true, isEndOfMessageCluster: Boolean = true, glide: RequestManager = Glide.with(this), - thread: RecipientV2, + thread: Recipient, searchQuery: String? = null, downloadPendingAttachment: (DatabaseAttachment) -> Unit, retryFailedAttachments: (List) -> Unit, @@ -359,7 +358,7 @@ class VisibleMessageContentView : ConstraintLayout { } private fun showAttachmentControl( - thread: RecipientV2, + thread: Recipient, message: MmsMessageRecord, attachments: List, type: AttachmentControlView.AttachmentType, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt index 67ac8f0287..2a2f511273 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt @@ -45,7 +45,7 @@ import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getGroup -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.upsertContact import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Hex @@ -100,7 +100,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( private val _dialogState: MutableStateFlow = MutableStateFlow(DialogsState()) val dialogState: StateFlow = _dialogState - private var recipient: RecipientV2? = null + private var recipient: Recipient? = null private var groupV2: GroupInfo.ClosedGroupInfo? = null @@ -490,7 +490,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( } } - private fun getNotificationsData(conversation: RecipientV2): Pair { + private fun getNotificationsData(conversation: Recipient): Pair { return when{ conversation.isMuted() -> R.drawable.ic_volume_off to context.getString(R.string.notificationsMuted) conversation.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt index 79ce003e9a..b114b13a02 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt @@ -22,7 +22,7 @@ import network.loki.messenger.R import org.session.libsession.LocalisedTimeUtil import org.session.libsession.utilities.StringSubstitutionConstants.DATE_TIME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_LARGE_KEY -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_ALL import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_MENTIONS @@ -46,7 +46,7 @@ class NotificationSettingsViewModel @AssistedInject constructor( private val dateUtils: DateUtils, private val recipientRepository: RecipientRepository, ) : ViewModel() { - private var thread: RecipientV2? = null + private var thread: Recipient? = null private val durationForever: Long = Long.MAX_VALUE diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java index becc4c1e73..ef80f320e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java @@ -43,7 +43,6 @@ import network.loki.messenger.R; import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.recipients.RecipientV2; import org.session.libsignal.utilities.ListenableFuture; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.SettableFuture; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index 1803524034..392eb1b599 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -45,7 +45,7 @@ import org.session.libsession.utilities.IdentityKeyMismatchList import org.session.libsession.utilities.NetworkFailure import org.session.libsession.utilities.NetworkFailureList import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.ThreadUtils.queue @@ -1328,13 +1328,13 @@ class MmsDatabase @Inject constructor( ) } - private fun getRecipientFor(serialized: String?): RecipientV2 { + private fun getRecipientFor(serialized: String?): Recipient { val address: Address = if (serialized.isNullOrEmpty() || "insert-address-token" == serialized) { UNKNOWN } else { fromSerialized(serialized) } - return recipientRepository.getRecipientSync(address) ?: RecipientV2.empty(address) + return recipientRepository.getRecipientSync(address) ?: Recipient.empty(address) } private fun getMismatchedIdentities(document: String?): List? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index ee1968297c..b89c9aba43 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -14,7 +14,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.shareIn -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import network.loki.messenger.libsession_util.ReadableGroupInfoConfig import network.loki.messenger.libsession_util.util.ExpiryMode @@ -28,7 +27,7 @@ import org.session.libsession.utilities.recipients.BasicRecipient import org.session.libsession.utilities.recipients.RecipientAvatar import org.session.libsession.utilities.recipients.RecipientAvatar.Companion.toRecipientAvatar import org.session.libsession.utilities.recipients.RecipientSettings -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.displayNameOrFallback import org.session.libsession.utilities.userConfigsChanged import org.session.libsignal.utilities.AccountId @@ -56,9 +55,9 @@ class RecipientRepository @Inject constructor( private val recipientDatabase: RecipientDatabase, private val preferences: TextSecurePreferences, ) { - private val recipientCache = HashMap>>() + private val recipientCache = HashMap>>() - fun observeRecipient(address: Address): Flow { + fun observeRecipient(address: Address): Flow { synchronized(recipientCache) { var cached = recipientCache[address]?.get() if (cached == null) { @@ -73,7 +72,7 @@ class RecipientRepository @Inject constructor( // This function creates a flow that emits the recipient information for the given address, // the function itself must be fast, not directly access db and lock free, as it is called from a locked context. @OptIn(FlowPreview::class) - private fun createRecipientFlow(address: Address): SharedFlow { + private fun createRecipientFlow(address: Address): SharedFlow { return flow { while (true) { val (value, changeSource) = fetchRecipient(address) { @@ -97,11 +96,11 @@ class RecipientRepository @Inject constructor( SharingStarted.WhileSubscribed(replayExpirationMillis = 0L), replay = 1) } - private inline fun fetchRecipient(address: Address, settingsFetcher: (address: Address) -> RecipientSettings?): Pair>? { + private inline fun fetchRecipient(address: Address, settingsFetcher: (address: Address) -> RecipientSettings?): Pair>? { val basicRecipient = getBasicRecipientFast(address) val changeSource: Flow<*> - val value: RecipientV2? + val value: Recipient? when (basicRecipient) { is BasicRecipient.Self -> { @@ -169,7 +168,7 @@ class RecipientRepository @Inject constructor( return value to changeSource } - suspend fun getRecipient(address: Address): RecipientV2? { + suspend fun getRecipient(address: Address): Recipient? { return observeRecipient(address).first() } @@ -177,11 +176,11 @@ class RecipientRepository @Inject constructor( "Use the suspend version of getRecipient instead", ReplaceWith("getRecipient(address)") ) - fun getRecipientSync(address: Address): RecipientV2? { + fun getRecipientSync(address: Address): Recipient? { val flow = observeRecipient(address) // If the flow is a SharedFlow, we might be able to access its last cached value directly. - if (flow is SharedFlow) { + if (flow is SharedFlow) { val lastCacheValue = flow.replayCache.lastOrNull() if (lastCacheValue != null) { return lastCacheValue @@ -262,7 +261,8 @@ class RecipientRepository @Inject constructor( address = address, avatar = configs.groupInfo.getProfilePic().toRecipientAvatar(), expiryMode = configs.groupInfo.expiryMode, - name = configs.groupInfo.getName() ?: groupInfo.name + name = configs.groupInfo.getName() ?: groupInfo.name, + approved = !groupInfo.invited ) } } @@ -283,19 +283,19 @@ class RecipientRepository @Inject constructor( "Use the suspend version of getRecipient instead", ReplaceWith("getRecipient(address)") ) - fun getRecipientSyncOrEmpty(address: Address): RecipientV2 { + fun getRecipientSyncOrEmpty(address: Address): Recipient { return getRecipientSync(address) ?: empty(address) } - suspend fun getRecipientOrEmpty(address: Address): RecipientV2 { + suspend fun getRecipientOrEmpty(address: Address): Recipient { return getRecipient(address) ?: empty(address) } companion object { private const val TAG = "RecipientRepository" - private fun createLocalRecipient(basic: BasicRecipient.Self): RecipientV2 { - return RecipientV2( + private fun createLocalRecipient(basic: BasicRecipient.Self): Recipient { + return Recipient( basic = basic, mutedUntil = null, autoDownloadAttachments = true, @@ -323,8 +323,8 @@ class RecipientRepository @Inject constructor( private fun createGroupV2Recipient( basic: BasicRecipient.Group, settings: RecipientSettings? - ): RecipientV2 { - return RecipientV2( + ): Recipient { + return Recipient( basic = basic, mutedUntil = settings?.muteUntilDate, notifyType = settings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, @@ -340,8 +340,8 @@ class RecipientRepository @Inject constructor( private fun createContactRecipient( basic: BasicRecipient.Contact, fallbackSettings: RecipientSettings?, // Local db data - ): RecipientV2 { - return RecipientV2( + ): Recipient { + return Recipient( basic = basic, mutedUntil = fallbackSettings?.muteUntilDate, autoDownloadAttachments = fallbackSettings?.autoDownloadAttachments, @@ -354,8 +354,8 @@ class RecipientRepository @Inject constructor( address: Address, group: GroupRecord, // Local db data settings: RecipientSettings?, // Local db data - ): RecipientV2 { - return RecipientV2( + ): Recipient { + return Recipient( basic = BasicRecipient.Generic( address = address, displayName = group.title, @@ -372,8 +372,8 @@ class RecipientRepository @Inject constructor( * Creates a RecipientV2 instance from the provided Address and RecipientSettings. * Note that this method assumes the recipient is not ourselves. */ - private fun createGenericRecipient(address: Address, settings: RecipientSettings): RecipientV2 { - return RecipientV2( + private fun createGenericRecipient(address: Address, settings: RecipientSettings): Recipient { + return Recipient( basic = BasicRecipient.Generic( address = address, displayName = settings.systemDisplayName?.takeIf { it.isNotBlank() } ?: settings.profileName.orEmpty(), @@ -389,8 +389,8 @@ class RecipientRepository @Inject constructor( ) } - fun empty(address: Address): RecipientV2 { - return RecipientV2( + fun empty(address: Address): Recipient { + return Recipient( basic = BasicRecipient.Generic(address = address), mutedUntil = null, autoDownloadAttachments = null, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 2ad66ddf38..acd41f422c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -40,7 +40,7 @@ import org.session.libsession.utilities.IdentityKeyMismatchList; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.JsonUtil; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.guava.Optional; @@ -834,7 +834,7 @@ public SmsMessageRecord getCurrent() { } List mismatches = getMismatches(mismatchDocument); - RecipientV2 recipient = recipientRepository.getRecipientSyncOrEmpty(address); + Recipient recipient = recipientRepository.getRecipientSyncOrEmpty(address); List reactions = DatabaseComponent.get(context).reactionDatabase().getReactions(cursor); return new SmsMessageRecord(messageId, body, recipient, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index b7a3881238..332162df76 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -58,7 +58,7 @@ import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.recipients.RecipientSettings -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.upsertContact import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.session.libsignal.crypto.ecc.ECKeyPair @@ -1654,7 +1654,7 @@ open class Storage @Inject constructor( } } - override fun blockedContacts(): List { + override fun blockedContacts(): List { val allBlockedContacts = hashSetOf
() // Source data from config first @@ -1669,7 +1669,7 @@ open class Storage @Inject constructor( allBlockedContacts.addAll(recipientDatabase.blockedContacts) return allBlockedContacts.map { - recipientRepository.getRecipientSync(it) ?: RecipientV2.empty(it) + recipientRepository.getRecipientSync(it) ?: Recipient.empty(it) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index c947962883..974e8f9cd9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -43,7 +43,7 @@ import org.session.libsession.utilities.DistributionTypes; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.AccountId; import org.session.libsignal.utilities.IdPrefix; import org.session.libsignal.utilities.Log; @@ -63,10 +63,8 @@ import java.io.Closeable; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Set; import javax.inject.Inject; @@ -855,9 +853,9 @@ public ThreadRecord getCurrent() { int distributionType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.DISTRIBUTION_TYPE)); Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.ADDRESS))); - RecipientV2 recipient = recipientRepository.get().getRecipientSync(address); + Recipient recipient = recipientRepository.get().getRecipientSync(address); if (recipient == null) { - recipient = RecipientV2.Companion.empty(address); + recipient = Recipient.Companion.empty(address); } String body = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET)); long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.THREAD_CREATION_DATE)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java index 8a80db17a7..c1b56629b0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -20,7 +20,7 @@ import androidx.annotation.NonNull; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; @@ -34,7 +34,7 @@ public abstract class DisplayRecord { protected final long type; - private final RecipientV2 recipient; + private final Recipient recipient; private final long dateSent; private final long dateReceived; private final long threadId; @@ -43,9 +43,9 @@ public abstract class DisplayRecord { private final int deliveryReceiptCount; private final int readReceiptCount; - DisplayRecord(String body, RecipientV2 recipient, long dateSent, - long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, - long type, int readReceiptCount) + DisplayRecord(String body, Recipient recipient, long dateSent, + long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, + long type, int readReceiptCount) { this.threadId = threadId; this.recipient = recipient; @@ -62,7 +62,7 @@ public abstract class DisplayRecord { return body == null ? "" : body; } public abstract CharSequence getDisplayBody(@NonNull Context context); - public RecipientV2 getRecipient() { return recipient; } + public Recipient getRecipient() { return recipient; } public long getDateSent() { return dateSent; } public long getDateReceived() { return dateReceived; } public long getThreadId() { return threadId; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java index ff574c1f01..fdba0510e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java @@ -25,7 +25,7 @@ import org.session.libsession.utilities.Contact; import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.NetworkFailure; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.SmsDatabase.Status; import org.thoughtcrime.securesms.mms.SlideDeck; @@ -42,8 +42,8 @@ public class MediaMmsMessageRecord extends MmsMessageRecord { private final int partCount; - public MediaMmsMessageRecord(long id, RecipientV2 conversationRecipient, - RecipientV2 individualRecipient, int recipientDeviceId, + public MediaMmsMessageRecord(long id, Recipient conversationRecipient, + Recipient individualRecipient, int recipientDeviceId, long dateSent, long dateReceived, int deliveryReceiptCount, long threadId, String body, @NonNull SlideDeck slideDeck, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index 67287a4f47..24ae1a1248 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -36,7 +36,7 @@ import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.NetworkFailure; import org.session.libsession.utilities.ThemeUtil; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.AccountId; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; @@ -51,7 +51,7 @@ * */ public abstract class MessageRecord extends DisplayRecord { - private final RecipientV2 individualRecipient; + private final Recipient individualRecipient; private final List mismatches; private final List networkFailures; private final long expiresIn; @@ -74,14 +74,14 @@ public final MessageId getMessageId() { return new MessageId(getId(), isMms()); } - MessageRecord(long id, String body, RecipientV2 conversationRecipient, - RecipientV2 individualRecipient, - long dateSent, long dateReceived, long threadId, - int deliveryStatus, int deliveryReceiptCount, long type, - List mismatches, - List networkFailures, - long expiresIn, long expireStarted, - int readReceiptCount, List reactions, boolean hasMention) + MessageRecord(long id, String body, Recipient conversationRecipient, + Recipient individualRecipient, + long dateSent, long dateReceived, long threadId, + int deliveryStatus, int deliveryReceiptCount, long type, + List mismatches, + List networkFailures, + long expiresIn, long expireStarted, + int readReceiptCount, List reactions, boolean hasMention) { super(body, conversationRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount); @@ -101,7 +101,7 @@ public long getId() { public long getTimestamp() { return getDateSent(); } - public RecipientV2 getIndividualRecipient() { + public Recipient getIndividualRecipient() { return individualRecipient; } public long getType() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java index 57b7c9129d..c1cd3e6158 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java @@ -7,7 +7,7 @@ import org.session.libsession.utilities.Contact; import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.NetworkFailure; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; @@ -20,14 +20,14 @@ public abstract class MmsMessageRecord extends MessageRecord { private final @NonNull List contacts = new LinkedList<>(); private final @NonNull List linkPreviews = new LinkedList<>(); - MmsMessageRecord(long id, String body, RecipientV2 conversationRecipient, - RecipientV2 individualRecipient, long dateSent, - long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, - long type, List mismatches, - List networkFailures, long expiresIn, - long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount, - @Nullable Quote quote, @NonNull List contacts, - @NonNull List linkPreviews, List reactions, boolean hasMention) + MmsMessageRecord(long id, String body, Recipient conversationRecipient, + Recipient individualRecipient, long dateSent, + long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, + long type, List mismatches, + List networkFailures, long expiresIn, + long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount, + @Nullable Quote quote, @NonNull List contacts, + @NonNull List linkPreviews, List reactions, boolean hasMention) { super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, expiresIn, expireStarted, readReceiptCount, reactions, hasMention); this.slideDeck = slideDeck; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java index 470f2c4472..2432a475a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java @@ -21,7 +21,7 @@ import androidx.annotation.NonNull; import org.session.libsession.utilities.IdentityKeyMismatch; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import java.util.LinkedList; import java.util.List; @@ -35,14 +35,14 @@ public class SmsMessageRecord extends MessageRecord { public SmsMessageRecord(long id, - String body, RecipientV2 recipient, - RecipientV2 individualRecipient, - long dateSent, long dateReceived, - int deliveryReceiptCount, - long type, long threadId, - int status, List mismatches, - long expiresIn, long expireStarted, - int readReceiptCount, List reactions, boolean hasMention) + String body, Recipient recipient, + Recipient individualRecipient, + long dateSent, long dateReceived, + int deliveryReceiptCount, + long type, long threadId, + int status, List mismatches, + long expiresIn, long expireStarted, + int readReceiptCount, List reactions, boolean hasMention) { super(id, body, recipient, individualRecipient, dateSent, dateReceived, threadId, status, deliveryReceiptCount, type, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 4b751e5dae..a93caebabf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -34,7 +34,7 @@ import network.loki.messenger.R; import org.session.libsession.messaging.utilities.UpdateMessageData; import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.ui.UtilKt; @@ -65,7 +65,7 @@ public class ThreadRecord extends DisplayRecord { private final GroupThreadStatus groupThreadStatus; public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri, - @Nullable MessageRecord lastMessage, @NonNull RecipientV2 recipient, long date, long count, int unreadCount, + @Nullable MessageRecord lastMessage, @NonNull Recipient recipient, long date, long count, int unreadCount, int unreadMentionCount, long threadId, int deliveryReceiptCount, int status, long snippetType, int distributionType, boolean archived, long expiresIn, long lastSeen, int readReceiptCount, boolean pinned, String invitingAdminId, @@ -193,7 +193,7 @@ else if (isGroupUpdateMessage()) { * Logic to get the body for non control messages */ public CharSequence getNonControlMessageDisplayBody(@NonNull Context context) { - RecipientV2 recipient = getRecipient(); + Recipient recipient = getRecipient(); // The logic will differ depending on the type. // 1-1, note to self and control messages (we shouldn't have any in here, but leaving the // logic to be safe) do not need author details diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt index 9ea05e5d13..4161859b48 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt @@ -44,11 +44,10 @@ import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.model.BatchResponse import org.session.libsession.snode.utilities.await import org.session.libsession.utilities.Address -import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.recipients.BasicRecipient -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.toUserPic import org.session.libsession.utilities.waitUntilGroupConfigsPushed import org.session.libsignal.protos.SignalServiceProtos.DataMessage @@ -115,7 +114,7 @@ class GroupManagerV2Impl @Inject constructor( groupName: String, groupDescription: String, members: Set - ): RecipientV2 = withContext(dispatcher) { + ): Recipient = withContext(dispatcher) { val ourAccountId = requireNotNull(storage.getUserPublicKey()) { "Our account ID is not available" } val ourProfile = storage.getUserProfile() diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/SelectContactsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/SelectContactsViewModel.kt index 8f226e573d..d9f410b8ef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/SelectContactsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/SelectContactsViewModel.kt @@ -24,7 +24,7 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.dependencies.ConfigFactory @@ -91,7 +91,7 @@ open class SelectContactsViewModel @AssistedInject constructor( allContacts.filterNotTo(mutableSetOf()) { it in excludingAccountIDs } }.map { val address = Address.fromSerialized(it.hexString) - recipientRepository.getRecipient(address) ?: RecipientV2.empty(address) + recipientRepository.getRecipient(address) ?: Recipient.empty(address) } if(applyDefaultFiltering){ @@ -103,7 +103,7 @@ open class SelectContactsViewModel @AssistedInject constructor( private suspend fun filterContacts( - contacts: Collection, + contacts: Collection, query: String, selectedAccountIDs: Set ): List { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index ae9d38afe7..d25b764360 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -18,7 +18,7 @@ import javax.inject.Inject import network.loki.messenger.R import network.loki.messenger.databinding.ViewConversationBinding import org.session.libsession.utilities.ThemeUtil -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_ALL import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_NONE @@ -145,7 +145,7 @@ class ConversationView : LinearLayout { fun recycle() { binding.profilePictureView.recycle() } - private fun getTitle(recipient: RecipientV2): String = when { + private fun getTitle(recipient: Recipient): String = when { recipient.isLocalNumber -> context.getString(R.string.noteToSelf) else -> recipient.displayName // Internally uses the Contact API } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt index 8c7c0f965e..d0a4193e4a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt @@ -21,7 +21,7 @@ import network.loki.messenger.databinding.FragmentUserDetailsBottomSheetBinding import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.BasicRecipient -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.upsertContact import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.IdPrefix @@ -129,7 +129,7 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() { window.setDimAmount(0.6f) } - fun saveNickName(recipient: RecipientV2) = with(binding) { + fun saveNickName(recipient: Recipient) = with(binding) { nicknameEditText.clearFocus() hideSoftKeyboard() nameTextViewContainer.visibility = View.VISIBLE diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt index 637f7c2d90..5040e9b850 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt @@ -13,7 +13,7 @@ import network.loki.messenger.databinding.ViewGlobalSearchResultBinding import network.loki.messenger.databinding.ViewGlobalSearchSubheaderBinding import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.utilities.GroupRecord -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.search.model.MessageResult import org.thoughtcrime.securesms.ui.GetString @@ -184,7 +184,7 @@ class GlobalSearchAdapter( val recipients = groupRecord.members.map { MessagingModuleConfiguration.shared.recipientRepository.getRecipientSyncOrEmpty(it) } - recipients.joinToString(transform = RecipientV2::getSearchName) + recipients.joinToString(transform = Recipient::getSearchName) } else { null } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt index d7b5a7ab34..80883f2b8a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt @@ -10,7 +10,7 @@ import network.loki.messenger.R import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.ContentView import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.GroupConversation @@ -150,7 +150,7 @@ fun ContentView.bindModel(query: String?, model: Message, dateUtils: DateUtils) searchResultSubtitle.isVisible = true } -fun RecipientV2.getSearchName(): String = +fun Recipient.getSearchName(): String = displayName.takeIf { it.isNotEmpty() && !it.looksLikeAccountId } ?: address.toString().let(::truncateIdForDisplay) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java index fd3bc134a7..aa9214fadb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java @@ -19,14 +19,12 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.WindowManager; import com.bumptech.glide.Glide; import com.squareup.phrase.Phrase; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; -import org.session.libsignal.utilities.guava.Optional; import org.thoughtcrime.securesms.util.ViewUtilitiesKt; import dagger.hilt.android.AndroidEntryPoint; @@ -45,7 +43,7 @@ public class MediaPickerFolderFragment extends Fragment implements MediaPickerFo private Controller controller; private GridLayoutManager layoutManager; - public static @NonNull MediaPickerFolderFragment newInstance(@NonNull RecipientV2 recipient) { + public static @NonNull MediaPickerFolderFragment newInstance(@NonNull Recipient recipient) { Bundle args = new Bundle(); args.putString(KEY_RECIPIENT_NAME, recipient.getDisplayName()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt index 3429d624bd..4fa027e673 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt @@ -29,7 +29,7 @@ import org.session.libsession.utilities.MediaTypes import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.Util.isEmpty import org.session.libsession.utilities.concurrent.SimpleTask -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ScreenLockActionBarActivity import org.thoughtcrime.securesms.database.RecipientRepository @@ -50,7 +50,7 @@ import org.thoughtcrime.securesms.util.applySafeInsetsPaddings class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragment.Controller, MediaPickerItemFragment.Controller, MediaSendFragment.Controller, ImageEditorFragment.Controller, CameraXFragment.Controller{ - private var recipient: RecipientV2? = null + private var recipient: Recipient? = null private val viewModel: MediaSendViewModel by viewModels() private lateinit var binding: MediasendActivityBinding diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt index 1dd3076bea..ee2fd08ba6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt @@ -10,11 +10,9 @@ import network.loki.messenger.R import network.loki.messenger.databinding.ViewMessageRequestBinding import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.database.model.ThreadRecord -import com.bumptech.glide.RequestManager -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.util.DateUtils import java.util.Locale -import javax.inject.Inject class MessageRequestView : LinearLayout { private lateinit var binding: ViewMessageRequestBinding @@ -61,7 +59,7 @@ class MessageRequestView : LinearLayout { binding.profilePictureView.recycle() } - private fun getUserDisplayName(recipient: RecipientV2): String? { + private fun getUserDisplayName(recipient: Recipient): String? { return if (recipient.isLocalNumber) { context.getString(R.string.noteToSelf) } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java index 92c5beabcd..9fb99daf1d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java @@ -14,8 +14,7 @@ import org.session.libsession.utilities.NotificationPrivacyPreference; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.RecipientV2; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import network.loki.messenger.R; @@ -40,7 +39,7 @@ public AbstractNotificationBuilder(Context context, NotificationPrivacyPreferenc setLed(); } - protected CharSequence getStyledMessage(@NonNull RecipientV2 recipient, @Nullable CharSequence message) { + protected CharSequence getStyledMessage(@NonNull Recipient recipient, @Nullable CharSequence message) { SpannableStringBuilder builder = new SpannableStringBuilder(); builder.append(Util.getBoldedString(recipient.getDisplayName())); builder.append(": "); @@ -62,7 +61,7 @@ private void setLed() { setLights(ledColor, 500,2000); } - public void setTicker(@NonNull RecipientV2 recipient, @Nullable CharSequence message) { + public void setTicker(@NonNull Recipient recipient, @Nullable CharSequence message) { if (privacy.isDisplayMessage()) { setTicker(getStyledMessage(recipient, trimToDisplayLength(message))); } else if (privacy.isDisplayContact()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt index e93abfcc14..eb59657a83 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt @@ -45,7 +45,7 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.getRepea import org.session.libsession.utilities.TextSecurePreferences.Companion.hasHiddenMessageRequests import org.session.libsession.utilities.TextSecurePreferences.Companion.isNotificationsEnabled import org.session.libsession.utilities.TextSecurePreferences.Companion.removeHasHiddenMessageRequests -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix @@ -474,7 +474,7 @@ class DefaultMessageNotifier @Inject constructor( val conversationRecipient = record.recipient val threadId = record.threadId var body: CharSequence = record.getDisplayBody(context) - var threadRecipients: RecipientV2? = null + var threadRecipients: Recipient? = null var slideDeck: SlideDeck? = null val timestamp = record.timestamp var messageRequest = false diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.kt index 65527eb24e..726f19f726 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.kt @@ -13,7 +13,7 @@ import org.session.libsession.utilities.StringSubstitutionConstants.CONVERSATION import org.session.libsession.utilities.StringSubstitutionConstants.MESSAGE_COUNT_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.Util.getBoldedString -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.home.HomeActivity import org.thoughtcrime.securesms.ui.getSubbedString import java.util.LinkedList @@ -36,7 +36,7 @@ class MultipleRecipientNotificationBuilder(context: Context, privacy: Notificati setNumber(messageCount) } - fun setMostRecentSender(recipient: RecipientV2) { + fun setMostRecentSender(recipient: Recipient) { if (privacy.isDisplayContact) { val txt = Phrase.from(context, R.string.notificationsMostRecent) .put(NAME_KEY, recipient.displayName) @@ -59,7 +59,7 @@ class MultipleRecipientNotificationBuilder(context: Context, privacy: Notificati fun putStringExtra(key: String?, value: String?) { extras.putString(key, value) } - fun addMessageBody(sender: RecipientV2, body: CharSequence?) { + fun addMessageBody(sender: Recipient, body: CharSequence?) { if (privacy.isDisplayMessage) { val builder = SpannableStringBuilder() builder.append(getBoldedString(sender.displayName)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java index 15f9d3ef64..0c8b6cbd57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java @@ -14,7 +14,7 @@ import org.session.libsession.utilities.Address; import org.session.libsession.utilities.ServiceUtil; import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; import java.util.Arrays; @@ -74,7 +74,7 @@ public static synchronized void create(@NonNull Context context) { return sound == null ? Uri.EMPTY : sound; } - public static synchronized @Nullable Uri getMessageRingtone(@NonNull Context context, @NonNull RecipientV2 recipient) { + public static synchronized @Nullable Uri getMessageRingtone(@NonNull Context context, @NonNull Recipient recipient) { NotificationManager notificationManager = ServiceUtil.getNotificationManager(context); NotificationChannel channel = notificationManager.getNotificationChannel(recipient.getNotificationChannel()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java index ad8015dc23..8a4f608a39 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java @@ -10,7 +10,7 @@ import androidx.annotation.Nullable; import androidx.core.app.TaskStackBuilder; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import org.thoughtcrime.securesms.mms.SlideDeck; @@ -18,18 +18,18 @@ public class NotificationItem { private final long id; private final boolean mms; - private final @NonNull RecipientV2 conversationRecipient; - private final @NonNull RecipientV2 individualRecipient; - private final @Nullable RecipientV2 threadRecipient; + private final @NonNull Recipient conversationRecipient; + private final @NonNull Recipient individualRecipient; + private final @Nullable Recipient threadRecipient; private final long threadId; private final @Nullable CharSequence text; private final long timestamp; private final @Nullable SlideDeck slideDeck; public NotificationItem(long id, boolean mms, - @NonNull RecipientV2 individualRecipient, - @NonNull RecipientV2 conversationRecipient, - @Nullable RecipientV2 threadRecipient, + @NonNull Recipient individualRecipient, + @NonNull Recipient conversationRecipient, + @Nullable Recipient threadRecipient, long threadId, @Nullable CharSequence text, long timestamp, @Nullable SlideDeck slideDeck) { @@ -44,11 +44,11 @@ public NotificationItem(long id, boolean mms, this.slideDeck = slideDeck; } - public @NonNull RecipientV2 getRecipient() { + public @NonNull Recipient getRecipient() { return threadRecipient == null ? conversationRecipient : threadRecipient; } - public @NonNull RecipientV2 getIndividualRecipient() { + public @NonNull Recipient getIndividualRecipient() { return individualRecipient; } @@ -70,7 +70,7 @@ public long getThreadId() { public PendingIntent getPendingIntent(Context context) { Intent intent = new Intent(context, ConversationActivityV2.class); - RecipientV2 notifyRecipients = threadRecipient != null ? threadRecipient : conversationRecipient; + Recipient notifyRecipients = threadRecipient != null ? threadRecipient : conversationRecipient; if (notifyRecipients != null) intent.putExtra(ConversationActivityV2.ADDRESS, notifyRecipients.getAddress()); intent.putExtra(ConversationActivityV2.THREAD_ID, threadId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java index d3bafba247..fcd72e16fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java @@ -12,7 +12,7 @@ import java.util.LinkedList; import java.util.List; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; @@ -46,7 +46,7 @@ public void addNotification(NotificationItem item) { public @Nullable Uri getRingtone(@NonNull Context context) { if (!notifications.isEmpty()) { - RecipientV2 recipient = notifications.getFirst().getRecipient(); + Recipient recipient = notifications.getFirst().getRecipient(); return NotificationChannels.getMessageRingtone(context, recipient); } @@ -92,7 +92,7 @@ public PendingIntent getMarkAsReadIntent(Context context, int notificationId) { return PendingIntent.getBroadcast(context, 0, intent, intentFlags); } - public PendingIntent getRemoteReplyIntent(Context context, RecipientV2 recipient, ReplyMethod replyMethod) { + public PendingIntent getRemoteReplyIntent(Context context, Recipient recipient, ReplyMethod replyMethod) { if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications!"); Intent intent = new Intent(RemoteReplyReceiver.REPLY_ACTION); @@ -110,7 +110,7 @@ public PendingIntent getRemoteReplyIntent(Context context, RecipientV2 recipient return PendingIntent.getBroadcast(context, 0, intent, intentFlags); } - public PendingIntent getAndroidAutoReplyIntent(Context context, RecipientV2 recipient) { + public PendingIntent getAndroidAutoReplyIntent(Context context, Recipient recipient) { if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications!"); Intent intent = new Intent(AndroidAutoReplyReceiver.REPLY_ACTION); @@ -153,7 +153,7 @@ public PendingIntent getAndroidAutoHeardIntent(Context context, int notification return PendingIntent.getBroadcast(context, 0, intent, intentFlags); } - public PendingIntent getQuickReplyIntent(Context context, RecipientV2 recipient) { + public PendingIntent getQuickReplyIntent(Context context, Recipient recipient) { if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications! " + threads.size()); Intent intent = new Intent(context, ConversationActivityV2.class); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/ReplyMethod.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/ReplyMethod.java index 7416006f7b..76f48f65d1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/ReplyMethod.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/ReplyMethod.java @@ -4,14 +4,14 @@ import androidx.annotation.NonNull; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; public enum ReplyMethod { GroupMessage, SecureMessage; - public static @NonNull ReplyMethod forRecipient(Context context, RecipientV2 recipient) { + public static @NonNull ReplyMethod forRecipient(Context context, Recipient recipient) { if (recipient.isGroupOrCommunityRecipient()) { return ReplyMethod.GroupMessage; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index 0d05678ab2..fcd6de6446 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -27,12 +27,9 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.session.libsession.avatars.ContactPhoto; -import org.session.libsession.messaging.MessagingModuleConfiguration; -import org.session.libsession.messaging.contacts.Contact; -import org.session.libsession.utilities.Address; import org.session.libsession.utilities.NotificationPrivacyPreference; import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; import org.thoughtcrime.securesms.mms.Slide; @@ -72,7 +69,7 @@ public SingleRecipientNotificationBuilder( setCategory(NotificationCompat.CATEGORY_MESSAGE); } - public void setThread(@NonNull RecipientV2 recipient) { + public void setThread(@NonNull Recipient recipient) { String channelId = recipient.getNotificationChannel(); setChannelId(channelId != null ? channelId : NotificationChannels.getMessagesChannel(context)); @@ -112,8 +109,8 @@ public void setMessageCount(int messageCount) { setNumber(messageCount); } - public void setPrimaryMessageBody(@NonNull RecipientV2 threadRecipient, - @NonNull RecipientV2 individualRecipient, + public void setPrimaryMessageBody(@NonNull Recipient threadRecipient, + @NonNull Recipient individualRecipient, @NonNull CharSequence message, @Nullable SlideDeck slideDeck) { @@ -200,8 +197,8 @@ public void putStringExtra(String key, String value) { extras.putString(key,value); } - public void addMessageBody(@NonNull RecipientV2 threadRecipient, - @NonNull RecipientV2 individualRecipient, + public void addMessageBody(@NonNull Recipient threadRecipient, + @NonNull Recipient individualRecipient, @Nullable CharSequence messageBody) { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); @@ -318,7 +315,7 @@ private CharSequence getBigText(List messageBodies) { return content; } - private static Drawable getPlaceholderDrawable(AvatarUtils avatarUtils, RecipientV2 recipient) { + private static Drawable getPlaceholderDrawable(AvatarUtils avatarUtils, Recipient recipient) { String publicKey = recipient.getAddress().toString(); String displayName = recipient.getDisplayName(); return avatarUtils.generateTextBitmap(ICON_SIZE, publicKey, displayName); diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt index ae72514fdf..a69d1d5975 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt @@ -9,10 +9,10 @@ import androidx.recyclerview.widget.RecyclerView import network.loki.messenger.R import network.loki.messenger.databinding.BlockedContactLayoutBinding import com.bumptech.glide.Glide -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.util.adapter.SelectableItem -typealias SelectableRecipient = SelectableItem +typealias SelectableRecipient = SelectableItem class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdapter(RecipientDiffer()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt index 93c4ce6066..c7cbe674d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt @@ -22,7 +22,7 @@ import network.loki.messenger.R import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.database.StorageProtocol -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.util.adapter.SelectableItem import javax.inject.Inject @@ -68,7 +68,7 @@ class BlockedContactsViewModel @Inject constructor(private val storage: StorageP _state.value = state.copy(selectedItems = emptySet()) } - fun select(selectedItem: RecipientV2, isSelected: Boolean) { + fun select(selectedItem: Recipient, isSelected: Boolean) { _state.value = state.run { if (isSelected) copy(selectedItems = selectedItems + selectedItem) else copy(selectedItems = selectedItems - selectedItem) @@ -78,7 +78,7 @@ class BlockedContactsViewModel @Inject constructor(private val storage: StorageP fun getTitle(context: Context): String = context.getString(R.string.blockUnblock) // Method to get the appropriate text to display when unblocking 1, 2, or several contacts - fun getText(context: Context, contactsToUnblock: Set): CharSequence { + fun getText(context: Context, contactsToUnblock: Set): CharSequence { return when (contactsToUnblock.size) { // Note: We do not have to handle 0 because if no contacts are chosen then the unblock button is deactivated 1 -> Phrase.from(context, R.string.blockUnblockName) @@ -99,7 +99,7 @@ class BlockedContactsViewModel @Inject constructor(private val storage: StorageP fun getMessage(context: Context): String = context.getString(R.string.blockUnblock) - fun toggle(selectable: SelectableItem) { + fun toggle(selectable: SelectableItem) { _state.value = state.run { if (selectable.item in selectedItems) copy(selectedItems = selectedItems - selectable.item) else copy(selectedItems = selectedItems + selectable.item) @@ -107,8 +107,8 @@ class BlockedContactsViewModel @Inject constructor(private val storage: StorageP } data class BlockedContactsViewState( - val blockedContacts: List = emptyList(), - val selectedItems: Set = emptySet() + val blockedContacts: List = emptyList(), + val selectedItems: Set = emptySet() ) { val items = blockedContacts.map { SelectableItem(it, it in selectedItems) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionDetails.kt b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionDetails.kt index b8ede10641..fbca19fca0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionDetails.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionDetails.kt @@ -1,17 +1,17 @@ package org.thoughtcrime.securesms.reactions -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.database.model.MessageId /** * A UI model for a reaction in the [ReactionsDialogFragment] */ data class ReactionDetails( - val sender: RecipientV2, - val baseEmoji: String, - val displayEmoji: String, - val timestamp: Long, - val serverId: String, - val localId: MessageId, - val count: Int + val sender: Recipient, + val baseEmoji: String, + val displayEmoji: String, + val timestamp: Long, + val serverId: String, + val localId: MessageId, + val count: Int ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt index 71c707982a..19c4741494 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt @@ -5,7 +5,7 @@ import io.reactivex.ObservableEmitter import io.reactivex.schedulers.Schedulers import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.components.emoji.EmojiUtil import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.model.MessageId @@ -32,7 +32,7 @@ class ReactionsRepository @Inject constructor( return reactions.map { reaction -> val authorAddress = Address.fromSerialized(reaction.author) ReactionDetails( - sender = recipientRepository.getRecipientSync(authorAddress) ?: RecipientV2.empty(authorAddress), + sender = recipientRepository.getRecipientSync(authorAddress) ?: Recipient.empty(authorAddress), baseEmoji = EmojiUtil.getCanonicalRepresentation(reaction.emoji), displayEmoji = reaction.emoji, timestamp = reaction.dateReceived, diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index 9d98245492..08c84470b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -26,7 +26,7 @@ import org.session.libsession.snode.utilities.await import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.upsertContact import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.IdPrefix @@ -60,7 +60,7 @@ interface ConversationRepository { fun deleteMessages(messages: Set, threadId: Long) fun deleteAllLocalMessagesInThreadFromSenderOfMessage(messageRecord: MessageRecord) fun setApproved(recipient: Address, isApproved: Boolean) - fun isGroupReadOnly(recipient: RecipientV2): Boolean + fun isGroupReadOnly(recipient: Recipient): Boolean fun getLastSentMessageID(threadId: Long): Flow suspend fun deleteCommunityMessagesRemotely(threadId: Long, messages: Set) @@ -173,7 +173,7 @@ class DefaultConversationRepository @Inject constructor( } } - override fun isGroupReadOnly(recipient: RecipientV2): Boolean { + override fun isGroupReadOnly(recipient: Recipient): Boolean { // We only care about group v2 recipient if (!recipient.isGroupV2Recipient) { return false diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt index d30af29479..e110d6be15 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt @@ -9,7 +9,7 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.concurrent.SignalExecutors -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.contacts.ContactAccessor import org.thoughtcrime.securesms.database.CursorList @@ -216,8 +216,8 @@ class SearchRepository @Inject constructor( fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.CONVERSATION_ADDRESS))) val messageAddress = fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.MESSAGE_ADDRESS))) - val conversationRecipient = recipientRepository.getRecipientSync(conversationAddress) ?: RecipientV2.empty(conversationAddress) - val messageRecipient = recipientRepository.getRecipientSync(messageAddress) ?: RecipientV2.empty(messageAddress) + val conversationRecipient = recipientRepository.getRecipientSync(conversationAddress) ?: Recipient.empty(conversationAddress) + val messageRecipient = recipientRepository.getRecipientSync(messageAddress) ?: Recipient.empty(messageAddress) val body = cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.SNIPPET)) val sentMs = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_SENT)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java b/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java index b8ebcfa760..b3ea0a4ecc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java +++ b/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java @@ -2,7 +2,7 @@ import androidx.annotation.NonNull; -import org.session.libsession.utilities.recipients.RecipientV2; +import org.session.libsession.utilities.recipients.Recipient; import java.util.Objects; @@ -11,14 +11,14 @@ */ public class MessageResult { - public final RecipientV2 conversationRecipient; - public final RecipientV2 messageRecipient; + public final Recipient conversationRecipient; + public final Recipient messageRecipient; public final String bodySnippet; public final long threadId; public final long sentTimestampMs; - public MessageResult(@NonNull RecipientV2 conversationRecipient, - @NonNull RecipientV2 messageRecipient, + public MessageResult(@NonNull Recipient conversationRecipient, + @NonNull Recipient messageRecipient, @NonNull String bodySnippet, long threadId, long sentTimestampMs) diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/CallForegroundService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/CallForegroundService.kt index 8fd9c38dde..7a8b0e3e45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/CallForegroundService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/CallForegroundService.kt @@ -10,7 +10,7 @@ import androidx.core.app.ServiceCompat import androidx.core.content.IntentCompat import dagger.hilt.android.AndroidEntryPoint import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder @@ -35,7 +35,7 @@ class CallForegroundService : Service() { } } - private fun getRemoteRecipient(intent: Intent): RecipientV2? { + private fun getRemoteRecipient(intent: Intent): Recipient? { val remoteAddress = IntentCompat.getParcelableExtra(intent, EXTRA_RECIPIENT_ADDRESS, Address::class.java) ?: return null @@ -43,7 +43,7 @@ class CallForegroundService : Service() { return recipientRepository.getRecipientSync(remoteAddress) } - private fun startForeground(type: Int, recipient: RecipientV2?) { + private fun startForeground(type: Int, recipient: Recipient?) { if (CallNotificationBuilder.areNotificationsEnabled(this)) { try { ServiceCompat.startForeground( diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java index 34869a152e..f2f3b76ce7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java @@ -15,8 +15,7 @@ import androidx.annotation.NonNull; -import org.session.libsession.utilities.recipients.RecipientV2; -import org.session.libsession.utilities.recipients.RecipientV2Kt; +import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.ShareActivity; import org.thoughtcrime.securesms.database.RecipientRepository; @@ -55,7 +54,7 @@ public List onGetChooserTargets(ComponentName targetActivityName, ThreadRecord record; while ((record = reader.getNext()) != null && results.size() < 10) { - RecipientV2 recipient = record.getRecipient(); + Recipient recipient = record.getRecipient(); String name = recipient.getDisplayName(); Bitmap avatar; @@ -95,7 +94,7 @@ public List onGetChooserTargets(ComponentName targetActivityName, } } - private Bitmap getFallbackDrawable(@NonNull RecipientV2 recipient) { + private Bitmap getFallbackDrawable(@NonNull Recipient recipient) { //TODO: Use proper color return BitmapUtil.createFromDrawable(new ColorDrawable(Color.RED), getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width), diff --git a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt index 0d6c6d8d45..bccab47999 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt @@ -23,13 +23,10 @@ import org.session.libsession.snode.utilities.await import org.session.libsession.utilities.NonTranslatableStringConstants.SESSION_NETWORK_DATA_PRICE import org.session.libsession.utilities.NonTranslatableStringConstants.TOKEN_NAME_SHORT import org.session.libsession.utilities.NonTranslatableStringConstants.USD_NAME_SHORT -import org.session.libsession.utilities.StringSubstitutionConstants.DATE_KEY import org.session.libsession.utilities.StringSubstitutionConstants.DATE_TIME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.RELATIVE_TIME_KEY -import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Snode import org.thoughtcrime.securesms.dependencies.DatabaseComponent @@ -259,7 +256,7 @@ class TokenPageViewModel @Inject constructor( // Grab the database and reader details we need to count the conversations / groups val threadDatabase = DatabaseComponent.get(context).threadDatabase() val cursor = threadDatabase.approvedConversationList - val result = mutableSetOf() + val result = mutableSetOf() // Look through the database to build up our conversation & group counts (still on Dispatchers.IO not the main thread) threadDatabase.readerFor(cursor).use { reader -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt index 8d46bbef00..cc91c41b6b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt @@ -25,7 +25,7 @@ import org.session.libsession.avatars.ContactPhoto import org.session.libsession.avatars.ProfileContactPhoto import org.session.libsession.database.StorageProtocol import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.IdPrefix import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.RecipientRepository @@ -58,7 +58,7 @@ class AvatarUtils @Inject constructor( getUIDataFromRecipient(recipientRepository.getRecipient(Address.fromSerialized(accountId))) } - suspend fun getUIDataFromRecipient(recipient: RecipientV2?): AvatarUIData { + suspend fun getUIDataFromRecipient(recipient: Recipient?): AvatarUIData { if (recipient == null) { return AvatarUIData(elements = emptyList()) } @@ -123,7 +123,7 @@ class AvatarUtils @Inject constructor( } } - private fun getUIElementForRecipient(recipient: RecipientV2): AvatarUIElement { + private fun getUIElementForRecipient(recipient: Recipient): AvatarUIElement { // name val name = recipient.displayName diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ContactUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ContactUtilities.kt index e93905a399..c9b91d50a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ContactUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ContactUtilities.kt @@ -1,17 +1,17 @@ package org.thoughtcrime.securesms.util import android.content.Context -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.dependencies.DatabaseComponent typealias LastMessageSentTimestamp = Long object ContactUtilities { @JvmStatic - fun getAllContacts(context: Context): Set> { + fun getAllContacts(context: Context): Set> { val threadDatabase = DatabaseComponent.get(context).threadDatabase() val cursor = threadDatabase.conversationList - val result = mutableSetOf>() + val result = mutableSetOf>() threadDatabase.readerFor(cursor).use { reader -> while (reader.next != null) { val thread = reader.current diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt index 7ad58d1dee..07dab8e7f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SessionMetaProtocol.kt @@ -1,7 +1,7 @@ package org.thoughtcrime.securesms.util import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.messages.SignalServiceDataMessage object SessionMetaProtocol { @@ -33,7 +33,7 @@ object SessionMetaProtocol { } @JvmStatic - fun canUserReplyToNotification(recipient: RecipientV2): Boolean { + fun canUserReplyToNotification(recipient: Recipient): Boolean { // TODO return !recipient.address.isRSSFeed return true } @@ -49,7 +49,7 @@ object SessionMetaProtocol { @JvmStatic - fun shouldSendTypingIndicator(recipient: RecipientV2): Boolean { + fun shouldSendTypingIndicator(recipient: Recipient): Boolean { return !recipient.isGroupOrCommunityRecipient && recipient.approved && !recipient.blocked } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.kt index 68ade010e5..10d7e37076 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.kt @@ -5,7 +5,6 @@ import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.Intent -import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.app.NotificationCompat @@ -13,7 +12,7 @@ import androidx.core.app.NotificationManagerCompat import com.squareup.phrase.Phrase import network.loki.messenger.R import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY -import org.session.libsession.utilities.recipients.RecipientV2 +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.notifications.NotificationChannels import org.thoughtcrime.securesms.webrtc.WebRtcCallBridge.Companion.ACTION_DENY_CALL import org.thoughtcrime.securesms.webrtc.WebRtcCallBridge.Companion.ACTION_IGNORE_CALL @@ -36,7 +35,7 @@ class CallNotificationBuilder { } @JvmStatic - fun getCallInProgressNotification(context: Context, type: Int, recipient: RecipientV2?): Notification { + fun getCallInProgressNotification(context: Context, type: Int, recipient: Recipient?): Notification { val contentIntent = WebRtcCallActivity.getCallActivityIntent(context) val pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) From 17612d1f56794e5ecae2760390484a961e5e068f Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:50:41 +1000 Subject: [PATCH 16/52] Profile pic refactoring WIP --- .../libsession/avatars/ContactPhoto.java | 21 --- .../avatars/GroupRecordContactPhoto.java | 73 -------- .../avatars/ProfileContactPhoto.java | 61 ------- .../messaging/file_server/FileServerApi.kt | 14 ++ .../libsession/messaging/jobs/JobQueue.kt | 2 - .../jobs/RetrieveProfileAvatarJob.kt | 144 ---------------- .../utilities/recipients/Recipient.kt | 65 +++---- .../components/ProfilePictureView.kt | 20 +-- .../securesms/database/GroupDatabase.java | 1 - .../securesms/database/RecipientRepository.kt | 15 +- .../glide/CommunityFileDownloadWorker.kt | 39 +++++ .../securesms/glide/ContactPhotoFetcher.java | 59 ------- .../securesms/glide/ContactPhotoLoader.java | 50 ------ .../glide/EncryptedFileDownloadWorker.kt | 157 +++++++++++++++++ .../glide/RecipientAvatarDownloadManager.kt | 162 ++++++++++++++++++ .../securesms/glide/RemoteFileLoader.kt | 128 ++++++++++++++ .../securesms/mms/SignalGlideModule.java | 6 +- .../SingleRecipientNotificationBuilder.java | 7 +- .../securesms/service/DirectShareService.java | 4 +- .../securesms/ui/components/Avatar.kt | 10 +- .../securesms/util/AvatarUtils.kt | 13 +- 21 files changed, 549 insertions(+), 502 deletions(-) delete mode 100644 app/src/main/java/org/session/libsession/avatars/ContactPhoto.java delete mode 100644 app/src/main/java/org/session/libsession/avatars/GroupRecordContactPhoto.java delete mode 100644 app/src/main/java/org/session/libsession/avatars/ProfileContactPhoto.java delete mode 100644 app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/glide/CommunityFileDownloadWorker.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/glide/ContactPhotoFetcher.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/glide/ContactPhotoLoader.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/glide/EncryptedFileDownloadWorker.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/glide/RecipientAvatarDownloadManager.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileLoader.kt diff --git a/app/src/main/java/org/session/libsession/avatars/ContactPhoto.java b/app/src/main/java/org/session/libsession/avatars/ContactPhoto.java deleted file mode 100644 index 0d256fd38c..0000000000 --- a/app/src/main/java/org/session/libsession/avatars/ContactPhoto.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.session.libsession.avatars; - -import android.content.Context; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.bumptech.glide.load.Key; - -import java.io.IOException; -import java.io.InputStream; - -public interface ContactPhoto extends Key { - - InputStream openInputStream(Context context) throws IOException; - - @Nullable Uri getUri(@NonNull Context context); - - boolean isProfilePhoto(); -} diff --git a/app/src/main/java/org/session/libsession/avatars/GroupRecordContactPhoto.java b/app/src/main/java/org/session/libsession/avatars/GroupRecordContactPhoto.java deleted file mode 100644 index e033b55f23..0000000000 --- a/app/src/main/java/org/session/libsession/avatars/GroupRecordContactPhoto.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.session.libsession.avatars; - -import android.content.Context; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.session.libsession.database.StorageProtocol; -import org.session.libsession.messaging.MessagingModuleConfiguration; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.Conversions; -import org.session.libsession.utilities.GroupRecord; -import org.session.libsignal.utilities.guava.Optional; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.MessageDigest; - -public class GroupRecordContactPhoto implements ContactPhoto { - - private final @NonNull - Address address; - private final long avatarId; - - public GroupRecordContactPhoto(@NonNull Address address, long avatarId) { - this.address = address; - this.avatarId = avatarId; - } - - @Override - public InputStream openInputStream(Context context) throws IOException { - StorageProtocol groupDatabase = MessagingModuleConfiguration.getShared().getStorage(); - Optional groupRecord = Optional.of(groupDatabase.getGroup(address.toGroupString())); - - if (groupRecord.isPresent() && groupRecord.get().getAvatar() != null) { - return new ByteArrayInputStream(groupRecord.get().getAvatar()); - } - - throw new IOException("Couldn't load avatar for group: " + address.toGroupString()); - } - - @Override - public @Nullable Uri getUri(@NonNull Context context) { - return null; - } - - @Override - public boolean isProfilePhoto() { - return false; - } - - @Override - public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { - messageDigest.update(address.toString().getBytes()); - messageDigest.update(Conversions.longToByteArray(avatarId)); - } - - @Override - public boolean equals(Object other) { - if (other == null || !(other instanceof GroupRecordContactPhoto)) return false; - - GroupRecordContactPhoto that = (GroupRecordContactPhoto)other; - return this.address.equals(that.address) && this.avatarId == that.avatarId; - } - - @Override - public int hashCode() { - return this.address.hashCode() ^ (int) avatarId; - } - -} diff --git a/app/src/main/java/org/session/libsession/avatars/ProfileContactPhoto.java b/app/src/main/java/org/session/libsession/avatars/ProfileContactPhoto.java deleted file mode 100644 index 86669f9d41..0000000000 --- a/app/src/main/java/org/session/libsession/avatars/ProfileContactPhoto.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.session.libsession.avatars; - -import android.content.Context; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.session.libsession.utilities.Address; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.security.MessageDigest; - -public class ProfileContactPhoto implements ContactPhoto { - - private final @NonNull - Address address; - public final @NonNull String avatarObject; - - public ProfileContactPhoto(@NonNull Address address, @NonNull String avatarObject) { - this.address = address; - this.avatarObject = avatarObject; - } - - @Override - public InputStream openInputStream(Context context) throws FileNotFoundException { - return AvatarHelper.getInputStreamFor(context, address); - } - - @Override - public @Nullable Uri getUri(@NonNull Context context) { - return Uri.fromFile(AvatarHelper.getAvatarFile(context, address)); - } - - @Override - public boolean isProfilePhoto() { - return true; - } - - @Override - public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { - messageDigest.update(address.toString().getBytes()); - messageDigest.update(avatarObject.getBytes()); - } - - @Override - public boolean equals(Object other) { - if (other == null || !(other instanceof ProfileContactPhoto)) return false; - - ProfileContactPhoto that = (ProfileContactPhoto)other; - - return this.address.equals(that.address) && this.avatarObject.equals(that.avatarObject); - } - - @Override - public int hashCode() { - return address.hashCode() ^ avatarObject.hashCode(); - } -} diff --git a/app/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt b/app/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt index 7e9aa23b58..c261533005 100644 --- a/app/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt +++ b/app/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt @@ -18,6 +18,7 @@ import org.session.libsignal.utilities.HTTP import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.toHexString +import java.util.regex.Pattern import kotlin.time.Duration.Companion.milliseconds object FileServerApi { @@ -28,6 +29,10 @@ object FileServerApi { val fileServerUrl: HttpUrl by lazy { FILE_SERVER_URL.toHttpUrl() } + val FILE_SERVER_FILE_URL_PATTERN: Pattern by lazy { + Pattern.compile("^https?://filev2\\.getsession\\.org/file/([a-zA-Z0-9]+)$", Pattern.CASE_INSENSITIVE) + } + sealed class Error(message: String) : Exception(message) { object ParsingFailed : Error("Invalid response.") object InvalidURL : Error("Invalid URL.") @@ -48,6 +53,15 @@ object FileServerApi { val useOnionRouting: Boolean = true ) + fun getFileIdFromUrl(url: String): String? { + val matcher = FILE_SERVER_FILE_URL_PATTERN.matcher(url) + return if (matcher.matches()) { + matcher.group(1) + } else { + null + } + } + private fun createBody(body: ByteArray?, parameters: Any?): RequestBody? { if (body != null) return RequestBody.create("application/octet-stream".toMediaType(), body) if (parameters == null) return null diff --git a/app/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/app/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt index 188f7cfaed..67f3b8bc4e 100644 --- a/app/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt +++ b/app/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt @@ -128,7 +128,6 @@ class JobQueue : JobDelegate { is MessageSendJob -> { txQueue.send(job) } - is RetrieveProfileAvatarJob, is AttachmentDownloadJob -> { mediaQueue.send(job) } @@ -233,7 +232,6 @@ class JobQueue : JobDelegate { GroupAvatarDownloadJob.KEY, BackgroundGroupAddJob.KEY, OpenGroupDeleteJob.KEY, - RetrieveProfileAvatarJob.KEY, InviteContactsJob.KEY, ) allJobTypes.forEach { type -> diff --git a/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt b/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt deleted file mode 100644 index d0542e6fc1..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt +++ /dev/null @@ -1,144 +0,0 @@ -package org.session.libsession.messaging.jobs - -import org.session.libsession.avatars.AvatarHelper -import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.utilities.Data -import org.session.libsession.utilities.AESGCM -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.DownloadUtilities.downloadFromFileServer -import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfileAvatarId -import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfilePictureURL -import org.session.libsession.utilities.Util.equals -import org.session.libsession.utilities.recipients.Recipient -import org.session.libsignal.exceptions.NonRetryableException -import org.session.libsignal.utilities.HTTP -import org.session.libsignal.utilities.Log -import org.session.libsignal.utilities.Util.SECURE_RANDOM -import java.io.FileOutputStream -import java.util.concurrent.ConcurrentSkipListSet - -class RetrieveProfileAvatarJob( - private val profileAvatar: String?, val recipientAddress: Address, - private val profileKey: ByteArray? -): Job { - override var delegate: JobDelegate? = null - override var id: String? = null - override var failureCount: Int = 0 - override val maxFailureCount: Int = 3 - - companion object { - val TAG = RetrieveProfileAvatarJob::class.simpleName - val KEY: String = "RetrieveProfileAvatarJob" - - // Keys used for database storage - private const val PROFILE_AVATAR_KEY = "profileAvatar" - private const val RECEIPIENT_ADDRESS_KEY = "recipient" - private const val PROFILE_KEY = "profileKey" - - val errorUrls = ConcurrentSkipListSet() - - } - - override suspend fun execute(dispatcherName: String) { - val delegate = delegate ?: return Log.w(TAG, "RetrieveProfileAvatarJob has no delegate method to work with!") - if (profileAvatar != null && profileAvatar in errorUrls) return delegate.handleJobFailed(this, dispatcherName, Exception("Profile URL 404'd this app instance")) - val context = MessagingModuleConfiguration.shared.context - val storage = MessagingModuleConfiguration.shared.storage - val recipient = MessagingModuleConfiguration.shared.recipientRepository.getRecipient(recipientAddress) - ?: Recipient.empty(recipientAddress) - - if (profileKey == null || (profileKey.size != 32 && profileKey.size != 16)) { - return delegate.handleJobFailedPermanently(this, dispatcherName, Exception("Recipient profile key is gone!")) - } - - // Commit '78d1e9d' (fix: open group threads and avatar downloads) had this commented out so - // it's now limited to just the current user case - if ( - recipient.isLocalNumber && - AvatarHelper.avatarFileExists(context, recipientAddress) && - equals(profileAvatar, recipient.profileAvatar) - ) { - Log.w(TAG, "Already retrieved profile avatar: $profileAvatar") - return - } - - if (profileAvatar.isNullOrEmpty()) { - Log.w(TAG, "Removing profile avatar for: $recipientAddress" ) - - if (recipient.isLocalNumber) { - setProfileAvatarId(context, SECURE_RANDOM.nextInt()) - setProfilePictureURL(context, null) - } - - AvatarHelper.delete(context, recipientAddress) -// storage.setProfilePicture(recipientAddress, null, null) - return - } - - - try { - val downloaded = downloadFromFileServer(profileAvatar) - val decrypted = AESGCM.decrypt( - downloaded.data, - offset = downloaded.offset, - len = downloaded.len, - symmetricKey = profileKey - ) - - FileOutputStream(AvatarHelper.getAvatarFile(context, recipientAddress)).use { out -> - out.write(decrypted) - } - - if (recipient.isLocalNumber) { - setProfileAvatarId(context, SECURE_RANDOM.nextInt()) - setProfilePictureURL(context, profileAvatar) - } - -// storage.setProfilePicture(recipientAddress, profileAvatar, profileKey) - } - catch (e: NonRetryableException){ - Log.e("Loki", "Failed to download profile avatar from non-retryable error", e) - errorUrls += profileAvatar - return delegate.handleJobFailedPermanently(this, dispatcherName, e) - } - catch (e: Exception) { - if(e is HTTP.HTTPRequestFailedException && e.statusCode == 404){ - Log.e("Loki", "Failed to download profile avatar from non-retryable error", e) - errorUrls += profileAvatar - return delegate.handleJobFailedPermanently(this, dispatcherName, e) - } else { - Log.e("Loki", "Failed to download profile avatar", e) - if (failureCount + 1 >= maxFailureCount) { - errorUrls += profileAvatar - } - return delegate.handleJobFailed(this, dispatcherName, e) - } - } - return delegate.handleJobSucceeded(this, dispatcherName) - } - - override fun serialize(): Data { - val data = Data.Builder() - .putString(PROFILE_AVATAR_KEY, profileAvatar) - .putString(RECEIPIENT_ADDRESS_KEY, recipientAddress.toString()) - - if (profileKey != null) { - data.putByteArray(PROFILE_KEY, profileKey) - } - - return data.build() - } - - override fun getFactoryKey(): String { - return KEY - } - - class Factory: Job.Factory { - override fun create(data: Data): RetrieveProfileAvatarJob { - val profileAvatar = if (data.hasString(PROFILE_AVATAR_KEY)) { data.getString(PROFILE_AVATAR_KEY) } else { null } - val recipientAddress = Address.fromSerialized(data.getString(RECEIPIENT_ADDRESS_KEY)) - val profileKey = data.getByteArray(PROFILE_KEY) - return RetrieveProfileAvatarJob(profileAvatar, recipientAddress, profileKey) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt index 3a46dc1ebc..4f6585b251 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt @@ -3,8 +3,6 @@ package org.session.libsession.utilities.recipients import network.loki.messenger.libsession_util.util.Bytes import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.UserPic -import org.session.libsession.avatars.ContactPhoto -import org.session.libsession.avatars.ProfileContactPhoto import org.session.libsession.utilities.Address import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.database.RecipientDatabase @@ -22,7 +20,7 @@ data class Recipient( ) { val isLocalNumber: Boolean get() = basic.isLocalNumber val address: Address get() = basic.address - val avatar: RecipientAvatar? get() = basic.avatar + val avatar: RemoteFile? get() = basic.avatar val isGroupOrCommunityRecipient: Boolean get() = basic.isGroupOrCommunityRecipient val isCommunityRecipient: Boolean get() = basic.isCommunityRecipient @@ -65,24 +63,7 @@ data class Recipient( val mutedUntilMills: Long? get() = mutedUntil?.toInstant()?.toEpochMilli() - - @Deprecated("use avatar instead") - //TODO: Not working - val contactPhoto: ContactPhoto? - get() = when (val a = avatar) { - is RecipientAvatar.EncryptedRemotePic -> ProfileContactPhoto(address, a.url) - is RecipientAvatar.Inline -> null - else -> null - } - - @Deprecated("Use `avatar` property instead", ReplaceWith("avatar")) - val profileAvatar: String? - get() = (avatar as? RecipientAvatar.EncryptedRemotePic)?.url - - @Deprecated("Use `avatar` property instead", ReplaceWith("avatar?.toUserPic()")) - val profileKey: ByteArray? - get() = (avatar as? RecipientAvatar.EncryptedRemotePic)?.key?.data - + companion object { fun empty(address: Address): Recipient { return Recipient( @@ -100,7 +81,7 @@ sealed interface BasicRecipient { val address: Address val isLocalNumber: Boolean val displayName: String - val avatar: RecipientAvatar? + val avatar: RemoteFile? /** * A recipient that is backed by the config system. @@ -110,7 +91,7 @@ sealed interface BasicRecipient { data class Generic( override val address: Address, override val displayName: String = "", - override val avatar: RecipientAvatar? = null, + override val avatar: RemoteFile? = null, override val isLocalNumber: Boolean = false, val blocked: Boolean = false, ) : BasicRecipient @@ -121,7 +102,7 @@ sealed interface BasicRecipient { data class Self( val name: String, override val address: Address, - override val avatar: RecipientAvatar.EncryptedRemotePic?, + override val avatar: RemoteFile.Encrypted?, val expiryMode: ExpiryMode, val acceptsCommunityMessageRequests: Boolean, ) : ConfigBasedRecipient { @@ -139,7 +120,7 @@ sealed interface BasicRecipient { override val address: Address, val name: String, val nickname: String?, - override val avatar: RecipientAvatar.EncryptedRemotePic?, + override val avatar: RemoteFile.Encrypted?, val approved: Boolean, val approvedMe: Boolean, val blocked: Boolean, @@ -158,7 +139,7 @@ sealed interface BasicRecipient { data class Group( override val address: Address, val name: String, - override val avatar: RecipientAvatar.EncryptedRemotePic?, + override val avatar: RemoteFile.Encrypted?, val expiryMode: ExpiryMode, val approved: Boolean, ) : ConfigBasedRecipient { @@ -181,36 +162,30 @@ val BasicRecipient.is1on1: Boolean get() = !isLocalNumber && address.isContact val BasicRecipient.isGroupRecipient: Boolean get() = address.isGroup -sealed interface RecipientAvatar { - data class EncryptedRemotePic(val url: String, val key: Bytes) : RecipientAvatar - data class Inline(val bytes: Bytes) : RecipientAvatar - +/** + * Represents a remote file that can be downloaded. + */ +sealed interface RemoteFile { + data class Encrypted(val url: String, val key: Bytes) : RemoteFile + data class Community(val communityServerBaseUrl: String, val fileId: Long) : RemoteFile companion object { - fun UserPic.toRecipientAvatar(): RecipientAvatar.EncryptedRemotePic? { + fun UserPic.toRecipientAvatar(): Encrypted? { return when { url.isBlank() -> null - else -> EncryptedRemotePic( + else -> Encrypted( url = url, key = key ) } } - fun from(url: String, bytes: ByteArray?): RecipientAvatar? { + fun from(url: String, bytes: ByteArray?): RemoteFile? { return if (url.isNotBlank() && bytes != null && bytes.isNotEmpty()) { - EncryptedRemotePic(url, Bytes(bytes)) + Encrypted(url, Bytes(bytes)) } else { null } } - - fun fromBytes(bytes: ByteArray?): RecipientAvatar? { - return if (bytes == null || bytes.isEmpty()) { - null - } else { - Inline(Bytes(bytes)) - } - } } } @@ -239,10 +214,10 @@ class RecipientSettings( } } -fun RecipientAvatar.toUserPic(): UserPic? { +fun RemoteFile.toUserPic(): UserPic? { return when (this) { - is RecipientAvatar.EncryptedRemotePic -> UserPic(url, key) - is RecipientAvatar.Inline -> null + is RemoteFile.Encrypted -> UserPic(url, key) + is RemoteFile.Community -> null } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index 2ae3d18a8b..5118c3efb3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -14,15 +14,13 @@ import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ViewProfilePictureBinding import org.session.libsession.avatars.ContactColors -import org.session.libsession.avatars.ContactPhoto import org.session.libsession.avatars.PlaceholderAvatarPhoto -import org.session.libsession.avatars.ProfileContactPhoto import org.session.libsession.avatars.ResourceContactPhoto import org.session.libsession.database.StorageProtocol import org.session.libsession.utilities.Address import org.session.libsession.utilities.AppTextSecurePreferences import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.recipients.RecipientAvatar +import org.session.libsession.utilities.recipients.RemoteFile import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.truncateIdForDisplay import org.session.libsignal.utilities.Log @@ -61,7 +59,6 @@ class ProfilePictureView @JvmOverloads constructor( @Inject lateinit var recipientRepository: RecipientRepository - private val profilePicturesCache = mutableMapOf() private val resourcePadding by lazy { context.resources.getDimensionPixelSize(R.dimen.normal_padding).toFloat() } @@ -86,7 +83,7 @@ class ProfilePictureView @JvmOverloads constructor( address = address, profileViewDataType = when { isGroupV2Recipient -> ProfileViewDataType.GroupvV2( - customGroupImage = (avatar as? RecipientAvatar.EncryptedRemotePic)?.url + customGroupImage = (avatar as? RemoteFile.Encrypted)?.url ) isLegacyGroupRecipient -> ProfileViewDataType.LegacyGroup isCommunityRecipient -> ProfileViewDataType.Community @@ -194,12 +191,6 @@ class ProfilePictureView @JvmOverloads constructor( this.recipient!! } - if (profilePicturesCache[imageView] == recipient) return - // recipient is mutable so without cloning it the line above always returns true as the changes to the underlying recipient happens on both shared instances - profilePicturesCache[imageView] = recipient - val signalProfilePicture: ContactPhoto? = null - val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject - glide.clear(imageView) val placeholder = PlaceholderAvatarPhoto( @@ -208,9 +199,11 @@ class ProfilePictureView @JvmOverloads constructor( avatarUtils.generateTextBitmap(128, publicKey, displayName) ) - if (signalProfilePicture != null && avatar != "0" && avatar != "") { + val avatar = recipient.avatar + + if (avatar != null) { val maxSizePx = context.resources.getDimensionPixelSize(R.dimen.medium_profile_picture_size) - glide.load(signalProfilePicture) + glide.load(avatar) .avatarOptions(maxSizePx) .placeholder(createUnknownRecipientDrawable()) .error(glide.load(placeholder)) @@ -238,7 +231,6 @@ class ProfilePictureView @JvmOverloads constructor( } fun recycle() { - profilePicturesCache.clear() } // endregion diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index 60f5cd6abd..0dfb98e028 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -44,7 +44,6 @@ * to query config system directly. The Storage class may also be more up-to-date. * */ -@Deprecated public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProtocol { @SuppressWarnings("unused") diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index b89c9aba43..281f2e5508 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -24,8 +24,8 @@ import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.recipients.BasicRecipient -import org.session.libsession.utilities.recipients.RecipientAvatar -import org.session.libsession.utilities.recipients.RecipientAvatar.Companion.toRecipientAvatar +import org.session.libsession.utilities.recipients.RemoteFile +import org.session.libsession.utilities.recipients.RemoteFile.Companion.toRecipientAvatar import org.session.libsession.utilities.recipients.RecipientSettings import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.displayNameOrFallback @@ -359,7 +359,14 @@ class RecipientRepository @Inject constructor( basic = BasicRecipient.Generic( address = address, displayName = group.title, - avatar = group.avatar?.let { RecipientAvatar.fromBytes(it) } + avatar = if (group.url != null && group.avatarId != null) { + RemoteFile.Community( + communityServerBaseUrl = group.url, + fileId = group.avatarId + ) + } else { + null + } ), mutedUntil = settings?.muteUntilDate, autoDownloadAttachments = settings?.autoDownloadAttachments, @@ -377,7 +384,7 @@ class RecipientRepository @Inject constructor( basic = BasicRecipient.Generic( address = address, displayName = settings.systemDisplayName?.takeIf { it.isNotBlank() } ?: settings.profileName.orEmpty(), - avatar = settings.profileAvatar?.let { RecipientAvatar.from(it, settings.profileKey) }, + avatar = settings.profileAvatar?.let { RemoteFile.from(it, settings.profileKey) }, isLocalNumber = false, blocked = settings.blocked ), diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/CommunityFileDownloadWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/CommunityFileDownloadWorker.kt new file mode 100644 index 0000000000..ce7a69dfa2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/CommunityFileDownloadWorker.kt @@ -0,0 +1,39 @@ +package org.thoughtcrime.securesms.glide + +import android.content.Context +import androidx.hilt.work.HiltWorker +import androidx.work.CoroutineWorker +import androidx.work.Operation +import androidx.work.WorkerParameters +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import org.session.libsession.utilities.recipients.RemoteFile + +@HiltWorker +class CommunityFileDownloadWorker @AssistedInject constructor( + @Assisted val context: Context, + @Assisted val params: WorkerParameters +) : CoroutineWorker(context, params) { + override suspend fun doWork(): Result { + TODO("Not yet implemented") + } + + companion object { + fun getFileForUrl( + context: Context, + folderName: String, + avatar: RemoteFile.Community, + ): EncryptedFileDownloadWorker.DownloadedFiles { + TODO("Implement logic to get file for URL") + } + + fun enqueueIfNeeded(context: Context, + avatar: RemoteFile.Community): Operation? { + TODO("Implement enqueue logic for community server download worker") + } + + fun cancel(context: Context, avatar: RemoteFile.Community): Operation? { + TODO("Implement cancel logic for community server download worker") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/ContactPhotoFetcher.java b/app/src/main/java/org/thoughtcrime/securesms/glide/ContactPhotoFetcher.java deleted file mode 100644 index 6ab528b785..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/glide/ContactPhotoFetcher.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.thoughtcrime.securesms.glide; - - -import android.content.Context; -import androidx.annotation.NonNull; - -import com.bumptech.glide.Priority; -import com.bumptech.glide.load.DataSource; -import com.bumptech.glide.load.data.DataFetcher; - -import org.session.libsession.avatars.ContactPhoto; - -import java.io.IOException; -import java.io.InputStream; - -class ContactPhotoFetcher implements DataFetcher { - - private final Context context; - private final ContactPhoto contactPhoto; - - private InputStream inputStream; - - ContactPhotoFetcher(@NonNull Context context, @NonNull ContactPhoto contactPhoto) { - this.context = context.getApplicationContext(); - this.contactPhoto = contactPhoto; - } - - @Override - public void loadData(@NonNull Priority priority, @NonNull DataCallback callback) { - try { - inputStream = contactPhoto.openInputStream(context); - callback.onDataReady(inputStream); - } catch (IOException e) { - callback.onLoadFailed(e); - } - } - - @Override - public void cleanup() { - try { - if (inputStream != null) inputStream.close(); - } catch (IOException e) {} - } - - @Override - public void cancel() { - - } - - @Override - public @NonNull Class getDataClass() { - return InputStream.class; - } - - @Override - public @NonNull DataSource getDataSource() { - return DataSource.LOCAL; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/ContactPhotoLoader.java b/app/src/main/java/org/thoughtcrime/securesms/glide/ContactPhotoLoader.java deleted file mode 100644 index 61a89c0bd5..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/glide/ContactPhotoLoader.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.thoughtcrime.securesms.glide; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.bumptech.glide.load.Options; -import com.bumptech.glide.load.model.ModelLoader; -import com.bumptech.glide.load.model.ModelLoaderFactory; -import com.bumptech.glide.load.model.MultiModelLoaderFactory; - -import org.session.libsession.avatars.ContactPhoto; - -import java.io.InputStream; - -public class ContactPhotoLoader implements ModelLoader { - - private final Context context; - - private ContactPhotoLoader(Context context) { - this.context = context; - } - - @Override - public @Nullable LoadData buildLoadData(@NonNull ContactPhoto contactPhoto, int width, int height, @NonNull Options options) { - return new LoadData<>(contactPhoto, new ContactPhotoFetcher(context, contactPhoto)); - } - - @Override - public boolean handles(@NonNull ContactPhoto contactPhoto) { - return true; - } - - public static class Factory implements ModelLoaderFactory { - - private final Context context; - - public Factory(Context context) { - this.context = context.getApplicationContext(); - } - - @Override - public @NonNull ModelLoader build(@NonNull MultiModelLoaderFactory multiFactory) { - return new ContactPhotoLoader(context); - } - - @Override - public void teardown() {} - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/EncryptedFileDownloadWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/EncryptedFileDownloadWorker.kt new file mode 100644 index 0000000000..e6da98c34b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/EncryptedFileDownloadWorker.kt @@ -0,0 +1,157 @@ +package org.thoughtcrime.securesms.glide + +import android.content.Context +import androidx.hilt.work.HiltWorker +import androidx.work.BackoffPolicy +import androidx.work.Constraints +import androidx.work.CoroutineWorker +import androidx.work.Data +import androidx.work.ExistingWorkPolicy +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.Operation +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.session.libsession.messaging.file_server.FileServerApi +import org.session.libsession.snode.utilities.await +import org.session.libsignal.exceptions.NonRetryableException +import org.session.libsignal.utilities.ByteArraySlice.Companion.write +import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.toHexString +import java.io.File +import java.io.FileOutputStream +import java.security.MessageDigest +import java.time.Duration + +/** + * A worker that downloads files from Session's file server. + */ +@HiltWorker +class EncryptedFileDownloadWorker @AssistedInject constructor( + @Assisted val context: Context, + @Assisted val params: WorkerParameters +) : CoroutineWorker(context, params) { + override suspend fun doWork(): Result = withContext(Dispatchers.Default) { + val fileId = requireNotNull(inputData.getString(ARG_FILE_ID)) { + "EncryptedFileDownloadWorker requires a URL to download" + } + + val folderName = requireNotNull(inputData.getString(ARG_FOLDER)) { + "EncryptedFileDownloadWorker requires a cache folder name" + } + + val files = getFileForUrl(applicationContext, folderName, fileId) + + if (files.completedFile.exists()) { + Log.i(TAG, "File already downloaded: ${files.completedFile}") + return@withContext Result.success() + } + + if (files.permanentErrorMarkerFile.exists()) { + Log.w(TAG, "Skipping downloading $fileId due to it being marked as a permanent error") + return@withContext Result.failure() + } + + Log.d(TAG, "Start downloading file from $fileId onto ${files.completedFile}") + + // Make sure the parent directory exists for the completed file. + files.completedFile.parentFile?.mkdirs() + + try { + val bytes = FileServerApi.download(fileId).await() + Log.d(TAG, "Downloaded ${bytes.len} bytes from file server: $fileId") + + // Write the downloaded bytes to a temporary file then move it to the final location. + // This is done to ensure that the file is fully written before being used. + val output = File.createTempFile("download-encrypted-", null, context.cacheDir) + FileOutputStream(output).use { out -> + out.write(bytes) + } + + require(output.renameTo(files.completedFile)) { + "Failed to rename temporary file ${output.absolutePath} to ${files.completedFile.absolutePath}" + } + + Result.success() + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + Log.e(TAG, "Failed to download file $fileId", e) + if (e is NonRetryableException) { + files.permanentErrorMarkerFile.parentFile?.mkdirs() + if (!files.permanentErrorMarkerFile.createNewFile()) { + Log.w(TAG, "Failed to create permanent error marker file: ${files.permanentErrorMarkerFile}") + } + + Result.failure() + } else { + Result.retry() + } + } + } + + data class DownloadedFiles( + val completedFile: File, + val permanentErrorMarkerFile: File, + ) + + companion object { + private const val TAG = "EncryptedFileDownloadWorker" + + private const val ARG_FILE_ID = "file_id" + private const val ARG_FOLDER = "folder" + + // Deterministically get the file path for the given URL, using SHA-256 hash for the + // filename to ensure uniqueness and avoid collisions. + fun getFileForUrl(context: Context, folderName: String, url: String): DownloadedFiles { + val hash = MessageDigest.getInstance("SHA-256") + .digest(url.lowercase().trim().toByteArray()) + .toHexString() + + return DownloadedFiles( + completedFile = File(context.cacheDir, "$folderName/$hash"), + permanentErrorMarkerFile = File(context.cacheDir, "$folderName/$hash.error") + ) + } + + fun cancelAll(context: Context) { + WorkManager.getInstance(context).cancelAllWorkByTag(TAG) + } + + private fun uniqueWorkName(fileId: String, cacheFolderName: String): String { + return "download-$cacheFolderName-$fileId" + } + + fun cancel(context: Context, fileId: String, cacheFolderName: String): Operation { + return WorkManager.getInstance(context).cancelUniqueWork( + uniqueWorkName(fileId, cacheFolderName) + ) + } + + fun enqueue(context: Context, fileId: String, cacheFolderName: String): Operation { + val request = OneTimeWorkRequestBuilder() + .setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED)) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, Duration.ofSeconds(5)) + .addTag(TAG) + .setInputData( + Data.Builder() + .putString(ARG_FILE_ID, fileId) + .putString(ARG_FOLDER, cacheFolderName) + .build() + ) + .build() + + return WorkManager.getInstance(context) + .enqueueUniqueWork( + uniqueWorkName(fileId, cacheFolderName), + ExistingWorkPolicy.REPLACE, + request + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/RecipientAvatarDownloadManager.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/RecipientAvatarDownloadManager.kt new file mode 100644 index 0000000000..4314e48552 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/RecipientAvatarDownloadManager.kt @@ -0,0 +1,162 @@ +package org.thoughtcrime.securesms.glide + +import android.app.Application +import androidx.work.await +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.scan +import kotlinx.coroutines.launch +import network.loki.messenger.libsession_util.util.GroupInfo +import org.session.libsession.messaging.file_server.FileServerApi +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.recipients.RemoteFile +import org.session.libsignal.utilities.AccountId +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.database.GroupDatabase +import org.thoughtcrime.securesms.dependencies.ConfigFactory +import javax.inject.Inject +import javax.inject.Singleton + +@Suppress("OPT_IN_USAGE") +@OptIn(FlowPreview::class) +@Singleton +class RecipientAvatarDownloadManager @Inject constructor( + private val application: Application, + private val prefs: TextSecurePreferences, + private val configFactory: ConfigFactory, + private val groupDatabase: GroupDatabase, +) { + init { + GlobalScope.launch { + prefs.watchLocalNumber() + .map { it != null } + .flatMapLatest { isLoggedIn -> + if (isLoggedIn) { + (configFactory.configUpdateNotifications as Flow<*>) + .debounce(500) + .onStart { emit(Unit) } + .map { getAllAvatars() } + } else { + flowOf(emptySet()) + } + } + .scan(State(emptySet())) { acc, newSet -> + val toDownload = newSet - acc.downloadedAvatar + for (file in toDownload) { + Log.d(TAG, "Downloading $file") + when (file) { + is AvatarFile.FileServer -> { + EncryptedFileDownloadWorker.enqueue( + context = application, + fileId = file.fileId, + cacheFolderName = CACHE_FOLDER_NAME, + ) + } + is AvatarFile.Community -> { + CommunityFileDownloadWorker.enqueueIfNeeded( + context = application, + avatar = file.avatar, + ) + } + } + } + + val toRemove = acc.downloadedAvatar - newSet + for (file in toRemove) { + Log.d(TAG, "Cancelling downloading of $file") + when (file) { + is AvatarFile.FileServer -> { + EncryptedFileDownloadWorker.cancel( + context = application, + fileId = file.fileId, + cacheFolderName = CACHE_FOLDER_NAME, + ).await() + } + + is AvatarFile.Community -> { + CommunityFileDownloadWorker.cancel( + context = application, + avatar = file.avatar, + ) + } + } + } + + acc.copy(downloadedAvatar = newSet) + } + .collect() + // Look at all the avatar URLs stored in the config and download them if necessary + } + } + + fun getAllAvatars(): Set { + val (contacts, groups) = configFactory.withUserConfigs { configs -> + configs.contacts.all() to configs.userGroups.all() + } + + val contactAvatars = contacts.asSequence() + .map { it.profilePicture.url } + + val groupsAvatars = groups.asSequence() + .filterIsInstance() + .flatMap { it.getGroupAvatarUrls() } + + val out = mutableSetOf() + + // Note that for contacts + groups avatars, contacts ones take precedence over groups, + // so their order in the set is important. + (groupsAvatars + contactAvatars) + .mapNotNull { url -> FileServerApi.getFileIdFromUrl(url) } + .mapTo(out) { AvatarFile.FileServer(it) } + + + groups.asSequence() + .filterIsInstance() + .flatMap { it.getCommunityAvatarFile() } + .mapTo(out) { it } + + return out + } + + private fun GroupInfo.ClosedGroupInfo.getGroupAvatarUrls(): List { + if (destroyed) { + return emptyList() + } + + return configFactory.withGroupConfigs(AccountId(groupAccountId)) { + buildList { + add(it.groupInfo.getProfilePic().url) + it.groupMembers.all().forEach { m -> + m.profilePic()?.url?.let(::add) + } + } + } + } + + private fun GroupInfo.CommunityGroupInfo.getCommunityAvatarFile(): Sequence { + // Don't download avatars from community servers yet, future improvement + return emptySequence() + } + + sealed interface AvatarFile { + data class FileServer(val fileId: String) : AvatarFile + data class Community(val avatar: RemoteFile.Community) : AvatarFile + } + + private data class State( + val downloadedAvatar: Set + ) + + companion object { + const val CACHE_FOLDER_NAME = "recipient_avatars" + + private const val TAG = "RecipientAvatarDownloadManager" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileLoader.kt new file mode 100644 index 0000000000..88b057dcaa --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileLoader.kt @@ -0,0 +1,128 @@ +package org.thoughtcrime.securesms.glide + +import android.content.Context +import androidx.work.await +import com.bumptech.glide.Priority +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.Key +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.data.DataFetcher +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.load.model.ModelLoaderFactory +import com.bumptech.glide.load.model.MultiModelLoaderFactory +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import org.session.libsession.messaging.file_server.FileServerApi +import org.session.libsession.utilities.AESGCM +import org.session.libsession.utilities.Conversions +import org.session.libsession.utilities.recipients.RemoteFile +import org.session.libsignal.exceptions.NonRetryableException +import java.security.MessageDigest + +class RemoteFileLoader( + private val context: Context, +) : ModelLoader { + override fun buildLoadData( + model: RemoteFile, + width: Int, + height: Int, + options: Options + ): ModelLoader.LoadData { + return ModelLoader.LoadData( + RemoteFileKey(model), + RemoteFileDataFetcher(model) + ) + } + + private inner class RemoteFileDataFetcher(private val file: RemoteFile) : DataFetcher { + private var job: Job? = null + + override fun loadData( + priority: Priority, + callback: DataFetcher.DataCallback + ) { + job = GlobalScope.launch { + try { + when (file) { + is RemoteFile.Encrypted -> { + val fileId = requireNotNull(FileServerApi.getFileIdFromUrl(file.url)) { + "Target URL is not supported, must be a session file server url but got: ${file.url}" + } + + val files = EncryptedFileDownloadWorker.getFileForUrl( + context, + RecipientAvatarDownloadManager.CACHE_FOLDER_NAME, + fileId + ) + + if (!files.permanentErrorMarkerFile.exists() && files.completedFile.exists()) { + // Files not exists, enqueue a download + EncryptedFileDownloadWorker.enqueue( + context = context, + fileId = fileId, + cacheFolderName = RecipientAvatarDownloadManager.CACHE_FOLDER_NAME + ).await() + } + + if (files.permanentErrorMarkerFile.exists()) { + throw NonRetryableException("Requested file is marked as a permanent error:") + } + + check(files.completedFile.exists()) { + "File not downloaded but no reason is given. Most likely a bug in the download worker." + } + + callback.onDataReady(AESGCM.decrypt(files.completedFile.readBytes(), symmetricKey = file.key.data)) + } + + is RemoteFile.Community -> TODO("Community file download not implemented yet") + } + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + callback.onLoadFailed(e) + } + } + } + + override fun cleanup() { + job?.cancel() + job = null + } + + override fun cancel() { + cleanup() + } + + override fun getDataClass(): Class = ByteArray::class.java + override fun getDataSource(): DataSource = DataSource.REMOTE + } + + private data class RemoteFileKey(val file: RemoteFile) : Key { + override fun updateDiskCacheKey(messageDigest: MessageDigest) { + when (file) { + is RemoteFile.Community -> { + messageDigest.update(file.communityServerBaseUrl.toByteArray()) + messageDigest.update(Conversions.longToByteArray(file.fileId)) + } + + is RemoteFile.Encrypted -> { + messageDigest.update(file.url.toByteArray()) + messageDigest.update(file.key.data) + } + } + } + } + + override fun handles(model: RemoteFile): Boolean = true + + class Factory(private val context: Context) : ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { + return RemoteFileLoader(context) + } + + override fun teardown() {} + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java b/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java index 0a24c26fad..4a9801e12c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java @@ -22,15 +22,15 @@ import com.bumptech.glide.load.resource.gif.StreamGifDecoder; import com.bumptech.glide.module.AppGlideModule; -import org.session.libsession.avatars.ContactPhoto; import org.session.libsession.avatars.PlaceholderAvatarPhoto; +import org.session.libsession.utilities.recipients.RemoteFile; import org.thoughtcrime.securesms.crypto.AttachmentSecret; import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider; import org.thoughtcrime.securesms.giph.model.ChunkedImageUrl; import org.thoughtcrime.securesms.glide.ChunkedImageUrlLoader; -import org.thoughtcrime.securesms.glide.ContactPhotoLoader; import org.thoughtcrime.securesms.glide.OkHttpUrlLoader; import org.thoughtcrime.securesms.glide.PlaceholderAvatarLoader; +import org.thoughtcrime.securesms.glide.RemoteFileLoader; import org.thoughtcrime.securesms.glide.cache.EncryptedBitmapCacheDecoder; import org.thoughtcrime.securesms.glide.cache.EncryptedBitmapResourceEncoder; import org.thoughtcrime.securesms.glide.cache.EncryptedCacheEncoder; @@ -69,7 +69,7 @@ public void registerComponents(@NonNull Context context, @NonNull Glide glide, @ registry.prepend(Bitmap.class, new EncryptedBitmapResourceEncoder(secret)); registry.prepend(GifDrawable.class, new EncryptedGifDrawableResourceEncoder(secret)); - registry.append(ContactPhoto.class, InputStream.class, new ContactPhotoLoader.Factory(context)); + registry.append(RemoteFile.class, byte[].class, new RemoteFileLoader.Factory(context)); registry.append(DecryptableUri.class, InputStream.class, new DecryptableStreamUriLoader.Factory(context)); registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory()); registry.append(ChunkedImageUrl.class, InputStream.class, new ChunkedImageUrlLoader.Factory()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index fcd6de6446..5eae553d7d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -26,7 +26,6 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.DiskCacheStrategy; -import org.session.libsession.avatars.ContactPhoto; import org.session.libsession.utilities.NotificationPrivacyPreference; import org.session.libsession.utilities.Util; import org.session.libsession.utilities.recipients.Recipient; @@ -76,14 +75,14 @@ public void setThread(@NonNull Recipient recipient) { if (privacy.isDisplayContact()) { setContentTitle(recipient.getDisplayName()); - ContactPhoto contactPhoto = recipient.getContactPhoto(); - if (contactPhoto != null) { + Object avatar = recipient.getAvatar(); + if (avatar != null) { try { // AC: For some reason, if not use ".asBitmap()" method, the returned BitmapDrawable // wraps a recycled bitmap and leads to a crash. Bitmap iconBitmap = Glide.with(context.getApplicationContext()) .asBitmap() - .load(contactPhoto) + .load(avatar) .diskCacheStrategy(DiskCacheStrategy.NONE) .circleCrop() .submit(context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width), diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java index f2f3b76ce7..8fe5d030a5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java @@ -59,11 +59,11 @@ public List onGetChooserTargets(ComponentName targetActivityName, Bitmap avatar; - if (recipient.getContactPhoto() != null) { + if (recipient.getAvatar() != null) { try { avatar = Glide.with(this) .asBitmap() - .load(recipient.getContactPhoto()) + .load(recipient.getAvatar()) .circleCrop() .submit(getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width), getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/Avatar.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/Avatar.kt index 57907d4332..d0dc7bf97d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/Avatar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/Avatar.kt @@ -29,18 +29,13 @@ import androidx.compose.ui.unit.sp import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.GlideImage import com.bumptech.glide.integration.compose.placeholder -import com.bumptech.glide.load.engine.DiskCacheStrategy import network.loki.messenger.R -import org.session.libsession.avatars.ProfileContactPhoto -import org.session.libsession.utilities.Address -import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.theme.LocalType import org.thoughtcrime.securesms.ui.theme.PreviewTheme import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider import org.thoughtcrime.securesms.ui.theme.ThemeColors import org.thoughtcrime.securesms.ui.theme.classicDark3 -import org.thoughtcrime.securesms.ui.theme.classicLight1 import org.thoughtcrime.securesms.ui.theme.primaryBlue import org.thoughtcrime.securesms.ui.theme.primaryGreen import org.thoughtcrime.securesms.util.AvatarBadge @@ -319,10 +314,7 @@ fun PreviewAvatarSinglePhoto(){ listOf(AvatarUIElement( name = "AT", color = primaryGreen, - contactPhoto = ProfileContactPhoto( - Address.fromSerialized("05c0d6db0f2d400c392a745105dc93b666642b9dd43993e97c2c4d7440c453b620"), - "305422957" - ) + contactPhoto = null ))) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt index cc91c41b6b..b5ea2e71a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtils.kt @@ -21,8 +21,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import network.loki.messenger.R -import org.session.libsession.avatars.ContactPhoto -import org.session.libsession.avatars.ProfileContactPhoto import org.session.libsession.database.StorageProtocol import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.Recipient @@ -72,7 +70,7 @@ class AvatarUtils @Inject constructor( // if the group has a custom image, use that // other wise make up a double avatar from the first two members // if there is only one member then use that member + an unknown icon coloured based on the group id - if (recipient.profileAvatar != null) { + if (recipient.avatar != null) { elements.add(getUIElementForRecipient(recipient)) } else { val members = if (recipient.isLegacyGroupRecipient) { @@ -132,7 +130,7 @@ class AvatarUtils @Inject constructor( // custom image val (contactPhoto, customIcon, color) = when { // use custom image if there is one - hasAvatar(recipient.avatar as? ContactPhoto) -> Triple(recipient.avatar as? ContactPhoto, null, defaultColor) + recipient.avatar != null -> Triple(recipient.avatar!!, null, defaultColor) // communities without a custom image should use a default image recipient.isCommunityRecipient -> Triple(null, R.drawable.session_logo, null) @@ -147,11 +145,6 @@ class AvatarUtils @Inject constructor( ) } - private fun hasAvatar(contactPhoto: ContactPhoto?): Boolean { - val avatar = (contactPhoto as? ProfileContactPhoto)?.avatarObject - return contactPhoto != null && avatar != "0" && avatar != "" - } - fun getColorFromKey(hashString: String): Int { val hash: Long if (hashString.length >= 12 && hashString.matches(Regex("^[0-9A-Fa-f]+\$"))) { @@ -252,7 +245,7 @@ data class AvatarUIElement( val name: String? = null, val color: Color? = null, @DrawableRes val icon: Int? = null, - val contactPhoto: ContactPhoto? = null, + val contactPhoto: Any? = null, ) sealed class AvatarBadge(@DrawableRes val icon: Int){ From 4b2aef2854456a0b024b7577393b22663c4a5f5a Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Mon, 30 Jun 2025 10:38:01 +1000 Subject: [PATCH 17/52] Download worker --- .../libsession/database/StorageProtocol.kt | 1 + .../session/libsession/utilities/AESGCM.kt | 9 +- .../utilities/recipients/Recipient.kt | 2 +- .../securesms/database/LokiAPIDatabase.kt | 3 + .../securesms/database/LokiThreadDatabase.kt | 20 +++- .../securesms/database/RecipientRepository.kt | 111 +++++++++++++++--- .../securesms/database/Storage.kt | 11 +- .../securesms/dependencies/DatabaseModule.kt | 4 - .../glide/CommunityFileDownloadWorker.kt | 86 ++++++++++++-- .../glide/EncryptedFileDownloadWorker.kt | 89 ++++---------- .../glide/RecipientAvatarDownloadManager.kt | 4 +- .../glide/RemoteFileDownloadWorker.kt | 77 ++++++++++++ .../securesms/glide/RemoteFileLoader.kt | 38 ++++-- 13 files changed, 335 insertions(+), 120 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileDownloadWorker.kt diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index af65ee9815..23d4dab23c 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -78,6 +78,7 @@ interface StorageProtocol { fun getAllOpenGroups(): Map fun updateOpenGroup(openGroup: OpenGroup) fun getOpenGroup(threadId: Long): OpenGroup? + fun getOpenGroup(address: Address): OpenGroup? suspend fun addOpenGroup(urlAsString: String) fun onOpenGroupAdded(server: String, room: String) fun hasBackgroundGroupAddJob(groupJoinUrl: String): Boolean diff --git a/app/src/main/java/org/session/libsession/utilities/AESGCM.kt b/app/src/main/java/org/session/libsession/utilities/AESGCM.kt index 4685b5767c..536bd310f8 100644 --- a/app/src/main/java/org/session/libsession/utilities/AESGCM.kt +++ b/app/src/main/java/org/session/libsession/utilities/AESGCM.kt @@ -4,6 +4,8 @@ import androidx.annotation.WorkerThread import network.loki.messenger.libsession_util.Curve25519 import network.loki.messenger.libsession_util.SessionEncrypt import org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK +import org.session.libsignal.utilities.ByteArraySlice +import org.session.libsignal.utilities.ByteArraySlice.Companion.view import org.session.libsignal.utilities.ByteUtil import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Util @@ -53,15 +55,18 @@ internal object AESGCM { /** * Sync. Don't call from the main thread. */ - internal fun encrypt(plaintext: ByteArray, symmetricKey: ByteArray): ByteArray { + fun encrypt(plaintext: ByteArraySlice, symmetricKey: ByteArray): ByteArray { val iv = Util.getSecretBytes(ivSize) synchronized(CIPHER_LOCK) { val cipher = Cipher.getInstance("AES/GCM/NoPadding") cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(symmetricKey, "AES"), GCMParameterSpec(gcmTagSize, iv)) - return ByteUtil.combine(iv, cipher.doFinal(plaintext)) + return ByteUtil.combine(iv, cipher.doFinal(plaintext.data, plaintext.offset, plaintext.len)) } } + internal fun encrypt(plaintext: ByteArray, symmetricKey: ByteArray): ByteArray = + encrypt(plaintext.view(), symmetricKey) + /** * Sync. Don't call from the main thread. */ diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt index 4f6585b251..2865cc30eb 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt @@ -167,7 +167,7 @@ val BasicRecipient.isGroupRecipient: Boolean get() = address.isGroup */ sealed interface RemoteFile { data class Encrypted(val url: String, val key: Bytes) : RemoteFile - data class Community(val communityServerBaseUrl: String, val fileId: Long) : RemoteFile + data class Community(val communityServerBaseUrl: String, val roomId: String, val fileId: String) : RemoteFile companion object { fun UserPic.toRecipientAvatar(): Encrypted? { return when { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt index aad47f0635..28f048df25 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.database import android.content.ContentValues import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.crypto.ecc.DjbECPrivateKey import org.session.libsignal.crypto.ecc.DjbECPublicKey @@ -17,7 +18,9 @@ import org.session.libsignal.utilities.toHexString import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import java.util.Date +import javax.inject.Inject import javax.inject.Provider +import javax.inject.Singleton class LokiAPIDatabase(context: Context, helper: Provider) : Database(context, helper), LokiAPIDatabaseProtocol { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt index 492462c79f..b4537c6afb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt @@ -3,16 +3,25 @@ package org.thoughtcrime.securesms.database import android.content.ContentValues import android.content.Context import android.database.Cursor +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsignal.utilities.JsonUtil import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper +import javax.inject.Inject import javax.inject.Provider +import javax.inject.Singleton -class LokiThreadDatabase(context: Context, helper: Provider) : Database(context, helper) { +@Singleton +class LokiThreadDatabase @Inject constructor( + @ApplicationContext context: Context, + helper: Provider +) : Database(context, helper) { companion object { private val sessionResetTable = "loki_thread_session_reset_database" - val publicChatTable = "loki_public_chat_database" + private val publicChatTable = "loki_public_chat_database" val threadID = "thread_id" private val sessionResetStatus = "session_reset_status" val publicChat = "public_chat" @@ -22,6 +31,10 @@ class LokiThreadDatabase(context: Context, helper: Provider val createPublicChatTableCommand = "CREATE TABLE $publicChatTable ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);" } + private val mutableChangeNotification = MutableSharedFlow() + + val changeNotification: SharedFlow get() = mutableChangeNotification + fun getAllOpenGroups(): Map { val database = readableDatabase var cursor: Cursor? = null @@ -69,6 +82,7 @@ class LokiThreadDatabase(context: Context, helper: Provider contentValues.put(Companion.threadID, threadID) contentValues.put(publicChat, JsonUtil.toJson(openGroup.toJson())) database.insertOrUpdate(publicChatTable, contentValues, "${Companion.threadID} = ?", arrayOf(threadID.toString())) + mutableChangeNotification.tryEmit(Unit) } fun removeOpenGroupChat(threadID: Long) { @@ -76,6 +90,8 @@ class LokiThreadDatabase(context: Context, helper: Provider val database = writableDatabase database.delete(publicChatTable,"${Companion.threadID} = ?", arrayOf(threadID.toString())) + + mutableChangeNotification.tryEmit(Unit) } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index 281f2e5508..29e9b0cceb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.database +import dagger.Lazy import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview @@ -17,6 +18,8 @@ import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.withContext import network.loki.messenger.libsession_util.ReadableGroupInfoConfig import network.loki.messenger.libsession_util.util.ExpiryMode +import org.session.libsession.database.StorageProtocol +import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigUpdateNotification @@ -54,6 +57,8 @@ class RecipientRepository @Inject constructor( private val groupDatabase: GroupDatabase, private val recipientDatabase: RecipientDatabase, private val preferences: TextSecurePreferences, + private val lokiThreadDatabase: LokiThreadDatabase, + private val storage: Lazy, ) { private val recipientCache = HashMap>>() @@ -75,9 +80,15 @@ class RecipientRepository @Inject constructor( private fun createRecipientFlow(address: Address): SharedFlow { return flow { while (true) { - val (value, changeSource) = fetchRecipient(address) { - withContext(Dispatchers.Default) { recipientDatabase.getRecipientSettings(it) } - } ?: run { + val (value, changeSource) = fetchRecipient( + address = address, + settingsFetcher = { + withContext(Dispatchers.Default) { recipientDatabase.getRecipientSettings(it) } + }, + openGroupFetcher = { + withContext(Dispatchers.Default) { storage.get().getOpenGroup(it) } + } + ) ?: run { // If we don't have a recipient for this address, emit null and terminate the flow. emit(null) return@flow @@ -90,13 +101,19 @@ class RecipientRepository @Inject constructor( Log.d(TAG, "Recipient changed for ${address.debugString}, triggering event: $evt") } - }.shareIn(GlobalScope, + }.shareIn( + GlobalScope, // replay must be cleared one when no one is subscribed, so that if no one is subscribed, // we will always fetch the latest data. The cache is only valid while there is at least one subscriber. - SharingStarted.WhileSubscribed(replayExpirationMillis = 0L), replay = 1) + SharingStarted.WhileSubscribed(replayExpirationMillis = 0L), replay = 1 + ) } - private inline fun fetchRecipient(address: Address, settingsFetcher: (address: Address) -> RecipientSettings?): Pair>? { + private inline fun fetchRecipient( + address: Address, + settingsFetcher: (address: Address) -> RecipientSettings?, + openGroupFetcher: (address: Address) -> OpenGroup? + ): Pair>? { val basicRecipient = getBasicRecipientFast(address) val changeSource: Flow<*> @@ -142,19 +159,32 @@ class RecipientRepository @Inject constructor( val settings = settingsFetcher(address) when { - address.isLegacyGroup || address.isCommunity -> { + address.isLegacyGroup -> { changeSource = merge( groupDatabase.updateNotification, recipientDatabase.updateNotifications.filter { it == address } ) - val group: GroupRecord? = groupDatabase.getGroup(address.toGroupString()).orNull() - value = group?.let { createCommunityOrLegacyGroupRecipient(address, it, settings) } + val group: GroupRecord? = + groupDatabase.getGroup(address.toGroupString()).orNull() + + value = group?.let { createLegacyGroupRecipient(address, it, settings) } + } + + address.isCommunity -> { + value = openGroupFetcher(address) + ?.let { createCommunityRecipient(address, it, settings) } + + changeSource = merge( + lokiThreadDatabase.changeNotification, + recipientDatabase.updateNotifications.filter { it == address } + ) } settings != null -> { value = createGenericRecipient(address, settings) - changeSource = recipientDatabase.updateNotifications.filter { it == address } + changeSource = + recipientDatabase.updateNotifications.filter { it == address } } else -> { @@ -188,7 +218,11 @@ class RecipientRepository @Inject constructor( } // Otherwise, we might have to go to the database to get the recipient.. - return fetchRecipient(address, recipientDatabase::getRecipientSettings)?.first + return fetchRecipient( + address = address, + settingsFetcher = recipientDatabase::getRecipientSettings, + openGroupFetcher = storage.get()::getOpenGroup + )?.first } /** @@ -200,7 +234,10 @@ class RecipientRepository @Inject constructor( * [getBasicRecipientFast] instead. */ @DelicateCoroutinesApi - inline fun getRecipientDisplayNameSync(address: Address, fallbackName: () -> String? = { null }): String { + inline fun getRecipientDisplayNameSync( + address: Address, + fallbackName: () -> String? = { null } + ): String { val basic = getBasicRecipientFast(address) if (basic != null) { return basic.displayName @@ -269,7 +306,10 @@ class RecipientRepository @Inject constructor( // Otherwise, there's no fast way to get a basic recipient else -> { - Log.w(TAG, "No fast way to get a basic recipient for address: ${address.debugString}") + Log.w( + TAG, + "No fast way to get a basic recipient for address: ${address.debugString}" + ) null } } @@ -350,7 +390,32 @@ class RecipientRepository @Inject constructor( ) } - private fun createCommunityOrLegacyGroupRecipient( + private fun createCommunityRecipient( + address: Address, + community: OpenGroup, + settings: RecipientSettings?, + ): Recipient { + return Recipient( + basic = BasicRecipient.Generic( + address = address, + displayName = community.name, + avatar = community.imageId?.let { + RemoteFile.Community( + communityServerBaseUrl = community.server, + roomId = community.room, + fileId = it, + ) + }, + ), + mutedUntil = settings?.muteUntilDate, + autoDownloadAttachments = settings?.autoDownloadAttachments, + notifyType = settings?.notifyType ?: RecipientDatabase.NOTIFY_TYPE_ALL, + acceptsCommunityMessageRequests = false, + notificationChannel = null, + ) + } + + private fun createLegacyGroupRecipient( address: Address, group: GroupRecord, // Local db data settings: RecipientSettings?, // Local db data @@ -362,7 +427,8 @@ class RecipientRepository @Inject constructor( avatar = if (group.url != null && group.avatarId != null) { RemoteFile.Community( communityServerBaseUrl = group.url, - fileId = group.avatarId + roomId = "", + fileId = group.avatarId.toString() ) } else { null @@ -379,12 +445,21 @@ class RecipientRepository @Inject constructor( * Creates a RecipientV2 instance from the provided Address and RecipientSettings. * Note that this method assumes the recipient is not ourselves. */ - private fun createGenericRecipient(address: Address, settings: RecipientSettings): Recipient { + private fun createGenericRecipient( + address: Address, + settings: RecipientSettings + ): Recipient { return Recipient( basic = BasicRecipient.Generic( address = address, - displayName = settings.systemDisplayName?.takeIf { it.isNotBlank() } ?: settings.profileName.orEmpty(), - avatar = settings.profileAvatar?.let { RemoteFile.from(it, settings.profileKey) }, + displayName = settings.systemDisplayName?.takeIf { it.isNotBlank() } + ?: settings.profileName.orEmpty(), + avatar = settings.profileAvatar?.let { + RemoteFile.from( + it, + settings.profileKey + ) + }, isLocalNumber = false, blocked = settings.blocked ), diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 332162df76..1b188628ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -532,12 +532,11 @@ open class Storage @Inject constructor( } override fun getOpenGroup(threadId: Long): OpenGroup? { - if (threadId.toInt() < 0) { return null } - val database = readableDatabase - return database.get(LokiThreadDatabase.publicChatTable, "${LokiThreadDatabase.threadID} = ?", arrayOf( threadId.toString() )) { cursor -> - val publicChatAsJson = cursor.getString(LokiThreadDatabase.publicChat) - OpenGroup.fromJSON(publicChatAsJson) - } + return lokiThreadDatabase.getOpenGroupChat(threadId) + } + + override fun getOpenGroup(address: Address): OpenGroup? { + return getThreadId(address)?.let(lokiThreadDatabase::getOpenGroupChat) } override fun getOpenGroupPublicKey(server: String): String? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt index 3f4f09558b..2e9312ba98 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt @@ -107,10 +107,6 @@ object DatabaseModule { @Singleton fun provideLokiMessageDatabase(@ApplicationContext context: Context, openHelper: Provider) = LokiMessageDatabase(context,openHelper) - @Provides - @Singleton - fun provideLokiThreadDatabase(@ApplicationContext context: Context, openHelper: Provider) = LokiThreadDatabase(context,openHelper) - @Provides @Singleton fun provideLokiUserDatabase(@ApplicationContext context: Context, openHelper: Provider) = LokiUserDatabase(context,openHelper) diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/CommunityFileDownloadWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/CommunityFileDownloadWorker.kt index ce7a69dfa2..3ee44e7c10 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/glide/CommunityFileDownloadWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/CommunityFileDownloadWorker.kt @@ -2,33 +2,101 @@ package org.thoughtcrime.securesms.glide import android.content.Context import androidx.hilt.work.HiltWorker -import androidx.work.CoroutineWorker import androidx.work.Operation import androidx.work.WorkerParameters import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import org.session.libsession.messaging.open_groups.OpenGroupApi +import org.session.libsession.snode.utilities.await +import org.session.libsession.utilities.AESGCM import org.session.libsession.utilities.recipients.RemoteFile +import org.session.libsignal.utilities.ByteArraySlice +import org.session.libsignal.utilities.toHexString +import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider +import java.io.File +import java.security.MessageDigest @HiltWorker class CommunityFileDownloadWorker @AssistedInject constructor( @Assisted val context: Context, @Assisted val params: WorkerParameters -) : CoroutineWorker(context, params) { - override suspend fun doWork(): Result { - TODO("Not yet implemented") +) : RemoteFileDownloadWorker(context, params) { + private val communityServer: String + get() = requireNotNull( + inputData.getString(ARG_COMMUNITY_SERVER) + ) { + "CommunityFileDownloadWorker requires a community server URL" + } + + private val roomId: String + get() = requireNotNull(inputData.getString(ARG_ROOM_ID)) { + "CommunityFileDownloadWorker requires a room ID" + } + + private val fileId: String + get() = requireNotNull(inputData.getString(ARG_FILE_ID)) { + "CommunityFileDownloadWorker requires a file ID" + } + + override suspend fun downloadFile(): ByteArraySlice { + return OpenGroupApi.download(fileId = fileId, room = roomId, server = communityServer).await() + } + + override fun getFilesFromInputData(): DownloadedFiles { + return getFileForUrl( + context, + RemoteFile.Community( + communityServerBaseUrl = communityServer, + roomId = roomId, + fileId = fileId + ) + ) + } + + override fun saveDownloadedFile(from: ByteArraySlice, out: File) { + val encrypted = AESGCM.encrypt( + plaintext = from, + symmetricKey = AttachmentSecretProvider.getInstance(context) + .orCreateAttachmentSecret.modernKey + ) + + // Write the encrypted bytes to a temporary file then move it to the final location. + val tmpOut = File.createTempFile("download-community-", null, context.cacheDir) + tmpOut.writeBytes(encrypted) + require(tmpOut.renameTo(out)) { + "Failed to rename temporary file ${tmpOut.absolutePath} to ${out.absolutePath}" + } } + override val debugName: String + get() = "CommunityFile(server=${communityServer.take(8)}, roomId=${roomId.take(3)}, fileId=$fileId)" + companion object { + private const val ARG_COMMUNITY_SERVER = "community_server" + private const val ARG_ROOM_ID = "room_id" + private const val ARG_FILE_ID = "file_id" + fun getFileForUrl( context: Context, - folderName: String, avatar: RemoteFile.Community, - ): EncryptedFileDownloadWorker.DownloadedFiles { - TODO("Implement logic to get file for URL") + ): DownloadedFiles { + val digest = MessageDigest.getInstance("SHA-256") + + digest.update(avatar.communityServerBaseUrl.lowercase().toByteArray()) + digest.update(avatar.roomId.lowercase().toByteArray()) + digest.update(avatar.fileId.lowercase().toByteArray()) + + val hashed = digest.digest().toHexString() + + return DownloadedFiles( + completedFile = File(context.cacheDir, "community_files/$hashed"), + ) } - fun enqueueIfNeeded(context: Context, - avatar: RemoteFile.Community): Operation? { + fun enqueue( + context: Context, + file: RemoteFile.Community + ): Operation { TODO("Implement enqueue logic for community server download worker") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/EncryptedFileDownloadWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/EncryptedFileDownloadWorker.kt index e6da98c34b..9facfb6258 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/glide/EncryptedFileDownloadWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/EncryptedFileDownloadWorker.kt @@ -4,7 +4,6 @@ import android.content.Context import androidx.hilt.work.HiltWorker import androidx.work.BackoffPolicy import androidx.work.Constraints -import androidx.work.CoroutineWorker import androidx.work.Data import androidx.work.ExistingWorkPolicy import androidx.work.NetworkType @@ -14,91 +13,50 @@ import androidx.work.WorkManager import androidx.work.WorkerParameters import dagger.assisted.Assisted import dagger.assisted.AssistedInject -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import org.session.libsession.messaging.file_server.FileServerApi import org.session.libsession.snode.utilities.await -import org.session.libsignal.exceptions.NonRetryableException +import org.session.libsignal.utilities.ByteArraySlice import org.session.libsignal.utilities.ByteArraySlice.Companion.write -import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.toHexString import java.io.File import java.io.FileOutputStream import java.security.MessageDigest import java.time.Duration -/** - * A worker that downloads files from Session's file server. - */ @HiltWorker class EncryptedFileDownloadWorker @AssistedInject constructor( - @Assisted val context: Context, - @Assisted val params: WorkerParameters -) : CoroutineWorker(context, params) { - override suspend fun doWork(): Result = withContext(Dispatchers.Default) { - val fileId = requireNotNull(inputData.getString(ARG_FILE_ID)) { - "EncryptedFileDownloadWorker requires a URL to download" + @Assisted private val context: Context, + @Assisted params: WorkerParameters +) : RemoteFileDownloadWorker(context, params) { + private val fileId: String + get() = requireNotNull(inputData.getString(ARG_FILE_ID)) { + "EncryptedFileDownloadWorker requires a file ID to download" } - val folderName = requireNotNull(inputData.getString(ARG_FOLDER)) { + private val folderName: String + get() = requireNotNull(inputData.getString(ARG_FOLDER)) { "EncryptedFileDownloadWorker requires a cache folder name" } - val files = getFileForUrl(applicationContext, folderName, fileId) + override suspend fun downloadFile(): ByteArraySlice { + return FileServerApi.download(fileId).await() + } - if (files.completedFile.exists()) { - Log.i(TAG, "File already downloaded: ${files.completedFile}") - return@withContext Result.success() - } + override fun getFilesFromInputData(): DownloadedFiles = getFileForUrl(context, folderName, fileId) - if (files.permanentErrorMarkerFile.exists()) { - Log.w(TAG, "Skipping downloading $fileId due to it being marked as a permanent error") - return@withContext Result.failure() - } + override fun saveDownloadedFile(from: ByteArraySlice, out: File) { + // Write the downloaded bytes to a temporary file then move it to the final location. + // This is done to ensure that the file is fully written before being used. + val tmpOut = File.createTempFile("download-remote-", null, context.cacheDir) + FileOutputStream(out).use { it.write(from) } - Log.d(TAG, "Start downloading file from $fileId onto ${files.completedFile}") - - // Make sure the parent directory exists for the completed file. - files.completedFile.parentFile?.mkdirs() - - try { - val bytes = FileServerApi.download(fileId).await() - Log.d(TAG, "Downloaded ${bytes.len} bytes from file server: $fileId") - - // Write the downloaded bytes to a temporary file then move it to the final location. - // This is done to ensure that the file is fully written before being used. - val output = File.createTempFile("download-encrypted-", null, context.cacheDir) - FileOutputStream(output).use { out -> - out.write(bytes) - } - - require(output.renameTo(files.completedFile)) { - "Failed to rename temporary file ${output.absolutePath} to ${files.completedFile.absolutePath}" - } - - Result.success() - } catch (e: CancellationException) { - throw e - } catch (e: Exception) { - Log.e(TAG, "Failed to download file $fileId", e) - if (e is NonRetryableException) { - files.permanentErrorMarkerFile.parentFile?.mkdirs() - if (!files.permanentErrorMarkerFile.createNewFile()) { - Log.w(TAG, "Failed to create permanent error marker file: ${files.permanentErrorMarkerFile}") - } - - Result.failure() - } else { - Result.retry() - } + require(tmpOut.renameTo(out)) { + "Failed to rename temporary file ${tmpOut.absolutePath} to ${out.absolutePath}" } } - data class DownloadedFiles( - val completedFile: File, - val permanentErrorMarkerFile: File, - ) + override val debugName: String + get() = "EncryptedFile(id=$fileId)" companion object { private const val TAG = "EncryptedFileDownloadWorker" @@ -114,8 +72,7 @@ class EncryptedFileDownloadWorker @AssistedInject constructor( .toHexString() return DownloadedFiles( - completedFile = File(context.cacheDir, "$folderName/$hash"), - permanentErrorMarkerFile = File(context.cacheDir, "$folderName/$hash.error") + completedFile = File(context.cacheDir, "$folderName/$hash") ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/RecipientAvatarDownloadManager.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/RecipientAvatarDownloadManager.kt index 4314e48552..3f72cf1c42 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/glide/RecipientAvatarDownloadManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/RecipientAvatarDownloadManager.kt @@ -60,9 +60,9 @@ class RecipientAvatarDownloadManager @Inject constructor( ) } is AvatarFile.Community -> { - CommunityFileDownloadWorker.enqueueIfNeeded( + CommunityFileDownloadWorker.enqueue( context = application, - avatar = file.avatar, + file = file.avatar, ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileDownloadWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileDownloadWorker.kt new file mode 100644 index 0000000000..316aa0a96a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileDownloadWorker.kt @@ -0,0 +1,77 @@ +package org.thoughtcrime.securesms.glide + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.session.libsignal.exceptions.NonRetryableException +import org.session.libsignal.utilities.ByteArraySlice +import org.session.libsignal.utilities.Log +import java.io.File + +/** + * A worker that downloads files from Session's file server. + */ +abstract class RemoteFileDownloadWorker( + context: Context, + params: WorkerParameters +) : CoroutineWorker(context, params) { + abstract suspend fun downloadFile(): ByteArraySlice + abstract fun getFilesFromInputData(): DownloadedFiles + abstract fun saveDownloadedFile(from: ByteArraySlice, out: File) + + abstract val debugName: String + + override suspend fun doWork(): Result = withContext(Dispatchers.Default) { + val files = getFilesFromInputData() + + if (files.completedFile.exists()) { + Log.i(TAG, "File already downloaded: ${files.completedFile}") + return@withContext Result.success() + } + + if (files.permanentErrorMarkerFile.exists()) { + Log.w(TAG, "Skipping downloading $debugName due to it being marked as a permanent error") + return@withContext Result.failure() + } + + Log.d(TAG, "Start downloading file from $debugName onto ${files.completedFile}") + + // Make sure the parent directory exists for the completed file. + files.completedFile.parentFile?.mkdirs() + + try { + val bytes = downloadFile() + Log.d(TAG, "Downloaded ${bytes.len} bytes from file server: $debugName") + + saveDownloadedFile(bytes, files.completedFile) + + Result.success() + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + Log.e(TAG, "Failed to download file $debugName", e) + if (e is NonRetryableException) { + files.permanentErrorMarkerFile.parentFile?.mkdirs() + if (!files.permanentErrorMarkerFile.createNewFile()) { + Log.w(TAG, "Failed to create permanent error marker file: ${files.permanentErrorMarkerFile}") + } + + Result.failure() + } else { + Result.retry() + } + } + } + + data class DownloadedFiles( + val completedFile: File, + val permanentErrorMarkerFile: File = File(completedFile.parentFile, "${completedFile.name}.error") + ) + + companion object { + private const val TAG = "RemoteFileDownloadWorker" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileLoader.kt index 88b057dcaa..5aa381ad0b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileLoader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileLoader.kt @@ -16,9 +16,9 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.launch import org.session.libsession.messaging.file_server.FileServerApi import org.session.libsession.utilities.AESGCM -import org.session.libsession.utilities.Conversions import org.session.libsession.utilities.recipients.RemoteFile import org.session.libsignal.exceptions.NonRetryableException +import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider import java.security.MessageDigest class RemoteFileLoader( @@ -45,13 +45,16 @@ class RemoteFileLoader( ) { job = GlobalScope.launch { try { + val files: RemoteFileDownloadWorker.DownloadedFiles + val encryptionKey: ByteArray + when (file) { is RemoteFile.Encrypted -> { val fileId = requireNotNull(FileServerApi.getFileIdFromUrl(file.url)) { "Target URL is not supported, must be a session file server url but got: ${file.url}" } - val files = EncryptedFileDownloadWorker.getFileForUrl( + files = EncryptedFileDownloadWorker.getFileForUrl( context, RecipientAvatarDownloadManager.CACHE_FOLDER_NAME, fileId @@ -66,19 +69,33 @@ class RemoteFileLoader( ).await() } - if (files.permanentErrorMarkerFile.exists()) { - throw NonRetryableException("Requested file is marked as a permanent error:") - } + encryptionKey = file.key.data + } - check(files.completedFile.exists()) { - "File not downloaded but no reason is given. Most likely a bug in the download worker." + is RemoteFile.Community -> { + files = CommunityFileDownloadWorker.getFileForUrl(context, file) + + if (!files.permanentErrorMarkerFile.exists() && files.completedFile.exists()) { + // Files not exists, enqueue a download + CommunityFileDownloadWorker.enqueue(context, file).await() } - callback.onDataReady(AESGCM.decrypt(files.completedFile.readBytes(), symmetricKey = file.key.data)) + encryptionKey = AttachmentSecretProvider.getInstance(context) + .orCreateAttachmentSecret.modernKey } + } + + + if (files.permanentErrorMarkerFile.exists()) { + throw NonRetryableException("Requested file is marked as a permanent error:") + } - is RemoteFile.Community -> TODO("Community file download not implemented yet") + check(files.completedFile.exists()) { + "File not downloaded but no reason is given. Most likely a bug in the download worker." } + + callback.onDataReady(AESGCM.decrypt(files.completedFile.readBytes(), symmetricKey = encryptionKey)) + } catch (e: CancellationException) { throw e } catch (e: Exception) { @@ -105,7 +122,8 @@ class RemoteFileLoader( when (file) { is RemoteFile.Community -> { messageDigest.update(file.communityServerBaseUrl.toByteArray()) - messageDigest.update(Conversions.longToByteArray(file.fileId)) + messageDigest.update(file.roomId.toByteArray()) + messageDigest.update(file.fileId.toByteArray()) } is RemoteFile.Encrypted -> { From 69820ca2bcf0b5bcd89247f5ad02ba52813fb45c Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:20:26 +1000 Subject: [PATCH 18/52] Clean stuff properly --- .../securesms/database/LokiThreadDatabase.kt | 32 +++++++++--- .../securesms/database/RecipientRepository.kt | 13 +---- .../glide/CommunityFileDownloadWorker.kt | 50 +++++++++++++++++-- .../glide/EncryptedFileDownloadWorker.kt | 18 ++++--- .../glide/RemoteFileDownloadWorker.kt | 5 +- .../securesms/glide/RemoteFileLoader.kt | 45 ++++++++++++----- .../securesms/mms/SignalGlideModule.java | 2 +- .../securesms/util/ClearDataUtils.kt | 25 ++++++---- 8 files changed, 138 insertions(+), 52 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt index b4537c6afb..e3a8986e40 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database import android.content.ContentValues import android.content.Context import android.database.Cursor +import androidx.collection.LruCache import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow @@ -35,6 +36,8 @@ class LokiThreadDatabase @Inject constructor( val changeNotification: SharedFlow get() = mutableChangeNotification + private val cacheByThreadId = LruCache(32) + fun getAllOpenGroups(): Map { val database = readableDatabase var cursor: Cursor? = null @@ -52,6 +55,12 @@ class LokiThreadDatabase @Inject constructor( } finally { cursor?.close() } + + // Update the cache with the results + for ((id, group) in result) { + cacheByThreadId.put(id, group) + } + return result } @@ -59,6 +68,12 @@ class LokiThreadDatabase @Inject constructor( if (threadID < 0) { return null } + + // Check the cache first + cacheByThreadId[threadID]?.let { + return it + } + val database = readableDatabase return database.get(publicChatTable, "${Companion.threadID} = ?", arrayOf(threadID.toString())) { cursor -> val json = cursor.getString(publicChat) @@ -66,17 +81,19 @@ class LokiThreadDatabase @Inject constructor( } } - fun getThreadId(openGroup: OpenGroup): Long? { - val database = readableDatabase - return database.get(publicChatTable, "$publicChat = ?", arrayOf(JsonUtil.toJson(openGroup.toJson()))) { cursor -> - cursor.getLong(threadID) - } - } - fun setOpenGroupChat(openGroup: OpenGroup, threadID: Long) { if (threadID < 0) { return } + + // Check if the group has really changed + val cache = cacheByThreadId[threadID] + if (cache == openGroup) { + return + } else { + cacheByThreadId.put(threadID, openGroup) + } + val database = writableDatabase val contentValues = ContentValues(2) contentValues.put(Companion.threadID, threadID) @@ -91,6 +108,7 @@ class LokiThreadDatabase @Inject constructor( val database = writableDatabase database.delete(publicChatTable,"${Companion.threadID} = ?", arrayOf(threadID.toString())) + cacheByThreadId.remove(threadID) mutableChangeNotification.tryEmit(Unit) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index 29e9b0cceb..870343dbe1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -187,10 +187,7 @@ class RecipientRepository @Inject constructor( recipientDatabase.updateNotifications.filter { it == address } } - else -> { - Log.w(TAG, "No recipient found for address: ${address.debugString}") - return null - } + else -> return null // No recipient found for this address } } } @@ -305,13 +302,7 @@ class RecipientRepository @Inject constructor( } // Otherwise, there's no fast way to get a basic recipient - else -> { - Log.w( - TAG, - "No fast way to get a basic recipient for address: ${address.debugString}" - ) - null - } + else -> null } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/CommunityFileDownloadWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/CommunityFileDownloadWorker.kt index 3ee44e7c10..1bfd283869 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/glide/CommunityFileDownloadWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/CommunityFileDownloadWorker.kt @@ -2,10 +2,19 @@ package org.thoughtcrime.securesms.glide import android.content.Context import androidx.hilt.work.HiltWorker +import androidx.work.BackoffPolicy +import androidx.work.Constraints +import androidx.work.Data +import androidx.work.ExistingWorkPolicy +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequestBuilder import androidx.work.Operation +import androidx.work.WorkInfo +import androidx.work.WorkManager import androidx.work.WorkerParameters import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.Flow import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.snode.utilities.await import org.session.libsession.utilities.AESGCM @@ -15,6 +24,7 @@ import org.session.libsignal.utilities.toHexString import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider import java.io.File import java.security.MessageDigest +import java.time.Duration @HiltWorker class CommunityFileDownloadWorker @AssistedInject constructor( @@ -72,6 +82,8 @@ class CommunityFileDownloadWorker @AssistedInject constructor( get() = "CommunityFile(server=${communityServer.take(8)}, roomId=${roomId.take(3)}, fileId=$fileId)" companion object { + private const val TAG = "CommunityFileDownloadWorker" + private const val ARG_COMMUNITY_SERVER = "community_server" private const val ARG_ROOM_ID = "room_id" private const val ARG_FILE_ID = "file_id" @@ -93,15 +105,45 @@ class CommunityFileDownloadWorker @AssistedInject constructor( ) } + private fun uniqueWorkName(file: RemoteFile.Community): String { + return "download-community-${file.communityServerBaseUrl}-${file.roomId}-${file.fileId}" + } + + fun cancelAll(context: Context) { + WorkManager.getInstance(context).cancelAllWorkByTag(TAG) + } + fun enqueue( context: Context, file: RemoteFile.Community - ): Operation { - TODO("Implement enqueue logic for community server download worker") + ): Flow { + val inputData = Data.Builder() + .putString(ARG_COMMUNITY_SERVER, file.communityServerBaseUrl) + .putString(ARG_ROOM_ID, file.roomId) + .putString(ARG_FILE_ID, file.fileId) + .build() + + val request = OneTimeWorkRequestBuilder() + .setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED)) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, Duration.ofSeconds(5)) + .setInputData(inputData) + .addTag(TAG) + .build() + + val workName = uniqueWorkName(file) + WorkManager.getInstance(context) + .enqueueUniqueWork( + workName, + ExistingWorkPolicy.REPLACE, + request + ) + + return WorkManager.getInstance(context) + .getWorkInfoByIdFlow(request.id) } - fun cancel(context: Context, avatar: RemoteFile.Community): Operation? { - TODO("Implement cancel logic for community server download worker") + fun cancel(context: Context, avatar: RemoteFile.Community): Operation { + return WorkManager.getInstance(context).cancelUniqueWork(uniqueWorkName(avatar)) } } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/EncryptedFileDownloadWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/EncryptedFileDownloadWorker.kt index 9facfb6258..0c0b07b47f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/glide/EncryptedFileDownloadWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/EncryptedFileDownloadWorker.kt @@ -9,10 +9,12 @@ import androidx.work.ExistingWorkPolicy import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.Operation +import androidx.work.WorkInfo import androidx.work.WorkManager import androidx.work.WorkerParameters import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.Flow import org.session.libsession.messaging.file_server.FileServerApi import org.session.libsession.snode.utilities.await import org.session.libsignal.utilities.ByteArraySlice @@ -47,8 +49,8 @@ class EncryptedFileDownloadWorker @AssistedInject constructor( override fun saveDownloadedFile(from: ByteArraySlice, out: File) { // Write the downloaded bytes to a temporary file then move it to the final location. // This is done to ensure that the file is fully written before being used. - val tmpOut = File.createTempFile("download-remote-", null, context.cacheDir) - FileOutputStream(out).use { it.write(from) } + val tmpOut = File.createTempFile("downloaded-", null, out.parentFile) + FileOutputStream(tmpOut).use { it.write(from) } require(tmpOut.renameTo(out)) { "Failed to rename temporary file ${tmpOut.absolutePath} to ${out.absolutePath}" @@ -90,7 +92,7 @@ class EncryptedFileDownloadWorker @AssistedInject constructor( ) } - fun enqueue(context: Context, fileId: String, cacheFolderName: String): Operation { + fun enqueue(context: Context, fileId: String, cacheFolderName: String): Flow { val request = OneTimeWorkRequestBuilder() .setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED)) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, Duration.ofSeconds(5)) @@ -103,12 +105,16 @@ class EncryptedFileDownloadWorker @AssistedInject constructor( ) .build() - return WorkManager.getInstance(context) + val workName = uniqueWorkName(fileId, cacheFolderName) + WorkManager.getInstance(context) .enqueueUniqueWork( - uniqueWorkName(fileId, cacheFolderName), - ExistingWorkPolicy.REPLACE, + workName, + ExistingWorkPolicy.KEEP, request ) + + return WorkManager.getInstance(context) + .getWorkInfoByIdFlow(request.id) } } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileDownloadWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileDownloadWorker.kt index 316aa0a96a..ee8a274cae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileDownloadWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileDownloadWorker.kt @@ -6,9 +6,11 @@ import androidx.work.WorkerParameters import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.session.libsession.snode.OnionRequestAPI import org.session.libsignal.exceptions.NonRetryableException import org.session.libsignal.utilities.ByteArraySlice import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.util.getRootCause import java.io.File /** @@ -50,10 +52,11 @@ abstract class RemoteFileDownloadWorker( Result.success() } catch (e: CancellationException) { + Log.i(TAG, "Download cancelled for file $debugName") throw e } catch (e: Exception) { Log.e(TAG, "Failed to download file $debugName", e) - if (e is NonRetryableException) { + if (e is NonRetryableException || (e.getRootCause())?.statusCode == 404) { files.permanentErrorMarkerFile.parentFile?.mkdirs() if (!files.permanentErrorMarkerFile.createNewFile()) { Log.w(TAG, "Failed to create permanent error marker file: ${files.permanentErrorMarkerFile}") diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileLoader.kt index 5aa381ad0b..8aa41f430f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileLoader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/RemoteFileLoader.kt @@ -1,7 +1,7 @@ package org.thoughtcrime.securesms.glide import android.content.Context -import androidx.work.await +import androidx.work.WorkInfo import com.bumptech.glide.Priority import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.Key @@ -13,35 +13,40 @@ import com.bumptech.glide.load.model.MultiModelLoaderFactory import kotlinx.coroutines.CancellationException import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import org.session.libsession.messaging.file_server.FileServerApi import org.session.libsession.utilities.AESGCM import org.session.libsession.utilities.recipients.RemoteFile import org.session.libsignal.exceptions.NonRetryableException +import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider +import java.io.ByteArrayInputStream +import java.io.InputStream import java.security.MessageDigest class RemoteFileLoader( private val context: Context, -) : ModelLoader { +) : ModelLoader { override fun buildLoadData( model: RemoteFile, width: Int, height: Int, options: Options - ): ModelLoader.LoadData { + ): ModelLoader.LoadData { return ModelLoader.LoadData( RemoteFileKey(model), RemoteFileDataFetcher(model) ) } - private inner class RemoteFileDataFetcher(private val file: RemoteFile) : DataFetcher { + private inner class RemoteFileDataFetcher(private val file: RemoteFile) : + DataFetcher { private var job: Job? = null override fun loadData( priority: Priority, - callback: DataFetcher.DataCallback + callback: DataFetcher.DataCallback ) { job = GlobalScope.launch { try { @@ -60,13 +65,13 @@ class RemoteFileLoader( fileId ) - if (!files.permanentErrorMarkerFile.exists() && files.completedFile.exists()) { + if (!files.permanentErrorMarkerFile.exists() && !files.completedFile.exists()) { // Files not exists, enqueue a download EncryptedFileDownloadWorker.enqueue( context = context, fileId = fileId, cacheFolderName = RecipientAvatarDownloadManager.CACHE_FOLDER_NAME - ).await() + ).first { it?.state == WorkInfo.State.FAILED || it?.state == WorkInfo.State.SUCCEEDED } } encryptionKey = file.key.data @@ -75,9 +80,10 @@ class RemoteFileLoader( is RemoteFile.Community -> { files = CommunityFileDownloadWorker.getFileForUrl(context, file) - if (!files.permanentErrorMarkerFile.exists() && files.completedFile.exists()) { + if (!files.permanentErrorMarkerFile.exists() && !files.completedFile.exists()) { // Files not exists, enqueue a download - CommunityFileDownloadWorker.enqueue(context, file).await() + CommunityFileDownloadWorker.enqueue(context, file) + .first { it?.state == WorkInfo.State.FAILED || it?.state == WorkInfo.State.SUCCEEDED } } encryptionKey = AttachmentSecretProvider.getInstance(context) @@ -94,11 +100,20 @@ class RemoteFileLoader( "File not downloaded but no reason is given. Most likely a bug in the download worker." } - callback.onDataReady(AESGCM.decrypt(files.completedFile.readBytes(), symmetricKey = encryptionKey)) + val encrypted = files.completedFile.readBytes() + Log.d(TAG, "About to decrypt file with size: ${encrypted.size} bytes") + + callback.onDataReady( + ByteArrayInputStream( + AESGCM.decrypt(encrypted, symmetricKey = encryptionKey) + ) + ) } catch (e: CancellationException) { + Log.i(TAG, "Download cancelled for file: $file") throw e } catch (e: Exception) { + Log.e(TAG, "Error downloading file: $file", e) callback.onLoadFailed(e) } } @@ -113,7 +128,7 @@ class RemoteFileLoader( cleanup() } - override fun getDataClass(): Class = ByteArray::class.java + override fun getDataClass(): Class = InputStream::class.java override fun getDataSource(): DataSource = DataSource.REMOTE } @@ -136,11 +151,15 @@ class RemoteFileLoader( override fun handles(model: RemoteFile): Boolean = true - class Factory(private val context: Context) : ModelLoaderFactory { - override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { + class Factory(private val context: Context) : ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { return RemoteFileLoader(context) } override fun teardown() {} } + + companion object { + private const val TAG = "RemoteFileLoader" + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java b/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java index 4a9801e12c..28bfa97c86 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java @@ -69,7 +69,7 @@ public void registerComponents(@NonNull Context context, @NonNull Glide glide, @ registry.prepend(Bitmap.class, new EncryptedBitmapResourceEncoder(secret)); registry.prepend(GifDrawable.class, new EncryptedGifDrawableResourceEncoder(secret)); - registry.append(RemoteFile.class, byte[].class, new RemoteFileLoader.Factory(context)); + registry.append(RemoteFile.class, InputStream.class, new RemoteFileLoader.Factory(context)); registry.append(DecryptableUri.class, InputStream.class, new DecryptableStreamUriLoader.Factory(context)); registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory()); registry.append(ChunkedImageUrl.class, InputStream.class, new ChunkedImageUrlLoader.Factory()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ClearDataUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ClearDataUtils.kt index 024d73d825..d4f1abab96 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ClearDataUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ClearDataUtils.kt @@ -3,25 +3,27 @@ package org.thoughtcrime.securesms.util import android.annotation.SuppressLint import android.app.Application import android.content.Intent +import androidx.core.content.edit +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.ApplicationContext -import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper -import org.thoughtcrime.securesms.dependencies.ConfigFactory -import org.thoughtcrime.securesms.home.HomeActivity -import javax.inject.Inject -import androidx.core.content.edit -import kotlinx.coroutines.CoroutineDispatcher import okio.ByteString.Companion.decodeHex import org.session.libsession.messaging.notifications.TokenFetcher +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.hexEncodedPublicKey +import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.KeyPairUtilities import org.thoughtcrime.securesms.database.Storage +import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper +import org.thoughtcrime.securesms.dependencies.ConfigFactory +import org.thoughtcrime.securesms.glide.CommunityFileDownloadWorker +import org.thoughtcrime.securesms.glide.EncryptedFileDownloadWorker +import org.thoughtcrime.securesms.home.HomeActivity import org.thoughtcrime.securesms.migration.DatabaseMigrationManager +import javax.inject.Inject class ClearDataUtils @Inject constructor( private val application: Application, @@ -48,8 +50,13 @@ class ClearDataUtils @Inject constructor( TextSecurePreferences.clearAll(application) application.getSharedPreferences(ApplicationContext.PREFERENCES_NAME, 0).edit(commit = true) { clear() } + application.cacheDir.deleteRecursively() + application.filesDir.deleteRecursively() configFactory.clearAll() + EncryptedFileDownloadWorker.cancelAll(application) + CommunityFileDownloadWorker.cancelAll(application) + // The token deletion is nice but not critical, so don't let it block the rest of the process runCatching { tokenFetcher.resetToken() From 558d3f24added3ee401638dc4816c1a50da02b2a Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Mon, 30 Jun 2025 17:05:19 +1000 Subject: [PATCH 19/52] WIP --- .../securesms/database/RecipientRepository.kt | 81 ++++++++++++-- .../securesms/database/ThreadDatabase.java | 97 +++++++++++----- .../securesms/dependencies/ConfigFactory.kt | 105 ------------------ .../securesms/home/HomeViewModel.kt | 2 +- .../MessageRequestsActivity.kt | 10 +- .../messagerequests/MessageRequestsLoader.kt | 11 +- 6 files changed, 152 insertions(+), 154 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index 870343dbe1..4d43d677f4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -17,20 +17,24 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.withContext import network.loki.messenger.libsession_util.ReadableGroupInfoConfig +import network.loki.messenger.libsession_util.ReadableUserProfile +import network.loki.messenger.libsession_util.util.Contact import network.loki.messenger.libsession_util.util.ExpiryMode +import network.loki.messenger.libsession_util.util.GroupInfo import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigUpdateNotification import org.session.libsession.utilities.GroupRecord +import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.recipients.BasicRecipient +import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientSettings import org.session.libsession.utilities.recipients.RemoteFile import org.session.libsession.utilities.recipients.RemoteFile.Companion.toRecipientAvatar -import org.session.libsession.utilities.recipients.RecipientSettings -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.displayNameOrFallback import org.session.libsession.utilities.userConfigsChanged import org.session.libsignal.utilities.AccountId @@ -199,10 +203,7 @@ class RecipientRepository @Inject constructor( return observeRecipient(address).first() } - @Deprecated( - "Use the suspend version of getRecipient instead", - ReplaceWith("getRecipient(address)") - ) + @DelicateCoroutinesApi fun getRecipientSync(address: Address): Recipient? { val flow = observeRecipient(address) @@ -310,10 +311,7 @@ class RecipientRepository @Inject constructor( * Returns a recipient for the given address, or an empty recipient if not found. * This is useful to avoid null checks in the UI. */ - @Deprecated( - "Use the suspend version of getRecipient instead", - ReplaceWith("getRecipient(address)") - ) + @DelicateCoroutinesApi fun getRecipientSyncOrEmpty(address: Address): Recipient { return getRecipientSync(address) ?: empty(address) } @@ -322,6 +320,69 @@ class RecipientRepository @Inject constructor( return getRecipient(address) ?: empty(address) } + fun getAllConfigBasedUnapprovedRecipients(): List
{ + return getConfigBasedConversations( + nts = { false }, + contactFilter = { !it.approved && !it.blocked }, + groupFilter = { it.invited }, + communityFilter = { false }, + legacyFilter = { false }, + ) + } + + fun getAllConfigBasedApprovedRecipients(): List
{ + return getConfigBasedConversations( + contactFilter = { it.approved && !it.blocked }, + groupFilter = { !it.invited } + ) + } + + fun getConfigBasedConversations( + nts: (ReadableUserProfile) -> Boolean = { true }, + contactFilter: (Contact) -> Boolean = { true }, + groupFilter: (GroupInfo.ClosedGroupInfo) -> Boolean = { true }, + legacyFilter: (GroupInfo.LegacyGroupInfo) -> Boolean = { true }, + communityFilter: (GroupInfo.CommunityGroupInfo) -> Boolean = { true } + ): List
{ + val (shouldHaveNts, contacts, groups) = configFactory.withUserConfigs { configs -> + Triple( + configs.userProfile.getNtsPriority() >= 0 && nts(configs.userProfile), + configs.contacts.all(), + configs.userGroups.all(), + ) + } + + val ntsSequence = sequenceOf( + preferences.getLocalNumber() + ?.takeIf { shouldHaveNts } + ?.let(Address::fromSerialized)) + .filterNotNull() + + val contactsSequence = contacts.asSequence() + .filter { it.priority >= 0 && contactFilter(it) } + .map { Address.fromSerialized(it.id) } + + val groupsSequence = groups.asSequence() + .filterIsInstance() + .filter { it.priority >= 0 && groupFilter(it) } + .map { Address.fromSerialized(it.groupAccountId) } + + val legacyGroupsSequence = groups.asSequence() + .filterIsInstance() + .filter { it.priority >= 0 && legacyFilter(it) } + .map { Address.fromSerialized(GroupUtil.doubleEncodeGroupID(it.accountId)) } + + val communityGroupsSequence = groups.asSequence() + .filterIsInstance() + .filter(communityFilter) + .map { Address.fromSerialized(GroupUtil.getEncodedOpenGroupID(it.groupId.toByteArray())) } + + return (ntsSequence + contactsSequence + groupsSequence + legacyGroupsSequence + communityGroupsSequence).toList() + } + + private val GroupInfo.CommunityGroupInfo.groupId: String + get() = "${community.baseUrl}.${community.room}" + companion object { private const val TAG = "RecipientRepository" diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 974e8f9cd9..45a8060734 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -17,8 +17,6 @@ */ package org.thoughtcrime.securesms.database; -import static org.session.libsession.utilities.GroupUtil.COMMUNITY_PREFIX; -import static org.session.libsession.utilities.GroupUtil.LEGACY_CLOSED_GROUP_PREFIX; import static org.thoughtcrime.securesms.database.GroupDatabase.GROUP_ID; import static org.thoughtcrime.securesms.database.UtilKt.generatePlaceholders; @@ -426,8 +424,9 @@ public Cursor searchConversationAddresses(String addressQuery, Set exclu } + @Nullable public Cursor getFilteredConversationList(@Nullable List
filter) { - if (filter == null || filter.size() == 0) + if (filter == null || filter.isEmpty()) return null; SQLiteDatabase db = getReadableDatabase(); @@ -464,38 +463,79 @@ public Cursor getRecentConversationList(int limit) { } public Cursor getConversationList() { - return getConversationList(ARCHIVED + " = 0 "); - } + // Conversations will come from two different sources: + // 1. Config based conversations + // 2. Blinded conversations stored in the database + final List
blindedConversations = getBlindedConversations(null, false); + final List
configBasedConversations = recipientRepository.get().getConfigBasedConversations( + c -> true, + c -> !c.getBlocked(), + value -> true, + value -> true, + value -> true + ); + + final List
allAddresses = new ArrayList<>(blindedConversations.size() + configBasedConversations.size()); + allAddresses.addAll(blindedConversations); + allAddresses.addAll(configBasedConversations); + + return getFilteredConversationList(allAddresses); + } + + private List
getBlindedConversations(@Nullable Boolean approved, @Nullable Boolean blocked) { + String query = "SELECT " + TABLE_NAME + "." + ADDRESS + " FROM " + TABLE_NAME + + " INNER JOIN " + RecipientDatabase.TABLE_NAME + " ON " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.ADDRESS + " = " + TABLE_NAME + "." + ADDRESS + + " WHERE " + TABLE_NAME + "." + ADDRESS + " LIKE '" + IdPrefix.BLINDED.getValue() + "%'"; + + if (approved != null) { + query += " AND " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.APPROVED + " = " + (approved ? 1 : 0); + } + + if (blocked != null) { + query += " AND " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.BLOCK + " = " + (blocked ? 1 : 0); + } - public Cursor getBlindedConversationList() { - String where = TABLE_NAME + "." + ADDRESS + " LIKE '" + IdPrefix.BLINDED.getValue() + "%' "; - return getConversationList(where); + try(final Cursor cursor = getReadableDatabase().rawQuery(query)) { + final ArrayList
addresses = new ArrayList<>(cursor.getCount()); + while (cursor.moveToNext()) { + String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)); + if (address != null && !address.isEmpty()) { + addresses.add(Address.fromSerialized(address)); + } + } + + return addresses; + } } + @Nullable public Cursor getApprovedConversationList() { - String where = "((" + HAS_SENT + " = 1 OR " + RecipientDatabase.APPROVED + " = 1 OR "+ GroupDatabase.TABLE_NAME +"."+GROUP_ID+" LIKE '"+ LEGACY_CLOSED_GROUP_PREFIX +"%') " + - "OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + COMMUNITY_PREFIX + "%') " + - "AND " + ARCHIVED + " = 0 "; - return getConversationList(where); - } + // Approved conversations will come from two different sources: + // 1. Config based conversations + // 2. Blinded conversations stored in the database + final List
blindedConversations = getBlindedConversations(true, false); + final List
configBasedConversations = recipientRepository.get().getAllConfigBasedApprovedRecipients(); - public Cursor getUnapprovedConversationList() { - String where = "("+MESSAGE_COUNT + " != 0 OR "+ThreadDatabase.TABLE_NAME+"."+ThreadDatabase.ADDRESS+" LIKE '"+IdPrefix.GROUP.getValue()+"%')" + - " AND " + ARCHIVED + " = 0 AND " + HAS_SENT + " = 0 AND " + - RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.APPROVED + " = 0 AND " + - RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.BLOCK + " = 0 AND " + - GroupDatabase.TABLE_NAME + "." + GROUP_ID + " IS NULL"; - return getConversationList(where); + final List
allAddresses = new ArrayList<>(blindedConversations.size() + configBasedConversations.size()); + allAddresses.addAll(blindedConversations); + allAddresses.addAll(configBasedConversations); + + return getFilteredConversationList(allAddresses); } - private Cursor getConversationList(String where) { - SQLiteDatabase db = getReadableDatabase(); - String query = createQuery(where, 0); - Cursor cursor = db.rawQuery(query, null); + @Nullable + public Cursor getUnapprovedConversationList() { + // Unapproved conversations will come from two different sources: + // 1. Config based conversations + // 2. Blinded conversations stored in the database + final List
blindedConversations = getBlindedConversations(false, false); + final List
configBasedConversations = recipientRepository.get().getAllConfigBasedUnapprovedRecipients(); - setNotifyConversationListListeners(cursor); + final List
allAddresses = new ArrayList<>(blindedConversations.size() + configBasedConversations.size()); + allAddresses.addAll(blindedConversations); + allAddresses.addAll(configBasedConversations); - return cursor; + return getFilteredConversationList(allAddresses); } public Cursor getDirectShareList() { @@ -853,10 +893,7 @@ public ThreadRecord getCurrent() { int distributionType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.DISTRIBUTION_TYPE)); Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.ADDRESS))); - Recipient recipient = recipientRepository.get().getRecipientSync(address); - if (recipient == null) { - recipient = Recipient.Companion.empty(address); - } + Recipient recipient = recipientRepository.get().getRecipientSyncOrEmpty(address); String body = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET)); long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.THREAD_CREATION_DATE)); long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt index 972e02ae6f..a853e944b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt @@ -14,37 +14,27 @@ import network.loki.messenger.libsession_util.Curve25519 import network.loki.messenger.libsession_util.GroupInfoConfig import network.loki.messenger.libsession_util.GroupKeysConfig import network.loki.messenger.libsession_util.GroupMembersConfig -import network.loki.messenger.libsession_util.MutableConversationVolatileConfig -import network.loki.messenger.libsession_util.MutableUserGroupsConfig import network.loki.messenger.libsession_util.UserGroupsConfig import network.loki.messenger.libsession_util.UserProfile -import network.loki.messenger.libsession_util.util.BaseCommunityInfo -import network.loki.messenger.libsession_util.util.Bytes import network.loki.messenger.libsession_util.util.ConfigPush -import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.MultiEncrypt import org.session.libsession.database.StorageProtocol import org.session.libsession.snode.OwnedSwarmAuth import org.session.libsession.snode.SnodeClock import org.session.libsession.snode.SwarmAuth -import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigMessage import org.session.libsession.utilities.ConfigPushResult import org.session.libsession.utilities.ConfigUpdateNotification import org.session.libsession.utilities.GroupConfigs -import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.MutableGroupConfigs import org.session.libsession.utilities.MutableUserConfigs import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.UserConfigType import org.session.libsession.utilities.UserConfigs import org.session.libsession.utilities.getGroup -import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.session.libsignal.utilities.AccountId -import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix -import org.session.libsignal.utilities.toHexString import org.thoughtcrime.securesms.configs.ConfigToDatabaseSync import org.thoughtcrime.securesms.database.ConfigDatabase import org.thoughtcrime.securesms.database.ConfigVariant @@ -564,91 +554,6 @@ private val UserConfigType.configVariant: ConfigVariant UserConfigType.USER_GROUPS -> ConfigDatabase.USER_GROUPS_VARIANT } -/** - * Sync group data from our local database - */ -private fun MutableUserGroupsConfig.initFrom(storage: StorageProtocol) { - storage - .getAllOpenGroups() - .values - .asSequence() - .mapNotNull { openGroup -> - val (baseUrl, room, pubKey) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: return@mapNotNull null - val pubKeyHex = Hex.toStringCondensed(pubKey) - val baseInfo = BaseCommunityInfo(baseUrl, room, pubKeyHex) - val threadId = storage.getThreadId(openGroup) ?: return@mapNotNull null - val isPinned = storage.isPinned(threadId) - GroupInfo.CommunityGroupInfo(baseInfo, if (isPinned) 1 else 0) - } - .forEach(this::set) - - storage - .getAllGroups(includeInactive = false) - .asSequence().filter { it.isLegacyGroup && it.isActive && it.members.size > 1 } - .mapNotNull { group -> - val groupAddress = Address.fromSerialized(group.encodedId) - val groupPublicKey = GroupUtil.doubleDecodeGroupID(groupAddress.toString()).toHexString() - val recipient = storage.getRecipientSettings(groupAddress) ?: return@mapNotNull null - val encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: return@mapNotNull null - val threadId = storage.getThreadId(group.encodedId) - val isPinned = threadId?.let { storage.isPinned(threadId) } ?: false - val admins = group.admins.associate { it.toString() to true } - val members = group.members.filterNot { it.toString() !in admins.keys }.associate { it.toString() to false } - GroupInfo.LegacyGroupInfo( - accountId = groupPublicKey, - name = group.title, - members = admins + members, - priority = if (isPinned) ConfigBase.PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE, - encPubKey = Bytes((encryptionKeyPair.publicKey as DjbECPublicKey).publicKey), // 'serialize()' inserts an extra byte - encSecKey = Bytes(encryptionKeyPair.privateKey.serialize()), - disappearingTimer = recipient.expireMessages.toLong(), - joinedAtSecs = (group.formationTimestamp / 1000L) - ) - } - .forEach(this::set) -} - -private fun MutableConversationVolatileConfig.initFrom(storage: StorageProtocol, threadDb: ThreadDatabase) { - threadDb.approvedConversationList.use { cursor -> - val reader = threadDb.readerFor(cursor, false) - var current = reader.next - while (current != null) { - val recipient = current.recipient - val contact = when { - recipient.isCommunityRecipient -> { - val openGroup = storage.getOpenGroup(current.threadId) ?: continue - val (base, room, pubKey) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: continue - getOrConstructCommunity(base, room, pubKey) - } - recipient.isGroupV2Recipient -> { - // It's probably safe to assume there will never be a case where new closed groups will ever be there before a dump is created... - // but just in case... - getOrConstructClosedGroup(recipient.address.toString()) - } - recipient.isLegacyGroupRecipient -> { - val groupPublicKey = GroupUtil.doubleDecodeGroupId(recipient.address.toString()) - getOrConstructLegacyGroup(groupPublicKey) - } - recipient.isContactRecipient -> { - if (recipient.isLocalNumber) null // this is handled by the user profile NTS data - else if (recipient.isCommunityInboxRecipient) null // specifically exclude - else if (!recipient.address.toString().startsWith(IdPrefix.STANDARD.value)) null - else getOrConstructOneToOne(recipient.address.toString()) - } - else -> null - } - if (contact == null) { - current = reader.next - continue - } - contact.lastRead = current.lastSeen - contact.unread = false - set(contact) - current = reader.next - } - } -} - private class UserConfigsImpl( userEd25519SecKey: ByteArray, @@ -690,16 +595,6 @@ private class UserConfigsImpl( ed25519SecretKey = userEd25519SecKey, initialDump = convoInfoDump, ) - - init { - if (userGroupsDump == null) { - userGroups.initFrom(storage) - } - - if (convoInfoDump == null) { - convoInfoVolatile.initFrom(storage, threadDb) - } - } } private class GroupConfigsImpl( diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index d9a615a768..cd531e8f09 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -141,7 +141,7 @@ class HomeViewModel @Inject constructor( ).flowOn(Dispatchers.Default) private fun unapprovedConversationCount() = reloadTriggersAndContentChanges() - .map { threadDb.unapprovedConversationList.use { cursor -> cursor.count } } + .map { threadDb.unapprovedConversationList?.use { cursor -> cursor.count } ?: 0 } @Suppress("OPT_IN_USAGE") private fun observeConversationList(): Flow> = reloadTriggersAndContentChanges() diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt index d8b0fc16cf..0f9b4a7c8d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt @@ -26,7 +26,7 @@ import org.thoughtcrime.securesms.util.applySafeInsetsPaddings import org.thoughtcrime.securesms.util.push @AndroidEntryPoint -class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClickListener, LoaderManager.LoaderCallbacks { +class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClickListener, LoaderManager.LoaderCallbacks { private lateinit var binding: ActivityMessageRequestsBinding private lateinit var glide: RequestManager @@ -66,16 +66,16 @@ class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClick LoaderManager.getInstance(this).restartLoader(0, null, this) } - override fun onCreateLoader(id: Int, bundle: Bundle?): Loader { - return MessageRequestsLoader(this@MessageRequestsActivity) + override fun onCreateLoader(id: Int, bundle: Bundle?): Loader { + return MessageRequestsLoader(threadDb, this) } - override fun onLoadFinished(loader: Loader, cursor: Cursor?) { + override fun onLoadFinished(loader: Loader, cursor: Cursor?) { adapter.changeCursor(cursor) updateEmptyState() } - override fun onLoaderReset(cursor: Loader) { + override fun onLoaderReset(cursor: Loader) { adapter.changeCursor(null) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsLoader.kt index 16d83be6db..59544e8515 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsLoader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsLoader.kt @@ -2,12 +2,17 @@ package org.thoughtcrime.securesms.messagerequests import android.content.Context import android.database.Cursor +import android.database.MatrixCursor +import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.util.AbstractCursorLoader -class MessageRequestsLoader(context: Context) : AbstractCursorLoader(context) { +class MessageRequestsLoader( + private val threadDatabase: ThreadDatabase, + context: Context +) : AbstractCursorLoader(context) { - override fun getCursor(): Cursor { - return DatabaseComponent.get(context).threadDatabase().unapprovedConversationList + override fun getCursor(): Cursor? { + return threadDatabase.unapprovedConversationList } } \ No newline at end of file From 629b135f1ce8cc77207125f66b99af012186860c Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Tue, 1 Jul 2025 14:44:15 +1000 Subject: [PATCH 20/52] Mention fix --- .../v2/utilities/MentionUtilities.kt | 26 +++++++++---------- .../securesms/database/RecipientRepository.kt | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt index 5950c44386..daabfa28be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt @@ -10,8 +10,10 @@ import android.util.Range import network.loki.messenger.R import network.loki.messenger.libsession_util.util.BlindKeyAPI import nl.komponents.kovenant.combine.Tuple2 +import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.open_groups.OpenGroup +import org.session.libsession.utilities.Address import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.getColorFromAttr @@ -60,22 +62,20 @@ object MentionUtilities { val publicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @ val isYou = isYou(publicKey, userPublicKey, openGroup) - val userDisplayName: String? = if (isYou) { + val userDisplayName: String = if (isYou) { context.getString(R.string.you) } else { - val contact = DatabaseComponent.get(context).sessionContactDatabase().getContactWithAccountID(publicKey) - @Suppress("NAME_SHADOWING") val context = if (openGroup != null) Contact.ContactContext.OPEN_GROUP else Contact.ContactContext.REGULAR - contact?.displayName(context) - } - if (userDisplayName != null) { - val mention = "@$userDisplayName" - text = text.subSequence(0, matcher.start()).toString() + mention + text.subSequence(matcher.end(), text.length) - val endIndex = matcher.start() + 1 + userDisplayName.length - startIndex = endIndex - mentions.add(Tuple2(Range.create(matcher.start(), endIndex), publicKey)) - } else { - startIndex = matcher.end() + MessagingModuleConfiguration.shared.recipientRepository.getRecipientDisplayNameSync( + Address.fromSerialized(publicKey) + ) } + + val mention = "@$userDisplayName" + text = text.subSequence(0, matcher.start()).toString() + mention + text.subSequence(matcher.end(), text.length) + val endIndex = matcher.start() + 1 + userDisplayName.length + startIndex = endIndex + mentions.add(Tuple2(Range.create(matcher.start(), endIndex), publicKey)) + matcher = pattern.matcher(text) if (!matcher.find(startIndex)) { break } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index 4d43d677f4..8d349b30c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -323,7 +323,7 @@ class RecipientRepository @Inject constructor( fun getAllConfigBasedUnapprovedRecipients(): List
{ return getConfigBasedConversations( nts = { false }, - contactFilter = { !it.approved && !it.blocked }, + contactFilter = { !it.approved && it.approvedMe && !it.blocked }, groupFilter = { it.invited }, communityFilter = { false }, legacyFilter = { false }, From 028664ad12c3c21ae1b8d8b6547e231029d71655 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Tue, 1 Jul 2025 17:16:53 +1000 Subject: [PATCH 21/52] Remove contact database --- .../libsession/messaging/contacts/Contact.kt | 76 ------------ .../libsession/utilities/SSKEnvironment.kt | 1 - .../utilities/recipients/Recipient.kt | 4 +- .../conversation/v2/ConversationActivityV2.kt | 23 ++-- .../conversation/v2/ConversationAdapter.kt | 60 +-------- .../conversation/v2/ConversationViewModel.kt | 5 - .../v2/mention/MentionViewModel.kt | 69 +++-------- .../conversation/v2/messages/QuoteView.kt | 18 ++- .../v2/messages/VisibleMessageView.kt | 63 +++++----- .../v2/utilities/MentionUtilities.kt | 2 - .../securesms/database/RecipientRepository.kt | 11 +- .../database/SessionContactDatabase.kt | 115 ++---------------- .../securesms/database/ThreadDatabase.java | 4 +- .../groups/SelectContactsViewModel.kt | 6 +- .../securesms/home/HomeActivity.kt | 17 ++- .../home/search/GlobalSearchAdapter.kt | 11 +- .../home/search/GlobalSearchAdapterUtils.kt | 19 ++- .../home/search/GlobalSearchResult.kt | 5 +- .../home/search/GlobalSearchViewModel.kt | 2 +- .../securesms/search/SearchRepository.kt | 95 ++++----------- .../securesms/search/model/SearchResult.java | 25 +--- 21 files changed, 153 insertions(+), 478 deletions(-) delete mode 100644 app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt diff --git a/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt b/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt deleted file mode 100644 index 6d899da758..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/contacts/Contact.kt +++ /dev/null @@ -1,76 +0,0 @@ -package org.session.libsession.messaging.contacts - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize -import org.session.libsession.utilities.recipients.Recipient -import org.session.libsession.utilities.truncateIdForDisplay - -@Parcelize -class Contact( - val accountID: String, - /** - * The URL from which to fetch the contact's profile picture. - */ - var profilePictureURL: String? = null, - /** - * The file name of the contact's profile picture on local storage. - */ - var profilePictureFileName: String? = null, - /** - * The key with which the profile picture is encrypted. - */ - var profilePictureEncryptionKey: ByteArray? = null, - /** - * The ID of the thread associated with this contact. - */ - var threadID: Long? = null, - /** - * The name of the contact. Use this whenever you need the "real", underlying name of a user (e.g. when sending a message). - */ - var name: String? = null, - /** - * The contact's nickname, if the user set one. - */ - var nickname: String? = null, -): Parcelable { - - constructor(id: String): this(accountID = id) - - /** - * The name to display in the UI. For local use only. - */ - fun displayName(context: ContactContext = ContactContext.REGULAR): String = nickname ?: when (context) { - ContactContext.REGULAR -> name - // In open groups, where it's more likely that multiple users have the same name, - // we display a bit of the Account ID after a user's display name for added context. - ContactContext.OPEN_GROUP -> name?.let { "$it (${truncateIdForDisplay(accountID)})" } - } ?: truncateIdForDisplay(accountID) - - enum class ContactContext { - REGULAR, OPEN_GROUP - } - - fun isValid(): Boolean { - if (profilePictureURL != null) { return profilePictureEncryptionKey != null } - if (profilePictureEncryptionKey != null) { return profilePictureURL != null} - return true - } - - override fun equals(other: Any?): Boolean { - return this.accountID == (other as? Contact)?.accountID - } - - override fun hashCode(): Int { - return accountID.hashCode() - } - - override fun toString(): String { - return nickname ?: name ?: accountID - } - - companion object { - fun contextForRecipient(recipient: Recipient): ContactContext { - return if (recipient.isCommunityRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt b/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt index 3b7b40817d..a41885fc33 100644 --- a/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt +++ b/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt @@ -1,7 +1,6 @@ package org.session.libsession.utilities import android.content.Context -import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.ProfileUpdateHandler import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt index 2865cc30eb..b1122eff69 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt @@ -222,7 +222,7 @@ fun RemoteFile.toUserPic(): UserPic? { } inline fun Recipient?.displayNameOrFallback(fallbackName: () -> String? = { null }, address: String): String { - return this?.displayName - ?: fallbackName()?.takeIf { it.isNotBlank() } + return (this?.displayName ?: fallbackName()) + ?.takeIf { it.isNotBlank() } ?: truncateIdForDisplay(address) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 24d9785b27..3361147832 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -19,6 +19,7 @@ import android.os.Bundle import android.os.Handler import android.os.Looper import android.provider.MediaStore +import android.provider.Settings import android.text.Spannable import android.text.SpannableStringBuilder import android.text.TextUtils @@ -106,6 +107,7 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED import org.session.libsession.utilities.concurrent.SimpleTask import org.session.libsession.utilities.getColorFromAttr +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.MnemonicCodec import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.IdPrefix @@ -172,7 +174,7 @@ import org.thoughtcrime.securesms.giph.ui.GiphyActivity import org.thoughtcrime.securesms.groups.GroupMembersActivity import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.home.UserDetailsBottomSheet -import org.thoughtcrime.securesms.home.search.getSearchName +import org.thoughtcrime.securesms.home.search.searchName import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel @@ -245,14 +247,12 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, @Inject lateinit var threadDb: ThreadDatabase @Inject lateinit var mmsSmsDb: MmsSmsDatabase @Inject lateinit var lokiThreadDb: LokiThreadDatabase - @Inject lateinit var sessionContactDb: SessionContactDatabase @Inject lateinit var groupDb: GroupDatabase @Inject lateinit var smsDb: SmsDatabase @Inject lateinit var mmsDb: MmsDatabase @Inject lateinit var lokiMessageDb: LokiMessageDatabase @Inject lateinit var storage: StorageProtocol @Inject lateinit var reactionDb: ReactionDatabase - @Inject lateinit var mentionViewModelFactory: MentionViewModel.AssistedFactory @Inject lateinit var dateUtils: DateUtils @Inject lateinit var configFactory: ConfigFactory @Inject lateinit var groupManagerV2: GroupManagerV2 @@ -320,9 +320,12 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, private var isLockViewExpanded = false private var isShowingAttachmentOptions = false // Mentions - private val mentionViewModel: MentionViewModel by viewModels { - mentionViewModelFactory.create(threadId) - } + private val mentionViewModel: MentionViewModel by viewModels(extrasProducer = { + defaultViewModelCreationExtras.withCreationCallback { + it.create(threadId) + } + }) + private val mentionCandidateAdapter = MentionCandidateAdapter { mentionViewModel.onCandidateSelected(it.member.publicKey) @@ -368,7 +371,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, val adapter = ConversationAdapter( this, null, - viewModel.recipient, storage.getLastSeen(viewModel.threadId), false, onItemPress = { message, position, view, event -> @@ -379,7 +381,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, }, onItemLongPress = { message, position, view -> // long pressing message for blocked users should show unblock dialog - if(viewModel.recipient?.blocked == true) unblock() + if (viewModel.recipient?.blocked == true) unblock() else { if (!viewModel.isMessageRequestThread) { showConversationReaction(message, view) @@ -396,7 +398,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, downloadPendingAttachment = viewModel::downloadPendingAttachment, retryFailedAttachments = viewModel::retryFailedAttachments, glide = glide, - lifecycleCoroutineScope = lifecycleScope ) adapter.visibleMessageViewDelegate = this @@ -1445,7 +1446,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, val invitingAdmin = viewModel.invitingAdmin val name = if (recipient.isGroupV2Recipient && invitingAdmin != null) { - invitingAdmin.getSearchName() + invitingAdmin.searchName } else { recipient.displayName } @@ -2517,7 +2518,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, // initially denied it but then have a change of heart when they realise they can't // proceed without it. dangerButton(R.string.theContinue) { - val intent = Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val uri = Uri.fromParts("package", packageName, null) intent.setData(uri) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index c8b2fb1da6..31bb4a0450 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -2,28 +2,12 @@ package org.thoughtcrime.securesms.conversation.v2 import android.content.Context import android.database.Cursor -import android.util.SparseArray -import android.util.SparseBooleanArray import android.view.MotionEvent import android.view.View import android.view.ViewGroup -import androidx.annotation.WorkerThread -import androidx.core.util.getOrDefault -import androidx.core.util.set -import androidx.lifecycle.LifecycleCoroutineScope import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.bumptech.glide.RequestManager -import java.util.concurrent.atomic.AtomicLong -import kotlin.math.min -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment -import org.session.libsession.utilities.recipients.Recipient -import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate @@ -31,11 +15,12 @@ import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent +import java.util.concurrent.atomic.AtomicLong +import kotlin.math.min class ConversationAdapter( context: Context, cursor: Cursor?, - conversation: Recipient?, originalLastSeen: Long, private val isReversed: Boolean, private val onItemPress: (MessageRecord, Int, VisibleMessageView, MotionEvent) -> Unit, @@ -44,19 +29,14 @@ class ConversationAdapter( private val onDeselect: (MessageRecord, Int) -> Unit, private val downloadPendingAttachment: (DatabaseAttachment) -> Unit, private val retryFailedAttachments: (List) -> Unit, - private val glide: RequestManager, - lifecycleCoroutineScope: LifecycleCoroutineScope + private val glide: RequestManager ) : CursorRecyclerViewAdapter(context, cursor) { private val messageDB by lazy { DatabaseComponent.get(context).mmsSmsDatabase() } - private val contactDB by lazy { DatabaseComponent.get(context).sessionContactDatabase() } var selectedItems = mutableSetOf() var isAdmin: Boolean = false private var searchQuery: String? = null var visibleMessageViewDelegate: VisibleMessageViewDelegate? = null - private val updateQueue = Channel(1024, onBufferOverflow = BufferOverflow.DROP_OLDEST) - private val contactCache = SparseArray(100) - private val contactLoadedCache = SparseBooleanArray(100) private val lastSeen = AtomicLong(originalLastSeen) var lastSentMessageId: MessageId? = null @@ -67,25 +47,8 @@ class ConversationAdapter( } } - private val groupId = if(conversation?.isGroupV2Recipient == true) - AccountId(conversation.address.toString()) - else null - private val expandedMessageIds = mutableSetOf() - init { - lifecycleCoroutineScope.launch(IO) { - while (isActive) { - val item = updateQueue.receive() - val contact = getSenderInfo(item) ?: continue - contactCache[item.hashCode()] = contact - contactLoadedCache[item.hashCode()] = true - } - } - } - - @WorkerThread - private fun getSenderInfo(sender: String): Contact? = contactDB.getContactWithAccountID(sender) sealed class ViewType(val rawValue: Int) { object Visible : ViewType(0) @@ -129,19 +92,6 @@ class ConversationAdapter( val isSelected = selectedItems.contains(message) visibleMessageView.snIsSelected = isSelected visibleMessageView.indexInAdapter = position - val senderId = message.individualRecipient.address.toString() - val senderIdHash = senderId.hashCode() - updateQueue.trySend(senderId) - if (contactCache[senderIdHash] == null && !contactLoadedCache.getOrDefault( - senderIdHash, - false - ) - ) { - getSenderInfo(senderId)?.let { contact -> - contactCache[senderIdHash] = contact - } - } - val contact = contactCache[senderIdHash] val isExpanded = expandedMessageIds.contains(message.messageId) visibleMessageView.bind( @@ -150,10 +100,6 @@ class ConversationAdapter( next = getMessageAfter(position, cursor), glide = glide, searchQuery = searchQuery, - contact = contact, - // we pass in the groupId for groupV2 to use for determining the name of the members - groupId = groupId, - senderAccountID = senderId, lastSeen = lastSeen.get(), lastSentMessageId = lastSentMessageId, delegate = visibleMessageViewDelegate, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 15d7893a31..ee523db0e4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -1139,11 +1139,6 @@ class ConversationViewModel @AssistedInject constructor( } } - fun updateRecipient() { - _recipient.updateTo(repository.maybeGetRecipientForThreadId(threadId)?.let(recipientRepository::getRecipientSync)) - updateAppBarData(recipient) - } - fun legacyBannerRecipient(context: Context): Recipient? = recipient?.run { storage.getLastLegacyRecipient(address.toString())?.let { recipientRepository.getRecipientSync(fromSerialized(it)) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt index cdb77773fd..42a6ecda88 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt @@ -9,10 +9,11 @@ import android.text.Spanned import android.text.style.StyleSpan import androidx.core.text.getSpans import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -29,14 +30,14 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext import network.loki.messenger.R import network.loki.messenger.libsession_util.allWithStatus -import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.database.DatabaseContentProviders.Conversation import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.GroupMemberDatabase import org.thoughtcrime.securesms.database.MmsDatabase -import org.thoughtcrime.securesms.database.SessionContactDatabase +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.util.observeChanges @@ -48,19 +49,20 @@ import org.thoughtcrime.securesms.util.observeChanges * 1. Observe the [autoCompleteState] to get the mention search results. * 2. Set the EditText's editable factory to [editableFactory], via [android.widget.EditText.setEditableFactory] */ -class MentionViewModel( +@HiltViewModel(assistedFactory = MentionViewModel.Factory::class) +class MentionViewModel @AssistedInject constructor( application: Application, - threadID: Long, + @Assisted threadID: Long, contentResolver: ContentResolver, threadDatabase: ThreadDatabase, groupDatabase: GroupDatabase, mmsDatabase: MmsDatabase, - contactDatabase: SessionContactDatabase, memberDatabase: GroupMemberDatabase, storage: Storage, configFactory: ConfigFactoryProtocol, - dispatcher: CoroutineDispatcher = Dispatchers.IO, + recipientRepository: RecipientRepository, ) : ViewModel() { + private val dispatcher: CoroutineDispatcher = Dispatchers.Default private val editable = MentionEditable() /** @@ -131,12 +133,6 @@ class MentionViewModel( emptySet() } - val contactContext = if (address.isCommunity) { - Contact.ContactContext.OPEN_GROUP - } else { - Contact.ContactContext.REGULAR - } - val myId = if (openGroup != null) { requireNotNull(storage.getUserBlindedAccountId(openGroup.publicKey)).hexString } else { @@ -150,14 +146,16 @@ class MentionViewModel( isModerator = myId in moderatorIDs, isMe = true ) - ) + contactDatabase.getContacts(memberIDs) + ) + memberIDs .asSequence() - .filter { it.accountID != myId } + .filter { it != myId } + .mapNotNull { recipientRepository.getRecipientSync(Address.fromSerialized(it)) } + .filter { !it.isGroupOrCommunityRecipient } .map { contact -> Member( - publicKey = contact.accountID, - name = contact.displayName(contactContext), - isModerator = contact.accountID in moderatorIDs, + publicKey = contact.address.toString(), + name = contact.displayName, + isModerator = contact.address.address in moderatorIDs, isMe = false ) }) @@ -297,37 +295,8 @@ class MentionViewModel( object Error : AutoCompleteState } - @dagger.assisted.AssistedFactory - interface AssistedFactory { - fun create(threadId: Long): Factory - } - - class Factory @AssistedInject constructor( - @Assisted private val threadId: Long, - private val contentResolver: ContentResolver, - private val threadDatabase: ThreadDatabase, - private val groupDatabase: GroupDatabase, - private val mmsDatabase: MmsDatabase, - private val contactDatabase: SessionContactDatabase, - private val storage: Storage, - private val memberDatabase: GroupMemberDatabase, - private val configFactory: ConfigFactoryProtocol, - private val application: Application, - ) : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return MentionViewModel( - threadID = threadId, - contentResolver = contentResolver, - threadDatabase = threadDatabase, - groupDatabase = groupDatabase, - mmsDatabase = mmsDatabase, - contactDatabase = contactDatabase, - memberDatabase = memberDatabase, - storage = storage, - configFactory = configFactory, - application = application, - ) as T - } + @AssistedFactory + interface Factory { + fun create(threadId: Long): MentionViewModel } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt index 4c9ee9bda6..4fbb50f62b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt @@ -13,13 +13,12 @@ import com.bumptech.glide.RequestManager import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ViewQuoteBinding -import org.session.libsession.messaging.contacts.Contact -import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.Address import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.recipients.Recipient -import org.session.libsession.utilities.truncateIdForDisplay +import org.session.libsession.utilities.recipients.displayNameOrFallback import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities -import org.thoughtcrime.securesms.database.SessionContactDatabase +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.toPx @@ -34,7 +33,7 @@ import javax.inject.Inject @AndroidEntryPoint class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : ConstraintLayout(context, attrs) { - @Inject lateinit var contactDb: SessionContactDatabase + @Inject lateinit var recipientRepository: RecipientRepository private val binding: ViewQuoteBinding by lazy { ViewQuoteBinding.bind(this) } private val vPadding by lazy { toPx(6, resources) } @@ -70,13 +69,12 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? isOutgoingMessage: Boolean, isOpenGroupInvitation: Boolean, threadID: Long, isOriginalMissing: Boolean, glide: RequestManager) { // Author - val author = contactDb.getContactWithAccountID(authorPublicKey) - val localNumber = TextSecurePreferences.getLocalNumber(context) - val quoteIsLocalUser = localNumber != null && authorPublicKey == localNumber + val author = recipientRepository.getRecipientSyncOrEmpty(Address.fromSerialized(authorPublicKey)) val authorDisplayName = - if (quoteIsLocalUser) context.getString(R.string.you) - else author?.displayName(Contact.contextForRecipient(thread)) ?: truncateIdForDisplay(authorPublicKey) + if (author.isLocalNumber) context.getString(R.string.you) + else author.displayNameOrFallback(address = authorPublicKey) + binding.quoteViewAuthorTextView.text = authorDisplayName val textColor = getTextColor(isOutgoingMessage) binding.quoteViewAuthorTextView.setTextColor(textColor) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 1b8ed9c98c..5ca7837e1f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -33,17 +33,14 @@ import network.loki.messenger.databinding.ViewEmojiReactionsBinding import network.loki.messenger.databinding.ViewVisibleMessageBinding import network.loki.messenger.databinding.ViewstubVisibleMessageMarkerContainerBinding import network.loki.messenger.libsession_util.getOrNull -import org.session.libsession.messaging.contacts.Contact -import org.session.libsession.messaging.contacts.Contact.ContactContext import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ThemeUtil.getThemedColor import org.session.libsession.utilities.ViewUtil import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.modifyLayoutParams +import org.session.libsession.utilities.recipients.displayNameOrFallback import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.IdPrefix import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 @@ -163,9 +160,6 @@ class VisibleMessageView : FrameLayout { next: MessageRecord? = null, glide: RequestManager = Glide.with(this), searchQuery: String? = null, - contact: Contact? = null, - groupId: AccountId? = null, - senderAccountID: String, lastSeen: Long, lastSentMessageId: MessageId?, delegate: VisibleMessageViewDelegate? = null, @@ -180,18 +174,20 @@ class VisibleMessageView : FrameLayout { isOutgoing = message.isOutgoing replyDisabled = message.isOpenGroupInvitation val threadID = message.threadId - val thread = threadDb.getRecipientForThreadId(threadID)?.let(recipientRepository::getRecipientSync) ?: return - val isGroupThread = thread.isGroupOrCommunityRecipient + val threadRecipient = threadDb.getRecipientForThreadId(threadID)?.let(recipientRepository::getRecipientSync) ?: return + val isGroupThread = threadRecipient.isGroupOrCommunityRecipient val isStartOfMessageCluster = isStartOfMessageCluster(message, previous, isGroupThread) val isEndOfMessageCluster = isEndOfMessageCluster(message, next, isGroupThread) // Show profile picture and sender name if this is a group thread AND the message is incoming binding.moderatorIconImageView.isVisible = false binding.profilePictureView.visibility = when { - thread.isGroupOrCommunityRecipient && !message.isOutgoing && isEndOfMessageCluster -> View.VISIBLE - thread.isGroupOrCommunityRecipient -> View.INVISIBLE + threadRecipient.isGroupOrCommunityRecipient && !message.isOutgoing && isEndOfMessageCluster -> View.VISIBLE + threadRecipient.isGroupOrCommunityRecipient -> View.INVISIBLE else -> View.GONE } + val sender = message.individualRecipient + val bottomMargin = if (isEndOfMessageCluster) resources.getDimensionPixelSize(R.dimen.small_spacing) else ViewUtil.dpToPx(context,2) @@ -207,30 +203,29 @@ class VisibleMessageView : FrameLayout { if (isGroupThread && !message.isOutgoing) { if (isEndOfMessageCluster) { - binding.profilePictureView.publicKey = senderAccountID - binding.profilePictureView.update(message.individualRecipient) + binding.profilePictureView.update(sender) binding.profilePictureView.setOnClickListener { - if (thread.isCommunityRecipient) { - val openGroup = lokiThreadDb.getOpenGroupChat(threadID) - if (IdPrefix.fromValue(senderAccountID) == IdPrefix.BLINDED && openGroup?.canWrite == true) { + if (threadRecipient.isCommunityRecipient) { + val openGroup = lokiThreadDb.getOpenGroupChat(message.threadId) + if (IdPrefix.fromValue(sender.address.address) == IdPrefix.BLINDED && openGroup?.canWrite == true) { // TODO: support v2 soon val intent = Intent(context, ConversationActivityV2::class.java) - intent.putExtra(ConversationActivityV2.FROM_GROUP_THREAD_ID, threadID) - intent.putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(senderAccountID)) + intent.putExtra(ConversationActivityV2.FROM_GROUP_THREAD_ID, message.threadId) + intent.putExtra(ConversationActivityV2.ADDRESS, sender.address) context.startActivity(intent) } } else { - maybeShowUserDetails(senderAccountID, threadID) + maybeShowUserDetails(sender.address.address, message.threadId) } } - if (thread.isCommunityRecipient) { - val openGroup = lokiThreadDb.getOpenGroupChat(threadID) ?: return + if (threadRecipient.isCommunityRecipient) { + val openGroup = lokiThreadDb.getOpenGroupChat(message.threadId) ?: return var standardPublicKey = "" var blindedPublicKey: String? = null - if (IdPrefix.fromValue(senderAccountID)?.isBlinded() == true) { - blindedPublicKey = senderAccountID + if (IdPrefix.fromValue(sender.address.address)?.isBlinded() == true) { + blindedPublicKey = sender.address.address } else { - standardPublicKey = senderAccountID + standardPublicKey = sender.address.address } val isModerator = openGroupManager.isUserModerator( openGroup.groupId, @@ -239,15 +234,15 @@ class VisibleMessageView : FrameLayout { ) binding.moderatorIconImageView.isVisible = isModerator } - else if (thread.isLegacyGroupRecipient) { // legacy groups - val groupRecord = groupDb.getGroup(thread.address.toGroupString()).orNull() - val isAdmin: Boolean = groupRecord?.admins?.contains(fromSerialized(senderAccountID)) ?: false + else if (threadRecipient.isLegacyGroupRecipient) { // legacy groups + val groupRecord = groupDb.getGroup(threadRecipient.address.toGroupString()).orNull() + val isAdmin: Boolean = groupRecord?.admins?.contains(sender.address) ?: false binding.moderatorIconImageView.isVisible = isAdmin } - else if (thread.isGroupV2Recipient) { // groups v2 - val isAdmin = configFactory.withGroupConfigs(AccountId(thread.address.toString())) { - it.groupMembers.getOrNull(senderAccountID)?.admin == true + else if (threadRecipient.isGroupV2Recipient) { // groups v2 + val isAdmin = configFactory.withGroupConfigs(AccountId(threadRecipient.address.toString())) { + it.groupMembers.getOrNull(sender.address.address)?.admin == true } binding.moderatorIconImageView.isVisible = isAdmin @@ -255,9 +250,7 @@ class VisibleMessageView : FrameLayout { } } binding.senderNameTextView.isVisible = !message.isOutgoing && (isStartOfMessageCluster && (isGroupThread || snIsSelected)) - val contactContext = - if (thread.isCommunityRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR - binding.senderNameTextView.text = recipientRepository.getRecipientDisplayNameSync(Address.fromSerialized(senderAccountID)) + binding.senderNameTextView.text = sender.displayNameOrFallback(address = sender.address.address) // Unread marker val shouldShowUnreadMarker = lastSeen != -1L && message.timestamp > lastSeen && (previous == null || previous.timestamp <= lastSeen) && !message.isOutgoing @@ -281,7 +274,7 @@ class VisibleMessageView : FrameLayout { // Emoji Reactions if (!message.isDeleted && message.reactions.isNotEmpty()) { - val capabilities = lokiThreadDb.getOpenGroupChat(threadID)?.server?.let { lokiApiDb.getServerCapabilities(it) } + val capabilities = lokiThreadDb.getOpenGroupChat(message.threadId)?.server?.let { lokiApiDb.getServerCapabilities(it) } if (capabilities.isNullOrEmpty() || capabilities.contains(OpenGroupApi.Capability.REACTIONS.name.lowercase())) { emojiReactionsBinding.value.root.let { root -> root.setReactions(message.messageId, message.reactions, message.isOutgoing, delegate) @@ -305,7 +298,7 @@ class VisibleMessageView : FrameLayout { isStartOfMessageCluster, isEndOfMessageCluster, glide, - thread, + threadRecipient, searchQuery, downloadPendingAttachment = downloadPendingAttachment, retryFailedAttachments = retryFailedAttachments, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt index daabfa28be..86b6e47be2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt @@ -11,13 +11,11 @@ import network.loki.messenger.R import network.loki.messenger.libsession_util.util.BlindKeyAPI import nl.komponents.kovenant.combine.Tuple2 import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.utilities.Address import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.getColorFromAttr -import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.util.RoundedBackgroundSpan import org.thoughtcrime.securesms.util.getAccentColor diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index 8d349b30c0..c3b0e26e16 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -203,6 +203,11 @@ class RecipientRepository @Inject constructor( return observeRecipient(address).first() } + /** + * Returns a [Recipient] for the given address, or null if not found. + * + * Note that this method might be querying database directly so use with caution. + */ @DelicateCoroutinesApi fun getRecipientSync(address: Address): Recipient? { val flow = observeRecipient(address) @@ -270,7 +275,7 @@ class RecipientRepository @Inject constructor( // Is this in our contact? !address.isGroupOrCommunity && - AccountId.fromStringOrNull(address.address)?.prefix == IdPrefix.STANDARD -> { + IdPrefix.fromValue(address.address) == IdPrefix.STANDARD -> { configFactory.withUserConfigs { configs -> configs.contacts.get(address.address) }?.let { contact -> @@ -320,7 +325,7 @@ class RecipientRepository @Inject constructor( return getRecipient(address) ?: empty(address) } - fun getAllConfigBasedUnapprovedRecipients(): List
{ + fun getAllConfigBasedUnapprovedConversations(): List
{ return getConfigBasedConversations( nts = { false }, contactFilter = { !it.approved && it.approvedMe && !it.blocked }, @@ -330,7 +335,7 @@ class RecipientRepository @Inject constructor( ) } - fun getAllConfigBasedApprovedRecipients(): List
{ + fun getAllConfigBasedApprovedConversations(): List
{ return getConfigBasedConversations( contactFilter = { it.approved && !it.blocked }, groupFilter = { !it.invited } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt index 9535872c50..59767a3f57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt @@ -1,14 +1,6 @@ package org.thoughtcrime.securesms.database -import android.content.ContentValues import android.content.Context -import android.database.Cursor -import org.json.JSONArray -import org.session.libsession.messaging.contacts.Contact -import org.session.libsignal.utilities.AccountId -import org.session.libsignal.utilities.Base64 -import org.session.libsignal.utilities.IdPrefix -import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import javax.inject.Provider @@ -17,14 +9,16 @@ class SessionContactDatabase(context: Context, helper: Provider - contactFromCursor(cursor) - } - } - - fun getContacts(accountIDs: Collection): List { - val database = readableDatabase - return database.getAll( - sessionContactTable, - "$accountID IN (SELECT value FROM json_each(?))", - arrayOf(JSONArray(accountIDs).toString()) - ) { cursor -> contactFromCursor(cursor) } - } - - fun getAllContacts(): Set { - val database = readableDatabase - return database.getAll(sessionContactTable, null, null) { cursor -> - contactFromCursor(cursor) - }.toSet() - } - - fun setContactIsTrusted(contact: Contact, isTrusted: Boolean, threadID: Long) { - val database = writableDatabase - val contentValues = ContentValues(1) - contentValues.put(Companion.isTrusted, if (isTrusted) 1 else 0) - database.update(sessionContactTable, contentValues, "$accountID = ?", arrayOf( contact.accountID )) - if (threadID >= 0) { - notifyConversationListeners(threadID) - } - notifyConversationListListeners() - } - - fun setContact(contact: Contact) { - val database = writableDatabase - val contentValues = ContentValues(8) - contentValues.put(accountID, contact.accountID) - contentValues.put(name, contact.name) - contentValues.put(nickname, contact.nickname) - contentValues.put(profilePictureURL, contact.profilePictureURL) - contentValues.put(profilePictureFileName, contact.profilePictureFileName) - contact.profilePictureEncryptionKey?.let { - contentValues.put(profilePictureEncryptionKey, Base64.encodeBytes(it)) - } - contentValues.put(threadID, contact.threadID) - database.insertOrUpdate(sessionContactTable, contentValues, "$accountID = ?", arrayOf( contact.accountID )) - notifyConversationListListeners() - } - - fun deleteContact(accountId: String) { - val database = writableDatabase - val rowsAffected = database.delete(sessionContactTable, "$accountID = ?", arrayOf( accountId )) - if (rowsAffected == 0) { - Log.w("SessionContactDatabase", "Failed to delete contact with id: $accountId") - } - notifyConversationListListeners() - } - - fun contactFromCursor(cursor: Cursor): Contact { - val contact = Contact(cursor.getString(accountID)) - contact.name = cursor.getStringOrNull(name) - contact.nickname = cursor.getStringOrNull(nickname) - contact.profilePictureURL = cursor.getStringOrNull(profilePictureURL) - contact.profilePictureFileName = cursor.getStringOrNull(profilePictureFileName) - cursor.getStringOrNull(profilePictureEncryptionKey)?.let { - contact.profilePictureEncryptionKey = Base64.decode(it) - } - contact.threadID = cursor.getLong(threadID) - return contact - } - - fun queryContactsByName(constraint: String, excludeUserAddresses: Set = emptySet()): Cursor { - val whereClause = StringBuilder("($name LIKE ? OR $nickname LIKE ?)") - val whereArgs = ArrayList() - whereArgs.add("%$constraint%") - whereArgs.add("%$constraint%") - - // filter out users is the list isn't empty - if (excludeUserAddresses.isNotEmpty()) { - whereClause.append(" AND $accountID NOT IN (") - whereClause.append(excludeUserAddresses.joinToString(", ") { "?" }) - whereClause.append(")") - - whereArgs.addAll(excludeUserAddresses) - } - - return readableDatabase.query( - sessionContactTable, null, whereClause.toString(), whereArgs.toTypedArray(), - null, null, null - ) - } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 45a8060734..e7dff3df47 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -514,7 +514,7 @@ public Cursor getApprovedConversationList() { // 1. Config based conversations // 2. Blinded conversations stored in the database final List
blindedConversations = getBlindedConversations(true, false); - final List
configBasedConversations = recipientRepository.get().getAllConfigBasedApprovedRecipients(); + final List
configBasedConversations = recipientRepository.get().getAllConfigBasedApprovedConversations(); final List
allAddresses = new ArrayList<>(blindedConversations.size() + configBasedConversations.size()); allAddresses.addAll(blindedConversations); @@ -529,7 +529,7 @@ public Cursor getUnapprovedConversationList() { // 1. Config based conversations // 2. Blinded conversations stored in the database final List
blindedConversations = getBlindedConversations(false, false); - final List
configBasedConversations = recipientRepository.get().getAllConfigBasedUnapprovedRecipients(); + final List
configBasedConversations = recipientRepository.get().getAllConfigBasedUnapprovedConversations(); final List
allAddresses = new ArrayList<>(blindedConversations.size() + configBasedConversations.size()); allAddresses.addAll(blindedConversations); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/SelectContactsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/SelectContactsViewModel.kt index d9f410b8ef..e60b209bf0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/SelectContactsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/SelectContactsViewModel.kt @@ -28,7 +28,7 @@ import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.dependencies.ConfigFactory -import org.thoughtcrime.securesms.home.search.getSearchName +import org.thoughtcrime.securesms.home.search.searchName import org.thoughtcrime.securesms.util.AvatarUIData import org.thoughtcrime.securesms.util.AvatarUtils @@ -109,12 +109,12 @@ open class SelectContactsViewModel @AssistedInject constructor( ): List { val items = mutableListOf() for (contact in contacts) { - if (query.isBlank() || contact.getSearchName().contains(query, ignoreCase = true)) { + if (query.isBlank() || contact.searchName.contains(query, ignoreCase = true)) { val accountId = AccountId(contact.address.toString()) val avatarData = avatarUtils.getUIDataFromRecipient(contact) items.add( ContactItem( - name = contact.getSearchName(), + name = contact.searchName, accountID = accountId, avatarUIData = avatarData, selected = selectedAccountIDs.contains(accountId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 12e11582db..5fd72855f9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -392,11 +392,10 @@ class HomeActivity : ScreenLockActionBarActivity(), return contacts // Remove ourself, we're shown above. - .filter { it.accountID != publicKey } + .filter { it.address.address != publicKey } // Get the name that we will display and sort by, and uppercase it to // help with sorting and we need the char uppercased later. - .map { (it.nickname?.takeIf(String::isNotEmpty) ?: it.name?.takeIf(String::isNotEmpty)) - .let { name -> NamedValue(name?.uppercase(), it) } } + .map { NamedValue(it.displayName.uppercase(), it) } // Digits are all grouped under a #, the rest are grouped by their first character.uppercased() // If there is no name, they go under Unknown .groupBy { it.name?.run { first().takeUnless(Char::isDigit)?.toString() ?: numbersTitle } ?: unknownTitle } @@ -412,14 +411,20 @@ class HomeActivity : ScreenLockActionBarActivity(), .flatMap { (key, contacts) -> listOf( GlobalSearchAdapter.Model.SubHeader(key) - ) + contacts.sortedBy { it.name ?: it.value.accountID }.map { it.value }.map { GlobalSearchAdapter.Model.Contact(it, it.accountID == publicKey) } + ) + contacts.sortedBy { it.name ?: it.value.address.address } + .map { + GlobalSearchAdapter.Model.Contact( + contact = it.value, + isSelf = it.value.address.address == publicKey + ) + } } } private val GlobalSearchResult.contactAndGroupList: List get() = - contacts.map { GlobalSearchAdapter.Model.Contact(it, it.accountID == publicKey) } + + contacts.map { GlobalSearchAdapter.Model.Contact(it, it.address.address == publicKey) } + threads.map { - GlobalSearchAdapter.Model.GroupConversation(this@HomeActivity, it) + GlobalSearchAdapter.Model.GroupConversation(it) } private val GlobalSearchResult.messageResults: List get() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt index 5040e9b850..0da0df9f33 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.home.search -import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -13,7 +12,7 @@ import network.loki.messenger.databinding.ViewGlobalSearchResultBinding import network.loki.messenger.databinding.ViewGlobalSearchSubheaderBinding import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.utilities.GroupRecord -import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.BasicRecipient import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.search.model.MessageResult import org.thoughtcrime.securesms.ui.GetString @@ -166,8 +165,8 @@ class GlobalSearchAdapter( data class SavedMessages(val currentUserPublicKey: String): Model // Note: "Note to Self" counts as SavedMessages rather than a Contact where `isSelf` is true. data class Contact(val contact: AccountId, val name: String, val isSelf: Boolean) : Model { - constructor(contact: org.session.libsession.messaging.contacts.Contact, isSelf: Boolean): - this(AccountId(contact.accountID), contact.getSearchName(), isSelf) + constructor(contact: BasicRecipient.Contact, isSelf: Boolean): + this(AccountId(contact.address.address), contact.searchName, isSelf) } data class GroupConversation( val isLegacy: Boolean, @@ -175,7 +174,7 @@ class GlobalSearchAdapter( val title: String, val legacyMembersString: String?, ) : Model { - constructor(context: Context, groupRecord: GroupRecord): + constructor(groupRecord: GroupRecord): this( isLegacy = groupRecord.isLegacyGroup, groupId = groupRecord.encodedId, @@ -184,7 +183,7 @@ class GlobalSearchAdapter( val recipients = groupRecord.members.map { MessagingModuleConfiguration.shared.recipientRepository.getRecipientSyncOrEmpty(it) } - recipients.joinToString(transform = Recipient::getSearchName) + recipients.joinToString(transform = { it.searchName }) } else { null } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt index 80883f2b8a..56dc664d6a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt @@ -8,8 +8,8 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import network.loki.messenger.R import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.Address +import org.session.libsession.utilities.recipients.BasicRecipient import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.ContentView @@ -57,7 +57,7 @@ fun ContentView.bindQuery(query: String, model: GlobalSearchAdapter.Model) { val textSpannable = SpannableStringBuilder() if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) { // group chat, bind - val text = "${model.messageResult.messageRecipient.getSearchName()}: " + val text = "${model.messageResult.messageRecipient.searchName}: " textSpannable.append(text) } textSpannable.append(getHighlight( @@ -66,7 +66,7 @@ fun ContentView.bindQuery(query: String, model: GlobalSearchAdapter.Model) { )) binding.searchResultSubtitle.text = textSpannable binding.searchResultSubtitle.isVisible = true - binding.searchResultTitle.text = model.messageResult.conversationRecipient.getSearchName() + binding.searchResultTitle.text = model.messageResult.conversationRecipient.searchName } is GroupConversation -> { binding.searchResultTitle.text = getHighlight( @@ -146,17 +146,14 @@ fun ContentView.bindModel(query: String?, model: Message, dateUtils: DateUtils) )) searchResultSubtitle.text = textSpannable searchResultTitle.text = if (model.isSelf) root.context.getString(R.string.noteToSelf) - else model.messageResult.conversationRecipient.getSearchName() + else model.messageResult.conversationRecipient.searchName searchResultSubtitle.isVisible = true } -fun Recipient.getSearchName(): String = - displayName.takeIf { it.isNotEmpty() && !it.looksLikeAccountId } - ?: address.toString().let(::truncateIdForDisplay) +val BasicRecipient.searchName: String + get() = displayName.takeIf { it.isNotBlank() && !it.looksLikeAccountId } + ?: address.toString().let(::truncateIdForDisplay) -fun Contact.getSearchName(): String = - nickname?.takeIf { it.isNotEmpty() && !it.looksLikeAccountId } - ?: name?.takeIf { it.isNotEmpty() && !it.looksLikeAccountId } - ?: truncateIdForDisplay(accountID) +val Recipient.searchName: String get() = basic.searchName private val String.looksLikeAccountId: Boolean get() = length > 60 && all { it.isDigit() || it.isLetter() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchResult.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchResult.kt index c2c5f01a20..9a7fe259cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchResult.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchResult.kt @@ -1,13 +1,14 @@ package org.thoughtcrime.securesms.home.search -import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.GroupRecord +import org.session.libsession.utilities.recipients.BasicRecipient +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.search.model.MessageResult import org.thoughtcrime.securesms.search.model.SearchResult data class GlobalSearchResult( val query: String, - val contacts: List = emptyList(), + val contacts: List = emptyList(), val threads: List = emptyList(), val messages: List = emptyList(), val showNoteToSelf: Boolean = false diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt index d986996501..4d2141f1bf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt @@ -60,7 +60,7 @@ class GlobalSearchViewModel @Inject constructor( // without a nickname/name who haven't approved us. GlobalSearchResult( query, - searchRepository.queryContacts("05").toList() + searchRepository.queryContacts().toList() ) } } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt index e110d6be15..4572b6c7bb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt @@ -2,24 +2,19 @@ package org.thoughtcrime.securesms.search import android.content.Context import android.database.Cursor -import android.database.MergeCursor import dagger.hilt.android.qualifiers.ApplicationContext -import org.session.libsession.messaging.contacts.Contact -import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.concurrent.SignalExecutors +import org.session.libsession.utilities.recipients.BasicRecipient import org.session.libsession.utilities.recipients.Recipient -import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.contacts.ContactAccessor import org.thoughtcrime.securesms.database.CursorList import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.MmsSmsColumns import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.SearchDatabase -import org.thoughtcrime.securesms.database.SessionContactDatabase import org.thoughtcrime.securesms.database.ThreadDatabase -import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.search.model.MessageResult import org.thoughtcrime.securesms.search.model.SearchResult import org.thoughtcrime.securesms.util.Stopwatch @@ -33,9 +28,7 @@ class SearchRepository @Inject constructor( private val searchDatabase: SearchDatabase, private val threadDatabase: ThreadDatabase, private val groupDatabase: GroupDatabase, - private val contactDatabase: SessionContactDatabase, private val contactAccessor: ContactAccessor, - private val configFactory: ConfigFactory, private val recipientRepository: RecipientRepository, ) { private val executor = SignalExecutors.SERIAL @@ -86,49 +79,31 @@ class SearchRepository @Inject constructor( } } - // Get set of blocked contact AccountIDs from the ConfigFactory - private fun getBlockedContacts(): MutableSet { - val blockedContacts = mutableSetOf() - configFactory.withUserConfigs { userConfigs -> - userConfigs.contacts.all().forEach { contact -> - if (contact.blocked) { - blockedContacts.add(contact.id) - } - } - } - return blockedContacts + private fun getBlockedContacts(): Set { + return recipientRepository.getConfigBasedConversations( + nts = { false }, + contactFilter = { it.blocked }, + groupFilter = { false }, + communityFilter = { false }, + legacyFilter = { false }, + ).mapTo(hashSetOf()) { it.address } } - fun queryContacts(query: String): CursorList { - val excludingAddresses = getBlockedContacts() - val contacts = contactDatabase.queryContactsByName(query, excludeUserAddresses = excludingAddresses) - val contactList: MutableList
= ArrayList() - - while (contacts.moveToNext()) { - try { - val contact = contactDatabase.contactFromCursor(contacts) - val contactAccountId = contact.accountID - val address = fromSerialized(contactAccountId) - contactList.add(address) - - // Add the address in this query to the excluded addresses so the next query - // won't get the same contact again - excludingAddresses.add(contactAccountId) - } catch (e: Exception) { - Log.e("Loki", "Error building Contact from cursor in query", e) - } - } - - contacts.close() - - val addressThreads = threadDatabase.searchConversationAddresses(query, excludingAddresses)// filtering threads by looking up the accountID itself - val individualRecipients = threadDatabase.getFilteredConversationList(contactList) - if (individualRecipients == null && addressThreads == null) { - return CursorList.emptyList() - } - val merged = MergeCursor(arrayOf(addressThreads, individualRecipients)) - - return CursorList(merged, ContactModelBuilder(contactDatabase, threadDatabase)) + fun queryContacts(searchName: String? = null): List { + return recipientRepository.getConfigBasedConversations( + nts = { false }, + contactFilter = { + !it.blocked && + it.approved && + (searchName == null || + it.name.contains(searchName, ignoreCase = true) || + it.nickname.contains(searchName, ignoreCase = true)) + }, + groupFilter = { false }, + communityFilter = { false }, + legacyFilter = { false }, + ).mapNotNull(recipientRepository::getBasicRecipientFast) + .filterIsInstance() } private fun queryConversations( @@ -148,7 +123,7 @@ class SearchRepository @Inject constructor( val blockedContacts = getBlockedContacts() val messages = searchDatabase.queryMessages(query, blockedContacts) return if (messages != null) - CursorList(messages, MessageModelBuilder(context)) + CursorList(messages, MessageModelBuilder()) else CursorList.emptyList() } @@ -157,7 +132,7 @@ class SearchRepository @Inject constructor( val blockedContacts = getBlockedContacts() val messages = searchDatabase.queryMessages(query, threadId, blockedContacts) return if (messages != null) - CursorList(messages, MessageModelBuilder(context)) + CursorList(messages, MessageModelBuilder()) else CursorList.emptyList() } @@ -184,22 +159,6 @@ class SearchRepository @Inject constructor( return out.toString() } - private class ContactModelBuilder( - private val contactDb: SessionContactDatabase, - private val threadDb: ThreadDatabase - ) : CursorList.ModelBuilder { - override fun build(cursor: Cursor): Contact { - val threadRecord = threadDb.readerFor(cursor).current - var contact = - contactDb.getContactWithAccountID(threadRecord.recipient.address.toString()) - if (contact == null) { - contact = Contact(threadRecord.recipient.address.toString()) - contact.threadID = threadRecord.threadId - } - return contact - } - } - private class GroupModelBuilder( private val threadDatabase: ThreadDatabase, private val groupDatabase: GroupDatabase @@ -210,7 +169,7 @@ class SearchRepository @Inject constructor( } } - private inner class MessageModelBuilder(private val context: Context) : CursorList.ModelBuilder { + private inner class MessageModelBuilder() : CursorList.ModelBuilder { override fun build(cursor: Cursor): MessageResult { val conversationAddress = fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.CONVERSATION_ADDRESS))) diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/model/SearchResult.java b/app/src/main/java/org/thoughtcrime/securesms/search/model/SearchResult.java index 33c687c939..a8b5bb1151 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/model/SearchResult.java +++ b/app/src/main/java/org/thoughtcrime/securesms/search/model/SearchResult.java @@ -1,13 +1,11 @@ package org.thoughtcrime.securesms.search.model; -import android.database.ContentObserver; - import androidx.annotation.NonNull; -import org.session.libsession.messaging.contacts.Contact; import org.session.libsession.utilities.GroupRecord; +import org.session.libsession.utilities.recipients.BasicRecipient; +import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.CursorList; -import org.thoughtcrime.securesms.database.model.ThreadRecord; import java.util.List; @@ -20,12 +18,12 @@ public class SearchResult { public static final SearchResult EMPTY = new SearchResult("", CursorList.emptyList(), CursorList.emptyList(), CursorList.emptyList()); private final String query; - private final CursorList contacts; + private final List contacts; private final CursorList conversations; private final CursorList messages; public SearchResult(@NonNull String query, - @NonNull CursorList contacts, + @NonNull List contacts, @NonNull CursorList conversations, @NonNull CursorList messages) { @@ -35,7 +33,7 @@ public SearchResult(@NonNull String query, this.messages = messages; } - public List getContacts() { + public List getContacts() { return contacts; } @@ -59,20 +57,7 @@ public boolean isEmpty() { return size() == 0; } - public void registerContentObserver(@NonNull ContentObserver observer) { - contacts.registerContentObserver(observer); - conversations.registerContentObserver(observer); - messages.registerContentObserver(observer); - } - - public void unregisterContentObserver(@NonNull ContentObserver observer) { - contacts.unregisterContentObserver(observer); - conversations.unregisterContentObserver(observer); - messages.unregisterContentObserver(observer); - } - public void close() { - contacts.close(); conversations.close(); messages.close(); } From f5302adbc72411d5a6b729a952c4c36b08a6a4bc Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Wed, 2 Jul 2025 13:24:50 +1000 Subject: [PATCH 22/52] Fixed conflicts --- .../securesms/notifications/DefaultMessageNotifier.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt index 3d64e31775..d64215bc3a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt @@ -510,19 +510,19 @@ class DefaultMessageNotifier @Inject constructor( val threadId = record.threadId val threadRecipients = if (threadId != -1L) { - threadDatabase.getRecipientForThreadId(threadId) + threadDatabase.getRecipientForThreadId(threadId)?.let(recipientRepository::getRecipientSync) } else null // Start by checking various scenario that we should skip // Skip if muted or calls - if (threadRecipients?.isMuted == true) continue + if (threadRecipients?.isMuted() == true) continue if (record.isIncomingCall || record.isOutgoingCall) continue // Handle message requests early val isMessageRequest = threadRecipients != null && !threadRecipients.isGroupOrCommunityRecipient && - !threadRecipients.isApproved && + !threadRecipients.approved && !threadDatabase.getLastSeenAndHasSent(threadId).second() if (isMessageRequest && (threadDatabase.getMessageCount(threadId) > 1 || !hasHiddenMessageRequests(context))) { @@ -640,7 +640,7 @@ class DefaultMessageNotifier @Inject constructor( val latestReaction = reactionsFromOthers.maxByOrNull { it.dateSent } if (latestReaction != null) { - val reactor = Recipient.from(context, fromSerialized(latestReaction.author), false) + val reactor = recipientRepository.getRecipientSyncOrEmpty(fromSerialized(latestReaction.author)) val emoji = Phrase.from(context, R.string.emojiReactsNotification) .put(EMOJI_KEY, latestReaction.emoji).format().toString() From 3942fa856db14268c0493c7d0f0bb52a8e89ce3e Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:05:58 +1000 Subject: [PATCH 23/52] Only pass in address to convo screen --- .../thoughtcrime/securesms/ShareActivity.kt | 22 ++--- .../securesms/ShortcutLauncherActivity.kt | 9 +- .../start/NewConversationFragment.kt | 5 +- .../start/newmessage/NewMessageFragment.kt | 6 +- .../conversation/v2/ConversationActivityV2.kt | 89 ++++++++++++------- .../conversation/v2/ConversationViewModel.kt | 9 +- .../v2/messages/VisibleMessageView.kt | 11 ++- .../securesms/groups/CreateGroupFragment.kt | 5 +- .../securesms/groups/CreateGroupViewModel.kt | 6 +- .../securesms/groups/GroupMembersViewModel.kt | 16 ++-- .../securesms/groups/JoinCommunityFragment.kt | 17 ++-- .../groups/compose/CreateGroupScreen.kt | 5 +- .../securesms/home/HomeActivity.kt | 60 +++++-------- .../securesms/home/UserDetailsBottomSheet.kt | 16 ++-- .../MessageRequestsActivity.kt | 4 +- .../notifications/NotificationItem.java | 6 +- .../notifications/NotificationState.java | 4 +- .../securesms/preferences/QRCodeActivity.kt | 18 ++-- 18 files changed, 137 insertions(+), 171 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt index af578a8ff6..4ba47e661a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt @@ -32,7 +32,6 @@ import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import org.session.libsession.utilities.Address -import org.session.libsession.utilities.DistributionTypes import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.ViewUtil import org.session.libsignal.utilities.Log @@ -206,21 +205,12 @@ class ShareActivity : ScreenLockActionBarActivity(), OnContactSelectedListener { contactsFragment.requireView().visibility = View.VISIBLE progressWheel.visibility = View.GONE } else { - createConversation(threadId, address, distributionType) + createConversation(address) } } - private fun createConversation(threadId: Long, address: Address?, distributionType: Int) { - val intent = getBaseShareIntent(ConversationActivityV2::class.java) - intent.putExtra(ConversationActivityV2.ADDRESS, address) - intent.putExtra(ConversationActivityV2.THREAD_ID, threadId) - - isPassingAlongMedia = true - startActivity(intent) - } - - private fun getBaseShareIntent(target: Class<*>): Intent { - val intent = Intent(this, target) + private fun createConversation(address: Address) { + val intent = ConversationActivityV2.createIntent(this, address) if (resolvedExtra != null) { intent.setDataAndType(resolvedExtra, mimeType) @@ -229,7 +219,8 @@ class ShareActivity : ScreenLockActionBarActivity(), OnContactSelectedListener { intent.setType("text/plain") } - return intent + isPassingAlongMedia = true + startActivity(intent) } private fun getMimeType(uri: Uri?): String? { @@ -241,8 +232,7 @@ class ShareActivity : ScreenLockActionBarActivity(), OnContactSelectedListener { } override fun onContactSelected(number: String) { - val existingThread = get(this).threadDatabase().getThreadIdIfExistsFor(number) - createConversation(existingThread, Address.fromSerialized(number), DistributionTypes.DEFAULT) + createConversation(Address.fromSerialized(number)) } override fun onContactDeselected(number: String?) { /* Nothing */ } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.kt index 67499e5a0c..878cebad10 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.kt @@ -14,7 +14,6 @@ import network.loki.messenger.R import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 -import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.home.HomeActivity class ShortcutLauncherActivity : AppCompatActivity() { @@ -36,14 +35,8 @@ class ShortcutLauncherActivity : AppCompatActivity() { // start the appropriate conversation activity and finish this one lifecycleScope.launch(Dispatchers.Default) { - val context = this@ShortcutLauncherActivity - val address = fromSerialized(serializedAddress) - val threadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(address) - - val intent = Intent(context, ConversationActivityV2::class.java) - intent.putExtra(ConversationActivityV2.ADDRESS, address) - intent.putExtra(ConversationActivityV2.THREAD_ID, threadId) + val intent = ConversationActivityV2.createIntent(this@ShortcutLauncherActivity, address = address) backStack.addNextIntent(intent) backStack.startActivities() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationFragment.kt index c6f3a5f809..7bd927e8d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationFragment.kt @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.conversation.start import android.app.Dialog -import android.content.Intent import android.content.res.Resources import android.os.Build import android.os.Bundle @@ -100,9 +99,7 @@ class StartConversationFragment : BottomSheetDialogFragment(), StartConversation } override fun onContactSelected(address: String) { - val intent = Intent(requireContext(), ConversationActivityV2::class.java) - intent.putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(address)) - requireContext().startActivity(intent) + startActivity(ConversationActivityV2.createIntent(requireContext(), Address.fromSerialized(address))) requireActivity().overridePendingTransition(R.anim.slide_from_bottom, R.anim.fade_scale_out) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessageFragment.kt index 2169a65915..b6fc190915 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessageFragment.kt @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.conversation.start.newmessage -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -14,7 +13,6 @@ import kotlinx.coroutines.launch import org.session.libsession.utilities.Address import org.thoughtcrime.securesms.conversation.start.StartConversationDelegate import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 -import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.openUrl import org.thoughtcrime.securesms.ui.createThemedComposeView @@ -50,10 +48,8 @@ class NewMessageFragment : Fragment() { private fun createPrivateChat(hexEncodedPublicKey: String) { val address = Address.fromSerialized(hexEncodedPublicKey) - Intent(requireContext(), ConversationActivityV2::class.java).apply { - putExtra(ConversationActivityV2.ADDRESS, address) + ConversationActivityV2.createIntent(requireContext(), address).apply { setDataAndType(requireActivity().intent.data, requireActivity().intent.type) - putExtra(ConversationActivityV2.THREAD_ID, DatabaseComponent.get(requireContext()).threadDatabase().getThreadIdIfExistsFor(address)) }.let(requireContext()::startActivity) delegate.onDialogClosePressed() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index a23e0c7758..7fedd45156 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -24,7 +24,6 @@ import android.text.Spannable import android.text.SpannableStringBuilder import android.text.TextUtils import android.text.style.ImageSpan -import android.util.Pair import android.util.TypedValue import android.view.ActionMode import android.view.MotionEvent @@ -96,7 +95,6 @@ import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized -import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.MediaTypes import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.CONVERSATION_NAME_KEY @@ -107,10 +105,7 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED import org.session.libsession.utilities.concurrent.SimpleTask import org.session.libsession.utilities.getColorFromAttr -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.MnemonicCodec -import org.session.libsignal.utilities.AccountId -import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.ListenableFuture import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.hexEncodedPrivateKey @@ -161,7 +156,6 @@ import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.database.MmsDatabase import org.thoughtcrime.securesms.database.MmsSmsDatabase import org.thoughtcrime.securesms.database.ReactionDatabase -import org.thoughtcrime.securesms.database.SessionContactDatabase import org.thoughtcrime.securesms.database.SmsDatabase import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.GroupThreadStatus @@ -217,7 +211,6 @@ import org.thoughtcrime.securesms.webrtc.WebRtcCallActivity.Companion.ACTION_STA import org.thoughtcrime.securesms.webrtc.WebRtcCallBridge.Companion.EXTRA_RECIPIENT_ADDRESS import java.io.File import java.util.LinkedList -import java.util.Locale import java.util.concurrent.ExecutionException import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicLong @@ -281,27 +274,34 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, .get(LinkPreviewViewModel::class.java) } - private val threadId: Long by lazy { - var threadId = intent.getLongExtra(THREAD_ID, -1L) - if (threadId == -1L) { - intent.getParcelableExtra
(ADDRESS)?.let { it -> - threadId = threadDb.getThreadIdIfExistsFor(it.toString()) - if (threadId == -1L) { - val accountId = AccountId(it.toString()) - val openGroup = lokiThreadDb.getOpenGroupChat(intent.getLongExtra(FROM_GROUP_THREAD_ID, -1)) - val address = if (accountId.prefix == IdPrefix.BLINDED && openGroup != null) { - storage.getOrCreateBlindedIdMapping(accountId.hexString, openGroup.server, openGroup.publicKey).accountId?.let { - fromSerialized(it) - } ?: GroupUtil.getEncodedOpenGroupInboxID(openGroup, accountId) - } else { - it - } - threadId = storage.getOrCreateThreadIdFor(address) - } - } ?: finish() + private val address: Address by lazy { + requireNotNull(IntentCompat.getParcelableExtra
(intent, ADDRESS, Address::class.java)) { + "Address must be provided in the intent extras" } + } - threadId + private val threadId: Long by lazy { + TODO() +// var threadId = intent.getLongExtra(THREAD_ID, -1L) +// if (threadId == -1L) { +// intent.getParcelableExtra
(ADDRESS)?.let { it -> +// threadId = threadDb.getThreadIdIfExistsFor(it.toString()) +// if (threadId == -1L) { +// val accountId = AccountId(it.toString()) +// val openGroup = lokiThreadDb.getOpenGroupChat(intent.getLongExtra(FROM_GROUP_THREAD_ID, -1)) +// val address = if (accountId.prefix == IdPrefix.BLINDED && openGroup != null) { +// storage.getOrCreateBlindedIdMapping(accountId.hexString, openGroup.server, openGroup.publicKey).accountId?.let { +// fromSerialized(it) +// } ?: GroupUtil.getEncodedOpenGroupInboxID(openGroup, accountId) +// } else { +// it +// } +// threadId = storage.getOrCreateThreadIdFor(address) +// } +// } ?: finish() +// } +// +// threadId } private val viewModel: ConversationViewModel by viewModels(extrasProducer = { @@ -498,17 +498,40 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, // region Settings companion object { // Extras - const val THREAD_ID = "thread_id" - const val ADDRESS = "address" - const val FROM_GROUP_THREAD_ID = "from_group_thread_id" - const val SCROLL_MESSAGE_ID = "scroll_message_id" - const val SCROLL_MESSAGE_AUTHOR = "scroll_message_author" + private const val ADDRESS = "address" + private const val FROM_GROUP_THREAD_ID = "from_group_thread_id" + private const val SCROLL_MESSAGE_ID = "scroll_message_id" + private const val SCROLL_MESSAGE_AUTHOR = "scroll_message_author" + + const val SHOW_SEARCH = "show_search" + // Request codes const val PICK_DOCUMENT = 2 const val TAKE_PHOTO = 7 const val PICK_GIF = 10 const val PICK_FROM_LIBRARY = 12 + + @JvmOverloads + fun createIntent( + context: Context, + address: Address, + fromGroupThreadId: Long? = null, + // If provided, this will scroll to the message with the given timestamp and author (TODO: use message id instead) + scrollToMessage: Pair? = null + ): Intent { + return Intent(context, ConversationActivityV2::class.java).apply { + putExtra(ADDRESS, address) + fromGroupThreadId?.let { + putExtra(FROM_GROUP_THREAD_ID, it) + } + + scrollToMessage?.let { (timestamp, author) -> + putExtra(SCROLL_MESSAGE_ID, timestamp) + putExtra(SCROLL_MESSAGE_AUTHOR, author) + } + } + } } // endregion @@ -660,8 +683,8 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, when (event) { is ConversationUiEvent.NavigateToConversation -> { finish() - startActivity(Intent(this@ConversationActivityV2, ConversationActivityV2::class.java) - .putExtra(THREAD_ID, event.threadId) + startActivity( + createIntent(this@ConversationActivityV2, event.address) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index ee523db0e4..9bec4eaea9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -1247,7 +1247,7 @@ class ConversationViewModel @AssistedInject constructor( } is Commands.NavigateToConversation -> { - _uiEvents.tryEmit(ConversationUiEvent.NavigateToConversation(command.threadId)) + _uiEvents.tryEmit(ConversationUiEvent.NavigateToConversation(command.address)) } } } @@ -1377,8 +1377,7 @@ class ConversationViewModel @AssistedInject constructor( } fun getUsername(accountId: String) = recipientRepository - .getRecipientSync(fromSerialized(accountId)) - .displayNameOrFallback(address = accountId) + .getRecipientDisplayNameSync(fromSerialized(accountId)) fun onSearchOpened(){ _appBarData.update { _appBarData.value.copy(showSearch = true) } @@ -1497,7 +1496,7 @@ class ConversationViewModel @AssistedInject constructor( data object HideRecreateGroupConfirm : Commands data object HideRecreateGroup : Commands data object HideSessionProCTA : Commands - data class NavigateToConversation(val threadId: Long) : Commands + data class NavigateToConversation(val address: Address) : Commands } } @@ -1534,7 +1533,7 @@ sealed interface InputBarContentState { sealed interface ConversationUiEvent { - data class NavigateToConversation(val threadId: Long) : ConversationUiEvent + data class NavigateToConversation(val address: Address) : ConversationUiEvent data class ShowDisappearingMessages(val threadId: Long) : ConversationUiEvent data class ShowNotificationSettings(val threadId: Long) : ConversationUiEvent data class ShowGroupMembers(val groupId: String) : ConversationUiEvent diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 3e26dca0e8..327a0fae1c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -209,10 +209,13 @@ class VisibleMessageView : FrameLayout { val openGroup = lokiThreadDb.getOpenGroupChat(message.threadId) if (IdPrefix.fromValue(sender.address.address) == IdPrefix.BLINDED && openGroup?.canWrite == true) { // TODO: support v2 soon - val intent = Intent(context, ConversationActivityV2::class.java) - intent.putExtra(ConversationActivityV2.FROM_GROUP_THREAD_ID, message.threadId) - intent.putExtra(ConversationActivityV2.ADDRESS, sender.address) - context.startActivity(intent) + context.startActivity( + ConversationActivityV2.createIntent( + context = context, + address = sender.address, + fromGroupThreadId = message.threadId + ) + ) } } else { maybeShowUserDetails(sender.address.address, message.threadId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt index 6a27b213a6..8d857e8c56 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt @@ -26,10 +26,9 @@ class CreateGroupFragment : Fragment() { setContent { SessionMaterialTheme { CreateGroupScreen( - onNavigateToConversationScreen = { threadID -> + onNavigateToConversationScreen = { address -> startActivity( - Intent(requireContext(), ConversationActivityV2::class.java) - .putExtra(ConversationActivityV2.THREAD_ID, threadID) + ConversationActivityV2.createIntent(requireContext(), address) ) }, onBack = delegate::onDialogBackPressed, diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt index dde24bd550..d9268aa386 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.withContext import network.loki.messenger.R import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupManagerV2 +import org.session.libsession.utilities.Address import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.textSizeInBytes import org.thoughtcrime.securesms.database.GroupDatabase @@ -128,8 +129,7 @@ class CreateGroupViewModel @AssistedInject constructor( } else -> { - val threadId = withContext(Dispatchers.Default) { storage.getOrCreateThreadIdFor(recipient.address) } - mutableEvents.emit(CreateGroupEvent.NavigateToConversation(threadId)) + mutableEvents.emit(CreateGroupEvent.NavigateToConversation(recipient.address)) } } @@ -150,7 +150,7 @@ class CreateGroupViewModel @AssistedInject constructor( } sealed interface CreateGroupEvent { - data class NavigateToConversation(val threadID: Long): CreateGroupEvent + data class NavigateToConversation(val address: Address): CreateGroupEvent data class Error(val message: String): CreateGroupEvent } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMembersViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMembersViewModel.kt index 04d2b845ef..c7dbb8c86a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMembersViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMembersViewModel.kt @@ -24,8 +24,8 @@ import org.thoughtcrime.securesms.util.AvatarUtils @HiltViewModel(assistedFactory = GroupMembersViewModel.Factory::class) class GroupMembersViewModel @AssistedInject constructor( @Assisted private val groupId: AccountId, - @ApplicationContext private val context: Context, - private val storage: StorageProtocol, + @param:ApplicationContext private val context: Context, + storage: StorageProtocol, configFactory: ConfigFactoryProtocol, avatarUtils: AvatarUtils, recipientRepository: RecipientRepository, @@ -42,16 +42,10 @@ class GroupMembersViewModel @AssistedInject constructor( fun onMemberClicked(accountId: AccountId) { viewModelScope.launch(Dispatchers.Default) { val address = Address.fromSerialized(accountId.hexString) - val threadId = storage.getThreadId(address) - val intent = Intent( - context, - ConversationActivityV2::class.java - ) - intent.putExtra(ConversationActivityV2.ADDRESS, address) - intent.putExtra(ConversationActivityV2.THREAD_ID, threadId) - - _navigationActions.send(intent) + _navigationActions.send(ConversationActivityV2.createIntent( + context, address + )) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt index 4d6c8c5d92..c4daacb706 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.groups import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.content.Context -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -113,12 +112,13 @@ class JoinCommunityFragment : Fragment() { ) val storage = MessagingModuleConfiguration.shared.storage storage.onOpenGroupAdded(sanitizedServer, openGroup.room) - val threadID = - GroupManager.getOpenGroupThreadID(openGroupID, requireContext()) val groupID = GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray()) withContext(Dispatchers.Main) { - openConversationActivity(requireContext(), threadID, Address.fromSerialized(groupID)) + openConversationActivity( + requireContext(), + Address.fromSerialized(groupID) + ) delegate.onDialogClosePressed() } } catch (e: Exception) { @@ -149,11 +149,10 @@ class JoinCommunityFragment : Fragment() { mediator.attach() } - private fun openConversationActivity(context: Context, threadId: Long, address: Address) { - val intent = Intent(context, ConversationActivityV2::class.java) - intent.putExtra(ConversationActivityV2.THREAD_ID, threadId) - intent.putExtra(ConversationActivityV2.ADDRESS, address) - context.startActivity(intent) + private fun openConversationActivity(context: Context, address: Address) { + context.startActivity( + ConversationActivityV2.createIntent(context, address) + ) } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroupScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroupScreen.kt index 396d19d834..4944b79dc3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroupScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroupScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel import network.loki.messenger.R +import org.session.libsession.utilities.Address import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.groups.ContactItem import org.thoughtcrime.securesms.groups.CreateGroupEvent @@ -50,7 +51,7 @@ import org.thoughtcrime.securesms.util.AvatarUIElement @Composable fun CreateGroupScreen( fromLegacyGroupId: String?, - onNavigateToConversationScreen: (threadID: Long) -> Unit, + onNavigateToConversationScreen: (address: Address) -> Unit, onBack: () -> Unit, onClose: () -> Unit, ) { @@ -65,7 +66,7 @@ fun CreateGroupScreen( when (event) { is CreateGroupEvent.NavigateToConversation -> { onClose() - onNavigateToConversationScreen(event.threadID) + onNavigateToConversationScreen(event.address) } is CreateGroupEvent.Error -> { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 5fd72855f9..627855af51 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -130,47 +130,35 @@ class HomeActivity : ScreenLockActionBarActivity(), GlobalSearchAdapter( dateUtils = dateUtils, onContactClicked = { model -> - when (model) { - is GlobalSearchAdapter.Model.Message -> push { - model.messageResult.run { - putExtra(ConversationActivityV2.THREAD_ID, threadId) - putExtra(ConversationActivityV2.SCROLL_MESSAGE_ID, sentTimestampMs) - putExtra( - ConversationActivityV2.SCROLL_MESSAGE_AUTHOR, - messageRecipient.address - ) - } - } + val intent = when (model) { + is GlobalSearchAdapter.Model.Message -> ConversationActivityV2 + .createIntent(this, + address = model.messageResult.conversationRecipient.address, + scrollToMessage = model.messageResult.sentTimestampMs to model.messageResult.messageRecipient.address + ) - is GlobalSearchAdapter.Model.SavedMessages -> push { - putExtra( - ConversationActivityV2.ADDRESS, - Address.fromSerialized(model.currentUserPublicKey) + is GlobalSearchAdapter.Model.SavedMessages -> ConversationActivityV2 + .createIntent(this, + address = Address.fromSerialized(model.currentUserPublicKey) ) - } - is GlobalSearchAdapter.Model.Contact -> push { - putExtra( - ConversationActivityV2.ADDRESS, - model.contact.hexString.let(Address::fromSerialized) + is GlobalSearchAdapter.Model.Contact -> ConversationActivityV2 + .createIntent(this, + address = Address.fromSerialized(model.contact.hexString) ) - } - is GlobalSearchAdapter.Model.GroupConversation -> model.groupId - .let { Address.fromSerialized(it) } - .let(threadDb::getThreadIdIfExistsFor) - .takeIf { it >= 0 } - ?.let { - push { - putExtra( - ConversationActivityV2.THREAD_ID, - it - ) - } - } + is GlobalSearchAdapter.Model.GroupConversation -> ConversationActivityV2 + .createIntent(this, + address = Address.fromSerialized(model.groupId) + ) - else -> Log.d("Loki", "callback with model: $model") + else -> { + Log.d("Loki", "callback with model: $model") + return@GlobalSearchAdapter + } } + + push(intent) }, onContactLongPressed = { model -> onSearchContactLongPress(model.contact.hexString, model.name) @@ -512,9 +500,7 @@ class HomeActivity : ScreenLockActionBarActivity(), } override fun onConversationClick(thread: ThreadRecord) { - val intent = Intent(this, ConversationActivityV2::class.java) - intent.putExtra(ConversationActivityV2.THREAD_ID, thread.threadId) - push(intent) + push(ConversationActivityV2.createIntent(this, address = thread.recipient.address)) } override fun onLongConversationClick(thread: ThreadRecord) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt index d0a4193e4a..3ab9b3c43a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt @@ -4,7 +4,6 @@ import android.annotation.SuppressLint import android.content.ClipData import android.content.ClipboardManager import android.content.Context -import android.content.Intent import android.os.Bundle import android.view.ContextThemeWrapper import android.view.LayoutInflater @@ -18,7 +17,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.FragmentUserDetailsBottomSheetBinding -import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.BasicRecipient import org.session.libsession.utilities.recipients.Recipient @@ -109,15 +107,13 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() { true } messageButton.setOnClickListener { - val threadId = MessagingModuleConfiguration.shared.storage.getThreadId(recipient.address) - val intent = Intent( - context, - ConversationActivityV2::class.java + startActivity( + ConversationActivityV2.createIntent( + requireContext(), + address = recipient.address, + fromGroupThreadId = threadID + ) ) - intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) - intent.putExtra(ConversationActivityV2.THREAD_ID, threadId ?: -1) - intent.putExtra(ConversationActivityV2.FROM_GROUP_THREAD_ID, threadID) - startActivity(intent) dismiss() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt index 0f9b4a7c8d..274e24ba95 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt @@ -80,9 +80,7 @@ class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClick } override fun onConversationClick(thread: ThreadRecord) { - val intent = Intent(this, ConversationActivityV2::class.java) - intent.putExtra(ConversationActivityV2.THREAD_ID, thread.threadId) - push(intent) + push(ConversationActivityV2.createIntent(this, thread.recipient.address)) } override fun onBlockConversationClick(thread: ThreadRecord) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java index 8a4f608a39..f2712499a9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java @@ -69,11 +69,9 @@ public long getThreadId() { } public PendingIntent getPendingIntent(Context context) { - Intent intent = new Intent(context, ConversationActivityV2.class); - Recipient notifyRecipients = threadRecipient != null ? threadRecipient : conversationRecipient; - if (notifyRecipients != null) intent.putExtra(ConversationActivityV2.ADDRESS, notifyRecipients.getAddress()); - intent.putExtra(ConversationActivityV2.THREAD_ID, threadId); + Recipient notifyRecipients = threadRecipient != null ? threadRecipient : conversationRecipient; + final Intent intent = ConversationActivityV2.Companion.createIntent(context, notifyRecipients.getAddress()); intent.setData((Uri.parse("custom://"+System.currentTimeMillis()))); int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT; diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java index 233b786539..7a52d11d04 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java @@ -161,9 +161,7 @@ public PendingIntent getAndroidAutoHeardIntent(Context context, int notification public PendingIntent getQuickReplyIntent(Context context, Recipient recipient) { if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications! " + threads.size()); - Intent intent = new Intent(context, ConversationActivityV2.class); - intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress()); - intent.putExtra(ConversationActivityV2.THREAD_ID, (long)threads.toArray()[0]); + final Intent intent = ConversationActivityV2.Companion.createIntent(context, recipient.getAddress()); intent.setData((Uri.parse("custom://"+System.currentTimeMillis()))); int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT; diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt index 5dc0dbc716..bbeb40a32e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.preferences +import android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP import android.os.Bundle import android.view.View import androidx.compose.foundation.background @@ -13,9 +14,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.updatePadding import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -26,7 +24,6 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.PublicKeyValidation import org.thoughtcrime.securesms.ScreenLockActionBarActivity import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 -import org.thoughtcrime.securesms.database.threadDatabase import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.ui.components.QRScannerScreen import org.thoughtcrime.securesms.ui.components.QrImage @@ -37,7 +34,6 @@ import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.theme.LocalType import org.thoughtcrime.securesms.util.applySafeInsetsPaddings -import org.thoughtcrime.securesms.util.start private val TITLES = listOf(R.string.view, R.string.scan) @@ -73,12 +69,12 @@ class QRCodeActivity : ScreenLockActionBarActivity() { errors.tryEmit(getString(R.string.qrNotAccountId)) } else if (!isFinishing) { val address = Address.fromSerialized(string) - start { - putExtra(ConversationActivityV2.ADDRESS, address) - setDataAndType(intent.data, intent.type) - val existingThread = threadDatabase().getThreadIdIfExistsFor(address) - putExtra(ConversationActivityV2.THREAD_ID, existingThread) - } + startActivity( + ConversationActivityV2.createIntent(this, address = address) + .setDataAndType(intent.data, intent.type) + .addFlags(FLAG_ACTIVITY_SINGLE_TOP) + ) + finish() } } From a42ea6c91c8468d4571a154d7dfcbc1fcf0d01c9 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:41:38 +1000 Subject: [PATCH 24/52] Tidy up --- .../libsession/database/StorageProtocol.kt | 5 +- .../sending_receiving/MessageSender.kt | 2 - .../libsession/utilities/ProfileKeyUtil.java | 25 +--- .../utilities/ProfilePictureUtilities.kt | 118 ++++++++---------- .../utilities/TextSecurePreferences.kt | 54 -------- .../org/session/libsession/utilities/Util.kt | 2 +- .../utilities/recipients/Recipient.kt | 20 ++- .../securesms/configs/ConfigToDatabaseSync.kt | 23 +--- .../DisappearingMessages.kt | 6 +- .../DisappearingMessagesActivity.kt | 14 ++- .../DisappearingMessagesViewModel.kt | 18 ++- .../conversation/v2/ConversationActivityV2.kt | 39 +----- .../conversation/v2/ConversationViewModel.kt | 14 ++- .../v2/mention/MentionViewModel.kt | 12 +- .../settings/ConversationSettingsActivity.kt | 11 +- .../settings/ConversationSettingsNavHost.kt | 16 +-- .../settings/ConversationSettingsViewModel.kt | 49 +++----- .../NotificationSettingsActivity.kt | 15 +-- .../NotificationSettingsViewModel.kt | 9 +- .../securesms/database/LokiThreadDatabase.kt | 8 +- .../securesms/database/RecipientRepository.kt | 42 +++++-- .../securesms/database/Storage.kt | 40 +++--- .../securesms/database/ThreadDatabase.java | 26 +--- .../securesms/database/model/NotifyType.java | 13 -- .../database/model/ThreadRecord.java | 4 +- .../securesms/debugmenu/DebugMenuViewModel.kt | 5 +- .../securesms/groups/ClosedGroupManager.kt | 28 ----- .../securesms/home/ConversationView.kt | 6 +- .../securesms/home/HomeActivity.kt | 13 +- .../securesms/home/HomeViewModel.kt | 42 ++++--- .../securesms/preferences/SettingsActivity.kt | 7 +- .../preferences/SettingsViewModel.kt | 13 +- 32 files changed, 248 insertions(+), 451 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/model/NotifyType.java diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 23d4dab23c..f9fff5423c 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -182,8 +182,7 @@ interface StorageProtocol { fun trimThread(threadID: Long, threadLimit: Int) fun trimThreadBefore(threadID: Long, timestamp: Long) fun getMessageCount(threadID: Long): Long - fun setPinned(threadID: Long, isPinned: Boolean) - fun isPinned(threadID: Long): Boolean + fun setPinned(address: Address, isPinned: Boolean) fun deleteConversation(threadID: Long) fun setThreadCreationDate(threadId: Long, newDate: Long) fun getLastLegacyRecipient(threadRecipient: String): String? @@ -255,7 +254,7 @@ interface StorageProtocol { fun setBlocked(recipients: Iterable
, isBlocked: Boolean, fromConfigUpdate: Boolean = false) fun blockedContacts(): List fun getExpirationConfiguration(threadId: Long): ExpiryMode - fun setExpirationConfiguration(threadId: Long, expiryMode: ExpiryMode) + fun setExpirationConfiguration(address: Address, expiryMode: ExpiryMode) fun getExpiringMessages(messageIds: List = emptyList()): List> // Shared configs diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 3573fe5ca3..b1fe03d34f 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -547,8 +547,6 @@ object MessageSender { // only show the NTS if it is currently marked as hidden MessagingModuleConfiguration.shared.configFactory.withUserConfigs { it.userProfile.getNtsPriority() == PRIORITY_HIDDEN } ){ - // make sure note to self is not hidden - MessagingModuleConfiguration.shared.preferences.setHasHiddenNoteToSelf(false) // update config in case it was marked as hidden there MessagingModuleConfiguration.shared.configFactory.withMutableUserConfigs { it.userProfile.setNtsPriority(PRIORITY_VISIBLE) diff --git a/app/src/main/java/org/session/libsession/utilities/ProfileKeyUtil.java b/app/src/main/java/org/session/libsession/utilities/ProfileKeyUtil.java index 4550965ae7..b3034df568 100644 --- a/app/src/main/java/org/session/libsession/utilities/ProfileKeyUtil.java +++ b/app/src/main/java/org/session/libsession/utilities/ProfileKeyUtil.java @@ -1,9 +1,6 @@ package org.session.libsession.utilities; -import android.content.Context; - import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import org.session.libsignal.utilities.Base64; @@ -13,22 +10,8 @@ public class ProfileKeyUtil { public static final int PROFILE_KEY_BYTES = 32; - public static synchronized @NonNull byte[] getProfileKey(@NonNull Context context) { - try { - String encodedProfileKey = TextSecurePreferences.getProfileKey(context); - - if (encodedProfileKey == null) { - encodedProfileKey = Util.getSecret(PROFILE_KEY_BYTES); - TextSecurePreferences.setProfileKey(context, encodedProfileKey); - } - return Base64.decode(encodedProfileKey); - } catch (IOException e) { - throw new AssertionError(e); - } - } - - public static synchronized @NonNull byte[] getProfileKeyFromEncodedString(String encodedProfileKey) { + public static @NonNull byte[] getProfileKeyFromEncodedString(String encodedProfileKey) { try { return Base64.decode(encodedProfileKey); } catch (IOException e) { @@ -36,11 +19,7 @@ public class ProfileKeyUtil { } } - public static synchronized @NonNull String generateEncodedProfileKey(@NonNull Context context) { + public static @NonNull String generateEncodedProfileKey() { return Util.getSecret(PROFILE_KEY_BYTES); } - - public static synchronized void setEncodedProfileKey(@NonNull Context context, @Nullable String key) { - TextSecurePreferences.setProfileKey(context, key); - } } diff --git a/app/src/main/java/org/session/libsession/utilities/ProfilePictureUtilities.kt b/app/src/main/java/org/session/libsession/utilities/ProfilePictureUtilities.kt index b682fbf193..843f4adff7 100644 --- a/app/src/main/java/org/session/libsession/utilities/ProfilePictureUtilities.kt +++ b/app/src/main/java/org/session/libsession/utilities/ProfilePictureUtilities.kt @@ -2,86 +2,72 @@ package org.session.libsession.utilities import android.content.Context import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import network.loki.messenger.libsession_util.util.Bytes -import network.loki.messenger.libsession_util.util.UserPic import okio.Buffer -import org.session.libsession.avatars.AvatarHelper -import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.file_server.FileServerApi import org.session.libsession.snode.utilities.await -import org.session.libsession.utilities.Address.Companion.fromSerialized -import org.session.libsession.utilities.TextSecurePreferences.Companion.getLastProfilePictureUpload -import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber -import org.session.libsession.utilities.TextSecurePreferences.Companion.getProfileKey -import org.session.libsession.utilities.TextSecurePreferences.Companion.setLastProfilePictureUpload import org.session.libsignal.streams.DigestingRequestBody import org.session.libsignal.streams.ProfileCipherOutputStream import org.session.libsignal.streams.ProfileCipherOutputStreamFactory -import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.ProfileAvatarData import org.session.libsignal.utilities.retryIfNeeded import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream import java.util.Date object ProfilePictureUtilities { @OptIn(DelicateCoroutinesApi::class) fun resubmitProfilePictureIfNeeded(context: Context) { - GlobalScope.launch(Dispatchers.IO) { - // Files expire on the file server after a while, so we simply re-upload the user's profile picture - // at a certain interval to ensure it's always available. - val userPublicKey = getLocalNumber(context) ?: return@launch - val now = Date().time - val lastProfilePictureUpload = getLastProfilePictureUpload(context) - if (now - lastProfilePictureUpload <= 14 * 24 * 60 * 60 * 1000) return@launch - - // Don't generate a new profile key here; we do that when the user changes their profile picture - Log.d("Loki-Avatar", "Uploading Avatar Started") - val encodedProfileKey = getProfileKey(context) - try { - // Read the file into a byte array - val inputStream = AvatarHelper.getInputStreamFor( - context, - fromSerialized(userPublicKey) - ) - val baos = ByteArrayOutputStream() - var count: Int - val buffer = ByteArray(1024) - while ((inputStream.read(buffer, 0, buffer.size) - .also { count = it }) != -1 - ) { - baos.write(buffer, 0, count) - } - baos.flush() - val profilePicture = baos.toByteArray() - // Re-upload it - val url = upload( - profilePicture, - encodedProfileKey!!, - context - ) - - // Update the last profile picture upload date - setLastProfilePictureUpload( - context, - Date().time - ) - - // update config with new URL for reuploaded file - val profileKey = ProfileKeyUtil.getProfileKey(context) - MessagingModuleConfiguration.shared.configFactory.withMutableUserConfigs { - it.userProfile.setPic(UserPic(url, Bytes(profileKey))) - } - - Log.d("Loki-Avatar", "Uploading Avatar Finished") - } catch (e: Exception) { - Log.e("Loki-Avatar", "Uploading avatar failed.") - } - } +// GlobalScope.launch(Dispatchers.IO) { +// // Files expire on the file server after a while, so we simply re-upload the user's profile picture +// // at a certain interval to ensure it's always available. +// val userPublicKey = getLocalNumber(context) ?: return@launch +// val now = Date().time +// val lastProfilePictureUpload = getLastProfilePictureUpload(context) +// if (now - lastProfilePictureUpload <= 14 * 24 * 60 * 60 * 1000) return@launch +// +// // Don't generate a new profile key here; we do that when the user changes their profile picture +// Log.d("Loki-Avatar", "Uploading Avatar Started") +// val encodedProfileKey = getProfileKey(context) +// try { +// // Read the file into a byte array +// val inputStream = AvatarHelper.getInputStreamFor( +// context, +// fromSerialized(userPublicKey) +// ) +// val baos = ByteArrayOutputStream() +// var count: Int +// val buffer = ByteArray(1024) +// while ((inputStream.read(buffer, 0, buffer.size) +// .also { count = it }) != -1 +// ) { +// baos.write(buffer, 0, count) +// } +// baos.flush() +// val profilePicture = baos.toByteArray() +// // Re-upload it +// val url = upload( +// profilePicture, +// encodedProfileKey!!, +// context +// ) +// +// // Update the last profile picture upload date +// setLastProfilePictureUpload( +// context, +// Date().time +// ) +// +// // update config with new URL for reuploaded file +// val profileKey = ProfileKeyUtil.getProfileKey(context) +// MessagingModuleConfiguration.shared.configFactory.withMutableUserConfigs { +// it.userProfile.setPic(UserPic(url, Bytes(profileKey))) +// } +// +// Log.d("Loki-Avatar", "Uploading Avatar Finished") +// } catch (e: Exception) { +// Log.e("Loki-Avatar", "Uploading avatar failed.") +// } +// } } suspend fun upload(profilePicture: ByteArray, encodedProfileKey: String, context: Context): String { @@ -113,7 +99,7 @@ object ProfilePictureUtilities { TextSecurePreferences.setLastProfilePictureUpload(context, Date().time) val url = "${FileServerApi.FILE_SERVER_URL}/file/$id" - TextSecurePreferences.setProfilePictureURL(context, url) +// TextSecurePreferences.setProfilePictureURL(context, url) return url } diff --git a/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 6704624b5a..5cb96acbbf 100644 --- a/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -25,7 +25,6 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_ import org.session.libsession.utilities.TextSecurePreferences.Companion.ENVIRONMENT import org.session.libsession.utilities.TextSecurePreferences.Companion.FOLLOW_SYSTEM_SETTINGS import org.session.libsession.utilities.TextSecurePreferences.Companion.HAS_HIDDEN_MESSAGE_REQUESTS -import org.session.libsession.utilities.TextSecurePreferences.Companion.HAS_HIDDEN_NOTE_TO_SELF import org.session.libsession.utilities.TextSecurePreferences.Companion.HAVE_SHOWN_A_NOTIFICATION_ABOUT_TOKEN_PAGE import org.session.libsession.utilities.TextSecurePreferences.Companion.HIDE_PASSWORD import org.session.libsession.utilities.TextSecurePreferences.Companion.LAST_VACUUM_TIME @@ -170,8 +169,6 @@ interface TextSecurePreferences { fun setForceIncomingMessagesAsPro(hidden: Boolean) fun forcePostPro(): Boolean fun setForcePostPro(hidden: Boolean) - fun hasHiddenNoteToSelf(): Boolean - fun setHasHiddenNoteToSelf(hidden: Boolean) fun setShownCallWarning(): Boolean fun setShownCallNotification(): Boolean fun isCallNotificationsEnabled(): Boolean @@ -286,7 +283,6 @@ interface TextSecurePreferences { const val LAST_PROFILE_UPDATE_TIME = "pref_last_profile_update_time" const val LAST_OPEN_DATE = "pref_last_open_date" const val HAS_HIDDEN_MESSAGE_REQUESTS = "pref_message_requests_hidden" - const val HAS_HIDDEN_NOTE_TO_SELF = "pref_note_to_self_hidden" const val SET_FORCE_CURRENT_USER_PRO = "pref_force_current_user_pro" const val SET_FORCE_INCOMING_MESSAGE_PRO = "pref_force_incoming_message_pro" const val SET_FORCE_POST_PRO = "pref_force_post_pro" @@ -535,46 +531,6 @@ interface TextSecurePreferences { setBooleanPreference(context, GIF_GRID_LAYOUT, isGrid) } - @JvmStatic - fun getProfileKey(context: Context): String? { - return getStringPreference(context, PROFILE_KEY_PREF, null) - } - - @JvmStatic - fun setProfileKey(context: Context, key: String?) { - setStringPreference(context, PROFILE_KEY_PREF, key) - } - - @JvmStatic - fun setProfileName(context: Context, name: String?) { - setStringPreference(context, PROFILE_NAME_PREF, name) - _events.tryEmit(PROFILE_NAME_PREF) - } - - @JvmStatic - fun getProfileName(context: Context): String? { - return getStringPreference(context, PROFILE_NAME_PREF, null) - } - - @JvmStatic - fun setProfileAvatarId(context: Context, id: Int) { - setIntegerPreference(context, PROFILE_AVATAR_ID_PREF, id) - } - - @JvmStatic - fun getProfileAvatarId(context: Context): Int { - return getIntegerPreference(context, PROFILE_AVATAR_ID_PREF, 0) - } - - fun setProfilePictureURL(context: Context, url: String?) { - setStringPreference(context, PROFILE_AVATAR_URL_PREF, url) - } - - @JvmStatic - fun getProfilePictureURL(context: Context): String? { - return getStringPreference(context, PROFILE_AVATAR_URL_PREF, null) - } - @JvmStatic fun getNotificationPrivacy(context: Context): NotificationPrivacyPreference { return NotificationPrivacyPreference(getStringPreference(context, NOTIFICATION_PRIVACY_PREF, "all")) @@ -1553,16 +1509,6 @@ class AppTextSecurePreferences @Inject constructor( setBooleanPreference(HAS_HIDDEN_MESSAGE_REQUESTS, hidden) _events.tryEmit(HAS_HIDDEN_MESSAGE_REQUESTS) } - - override fun hasHiddenNoteToSelf(): Boolean { - return getBooleanPreference(HAS_HIDDEN_NOTE_TO_SELF, false) - } - - override fun setHasHiddenNoteToSelf(hidden: Boolean) { - setBooleanPreference(HAS_HIDDEN_NOTE_TO_SELF, hidden) - _events.tryEmit(HAS_HIDDEN_NOTE_TO_SELF) - } - override fun forceCurrentUserAsPro(): Boolean { return getBooleanPreference(SET_FORCE_CURRENT_USER_PRO, false) } diff --git a/app/src/main/java/org/session/libsession/utilities/Util.kt b/app/src/main/java/org/session/libsession/utilities/Util.kt index 5afa6c09bd..50215e321c 100644 --- a/app/src/main/java/org/session/libsession/utilities/Util.kt +++ b/app/src/main/java/org/session/libsession/utilities/Util.kt @@ -271,7 +271,7 @@ object Util { } @JvmStatic - fun getSecret(size: Int): String? { + fun getSecret(size: Int): String { val secret = getSecretBytes(size) return Base64.encodeBytes(secret) } diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt index b1122eff69..9b1bf14757 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt @@ -1,12 +1,14 @@ package org.session.libsession.utilities.recipients +import androidx.annotation.IntDef +import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_PINNED +import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.util.Bytes import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.utilities.Address import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.database.RecipientDatabase -import org.thoughtcrime.securesms.database.model.NotifyType import java.time.ZonedDateTime data class Recipient( @@ -53,6 +55,10 @@ data class Recipient( else -> false } + val priority: Long get() = basic.priority + + val isPinned: Boolean get() = priority == PRIORITY_PINNED + @JvmOverloads fun isMuted(now: ZonedDateTime = ZonedDateTime.now()): Boolean { return mutedUntil?.isAfter(now) == true @@ -82,6 +88,7 @@ sealed interface BasicRecipient { val isLocalNumber: Boolean val displayName: String val avatar: RemoteFile? + val priority: Long /** * A recipient that is backed by the config system. @@ -94,6 +101,7 @@ sealed interface BasicRecipient { override val avatar: RemoteFile? = null, override val isLocalNumber: Boolean = false, val blocked: Boolean = false, + override val priority: Long = PRIORITY_VISIBLE, ) : BasicRecipient /** @@ -105,6 +113,7 @@ sealed interface BasicRecipient { override val avatar: RemoteFile.Encrypted?, val expiryMode: ExpiryMode, val acceptsCommunityMessageRequests: Boolean, + override val priority: Long, ) : ConfigBasedRecipient { override val displayName: String get() = name @@ -124,7 +133,8 @@ sealed interface BasicRecipient { val approved: Boolean, val approvedMe: Boolean, val blocked: Boolean, - val expiryMode: ExpiryMode + val expiryMode: ExpiryMode, + override val priority: Long, ) : ConfigBasedRecipient { override val displayName: String get() = nickname?.takeIf { it.isNotBlank() } ?: name @@ -142,6 +152,7 @@ sealed interface BasicRecipient { override val avatar: RemoteFile.Encrypted?, val expiryMode: ExpiryMode, val approved: Boolean, + override val priority: Long, ) : ConfigBasedRecipient { override val displayName: String get() = name @@ -214,6 +225,11 @@ class RecipientSettings( } } +@Retention(AnnotationRetention.SOURCE) +@IntDef(RecipientDatabase.NOTIFY_TYPE_MENTIONS, RecipientDatabase.NOTIFY_TYPE_ALL, RecipientDatabase.NOTIFY_TYPE_NONE) +annotation class NotifyType + + fun RemoteFile.toUserPic(): UserPic? { return when (this) { is RemoteFile.Encrypted -> UserPic(url, key) diff --git a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt index a04b654b68..9d6b9915c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt @@ -120,17 +120,12 @@ class ConfigToDatabaseSync @Inject constructor( val userPublicKey = storage.getUserPublicKey() ?: return val address = fromSerialized(userPublicKey) - if (userProfile.ntsPriority == PRIORITY_HIDDEN) { - // hide nts thread if needed - preferences.setHasHiddenNoteToSelf(true) - } else { + if (userProfile.ntsPriority != PRIORITY_HIDDEN) { // create note to self thread if needed (?) val ourThread = storage.getThreadId(address) ?: storage.getOrCreateThreadIdFor(address).also { storage.setThreadCreationDate(it, 0) } threadDatabase.setHasSent(ourThread, true) - storage.setPinned(ourThread, userProfile.ntsPriority > 0) - preferences.setHasHiddenNoteToSelf(false) } } @@ -261,15 +256,6 @@ class ConfigToDatabaseSync @Inject constructor( } } - for (groupInfo in userGroups.communityInfo) { - val groupBaseCommunity = groupInfo.community - if (groupBaseCommunity.fullUrl() in existingJoinUrls) { - // add it - val (threadId, _) = existingCommunities.entries.first { (_, v) -> v.joinURL == groupInfo.community.fullUrl() } - threadDatabase.setPinned(threadId, groupInfo.priority == PRIORITY_PINNED) - } - } - val existingClosedGroupThreads: Map = threadDatabase.readerFor(threadDatabase.conversationList).use { reader -> buildMap(reader.count) { var current = reader.next @@ -299,8 +285,6 @@ class ConfigToDatabaseSync @Inject constructor( groupThreadsToKeep[AccountId(closedGroup.groupAccountId)] = threadId - storage.setPinned(threadId, closedGroup.priority == PRIORITY_PINNED) - if (closedGroup.destroyed) { handleDestroyedGroup(threadId = threadId) } @@ -320,11 +304,6 @@ class ConfigToDatabaseSync @Inject constructor( if (group.priority == PRIORITY_HIDDEN && existingThread != null) { ClosedGroupManager.silentlyRemoveGroup(context,existingThread, GroupUtil.doubleDecodeGroupId(existingGroup.encodedId), existingGroup.encodedId, localUserPublicKey, delete = true) - } else if (existingThread == null) { - Log.w(TAG, "Existing group had no thread to hide") - } else { - Log.d(TAG, "Setting existing group pinned status to ${group.priority}") - threadDatabase.setPinned(existingThread, group.priority == PRIORITY_PINNED) } } else { val members = group.members.keys.map { fromSerialized(it) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt index 40314e45a1..e4fedc8d57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt @@ -29,8 +29,8 @@ class DisappearingMessages @Inject constructor( private val groupManagerV2: GroupManagerV2, private val clock: SnodeClock, ) { - fun set(threadId: Long, address: Address, mode: ExpiryMode, isGroup: Boolean) { - storage.setExpirationConfiguration(threadId, mode) + fun set(address: Address, mode: ExpiryMode, isGroup: Boolean) { + storage.setExpirationConfiguration(address, mode) if (address.isGroupV2) { groupManagerV2.setExpirationTimer(AccountId(address.toString()), mode) @@ -62,7 +62,7 @@ class DisappearingMessages @Inject constructor( text = if (message.expiresIn == 0L) R.string.confirm else R.string.set, contentDescriptionRes = if (message.expiresIn == 0L) R.string.AccessibilityId_confirm else R.string.AccessibilityId_setButton ) { - set(message.threadId, message.recipient.address, message.expiryMode, message.recipient.address.isGroup) + set(message.recipient.address, message.expiryMode, message.recipient.address.isGroup) } cancelButton() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesActivity.kt index 3c86834ebe..67d3a773a5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesActivity.kt @@ -1,26 +1,28 @@ package org.thoughtcrime.securesms.conversation.disappearingmessages import androidx.compose.runtime.Composable +import androidx.core.content.IntentCompat import androidx.hilt.navigation.compose.hiltViewModel import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.BuildConfig import org.session.libsession.messaging.messages.ExpirationConfiguration +import org.session.libsession.utilities.Address import org.thoughtcrime.securesms.FullComposeScreenLockActivity import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.DisappearingMessagesScreen @AndroidEntryPoint class DisappearingMessagesActivity: FullComposeScreenLockActivity() { - private val threadId: Long by lazy { - intent.getLongExtra(THREAD_ID, -1) - } - @Composable override fun ComposeContent() { val viewModel: DisappearingMessagesViewModel = hiltViewModel { factory -> factory.create( - threadId = threadId, + address = requireNotNull( + IntentCompat.getParcelableExtra(intent, ARG_ADDRESS, Address::class.java) + ) { + "DisappearingMessagesActivity requires an Address to be passed in via the intent." + }, isNewConfigEnabled = ExpirationConfiguration.isNewConfigEnabled, showDebugOptions = BuildConfig.DEBUG ) @@ -33,6 +35,6 @@ class DisappearingMessagesActivity: FullComposeScreenLockActivity() { } companion object { - const val THREAD_ID = "thread_id" + const val ARG_ADDRESS = "address" } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt index 015b2906b6..9ecc6dc9b9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt @@ -18,28 +18,27 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.libsession_util.util.ExpiryMode -import org.session.libsession.utilities.ConfigFactoryProtocol +import org.session.libsession.utilities.Address import org.session.libsession.utilities.TextSecurePreferences import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.UiState import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.toUiState import org.thoughtcrime.securesms.conversation.v2.settings.ConversationSettingsNavigator import org.thoughtcrime.securesms.database.GroupDatabase +import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.Storage -import org.thoughtcrime.securesms.database.ThreadDatabase @HiltViewModel(assistedFactory = DisappearingMessagesViewModel.Factory::class) class DisappearingMessagesViewModel @AssistedInject constructor( - @Assisted("threadId") private val threadId: Long, + @Assisted private val address: Address, @Assisted("isNewConfigEnabled") private val isNewConfigEnabled: Boolean, @Assisted("showDebugOptions") private val showDebugOptions: Boolean, - @ApplicationContext private val context: Context, + @param:ApplicationContext private val context: Context, private val textSecurePreferences: TextSecurePreferences, private val disappearingMessages: DisappearingMessages, - private val threadDb: ThreadDatabase, private val groupDb: GroupDatabase, private val storage: Storage, private val navigator: ConversationSettingsNavigator, - private val configFactory: ConfigFactoryProtocol, + private val recipientRepository: RecipientRepository, ) : ViewModel() { private val _state = MutableStateFlow( @@ -56,8 +55,7 @@ class DisappearingMessagesViewModel @AssistedInject constructor( init { viewModelScope.launch { - val expiryMode = storage.getExpirationConfiguration(threadId) - val address = threadDb.getRecipientForThreadId(threadId)?: return@launch + val expiryMode = recipientRepository.getRecipientOrEmpty(address).expiryMode val isAdmin = when { address.isGroupV2 -> { @@ -99,7 +97,7 @@ class DisappearingMessagesViewModel @AssistedInject constructor( return@launch } - disappearingMessages.set(threadId, address, mode, state.isGroup) + disappearingMessages.set(address, mode, state.isGroup) navigator.navigateUp() } @@ -107,7 +105,7 @@ class DisappearingMessagesViewModel @AssistedInject constructor( @AssistedFactory interface Factory { fun create( - @Assisted("threadId") threadId: Long, + address: Address, @Assisted("isNewConfigEnabled") isNewConfigEnabled: Boolean, @Assisted("showDebugOptions") showDebugOptions: Boolean ): DisappearingMessagesViewModel diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 7fedd45156..11bed0b4ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -280,33 +280,9 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } } - private val threadId: Long by lazy { - TODO() -// var threadId = intent.getLongExtra(THREAD_ID, -1L) -// if (threadId == -1L) { -// intent.getParcelableExtra
(ADDRESS)?.let { it -> -// threadId = threadDb.getThreadIdIfExistsFor(it.toString()) -// if (threadId == -1L) { -// val accountId = AccountId(it.toString()) -// val openGroup = lokiThreadDb.getOpenGroupChat(intent.getLongExtra(FROM_GROUP_THREAD_ID, -1)) -// val address = if (accountId.prefix == IdPrefix.BLINDED && openGroup != null) { -// storage.getOrCreateBlindedIdMapping(accountId.hexString, openGroup.server, openGroup.publicKey).accountId?.let { -// fromSerialized(it) -// } ?: GroupUtil.getEncodedOpenGroupInboxID(openGroup, accountId) -// } else { -// it -// } -// threadId = storage.getOrCreateThreadIdFor(address) -// } -// } ?: finish() -// } -// -// threadId - } - private val viewModel: ConversationViewModel by viewModels(extrasProducer = { defaultViewModelCreationExtras.withCreationCallback { - it.create(threadId) + it.create(address) } }) @@ -323,7 +299,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, // Mentions private val mentionViewModel: MentionViewModel by viewModels(extrasProducer = { defaultViewModelCreationExtras.withCreationCallback { - it.create(threadId) + it.create(address) } }) @@ -690,7 +666,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, is ConversationUiEvent.ShowDisappearingMessages -> { val intent = Intent(this@ConversationActivityV2, DisappearingMessagesActivity::class.java).apply { - putExtra(DisappearingMessagesActivity.THREAD_ID, event.threadId) + putExtra(DisappearingMessagesActivity.ARG_ADDRESS, event.address) } startActivity(intent) } @@ -704,7 +680,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, is ConversationUiEvent.ShowNotificationSettings -> { val intent = Intent(this@ConversationActivityV2, NotificationSettingsActivity::class.java).apply { - putExtra(NotificationSettingsActivity.THREAD_ID, event.threadId) + putExtra(NotificationSettingsActivity.ARG_ADDRESS, event.address) } startActivity(intent) } @@ -867,12 +843,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, onSearchQueryClear = { onSearchQueryUpdated("") }, onSearchCanceled = ::onSearchClosed, onAvatarPressed = { - val intent = ConversationSettingsActivity.createIntent( - context = this, - threadId = viewModel.threadId, - threadAddress = viewModel.recipient?.address - ) - + val intent = ConversationSettingsActivity.createIntent(this, address) settingsLauncher.launch(intent) } ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 9bec4eaea9..4e26ee370c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -93,7 +93,7 @@ private const val CHARACTER_LIMIT_THRESHOLD = 200 @HiltViewModel(assistedFactory = ConversationViewModel.Factory::class) class ConversationViewModel @AssistedInject constructor( - @Assisted val threadId: Long, + @Assisted val address: Address, private val application: Application, private val repository: ConversationRepository, private val storage: StorageProtocol, @@ -117,6 +117,8 @@ class ConversationViewModel @AssistedInject constructor( private val recipientRepository: RecipientRepository, ) : ViewModel() { + val threadId: Long = threadDb.getThreadIdIfExistsFor(address) + private val edKeyPair by lazy { storage.getUserED25519KeyPair() } @@ -1395,7 +1397,7 @@ class ConversationViewModel @AssistedInject constructor( } } - _uiEvents.tryEmit(ConversationUiEvent.ShowDisappearingMessages(threadId)) + _uiEvents.tryEmit(ConversationUiEvent.ShowDisappearingMessages(address)) } } @@ -1408,7 +1410,7 @@ class ConversationViewModel @AssistedInject constructor( } private fun showNotificationSettings() { - _uiEvents.tryEmit(ConversationUiEvent.ShowNotificationSettings(threadId)) + _uiEvents.tryEmit(ConversationUiEvent.ShowNotificationSettings(address)) } fun onResume() { @@ -1448,7 +1450,7 @@ class ConversationViewModel @AssistedInject constructor( @AssistedFactory interface Factory { - fun create(threadId: Long): ConversationViewModel + fun create(address: Address): ConversationViewModel } data class DialogsState( @@ -1534,8 +1536,8 @@ sealed interface InputBarContentState { sealed interface ConversationUiEvent { data class NavigateToConversation(val address: Address) : ConversationUiEvent - data class ShowDisappearingMessages(val threadId: Long) : ConversationUiEvent - data class ShowNotificationSettings(val threadId: Long) : ConversationUiEvent + data class ShowDisappearingMessages(val address: Address) : ConversationUiEvent + data class ShowNotificationSettings(val address: Address) : ConversationUiEvent data class ShowGroupMembers(val groupId: String) : ConversationUiEvent data object ShowUnblockConfirmation : ConversationUiEvent } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt index 42a6ecda88..16bb83c446 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt @@ -52,7 +52,7 @@ import org.thoughtcrime.securesms.util.observeChanges @HiltViewModel(assistedFactory = MentionViewModel.Factory::class) class MentionViewModel @AssistedInject constructor( application: Application, - @Assisted threadID: Long, + @Assisted address: Address, contentResolver: ContentResolver, threadDatabase: ThreadDatabase, groupDatabase: GroupDatabase, @@ -85,12 +85,10 @@ class MentionViewModel @AssistedInject constructor( @Suppress("OPT_IN_USAGE") private val members: StateFlow?> = - (contentResolver.observeChanges(Conversation.getUriForThread(threadID)) as Flow) - .debounce(500L) - .onStart { emit(Unit) } + recipientRepository.observeRecipient(address) .mapLatest { - val address = checkNotNull(threadDatabase.getRecipientForThreadId(threadID)) { - "Recipient not found for thread ID: $threadID" + val threadID = withContext(Dispatchers.Default) { + threadDatabase.getThreadIdIfExistsFor(address) } val memberIDs = when { @@ -297,6 +295,6 @@ class MentionViewModel @AssistedInject constructor( @AssistedFactory interface Factory { - fun create(threadId: Long): MentionViewModel + fun create(address: Address): MentionViewModel } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsActivity.kt index 4317090da8..c339446c3f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsActivity.kt @@ -13,13 +13,11 @@ import javax.inject.Inject class ConversationSettingsActivity: FullComposeScreenLockActivity() { companion object { - const val THREAD_ID = "conversation_settings_thread_id" const val THREAD_ADDRESS = "conversation_settings_thread_address" - fun createIntent(context: Context, threadId: Long, threadAddress: Address?): Intent { + fun createIntent(context: Context, address: Address): Intent { return Intent(context, ConversationSettingsActivity::class.java).apply { - putExtra(THREAD_ID, threadId) - putExtra(THREAD_ADDRESS, threadAddress) + putExtra(THREAD_ADDRESS, address) } } } @@ -30,8 +28,9 @@ class ConversationSettingsActivity: FullComposeScreenLockActivity() { @Composable override fun ComposeContent() { ConversationSettingsNavHost( - threadId = intent.getLongExtra(THREAD_ID, 0), - threadAddress = IntentCompat.getParcelableExtra(intent, THREAD_ADDRESS, Address::class.java), + address = requireNotNull(IntentCompat.getParcelableExtra(intent, THREAD_ADDRESS, Address::class.java)) { + "ConversationSettingsActivity requires an Address to be passed in the intent." + }, navigator = navigator, returnResult = { code, value -> setResult(RESULT_OK, Intent().putExtra(code, value)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsNavHost.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsNavHost.kt index 10b29b5ecc..77a42eccec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsNavHost.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsNavHost.kt @@ -102,8 +102,7 @@ sealed interface ConversationSettingsDestination { @OptIn(ExperimentalSharedTransitionApi::class) @Composable fun ConversationSettingsNavHost( - threadId: Long, - threadAddress: Address?, + address: Address, navigator: ConversationSettingsNavigator, returnResult: (String, Boolean) -> Unit, onBack: () -> Unit @@ -181,7 +180,7 @@ fun ConversationSettingsNavHost( ) { val viewModel = hiltViewModel { factory -> - factory.create(threadId) + factory.create(address) } val lifecycleOwner = LocalLifecycleOwner.current @@ -316,7 +315,7 @@ fun ConversationSettingsNavHost( val viewModel: DisappearingMessagesViewModel = hiltViewModel { factory -> factory.create( - threadId = threadId, + address = address, isNewConfigEnabled = ExpirationConfiguration.isNewConfigEnabled, showDebugOptions = BuildConfig.DEBUG ) @@ -332,14 +331,9 @@ fun ConversationSettingsNavHost( // All Media horizontalSlideComposable { - if (threadAddress == null) { - navController.popBackStack() - return@horizontalSlideComposable - } - val viewModel = hiltViewModel { factory -> - factory.create(threadAddress) + factory.create(address) } MediaOverviewScreen( @@ -354,7 +348,7 @@ fun ConversationSettingsNavHost( horizontalSlideComposable { val viewModel = hiltViewModel { factory -> - factory.create(threadId) + factory.create(address) } NotificationSettingsScreen( diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt index 8b92a183d9..649d280a4a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt @@ -23,7 +23,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -36,6 +36,7 @@ import network.loki.messenger.libsession_util.util.GroupInfo import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.open_groups.OpenGroup +import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ExpirationUtil @@ -62,7 +63,6 @@ import org.thoughtcrime.securesms.dependencies.ConfigFactory.Companion.MAX_NAME_ import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.home.HomeActivity import org.thoughtcrime.securesms.repository.ConversationRepository -import org.thoughtcrime.securesms.ui.DialogButtonData import org.thoughtcrime.securesms.ui.SimpleDialogData import org.thoughtcrime.securesms.ui.getSubbedString import org.thoughtcrime.securesms.util.AvatarUIData @@ -74,8 +74,8 @@ import kotlin.math.min @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) @HiltViewModel(assistedFactory = ConversationSettingsViewModel.Factory::class) class ConversationSettingsViewModel @AssistedInject constructor( - @Assisted private val threadId: Long, - @ApplicationContext private val context: Context, + @Assisted private val address: Address, + @param:ApplicationContext private val context: Context, private val avatarUtils: AvatarUtils, private val repository: ConversationRepository, private val configFactory: ConfigFactoryProtocol, @@ -83,7 +83,6 @@ class ConversationSettingsViewModel @AssistedInject constructor( private val conversationRepository: ConversationRepository, private val textSecurePreferences: TextSecurePreferences, private val navigator: ConversationSettingsNavigator, - private val threadDb: ThreadDatabase, private val groupManagerV2: GroupManagerV2, private val prefs: TextSecurePreferences, private val lokiThreadDatabase: LokiThreadDatabase, @@ -92,6 +91,12 @@ class ConversationSettingsViewModel @AssistedInject constructor( private val recipientRepository: RecipientRepository, ) : ViewModel() { + private val threadId by lazy { + requireNotNull(storage.getThreadId(address)) { + "Thread doesn't exist for this conversation" + } + } + private val _uiState: MutableStateFlow = MutableStateFlow( UIState( avatarUIData = AvatarUIData(emptyList()) @@ -326,11 +331,12 @@ class ConversationSettingsViewModel @AssistedInject constructor( init { // update data when we have a recipient and update when there are changes from the thread or recipient - viewModelScope.launch(Dispatchers.Default) { - (repository.maybeGetRecipientForThreadId(threadId)?.let(recipientRepository::observeRecipient) ?: flowOf(null)) + viewModelScope.launch { + recipientRepository.observeRecipient(address) + .filterNotNull() .collect { recipient = it - getStateFromRecipient() + getStateFromRecipient(it) } } } @@ -376,8 +382,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( } } - private suspend fun getStateFromRecipient(){ - val conversation = recipient ?: return + private suspend fun getStateFromRecipient(conversation: Recipient){ val configContact = configFactory.withUserConfigs { configs -> configs.contacts.get(conversation.address.toString()) } @@ -469,7 +474,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( ) } else context.getString(R.string.off) - val pinned = threadDb.isPinned(threadId) + val pinned = recipient?.isPinned == true val (notificationIconRes, notificationSubtitle) = getNotificationsData(conversation) @@ -479,7 +484,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( val mainOptions = mutableListOf() val dangerOptions = mutableListOf() - val ntsHidden = prefs.hasHiddenNoteToSelf() + val ntsHidden = conversation.priority == PRIORITY_HIDDEN mainOptions.addAll(listOf( optionCopyAccountId, @@ -747,15 +752,11 @@ class ConversationSettingsViewModel @AssistedInject constructor( } private fun pinConversation(){ - viewModelScope.launch { - storage.setPinned(threadId, true) - } + storage.setPinned(address, true) } private fun unpinConversation(){ - viewModelScope.launch { - storage.setPinned(threadId, false) - } + storage.setPinned(address, false) } private fun confirmBlockUser(){ @@ -852,25 +853,15 @@ class ConversationSettingsViewModel @AssistedInject constructor( } private fun hideNoteToSelf() { - prefs.setHasHiddenNoteToSelf(true) configFactory.withMutableUserConfigs { it.userProfile.setNtsPriority(PRIORITY_HIDDEN) } - // update state to reflect the change - viewModelScope.launch { - getStateFromRecipient() - } } fun showNoteToSelf() { - prefs.setHasHiddenNoteToSelf(false) configFactory.withMutableUserConfigs { it.userProfile.setNtsPriority(PRIORITY_VISIBLE) } - // update state to reflect the change - viewModelScope.launch { - getStateFromRecipient() - } } private fun confirmDeleteContact(){ @@ -1396,7 +1387,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( @AssistedFactory interface Factory { - fun create(threadId: Long): ConversationSettingsViewModel + fun create(address: Address): ConversationSettingsViewModel } data class UIState( diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsActivity.kt index 713af5a710..7c0bad06ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsActivity.kt @@ -1,10 +1,11 @@ package org.thoughtcrime.securesms.conversation.v2.settings.notification import androidx.compose.runtime.Composable +import androidx.core.content.IntentCompat import androidx.hilt.navigation.compose.hiltViewModel import dagger.hilt.android.AndroidEntryPoint +import org.session.libsession.utilities.Address import org.thoughtcrime.securesms.FullComposeScreenLockActivity -import org.thoughtcrime.securesms.conversation.disappearingmessages.DisappearingMessagesActivity /** * Forced to add an activity entry point for this screen @@ -14,15 +15,15 @@ import org.thoughtcrime.securesms.conversation.disappearingmessages.Disappearing @AndroidEntryPoint class NotificationSettingsActivity: FullComposeScreenLockActivity() { - private val threadId: Long by lazy { - intent.getLongExtra(DisappearingMessagesActivity.THREAD_ID, -1) - } - @Composable override fun ComposeContent() { val viewModel = hiltViewModel { factory -> - factory.create(threadId) + factory.create(requireNotNull( + IntentCompat.getParcelableExtra(intent, ARG_ADDRESS, Address::class.java) + ) { + "NotificationSettingsActivity requires an Address to be passed in via the intent." + }) } NotificationSettingsScreen( @@ -32,6 +33,6 @@ class NotificationSettingsActivity: FullComposeScreenLockActivity() { } companion object { - const val THREAD_ID = "thread_id" + const val ARG_ADDRESS = "address" } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt index b114b13a02..d45c5e739b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/notification/NotificationSettingsViewModel.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.withContext import network.loki.messenger.BuildConfig import network.loki.messenger.R import org.session.libsession.LocalisedTimeUtil +import org.session.libsession.utilities.Address import org.session.libsession.utilities.StringSubstitutionConstants.DATE_TIME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_LARGE_KEY import org.session.libsession.utilities.recipients.Recipient @@ -39,7 +40,7 @@ import kotlin.time.Duration.Companion.milliseconds @HiltViewModel(assistedFactory = NotificationSettingsViewModel.Factory::class) class NotificationSettingsViewModel @AssistedInject constructor( - @Assisted private val threadId: Long, + @Assisted private val address: Address, @ApplicationContext private val context: Context, private val recipientDatabase: RecipientDatabase, private val repository: ConversationRepository, @@ -64,10 +65,6 @@ class NotificationSettingsViewModel @AssistedInject constructor( init { // update data when we have a recipient and update when there are changes from the thread or recipient viewModelScope.launch(Dispatchers.Default) { - val address = repository.maybeGetRecipientForThreadId(threadId) - ?: // if we don't have a recipient, we can't do anything - return@launch - recipientRepository.observeRecipient(address).collectLatest { thread = it @@ -299,6 +296,6 @@ class NotificationSettingsViewModel @AssistedInject constructor( @AssistedFactory interface Factory { - fun create(threadId: Long): NotificationSettingsViewModel + fun create(address: Address): NotificationSettingsViewModel } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt index e3a8986e40..2049a0885b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt @@ -32,9 +32,9 @@ class LokiThreadDatabase @Inject constructor( val createPublicChatTableCommand = "CREATE TABLE $publicChatTable ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);" } - private val mutableChangeNotification = MutableSharedFlow() + private val mutableChangeNotification = MutableSharedFlow() - val changeNotification: SharedFlow get() = mutableChangeNotification + val changeNotification: SharedFlow get() = mutableChangeNotification private val cacheByThreadId = LruCache(32) @@ -99,7 +99,7 @@ class LokiThreadDatabase @Inject constructor( contentValues.put(Companion.threadID, threadID) contentValues.put(publicChat, JsonUtil.toJson(openGroup.toJson())) database.insertOrUpdate(publicChatTable, contentValues, "${Companion.threadID} = ?", arrayOf(threadID.toString())) - mutableChangeNotification.tryEmit(Unit) + mutableChangeNotification.tryEmit(threadID) } fun removeOpenGroupChat(threadID: Long) { @@ -109,7 +109,7 @@ class LokiThreadDatabase @Inject constructor( database.delete(publicChatTable,"${Companion.threadID} = ?", arrayOf(threadID.toString())) cacheByThreadId.remove(threadID) - mutableChangeNotification.tryEmit(Unit) + mutableChangeNotification.tryEmit(threadID) } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index c3b0e26e16..b88f43dbc2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.withContext +import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.ReadableGroupInfoConfig import network.loki.messenger.libsession_util.ReadableUserProfile import network.loki.messenger.libsession_util.util.Contact @@ -42,6 +43,7 @@ import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log import java.lang.ref.WeakReference import java.time.Instant +import java.time.ZoneId import java.time.ZonedDateTime import javax.inject.Inject import javax.inject.Singleton @@ -166,22 +168,39 @@ class RecipientRepository @Inject constructor( address.isLegacyGroup -> { changeSource = merge( groupDatabase.updateNotification, - recipientDatabase.updateNotifications.filter { it == address } + recipientDatabase.updateNotifications.filter { it == address }, + configFactory.userConfigsChanged(), ) val group: GroupRecord? = groupDatabase.getGroup(address.toGroupString()).orNull() - value = group?.let { createLegacyGroupRecipient(address, it, settings) } + val groupConfig = configFactory.withUserConfigs { + it.userGroups.getLegacyGroupInfo(GroupUtil.doubleDecodeGroupId(address.address)) + } + + value = group?.let { createLegacyGroupRecipient(address, groupConfig, it, settings) } } address.isCommunity -> { value = openGroupFetcher(address) - ?.let { createCommunityRecipient(address, it, settings) } + ?.let { openGroup -> + val groupConfig = configFactory.withUserConfigs { + it.userGroups.getCommunityInfo(openGroup.server, openGroup.room) + } + + createCommunityRecipient( + address, + groupConfig, + openGroup, + settings + ) + } changeSource = merge( lokiThreadDatabase.changeNotification, - recipientDatabase.updateNotifications.filter { it == address } + recipientDatabase.updateNotifications.filter { it == address }, + configFactory.userConfigsChanged(), ) } @@ -269,6 +288,7 @@ class RecipientRepository @Inject constructor( avatar = configs.userProfile.getPic().toRecipientAvatar(), expiryMode = configs.userProfile.getNtsExpiry(), acceptsCommunityMessageRequests = configs.userProfile.getCommunityMessageRequests(), + priority = configs.userProfile.getNtsPriority(), ) } } @@ -287,7 +307,8 @@ class RecipientRepository @Inject constructor( approved = contact.approved, approvedMe = contact.approvedMe, blocked = contact.blocked, - expiryMode = contact.expiryMode + expiryMode = contact.expiryMode, + priority = contact.priority, ) } } @@ -302,7 +323,8 @@ class RecipientRepository @Inject constructor( avatar = configs.groupInfo.getProfilePic().toRecipientAvatar(), expiryMode = configs.groupInfo.expiryMode, name = configs.groupInfo.getName() ?: groupInfo.name, - approved = !groupInfo.invited + approved = !groupInfo.invited, + priority = groupInfo.priority ) } } @@ -403,7 +425,7 @@ class RecipientRepository @Inject constructor( private val RecipientSettings.muteUntilDate: ZonedDateTime? get() = if (muteUntil > 0) { - ZonedDateTime.from(Instant.ofEpochMilli(muteUntil)) + Instant.ofEpochMilli(muteUntil).atZone(ZoneId.of("UTC")) } else { null } @@ -449,6 +471,7 @@ class RecipientRepository @Inject constructor( private fun createCommunityRecipient( address: Address, + config: GroupInfo.CommunityGroupInfo?, community: OpenGroup, settings: RecipientSettings?, ): Recipient { @@ -463,6 +486,7 @@ class RecipientRepository @Inject constructor( fileId = it, ) }, + priority = config?.priority ?: PRIORITY_VISIBLE, ), mutedUntil = settings?.muteUntilDate, autoDownloadAttachments = settings?.autoDownloadAttachments, @@ -474,6 +498,7 @@ class RecipientRepository @Inject constructor( private fun createLegacyGroupRecipient( address: Address, + config: GroupInfo.LegacyGroupInfo?, group: GroupRecord, // Local db data settings: RecipientSettings?, // Local db data ): Recipient { @@ -489,7 +514,8 @@ class RecipientRepository @Inject constructor( ) } else { null - } + }, + priority = config?.priority ?: PRIORITY_VISIBLE, ), mutedUntil = settings?.muteUntilDate, autoDownloadAttachments = settings?.autoDownloadAttachments, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 1b188628ab..2ee30ef367 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1098,11 +1098,9 @@ open class Storage @Inject constructor( if (contact.priority == PRIORITY_HIDDEN) { getThreadId(address)?.let(::deleteConversation) } else { - ( - getThreadId(address) ?: getOrCreateThreadIdFor(address).also { - setThreadCreationDate(it, 0) - } - ).also { setPinned(it, contact.priority == PRIORITY_PINNED) } + getOrCreateThreadIdFor(address).also { + setThreadCreationDate(it, 0) + } } } @@ -1151,10 +1149,7 @@ open class Storage @Inject constructor( return mmsSmsDb.getConversationCount(threadID) } - override fun setPinned(threadID: Long, isPinned: Boolean) { - val threadDB = threadDatabase - threadDB.setPinned(threadID, isPinned) - val address = getRecipientForThread(threadID) ?: return + override fun setPinned(address: Address, isPinned: Boolean) { val isLocalNumber = address == getUserPublicKey()?.let { fromSerialized(it) } configFactory.withMutableUserConfigs { configs -> if (isLocalNumber) { @@ -1181,7 +1176,7 @@ open class Storage @Inject constructor( } address.isCommunity -> { - val openGroup = getOpenGroup(threadID) ?: return@withMutableUserConfigs + val openGroup = getOpenGroup(address) ?: return@withMutableUserConfigs val (baseUrl, room, pubKeyHex) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: return@withMutableUserConfigs val newGroupInfo = configs.userGroups.getOrConstructCommunityInfo( @@ -1196,11 +1191,6 @@ open class Storage @Inject constructor( } } - override fun isPinned(threadID: Long): Boolean { - val threadDB = threadDatabase - return threadDB.isPinned(threadID) - } - override fun setThreadCreationDate(threadId: Long, newDate: Long) { val threadDb = threadDatabase threadDb.setCreationDate(threadId, newDate) @@ -1678,35 +1668,33 @@ open class Storage @Inject constructor( return recipientRepository.getRecipientSync(recipient)?.expiryMode ?: ExpiryMode.NONE } - override fun setExpirationConfiguration(threadId: Long, expiryMode: ExpiryMode) { - val recipient = getRecipientForThread(threadId) ?: return - + override fun setExpirationConfiguration(address: Address, expiryMode: ExpiryMode) { if (expiryMode == ExpiryMode.NONE) { // Clear the legacy recipients on updating config to be none - lokiAPIDatabase.setLastLegacySenderAddress(recipient.toString(), null) + lokiAPIDatabase.setLastLegacySenderAddress(address.toString(), null) } - if (recipient.isLegacyGroup) { - val groupPublicKey = GroupUtil.addressToGroupAccountId(recipient) + if (address.isLegacyGroup) { + val groupPublicKey = GroupUtil.addressToGroupAccountId(address) configFactory.withMutableUserConfigs { val groupInfo = it.userGroups.getLegacyGroupInfo(groupPublicKey) ?.copy(disappearingTimer = expiryMode.expirySeconds) ?: return@withMutableUserConfigs it.userGroups.set(groupInfo) } - } else if (recipient.isGroupV2) { - val groupSessionId = AccountId(recipient.toString()) + } else if (address.isGroupV2) { + val groupSessionId = AccountId(address.toString()) configFactory.withMutableGroupConfigs(groupSessionId) { configs -> configs.groupInfo.setExpiryTimer(expiryMode.expirySeconds) } - } else if (recipient.address == getUserPublicKey()) { + } else if (address.address == getUserPublicKey()) { configFactory.withMutableUserConfigs { it.userProfile.setNtsExpiry(expiryMode) } - } else if (recipient.isContact) { + } else if (address.isContact) { configFactory.withMutableUserConfigs { - val contact = it.contacts.get(recipient.address.toString())?.copy(expiryMode = expiryMode) ?: return@withMutableUserConfigs + val contact = it.contacts.get(address.toString())?.copy(expiryMode = expiryMode) ?: return@withMutableUserConfigs it.contacts.set(contact) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index e7dff3df47..921c5bd9fd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -104,6 +104,8 @@ public interface ConversationThreadUpdateListener { public static final String EXPIRES_IN = "expires_in"; public static final String LAST_SEEN = "last_seen"; public static final String HAS_SENT = "has_sent"; + + @Deprecated(forRemoval = true) public static final String IS_PINNED = "is_pinned"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + @@ -758,30 +760,6 @@ record = reader.getNext(); } } - public void setPinned(long threadId, boolean pinned) { - ContentValues contentValues = new ContentValues(1); - contentValues.put(IS_PINNED, pinned ? 1 : 0); - - getWritableDatabase().update(TABLE_NAME, contentValues, ID_WHERE, - new String[] {String.valueOf(threadId)}); - - notifyConversationListeners(threadId); - notifyConversationListListeners(); - } - - public boolean isPinned(long threadId) { - SQLiteDatabase db = getReadableDatabase(); - Cursor cursor = db.query(TABLE_NAME, new String[]{IS_PINNED}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null); - try { - if (cursor != null && cursor.moveToFirst()) { - return cursor.getInt(0) == 1; - } - return false; - } finally { - if (cursor != null) cursor.close(); - } - } - /** * @param threadId * @param isGroupRecipient diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/NotifyType.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/NotifyType.java deleted file mode 100644 index b9ea2508c5..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/NotifyType.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.thoughtcrime.securesms.database.model; - -import androidx.annotation.IntDef; - -import org.thoughtcrime.securesms.database.RecipientDatabase; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Retention(RetentionPolicy.SOURCE) -@IntDef({RecipientDatabase.NOTIFY_TYPE_MENTIONS, RecipientDatabase.NOTIFY_TYPE_ALL, RecipientDatabase.NOTIFY_TYPE_NONE}) -public @interface NotifyType { -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index a93caebabf..3b658d69b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -230,15 +230,13 @@ public boolean isGroupUpdateMessage() { public long getDate() { return getDateReceived(); } - public boolean isArchived() { return archived; } - public int getDistributionType() { return distributionType; } public long getExpiresIn() { return expiresIn; } public long getLastSeen() { return lastSeen; } - public boolean isPinned() { return pinned; } + public boolean isPinned() { return getRecipient().isPinned(); } public int getInitialRecipientHash() { return initialRecipientHash; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt index 473aa724ee..2d41f87287 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt @@ -42,7 +42,7 @@ import javax.inject.Inject @HiltViewModel class DebugMenuViewModel @Inject constructor( - @ApplicationContext private val context: Context, + @param:ApplicationContext private val context: Context, private val textSecurePreferences: TextSecurePreferences, private val tokenPageNotificationManager: TokenPageNotificationManager, private val configFactory: ConfigFactory, @@ -64,7 +64,7 @@ class DebugMenuViewModel @Inject constructor( showLoadingDialog = false, showDeprecatedStateWarningDialog = false, hideMessageRequests = textSecurePreferences.hasHiddenMessageRequests(), - hideNoteToSelf = textSecurePreferences.hasHiddenNoteToSelf(), + hideNoteToSelf = configFactory.withUserConfigs { it.userProfile.getNtsPriority() == PRIORITY_HIDDEN }, forceDeprecationState = deprecationManager.deprecationStateOverride.value, availableDeprecationState = listOf(null) + LegacyGroupDeprecationManager.DeprecationState.entries.toList(), deprecatedTime = deprecationManager.deprecatedTime.value, @@ -135,7 +135,6 @@ class DebugMenuViewModel @Inject constructor( } is Commands.HideNoteToSelf -> { - textSecurePreferences.setHasHiddenNoteToSelf(command.hide) configFactory.withMutableUserConfigs { it.userProfile.setNtsPriority(if(command.hide) PRIORITY_HIDDEN else PRIORITY_VISIBLE) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt index 9d5a7dbbc5..41cf84bcd4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt @@ -1,16 +1,10 @@ package org.thoughtcrime.securesms.groups import android.content.Context -import network.loki.messenger.libsession_util.ConfigBase -import network.loki.messenger.libsession_util.util.Bytes import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1 import org.session.libsession.utilities.Address -import org.session.libsession.utilities.GroupRecord -import org.session.libsession.utilities.GroupUtil -import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.thoughtcrime.securesms.ApplicationContext -import org.thoughtcrime.securesms.dependencies.ConfigFactory object ClosedGroupManager { @@ -32,26 +26,4 @@ object ClosedGroupManager { } } - fun ConfigFactory.updateLegacyGroup(group: GroupRecord) { - if (!group.isLegacyGroup) return - val storage = MessagingModuleConfiguration.shared.storage - val threadId = storage.getThreadId(group.encodedId) ?: return - val groupPublicKey = GroupUtil.doubleEncodeGroupID(group.getId()) - val latestKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: return - - withMutableUserConfigs { - val groups = it.userGroups - - val legacyInfo = groups.getOrConstructLegacyGroupInfo(groupPublicKey) - val latestMemberMap = GroupUtil.createConfigMemberMap(group.members.map(Address::toString), group.admins.map(Address::toString)) - val toSet = legacyInfo.copy( - members = latestMemberMap, - name = group.title, - priority = if (storage.isPinned(threadId)) ConfigBase.PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE, - encPubKey = Bytes((latestKeyPair.publicKey as DjbECPublicKey).publicKey), // 'serialize()' inserts an extra byte - encSecKey = Bytes(latestKeyPair.privateKey.serialize()) - ) - groups.set(toSet) - } - } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index f42d1e51b5..4339f6539e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -52,11 +52,7 @@ class ConversationView : LinearLayout { // region Updating fun bind(thread: ThreadRecord, isTyping: Boolean) { this.thread = thread - if (thread.isPinned) { - binding.iconPinned.isVisible = true - } else { - binding.iconPinned.isVisible = false - } + binding.iconPinned.isVisible = thread.isPinned binding.root.background = if (thread.unreadCount > 0) { ContextCompat.getDrawable(context, R.drawable.conversation_unread_background) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 627855af51..cb73911327 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -556,17 +556,17 @@ class HomeActivity : ScreenLockActionBarActivity(), bottomSheet.dismiss() // go to the notification settings val intent = Intent(this, NotificationSettingsActivity::class.java).apply { - putExtra(NotificationSettingsActivity.THREAD_ID, thread.threadId) + putExtra(NotificationSettingsActivity.ARG_ADDRESS, thread.recipient.address) } startActivity(intent) } bottomSheet.onPinTapped = { bottomSheet.dismiss() - setConversationPinned(thread.threadId, true) + setConversationPinned(thread.recipient.address, true) } bottomSheet.onUnpinTapped = { bottomSheet.dismiss() - setConversationPinned(thread.threadId, false) + setConversationPinned(thread.recipient.address, false) } bottomSheet.onMarkAllAsReadTapped = { bottomSheet.dismiss() @@ -632,11 +632,8 @@ class HomeActivity : ScreenLockActionBarActivity(), } } - private fun setConversationPinned(threadId: Long, pinned: Boolean) { - lifecycleScope.launch(Dispatchers.Default) { - storage.setPinned(threadId, pinned) - homeViewModel.tryReload() - } + private fun setConversationPinned(address: Address, pinned: Boolean) { + storage.setPinned(address, pinned) } private fun markAllAsRead(thread: ThreadRecord) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index cd531e8f09..5fcaa4d987 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.home import android.content.ContentResolver import android.content.Context -import androidx.annotation.AttrRes import androidx.lifecycle.ViewModel import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope @@ -29,6 +28,7 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import network.loki.messenger.R import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN import org.session.libsession.database.StorageProtocol @@ -38,6 +38,7 @@ import org.session.libsession.utilities.ConfigUpdateNotification import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Log import org.session.libsession.utilities.currentUserName +import org.session.libsession.utilities.userConfigsChanged import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.ThreadDatabase @@ -51,14 +52,13 @@ import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( - @ApplicationContext - private val context: Context, + @param:ApplicationContext private val context: Context, private val threadDb: ThreadDatabase, private val contentResolver: ContentResolver, private val prefs: TextSecurePreferences, private val typingStatusRepository: TypingStatusRepository, private val configFactory: ConfigFactory, - private val callManager: CallManager, + callManager: CallManager, private val storage: StorageProtocol, private val groupManager: GroupManagerV2 ) : ViewModel() { @@ -123,10 +123,12 @@ class HomeViewModel @Inject constructor( .map { prefs.hasHiddenMessageRequests() } .onStart { emit(prefs.hasHiddenMessageRequests()) } - private fun hasHiddenNoteToSelf() = TextSecurePreferences.events - .filter { it == TextSecurePreferences.HAS_HIDDEN_NOTE_TO_SELF } - .map { prefs.hasHiddenNoteToSelf() } - .onStart { emit(prefs.hasHiddenNoteToSelf()) } + private fun hasHiddenNoteToSelf(): Flow = + configFactory.userConfigsChanged() + .debounce(1000L) + .onStart { emit(Unit) } + .map { configFactory.withUserConfigs { it.userProfile.getNtsPriority() == PRIORITY_HIDDEN } } + private fun observeTypingStatus(): Flow> = typingStatusRepository .typingThreads @@ -146,11 +148,23 @@ class HomeViewModel @Inject constructor( @Suppress("OPT_IN_USAGE") private fun observeConversationList(): Flow> = reloadTriggersAndContentChanges() .mapLatest { _ -> - threadDb.approvedConversationList.use { openCursor -> - threadDb.readerFor(openCursor).run { generateSequence { next }.toList() } + withContext(Dispatchers.Default) { + val records = threadDb.approvedConversationList.use { openCursor -> + threadDb.readerFor(openCursor).run { generateSequence { next }.toMutableList() } + } + + // Sort the threads by priority and last message timestamp + records.sortWith(threadRecordComparator) + + records } } - .flowOn(Dispatchers.IO) + + private val threadRecordComparator = compareByDescending { it.recipient.isPinned } + .thenByDescending { it.recipient.priority } + .thenByDescending { it.lastMessage?.timestamp ?: 0L } + .thenByDescending { it.date } + .thenBy { it.recipient.displayName } @OptIn(FlowPreview::class) private fun reloadTriggersAndContentChanges(): Flow<*> = merge( @@ -184,11 +198,6 @@ class HomeViewModel @Inject constructor( val items: List, ) - data class MessageSnippetOverride( - val text: CharSequence, - @AttrRes val colorAttr: Int, - ) - sealed interface Item { data class Thread( val thread: ThreadRecord, @@ -205,7 +214,6 @@ class HomeViewModel @Inject constructor( fun hideNoteToSelf() { - prefs.setHasHiddenNoteToSelf(true) configFactory.withMutableUserConfigs { it.userProfile.setNtsPriority(PRIORITY_HIDDEN) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt index 99d8150a11..84fea48efc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -98,10 +98,10 @@ import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.LargeItemButton import org.thoughtcrime.securesms.ui.LargeItemButtonWithDrawable import org.thoughtcrime.securesms.ui.OpenURLAlertDialog +import org.thoughtcrime.securesms.ui.components.AcccentOutlineCopyButton +import org.thoughtcrime.securesms.ui.components.AccentOutlineButton import org.thoughtcrime.securesms.ui.components.Avatar import org.thoughtcrime.securesms.ui.components.BaseBottomSheet -import org.thoughtcrime.securesms.ui.components.AccentOutlineButton -import org.thoughtcrime.securesms.ui.components.AcccentOutlineCopyButton import org.thoughtcrime.securesms.ui.getCellBottomShape import org.thoughtcrime.securesms.ui.getCellTopShape import org.thoughtcrime.securesms.ui.qaTag @@ -112,8 +112,8 @@ import org.thoughtcrime.securesms.ui.theme.LocalType import org.thoughtcrime.securesms.ui.theme.PreviewTheme import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider import org.thoughtcrime.securesms.ui.theme.ThemeColors -import org.thoughtcrime.securesms.ui.theme.dangerButtonColors import org.thoughtcrime.securesms.ui.theme.accentTextButtonColors +import org.thoughtcrime.securesms.ui.theme.dangerButtonColors import org.thoughtcrime.securesms.util.FileProviderUtil import org.thoughtcrime.securesms.util.applyCommonWindowInsetsOnViews import org.thoughtcrime.securesms.util.push @@ -354,7 +354,6 @@ class SettingsActivity : ScreenLockActionBarActivity() { Log.w(TAG, "Cannot update display name - no network connection.") } else { // if we have a network connection then attempt to update the display name - TextSecurePreferences.setProfileName(this, displayName) viewModel.updateName(displayName) binding.btnGroupNameDisplay.text = displayName updateWasSuccessful = true diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt index 28a12fb46d..316b76d97c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt @@ -17,7 +17,6 @@ import kotlinx.coroutines.withContext import network.loki.messenger.R import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.avatars.AvatarHelper -import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.utilities.Address import org.session.libsession.utilities.ProfileKeyUtil import org.session.libsession.utilities.ProfilePictureUtilities @@ -26,7 +25,6 @@ import org.session.libsession.utilities.currentUserName import org.session.libsignal.utilities.ExternalStorageUtil.getImageDir import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.NoExternalStorageException -import org.session.libsignal.utilities.Util.SECURE_RANDOM import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.preferences.SettingsViewModel.AvatarDialogState.TempAvatar @@ -199,7 +197,7 @@ class SettingsViewModel @Inject constructor( try { // Grab the profile key and kick of the promise to update the profile picture - val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(context) + val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey() val url = ProfilePictureUtilities.upload(profilePicture, encodedProfileKey, context) // If the online portion of the update succeeded then update the local state @@ -218,15 +216,10 @@ class SettingsViewModel @Inject constructor( // update dialog state _avatarDialogState.value = AvatarDialogState.NoAvatar } else { - ProfileKeyUtil.setEncodedProfileKey(context, encodedProfileKey) - - // Attempt to grab the details we require to update the profile picture - val profileKey = ProfileKeyUtil.getProfileKey(context) - // If we have a URL and a profile key then set the user's profile picture - if (url.isNotEmpty() && profileKey.isNotEmpty()) { + if (url.isNotBlank() && encodedProfileKey.isNotBlank()) { configFactory.withMutableUserConfigs { - it.userProfile.setPic(UserPic(url, profileKey)) + it.userProfile.setPic(UserPic(url, ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey))) } } From 22cf75ac6d3575ea62d2e4e1e43f5df7cbda8564 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Thu, 3 Jul 2025 12:14:53 +1000 Subject: [PATCH 25/52] WIP --- .../libsession/database/StorageProtocol.kt | 1 - .../securesms/configs/ConfigToDatabaseSync.kt | 16 +- .../contacts/ShareContactListFragment.kt | 12 +- .../contacts/ShareContactListLoader.kt | 43 +-- .../securesms/database/Storage.kt | 5 - .../securesms/database/ThreadDatabase.java | 248 ++++-------------- .../securesms/debugmenu/DebugMenuViewModel.kt | 4 +- .../securesms/home/HomeViewModel.kt | 8 +- .../MessageRequestsActivity.kt | 23 +- .../messagerequests/MessageRequestsAdapter.kt | 47 ++-- .../messagerequests/MessageRequestsLoader.kt | 21 +- .../repository/ConversationRepository.kt | 12 +- .../securesms/search/SearchRepository.kt | 19 +- .../securesms/search/model/SearchResult.java | 4 +- .../securesms/tokenpage/TokenPageViewModel.kt | 25 +- .../securesms/util/ContactUtilities.kt | 24 -- 16 files changed, 168 insertions(+), 344 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/ContactUtilities.kt diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index f9fff5423c..b6a6e90e7c 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -179,7 +179,6 @@ interface StorageProtocol { fun getThreadId(address: Address): Long? fun getThreadIdForMms(mmsId: Long): Long fun getLastUpdated(threadID: Long): Long - fun trimThread(threadID: Long, threadLimit: Int) fun trimThreadBefore(threadID: Long, timestamp: Long) fun getMessageCount(threadID: Long): Long fun setPinned(address: Address, isPinned: Boolean) diff --git a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt index 9d6b9915c6..3c2fe7f1cb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt @@ -7,7 +7,6 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_PINNED import network.loki.messenger.libsession_util.ReadableGroupInfoConfig import network.loki.messenger.libsession_util.ReadableUserGroupsConfig import network.loki.messenger.libsession_util.ReadableUserProfile @@ -256,18 +255,11 @@ class ConfigToDatabaseSync @Inject constructor( } } - val existingClosedGroupThreads: Map = threadDatabase.readerFor(threadDatabase.conversationList).use { reader -> - buildMap(reader.count) { - var current = reader.next - while (current != null) { - if (current.recipient?.isGroupV2Recipient == true) { - put(AccountId(current.recipient.address.toString()), current.threadId) - } + val existingClosedGroupThreads: Map = threadDatabase.allThreads + .asSequence() + .filter { (address, _) -> address.isGroupV2 } + .associate { (address, threadId) -> AccountId(address.toString()) to threadId } - current = reader.next - } - } - } val groupThreadsToKeep = hashMapOf() diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt index 982547cdcd..036653a624 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt @@ -14,9 +14,11 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.bumptech.glide.Glide import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.databinding.ShareContactListFragmentBinding +import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.database.ThreadDatabase import javax.inject.Inject @AndroidEntryPoint @@ -28,6 +30,12 @@ class ShareContactListFragment : Fragment(), LoaderManager.LoaderCallbacks>(context) { override fun loadInBackground(): List { - val contacts = ContactUtilities.getAllContacts(context).asSequence() - .filter { - if(it.first.isLegacyGroupRecipient && deprecationManager.isDeprecated) return@filter false // ignore legacy group when deprecated - if(it.first.isCommunityRecipient) { // ignore communities without write access - val storage = MessagingModuleConfiguration.shared.storage - val threadId = storage.getThreadId(it.first.address) ?: return@filter false + val threads = threadDatabase.approvedConversationList + .asSequence() + .filter { thread -> + val recipient = thread.recipient + if(recipient.isLegacyGroupRecipient && deprecationManager.isDeprecated) return@filter false // ignore legacy group when deprecated + if(recipient.isCommunityRecipient) { // ignore communities without write access + val threadId = storage.getThreadId(recipient.address) ?: return@filter false val openGroup = storage.getOpenGroup(threadId) ?: return@filter false return@filter openGroup.canWrite } if (filter.isNullOrEmpty()) return@filter true - it.first.displayName.contains(filter.trim(), true) || it.first.address.toString().contains(filter.trim(), true) - }.sortedWith( - compareBy> { !it.first.isLocalNumber } // NTS come first - .thenByDescending { it.second } // then order by last message time - ) - .map { it.first }.toList() + recipient.displayName.contains(filter.trim(), true) || recipient.address.toString().contains(filter.trim(), true) + } + .toMutableList() - return getItems(contacts) + threads.sortWith(COMPARATOR) + + return threads.map { ContactSelectionListItem.Contact(it.recipient) } } - private fun getItems(contacts: List): List { - val items = contacts.map { - ContactSelectionListItem.Contact(it) - } - if (items.isEmpty()) return listOf() - return items + companion object { + private val COMPARATOR = compareByDescending { it.recipient.isLocalNumber } // NTS come first + .thenByDescending { it.lastMessage?.timestamp ?: 0L } // then order by last message time + .thenBy { it.recipient.displayName } } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 2ee30ef367..01810b82c1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1134,11 +1134,6 @@ open class Storage @Inject constructor( return threadDB.getLastUpdated(threadID) } - override fun trimThread(threadID: Long, threadLimit: Int) { - val threadDB = threadDatabase - threadDB.trimThread(threadID, threadLimit) - } - override fun trimThreadBefore(threadID: Long, timestamp: Long) { val threadDB = threadDatabase threadDB.trimThreadBefore(threadID, timestamp) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 921c5bd9fd..74ad3292cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -23,7 +23,6 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; -import android.database.MergeCursor; import android.net.Uri; import androidx.annotation.NonNull; @@ -33,11 +32,11 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase; +import org.json.JSONArray; import org.session.libsession.messaging.MessagingModuleConfiguration; import org.session.libsession.snode.SnodeAPI; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.ConfigFactoryProtocolKt; -import org.session.libsession.utilities.DelimiterUtil; import org.session.libsession.utilities.DistributionTypes; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; @@ -70,6 +69,7 @@ import javax.inject.Singleton; import dagger.Lazy; +import kotlin.collections.CollectionsKt; import network.loki.messenger.libsession_util.util.GroupInfo; @Singleton @@ -216,99 +216,12 @@ public void clearSnippet(long threadId){ notifyConversationListListeners(); } - public void updateSnippet(long threadId, String snippet, @Nullable Uri attachment, long date, long type, boolean unarchive) { - ContentValues contentValues = new ContentValues(4); - - contentValues.put(THREAD_CREATION_DATE, date - date % 1000); - if (!snippet.isEmpty()) { - contentValues.put(SNIPPET, snippet); - } - contentValues.put(SNIPPET_TYPE, type); - contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString()); - - if (unarchive) { - contentValues.put(ARCHIVED, 0); - } - - SQLiteDatabase db = getWritableDatabase(); - db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""}); - notifyConversationListListeners(); - } - public void deleteThread(long threadId) { SQLiteDatabase db = getWritableDatabase(); db.delete(TABLE_NAME, ID_WHERE, new String[] {threadId + ""}); notifyConversationListListeners(); } - private void deleteThreads(Set threadIds) { - SQLiteDatabase db = getWritableDatabase(); - String where = ""; - - for (long threadId : threadIds) { where += ID + " = '" + threadId + "' OR "; } - - where = where.substring(0, where.length() - 4); - - db.delete(TABLE_NAME, where, null); - notifyConversationListListeners(); - } - - private void deleteAllThreads() { - SQLiteDatabase db = getWritableDatabase(); - db.delete(TABLE_NAME, null, null); - notifyConversationListListeners(); - } - - public void trimAllThreads(int length, ProgressListener listener) { - Cursor cursor = null; - int threadCount = 0; - int complete = 0; - - try { - cursor = this.getConversationList(); - - if (cursor != null) - threadCount = cursor.getCount(); - - while (cursor != null && cursor.moveToNext()) { - long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); - trimThread(threadId, length); - - listener.onProgress(++complete, threadCount); - } - } finally { - if (cursor != null) - cursor.close(); - } - } - - public void trimThread(long threadId, int length) { - Log.i("ThreadDatabase", "Trimming thread: " + threadId + " to: " + length); - Cursor cursor = null; - - try { - cursor = DatabaseComponent.get(context).mmsSmsDatabase().getConversation(threadId, true); - - if (cursor != null && length > 0 && cursor.getCount() > length) { - Log.w("ThreadDatabase", "Cursor count is greater than length!"); - cursor.moveToPosition(length - 1); - - long lastTweetDate = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_RECEIVED)); - - Log.i("ThreadDatabase", "Cut off tweet date: " + lastTweetDate); - - DatabaseComponent.get(context).smsDatabase().deleteMessagesInThreadBeforeDate(threadId, lastTweetDate); - DatabaseComponent.get(context).mmsDatabase().deleteMessagesInThreadBeforeDate(threadId, lastTweetDate, false); - - update(threadId, false); - notifyConversationListeners(threadId); - } - } finally { - if (cursor != null) - cursor.close(); - } - } - public void trimThreadBefore(long threadId, long timestamp) { Log.i("ThreadDatabase", "Trimming thread: " + threadId + " before :"+timestamp); DatabaseComponent.get(context).smsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp); @@ -421,67 +334,38 @@ public Cursor searchConversationAddresses(String addressQuery, Set exclu selectionArgs.addAll(excludeAddresses); } - String query = createQuery(selection.toString(), 0); + String query = createQuery(selection.toString()); return db.rawQuery(query, selectionArgs.toArray(new String[0])); } - - @Nullable - public Cursor getFilteredConversationList(@Nullable List
filter) { - if (filter == null || filter.isEmpty()) - return null; - - SQLiteDatabase db = getReadableDatabase(); - List> partitionedAddresses = Util.partition(filter, 900); - List cursors = new LinkedList<>(); - - for (List
addresses : partitionedAddresses) { - StringBuilder selection = new StringBuilder(TABLE_NAME + "." + ADDRESS + " = ?"); - String[] selectionArgs = new String[addresses.size()]; - - for (int i = 0; i < addresses.size() - 1; i++) { - selection.append(" OR " + TABLE_NAME + "." + ADDRESS + " = ?"); - } - - int i= 0; - for (Address address : addresses) { - selectionArgs[i++] = DelimiterUtil.escape(address.toString(), ' '); - } - - String query = createQuery(selection.toString(), 0); - cursors.add(db.rawQuery(query, selectionArgs)); + @NonNull + private ArrayList getThreadRecords(@NonNull final Cursor cursor) { + final ArrayList threads = new ArrayList<>(cursor.getCount()); + Reader reader = new Reader(cursor); + ThreadRecord thread; + while ((thread = reader.getNext()) != null) { + threads.add(thread); } - Cursor cursor = cursors.size() > 1 ? new MergeCursor(cursors.toArray(new Cursor[0])) : cursors.get(0); - setNotifyConversationListListeners(cursor); - return cursor; + return threads; } - public Cursor getRecentConversationList(int limit) { - SQLiteDatabase db = getReadableDatabase(); - String query = createQuery("", limit); - - return db.rawQuery(query, null); - } + @NonNull + public ArrayList getFilteredConversationList(@Nullable List
filter) { + if (filter == null || filter.isEmpty()) + return new ArrayList<>(0); - public Cursor getConversationList() { - // Conversations will come from two different sources: - // 1. Config based conversations - // 2. Blinded conversations stored in the database - final List
blindedConversations = getBlindedConversations(null, false); - final List
configBasedConversations = recipientRepository.get().getConfigBasedConversations( - c -> true, - c -> !c.getBlocked(), - value -> true, - value -> true, - value -> true + final String query = createQuery( + TABLE_NAME + "." + ADDRESS + " IN (SELECT value FROM json_each(?))" ); - final List
allAddresses = new ArrayList<>(blindedConversations.size() + configBasedConversations.size()); - allAddresses.addAll(blindedConversations); - allAddresses.addAll(configBasedConversations); + final String[] selectionArgs = new String[] { + new JSONArray(CollectionsKt.map(filter, Address::toString)).toString() + }; - return getFilteredConversationList(allAddresses); + try(final Cursor cursor = getReadableDatabase().rawQuery(query, selectionArgs)) { + return getThreadRecords(cursor); + } } private List
getBlindedConversations(@Nullable Boolean approved, @Nullable Boolean blocked) { @@ -510,41 +394,48 @@ private List
getBlindedConversations(@Nullable Boolean approved, @Nulla } } - @Nullable - public Cursor getApprovedConversationList() { + /** + * @return All threads in the database, with their thread ID and Address. Note that + * threads don't necessarily mean conversations, as whether you have a conversation + * or not depend on the config data. This method returns all threads that exist + * in the database, normally this is useful only for data integrity purposes. + */ + public List> getAllThreads() { + SQLiteDatabase db = getReadableDatabase(); + String query = "SELECT " + ID + ", " + ADDRESS + " FROM " + TABLE_NAME + " WHERE nullif(" + ADDRESS + ", '') IS NOT NULL"; + try (Cursor cursor = db.rawQuery(query, null)) { + List> threads = new ArrayList<>(cursor.getCount()); + while (cursor.moveToNext()) { + long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); + String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)); + if (address != null && !address.isEmpty()) { + threads.add(new kotlin.Pair<>(Address.fromSerialized(address), threadId)); + } + } + return threads; + } + } + + @NonNull + public ArrayList getApprovedConversationList() { // Approved conversations will come from two different sources: // 1. Config based conversations // 2. Blinded conversations stored in the database final List
blindedConversations = getBlindedConversations(true, false); final List
configBasedConversations = recipientRepository.get().getAllConfigBasedApprovedConversations(); - final List
allAddresses = new ArrayList<>(blindedConversations.size() + configBasedConversations.size()); - allAddresses.addAll(blindedConversations); - allAddresses.addAll(configBasedConversations); - - return getFilteredConversationList(allAddresses); + return getFilteredConversationList(CollectionsKt.plus(blindedConversations, configBasedConversations)); } - @Nullable - public Cursor getUnapprovedConversationList() { + @NonNull + public ArrayList getUnapprovedConversationList() { // Unapproved conversations will come from two different sources: // 1. Config based conversations // 2. Blinded conversations stored in the database final List
blindedConversations = getBlindedConversations(false, false); final List
configBasedConversations = recipientRepository.get().getAllConfigBasedUnapprovedConversations(); - final List
allAddresses = new ArrayList<>(blindedConversations.size() + configBasedConversations.size()); - allAddresses.addAll(blindedConversations); - allAddresses.addAll(configBasedConversations); - - return getFilteredConversationList(allAddresses); - } - - public Cursor getDirectShareList() { - SQLiteDatabase db = getReadableDatabase(); - String query = createQuery("", 0); - - return db.rawQuery(query, null); + return getFilteredConversationList(CollectionsKt.plus(blindedConversations, configBasedConversations)); } /** @@ -802,24 +693,16 @@ public boolean markAllAsRead(long threadId, boolean isGroupRecipient, long lastS return null; } - private @NonNull String createQuery(@NonNull String where, int limit) { + private @NonNull String createQuery(@NonNull String where) { String projection = Util.join(COMBINED_THREAD_RECIPIENT_GROUP_PROJECTION, ","); - String query = - "SELECT " + projection + " FROM " + TABLE_NAME + + return "SELECT " + projection + " FROM " + TABLE_NAME + " LEFT OUTER JOIN " + RecipientDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ADDRESS + " = " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.ADDRESS + " LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ADDRESS + " = " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LEFT OUTER JOIN " + LokiMessageDatabase.groupInviteTable + " ON "+ TABLE_NAME + "." + ID + " = " + LokiMessageDatabase.groupInviteTable+"."+LokiMessageDatabase.invitingSessionId + - " WHERE " + where + - " ORDER BY " + TABLE_NAME + "." + IS_PINNED + " DESC, " + TABLE_NAME + "." + THREAD_CREATION_DATE + " DESC"; - - if (limit > 0) { - query += " LIMIT " + limit; - } - - return query; + " WHERE " + where; } public void notifyThreadUpdated(long threadId) { @@ -830,29 +713,12 @@ public interface ProgressListener { void onProgress(int complete, int total); } - public Reader readerFor(Cursor cursor) { - return readerFor(cursor, true); - } - - /** - * Create a reader to conveniently access the thread cursor - * - * @param retrieveGroupStatus Whether group status should be calculated based on the config data. - * Normally you always want it, but if you don't want the reader - * to access the config system, this is the flag to turn it off. - */ - public Reader readerFor(Cursor cursor, boolean retrieveGroupStatus) { - return new Reader(cursor, retrieveGroupStatus); - } - - public class Reader implements Closeable { + private class Reader implements Closeable { private final Cursor cursor; - private final boolean retrieveGroupStatus; - public Reader(Cursor cursor, boolean retrieveGroupStatus) { + public Reader(Cursor cursor) { this.cursor = cursor; - this.retrieveGroupStatus = retrieveGroupStatus; } public int getCount() { @@ -900,7 +766,7 @@ public ThreadRecord getCurrent() { } final GroupThreadStatus groupThreadStatus; - if (recipient.isGroupV2Recipient() && retrieveGroupStatus) { + if (recipient.isGroupV2Recipient()) { GroupInfo.ClosedGroupInfo group = ConfigFactoryProtocolKt.getGroup( MessagingModuleConfiguration.getShared().getConfigFactory(), new AccountId(recipient.getAddress().toString()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt index 2d41f87287..d7eb78a13a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt @@ -282,9 +282,7 @@ class DebugMenuViewModel @Inject constructor( // clear trusted downloads for all recipients viewModelScope.launch { - val conversations: List = threadDb.approvedConversationList.use { openCursor -> - threadDb.readerFor(openCursor).run { generateSequence { next }.toList() } - } + val conversations: List = threadDb.approvedConversationList conversations.filter { !it.recipient.isLocalNumber }.forEach { recipientDatabase.setAutoDownloadAttachments(it.recipient.address, false) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index 5fcaa4d987..e24f1cda7d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -36,10 +36,10 @@ import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigUpdateNotification import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.Log import org.session.libsession.utilities.currentUserName import org.session.libsession.utilities.userConfigsChanged import org.session.libsignal.utilities.AccountId +import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord @@ -143,15 +143,13 @@ class HomeViewModel @Inject constructor( ).flowOn(Dispatchers.Default) private fun unapprovedConversationCount() = reloadTriggersAndContentChanges() - .map { threadDb.unapprovedConversationList?.use { cursor -> cursor.count } ?: 0 } + .map { threadDb.unapprovedConversationList.size } @Suppress("OPT_IN_USAGE") private fun observeConversationList(): Flow> = reloadTriggersAndContentChanges() .mapLatest { _ -> withContext(Dispatchers.Default) { - val records = threadDb.approvedConversationList.use { openCursor -> - threadDb.readerFor(openCursor).run { generateSequence { next }.toMutableList() } - } + val records = threadDb.approvedConversationList // Sort the threads by priority and last message timestamp records.sortWith(threadRecordComparator) diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt index 274e24ba95..78037a1216 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt @@ -1,7 +1,5 @@ package org.thoughtcrime.securesms.messagerequests -import android.content.Intent -import android.database.Cursor import android.os.Bundle import androidx.activity.viewModels import androidx.core.view.isVisible @@ -11,7 +9,6 @@ import com.bumptech.glide.Glide import com.bumptech.glide.RequestManager import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject import network.loki.messenger.R import network.loki.messenger.databinding.ActivityMessageRequestsBinding import org.session.libsession.utilities.Address @@ -24,9 +21,10 @@ import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.applySafeInsetsPaddings import org.thoughtcrime.securesms.util.push +import javax.inject.Inject @AndroidEntryPoint -class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClickListener, LoaderManager.LoaderCallbacks { +class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClickListener, LoaderManager.LoaderCallbacks> { private lateinit var binding: ActivityMessageRequestsBinding private lateinit var glide: RequestManager @@ -37,7 +35,7 @@ class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClick private val viewModel: MessageRequestsViewModel by viewModels() private val adapter: MessageRequestsAdapter by lazy { - MessageRequestsAdapter(context = this, cursor = null, dateUtils = dateUtils, listener = this) + MessageRequestsAdapter(dateUtils = dateUtils, listener = this) } override val applyDefaultWindowInsets: Boolean @@ -51,7 +49,6 @@ class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClick glide = Glide.with(this) adapter.setHasStableIds(true) - adapter.glide = glide binding.recyclerView.adapter = adapter binding.clearAllMessageRequestsButton.setOnClickListener { deleteAll() } @@ -66,17 +63,21 @@ class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClick LoaderManager.getInstance(this).restartLoader(0, null, this) } - override fun onCreateLoader(id: Int, bundle: Bundle?): Loader { + override fun onCreateLoader(id: Int, bundle: Bundle?): Loader> { return MessageRequestsLoader(threadDb, this) } - override fun onLoadFinished(loader: Loader, cursor: Cursor?) { - adapter.changeCursor(cursor) + override fun onLoadFinished( + loader: Loader>, + data: List + ) { + adapter.conversations = data updateEmptyState() } - override fun onLoaderReset(cursor: Loader) { - adapter.changeCursor(null) + override fun onLoaderReset(loader: Loader?>) { + adapter.conversations = emptyList() + updateEmptyState() } override fun onConversationClick(thread: ThreadRecord) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt index 409dfa43e8..fa5c484c30 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt @@ -1,7 +1,5 @@ package org.thoughtcrime.securesms.messagerequests -import android.content.Context -import android.database.Cursor import android.os.Build import android.text.SpannableString import android.text.style.ForegroundColorSpan @@ -9,28 +7,30 @@ import android.view.ContextThemeWrapper import android.view.ViewGroup import android.widget.PopupMenu import androidx.core.graphics.drawable.DrawableCompat +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.RequestManager import network.loki.messenger.R import org.session.libsession.utilities.ThemeUtil -import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter import org.thoughtcrime.securesms.database.model.ThreadRecord -import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import com.bumptech.glide.RequestManager import org.thoughtcrime.securesms.util.DateUtils class MessageRequestsAdapter( - context: Context, - cursor: Cursor?, val dateUtils: DateUtils, val listener: ConversationClickListener -) : CursorRecyclerViewAdapter(context, cursor) { - private val threadDatabase = DatabaseComponent.get(context).threadDatabase() - lateinit var glide: RequestManager +) : RecyclerView.Adapter() { + var conversations: List = emptyList() + set(value) { + if (field != value) { + field = value + notifyDataSetChanged() + } + } class ViewHolder(val view: MessageRequestView) : RecyclerView.ViewHolder(view) - override fun onCreateItemViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val view = MessageRequestView(context) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = MessageRequestView(parent.context) view.setOnClickListener { view.thread?.let { listener.onConversationClick(it) } } view.setOnLongClickListener { view.thread?.let { thread -> @@ -44,18 +44,21 @@ class MessageRequestsAdapter( return ViewHolder(view) } - override fun onBindItemViewHolder(viewHolder: ViewHolder, cursor: Cursor) { - val thread = getThread(cursor)!! - viewHolder.view.bind(thread, dateUtils) + override fun onBindViewHolder( + holder: ViewHolder, + position: Int + ) { + holder.view.bind(conversations[position], dateUtils) } - override fun onItemViewRecycled(holder: ViewHolder?) { - super.onItemViewRecycled(holder) - holder?.view?.recycle() + override fun getItemCount(): Int = conversations.size + + override fun onViewRecycled(holder: ViewHolder) { + holder.view.recycle() } private fun showPopupMenu(view: MessageRequestView, legacyOrCommunityGroup: Boolean, invitingAdmin: String?) { - val popupMenu = PopupMenu(ContextThemeWrapper(context, R.style.PopupMenu_MessageRequests), view) + val popupMenu = PopupMenu(ContextThemeWrapper(view.context, R.style.PopupMenu_MessageRequests), view) // still show the block option if we have an inviting admin for the group if ((legacyOrCommunityGroup && invitingAdmin == null) || view.thread!!.recipient.isCommunityInboxRecipient) { popupMenu.menuInflater.inflate(R.menu.menu_group_request, popupMenu.menu) @@ -73,7 +76,7 @@ class MessageRequestsAdapter( for (i in 0 until popupMenu.menu.size()) { val item = popupMenu.menu.getItem(i) val s = SpannableString(item.title) - val danger = ThemeUtil.getThemedColor(context, R.attr.danger) + val danger = ThemeUtil.getThemedColor(view.context, R.attr.danger) s.setSpan(ForegroundColorSpan(danger), 0, s.length, 0) item.icon?.let { DrawableCompat.setTint( @@ -88,10 +91,6 @@ class MessageRequestsAdapter( } popupMenu.show() } - - private fun getThread(cursor: Cursor): ThreadRecord? { - return threadDatabase.readerFor(cursor).current - } } interface ConversationClickListener { diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsLoader.kt index 59544e8515..5962c82564 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsLoader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsLoader.kt @@ -1,18 +1,25 @@ package org.thoughtcrime.securesms.messagerequests import android.content.Context -import android.database.Cursor -import android.database.MatrixCursor +import androidx.loader.content.AsyncTaskLoader import org.thoughtcrime.securesms.database.ThreadDatabase -import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.util.AbstractCursorLoader +import org.thoughtcrime.securesms.database.model.ThreadRecord class MessageRequestsLoader( private val threadDatabase: ThreadDatabase, context: Context -) : AbstractCursorLoader(context) { +) : AsyncTaskLoader>(context) { - override fun getCursor(): Cursor? { - return threadDatabase.unapprovedConversationList + + override fun loadInBackground(): List { + val list = threadDatabase.unapprovedConversationList + list.sortWith(UNAPPROVED_THREAD_COMPARATOR) + return list + } + + companion object { + private val UNAPPROVED_THREAD_COMPARATOR = compareByDescending { it.lastMessage?.timestamp ?: 0 } + .thenByDescending { it.date } + .thenBy { it.recipient.displayName } } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index efca18509f..557f06561e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -407,13 +407,11 @@ class DefaultConversationRepository @Inject constructor( override suspend fun clearAllMessageRequests(block: Boolean) = runCatching { withContext(Dispatchers.Default) { - threadDb.readerFor(threadDb.unapprovedConversationList).use { reader -> - while (reader.next != null) { - deleteMessageRequest(reader.current) - val recipient = reader.current.recipient - if (block && !recipient.isGroupV2Recipient) { - setBlocked(recipient.address, true) - } + threadDb.unapprovedConversationList.forEach { record -> + deleteMessageRequest(record) + val recipient = record.recipient + if (block && !recipient.isGroupV2Recipient) { + setBlocked(recipient.address, true) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt index 4572b6c7bb..9a8c5aa599 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt @@ -108,15 +108,12 @@ class SearchRepository @Inject constructor( private fun queryConversations( query: String, - ): CursorList { + ): List { val numbers = contactAccessor.getNumbersForThreadSearchFilter(context, query) val addresses = numbers.map { fromSerialized(it) } - val conversations = threadDatabase.getFilteredConversationList(addresses) - return if (conversations != null) - CursorList(conversations, GroupModelBuilder(threadDatabase, groupDatabase)) - else - CursorList.emptyList() + return threadDatabase.getFilteredConversationList(addresses) + .map { groupDatabase.getGroup(it.recipient.address.toGroupString()).get() } } private fun queryMessages(query: String): CursorList { @@ -159,16 +156,6 @@ class SearchRepository @Inject constructor( return out.toString() } - private class GroupModelBuilder( - private val threadDatabase: ThreadDatabase, - private val groupDatabase: GroupDatabase - ) : CursorList.ModelBuilder { - override fun build(cursor: Cursor): GroupRecord { - val threadRecord = threadDatabase.readerFor(cursor).current - return groupDatabase.getGroup(threadRecord.recipient.address.toGroupString()).get() - } - } - private inner class MessageModelBuilder() : CursorList.ModelBuilder { override fun build(cursor: Cursor): MessageResult { val conversationAddress = diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/model/SearchResult.java b/app/src/main/java/org/thoughtcrime/securesms/search/model/SearchResult.java index a8b5bb1151..e865961545 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/model/SearchResult.java +++ b/app/src/main/java/org/thoughtcrime/securesms/search/model/SearchResult.java @@ -19,12 +19,12 @@ public class SearchResult { private final String query; private final List contacts; - private final CursorList conversations; + private final List conversations; private final CursorList messages; public SearchResult(@NonNull String query, @NonNull List contacts, - @NonNull CursorList conversations, + @NonNull List conversations, @NonNull CursorList messages) { this.query = query; diff --git a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt index bccab47999..b35f726b3d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt @@ -255,23 +255,20 @@ class TokenPageViewModel @Inject constructor( // Grab the database and reader details we need to count the conversations / groups val threadDatabase = DatabaseComponent.get(context).threadDatabase() - val cursor = threadDatabase.approvedConversationList + val convoList = threadDatabase.approvedConversationList val result = mutableSetOf() // Look through the database to build up our conversation & group counts (still on Dispatchers.IO not the main thread) - threadDatabase.readerFor(cursor).use { reader -> - while (reader.next != null) { - val thread = reader.current - val recipient = thread.recipient - result.add(recipient) - - if (recipient.is1on1) { - num1to1Convos += 1 - } else if (recipient.isGroupV2Recipient) { - numGroupV2Convos += 1 - } else if (recipient.isLegacyGroupRecipient) { - numLegacyGroupConvos += 1 - } + convoList.forEach { thread -> + val recipient = thread.recipient + result.add(recipient) + + if (recipient.is1on1) { + num1to1Convos += 1 + } else if (recipient.isGroupV2Recipient) { + numGroupV2Convos += 1 + } else if (recipient.isLegacyGroupRecipient) { + numLegacyGroupConvos += 1 } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ContactUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ContactUtilities.kt deleted file mode 100644 index c9b91d50a4..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ContactUtilities.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.thoughtcrime.securesms.util - -import android.content.Context -import org.session.libsession.utilities.recipients.Recipient -import org.thoughtcrime.securesms.dependencies.DatabaseComponent - -typealias LastMessageSentTimestamp = Long -object ContactUtilities { - - @JvmStatic - fun getAllContacts(context: Context): Set> { - val threadDatabase = DatabaseComponent.get(context).threadDatabase() - val cursor = threadDatabase.conversationList - val result = mutableSetOf>() - threadDatabase.readerFor(cursor).use { reader -> - while (reader.next != null) { - val thread = reader.current - result.add(Pair(thread.recipient, thread.lastMessage?.timestamp ?: 0)) - } - } - return result - } - -} \ No newline at end of file From fb05ef12e48167f4836dc9f7cf9d886ed621ff79 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:02:54 +1000 Subject: [PATCH 26/52] WIP --- .../thoughtcrime/securesms/ShareActivity.kt | 23 +- .../contacts/ContactSelectionListAdapter.kt | 15 +- .../contacts/ContactsCursorLoader.java | 243 ------------------ .../contacts/ShareContactListFragment.kt | 15 +- .../contacts/ShareContactListLoader.kt | 11 +- .../securesms/database/RecipientRepository.kt | 2 +- .../securesms/database/Storage.kt | 23 +- .../securesms/search/model/SearchResult.java | 1 - .../securesms/service/DirectShareService.java | 92 ++++--- 9 files changed, 74 insertions(+), 351 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt index 4ba47e661a..ce0aa87a76 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.kt @@ -22,12 +22,12 @@ import android.content.Intent import android.net.Uri import android.os.AsyncTask import android.os.Bundle -import android.os.Parcel import android.provider.OpenableColumns import android.view.MenuItem import android.view.View import android.widget.ImageView import android.widget.TextView +import androidx.core.content.IntentCompat import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R @@ -40,7 +40,6 @@ import org.thoughtcrime.securesms.components.SearchToolbar.SearchListener import org.thoughtcrime.securesms.contacts.ShareContactListFragment import org.thoughtcrime.securesms.contacts.ShareContactListFragment.OnContactSelectedListener import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 -import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.providers.BlobUtils import org.thoughtcrime.securesms.util.MediaUtil @@ -55,9 +54,7 @@ class ShareActivity : ScreenLockActionBarActivity(), OnContactSelectedListener { private val TAG = ShareActivity::class.java.simpleName companion object { - const val EXTRA_THREAD_ID = "thread_id" - const val EXTRA_ADDRESS_MARSHALLED = "address_marshalled" - const val EXTRA_DISTRIBUTION_TYPE = "distribution_type" + const val EXTRA_ADDRESS = "address" } override val applyDefaultWindowInsets: Boolean @@ -183,20 +180,8 @@ class ShareActivity : ScreenLockActionBarActivity(), OnContactSelectedListener { } private fun handleResolvedMedia(intent: Intent, animate: Boolean) { - val threadId = intent.getLongExtra(EXTRA_THREAD_ID, -1) - val distributionType = intent.getIntExtra(EXTRA_DISTRIBUTION_TYPE, -1) - var address: Address? = null - - if (intent.hasExtra(EXTRA_ADDRESS_MARSHALLED)) { - val parcel = Parcel.obtain() - val marshalled = intent.getByteArrayExtra(EXTRA_ADDRESS_MARSHALLED) - parcel.unmarshall(marshalled!!, 0, marshalled.size) - parcel.setDataPosition(0) - address = parcel.readParcelable(classLoader) - parcel.recycle() - } - - val hasResolvedDestination = threadId != -1L && address != null && distributionType != -1 + val address = IntentCompat.getParcelableExtra
(intent, EXTRA_ADDRESS, Address::class.java) + val hasResolvedDestination = address != null if (!hasResolvedDestination && animate) { ViewUtil.fadeIn(contactsFragment.requireView(), 300) diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.kt index 229e7b6cba..898c7d85d6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.kt @@ -9,7 +9,7 @@ import org.session.libsession.utilities.recipients.Recipient class ContactSelectionListAdapter(private val context: Context, private val multiSelect: Boolean) : RecyclerView.Adapter() { lateinit var glide: RequestManager val selectedContacts = mutableSetOf() - var items = listOf() + var items = listOf() set(value) { field = value; notifyDataSetChanged() } var contactClickListener: ContactClickListener? = null @@ -41,11 +41,10 @@ class ContactSelectionListAdapter(private val context: Context, private val mult override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) { val item = items[position] if (viewHolder is UserViewHolder) { - item as ContactSelectionListItem.Contact - viewHolder.view.setOnClickListener { contactClickListener?.onContactClick(item.recipient) } - val isSelected = selectedContacts.contains(item.recipient) + viewHolder.view.setOnClickListener { contactClickListener?.onContactClick(item) } + val isSelected = selectedContacts.contains(item) viewHolder.view.bind( - item.recipient, + item, if (multiSelect) UserView.ActionIndicator.Tick else UserView.ActionIndicator.None, isSelected, showCurrentUserAsNoteToSelf = true @@ -61,11 +60,7 @@ class ContactSelectionListAdapter(private val context: Context, private val mult selectedContacts.add(recipient) contactClickListener?.onContactSelected(recipient) } - val index = items.indexOfFirst { - when (it) { - is ContactSelectionListItem.Contact -> it.recipient == recipient - } - } + val index = items.indexOf(recipient) notifyItemChanged(index) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java deleted file mode 100644 index 62dfed2abc..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) 2013-2017 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.contacts; - -import android.content.Context; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.database.MergeCursor; -import android.provider.ContactsContract; -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.loader.content.CursorLoader; - -import org.session.libsession.utilities.GroupRecord; -import org.thoughtcrime.securesms.database.GroupDatabase; -import org.thoughtcrime.securesms.database.ThreadDatabase; -import org.thoughtcrime.securesms.database.model.ThreadRecord; -import org.thoughtcrime.securesms.dependencies.DatabaseComponent; - -import java.util.ArrayList; -import java.util.List; - -import network.loki.messenger.R; - -/** - * CursorLoader that initializes a ContactsDatabase instance - * - * @author Jake McGinty - */ -public class ContactsCursorLoader extends CursorLoader { - private static final String TAG = ContactsCursorLoader.class.getSimpleName(); - - static final int NORMAL_TYPE = 0; - static final int PUSH_TYPE = 1; - static final int NEW_TYPE = 2; - static final int RECENT_TYPE = 3; - static final int DIVIDER_TYPE = 4; - - static final String CONTACT_TYPE_COLUMN = "contact_type"; - static final String LABEL_COLUMN = "label"; - static final String NUMBER_TYPE_COLUMN = "number_type"; - static final String NUMBER_COLUMN = "number"; - static final String NAME_COLUMN = "name"; - - public static final class DisplayMode { - public static final int FLAG_PUSH = 1; - public static final int FLAG_SMS = 1 << 1; - public static final int FLAG_GROUPS = 1 << 2; - public static final int FLAG_ALL = FLAG_PUSH | FLAG_SMS | FLAG_GROUPS; - } - - private static final String[] CONTACT_PROJECTION = new String[]{NAME_COLUMN, - NUMBER_COLUMN, - NUMBER_TYPE_COLUMN, - LABEL_COLUMN, - CONTACT_TYPE_COLUMN}; - - private static final int RECENT_CONVERSATION_MAX = 25; - - private final String filter; - private final int mode; - private final boolean recents; - - public ContactsCursorLoader(@NonNull Context context, int mode, String filter, boolean recents) - { - super(context); - - this.filter = filter; - this.mode = mode; - this.recents = recents; - } - - @Override - public Cursor loadInBackground() { - List cursorList = TextUtils.isEmpty(filter) ? getUnfilteredResults() - : getFilteredResults(); - if (cursorList.size() > 0) { - return new MergeCursor(cursorList.toArray(new Cursor[0])); - } - return null; - } - - private List getUnfilteredResults() { - ArrayList cursorList = new ArrayList<>(); - - if (recents) { - Cursor recentConversations = getRecentConversationsCursor(); - if (recentConversations.getCount() > 0) { - cursorList.add(getRecentsHeaderCursor()); - cursorList.add(recentConversations); - cursorList.add(getContactsHeaderCursor()); - } - } - cursorList.addAll(getContactsCursors()); - return cursorList; - } - - private List getFilteredResults() { - ArrayList cursorList = new ArrayList<>(); - - if (groupsEnabled(mode)) { - Cursor groups = getGroupsCursor(); - if (groups.getCount() > 0) { - List contacts = getContactsCursors(); - if (!isCursorListEmpty(contacts)) { - cursorList.add(getContactsHeaderCursor()); - cursorList.addAll(contacts); - cursorList.add(getGroupsHeaderCursor()); - } - cursorList.add(groups); - } else { - cursorList.addAll(getContactsCursors()); - } - } else { - cursorList.addAll(getContactsCursors()); - } - - return cursorList; - } - - private Cursor getRecentsHeaderCursor() { - MatrixCursor recentsHeader = new MatrixCursor(CONTACT_PROJECTION); - /* - recentsHeader.addRow(new Object[]{ getContext().getString(R.string.ContactsCursorLoader_recent_chats), - "", - ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE, - "", - ContactsDatabase.DIVIDER_TYPE }); - */ - return recentsHeader; - } - - private Cursor getContactsHeaderCursor() { - MatrixCursor contactsHeader = new MatrixCursor(CONTACT_PROJECTION, 1); - /* - contactsHeader.addRow(new Object[] { getContext().getString(R.string.ContactsCursorLoader_contacts), - "", - ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE, - "", - ContactsDatabase.DIVIDER_TYPE }); - */ - return contactsHeader; - } - - private Cursor getGroupsHeaderCursor() { - MatrixCursor groupHeader = new MatrixCursor(CONTACT_PROJECTION, 1); - groupHeader.addRow(new Object[]{ getContext().getString(R.string.conversationsGroups), - "", - ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE, - "", - DIVIDER_TYPE }); - return groupHeader; - } - - - private Cursor getRecentConversationsCursor() { - ThreadDatabase threadDatabase = DatabaseComponent.get(getContext()).threadDatabase(); - - MatrixCursor recentConversations = new MatrixCursor(CONTACT_PROJECTION, RECENT_CONVERSATION_MAX); - try (Cursor rawConversations = threadDatabase.getRecentConversationList(RECENT_CONVERSATION_MAX)) { - ThreadDatabase.Reader reader = threadDatabase.readerFor(rawConversations); - ThreadRecord threadRecord; - while ((threadRecord = reader.getNext()) != null) { - recentConversations.addRow(new Object[] { threadRecord.getRecipient().getDisplayName(), - threadRecord.getRecipient().getAddress().toString(), - ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE, - "", - RECENT_TYPE }); - } - } - return recentConversations; - } - - private List getContactsCursors() { - return new ArrayList<>(2); - /* - if (!Permissions.hasAny(getContext(), Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) { - return cursorList; - } - - if (pushEnabled(mode)) { - cursorList.add(contactsDatabase.queryTextSecureContacts(filter)); - } - - if (pushEnabled(mode) && smsEnabled(mode)) { - cursorList.add(contactsDatabase.querySystemContacts(filter)); - } else if (smsEnabled(mode)) { - cursorList.add(filterNonPushContacts(contactsDatabase.querySystemContacts(filter))); - } - return cursorList; - */ - } - - private Cursor getGroupsCursor() { - MatrixCursor groupContacts = new MatrixCursor(CONTACT_PROJECTION); - try (GroupDatabase.Reader reader = DatabaseComponent.get(getContext()).groupDatabase().getGroupsFilteredByTitle(filter)) { - GroupRecord groupRecord; - while ((groupRecord = reader.getNext()) != null) { - groupContacts.addRow(new Object[] { groupRecord.getTitle(), - groupRecord.getEncodedId(), - ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM, - "", - NORMAL_TYPE }); - } - } - return groupContacts; - } - - private static boolean isCursorListEmpty(List list) { - int sum = 0; - for (Cursor cursor : list) { - sum += cursor.getCount(); - } - return sum == 0; - } - - private static boolean pushEnabled(int mode) { - return (mode & DisplayMode.FLAG_PUSH) > 0; - } - - private static boolean smsEnabled(int mode) { - return (mode & DisplayMode.FLAG_SMS) > 0; - } - - private static boolean groupsEnabled(int mode) { - return (mode & DisplayMode.FLAG_GROUPS) > 0; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt index 036653a624..f16dcab10b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt @@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.database.ThreadDatabase import javax.inject.Inject @AndroidEntryPoint -class ShareContactListFragment : Fragment(), LoaderManager.LoaderCallbacks>, ContactClickListener { +class ShareContactListFragment : Fragment(), LoaderManager.LoaderCallbacks>, ContactClickListener { private lateinit var binding: ShareContactListFragmentBinding private var cursorFilter: String? = null var onContactSelectedListener: OnContactSelectedListener? = null @@ -96,26 +96,25 @@ class ShareContactListFragment : Fragment(), LoaderManager.LoaderCallbacks> { + override fun onCreateLoader(id: Int, args: Bundle?): Loader> { return ShareContactListLoader( context = requireActivity(), - mode = ContactsCursorLoader.DisplayMode.FLAG_ALL, filter = cursorFilter, deprecationManager = deprecationManager, - storage = storage, - threadDatabase = threadDatabase + threadDatabase = threadDatabase, + storage = storage ) } - override fun onLoadFinished(loader: Loader>, items: List) { + override fun onLoadFinished(loader: Loader>, items: List) { update(items) } - override fun onLoaderReset(loader: Loader>) { + override fun onLoaderReset(loader: Loader>) { update(listOf()) } - private fun update(items: List) { + private fun update(items: List) { if (activity?.isDestroyed == true) { Log.e(ShareContactListFragment::class.java.name, "Received a loader callback after the fragment was detached from the activity.", diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt index 3fb5d0b2e0..5f359b8733 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt @@ -2,27 +2,22 @@ package org.thoughtcrime.securesms.contacts import android.content.Context import org.session.libsession.database.StorageProtocol -import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.util.AsyncLoader -sealed class ContactSelectionListItem { - class Contact(val recipient: Recipient) : ContactSelectionListItem() -} class ShareContactListLoader( context: Context, - val mode: Int, val filter: String?, private val deprecationManager: LegacyGroupDeprecationManager, private val threadDatabase: ThreadDatabase, private val storage: StorageProtocol, -) : AsyncLoader>(context) { +) : AsyncLoader>(context) { - override fun loadInBackground(): List { + override fun loadInBackground(): List { val threads = threadDatabase.approvedConversationList .asSequence() .filter { thread -> @@ -40,7 +35,7 @@ class ShareContactListLoader( threads.sortWith(COMPARATOR) - return threads.map { ContactSelectionListItem.Contact(it.recipient) } + return threads.map { it.recipient } } companion object { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index b88f43dbc2..e813b4e40f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -102,7 +102,7 @@ class RecipientRepository @Inject constructor( emit(value) val evt = changeSource - .debounce(1000) // Debounce to avoid too frequent updates + .debounce(200) // Debounce to avoid too frequent updates .first() Log.d(TAG, "Recipient changed for ${address.debugString}, triggering event: $evt") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 01810b82c1..8e60e9caee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1352,20 +1352,19 @@ open class Storage @Inject constructor( val mappingDb = blindedIdMappingDatabase val mappings = mutableMapOf() - threadDatabase.readerFor(threadDatabase.conversationList).use { reader -> - while (reader.next != null) { - val recipient = reader.current.recipient - val address = recipient.address.toString() - val blindedId = when { - recipient.isGroupOrCommunityRecipient -> null - recipient.isCommunityInboxRecipient -> GroupUtil.getDecodedOpenGroupInboxAccountId(address) - else -> address.takeIf { AccountId(it).prefix == IdPrefix.BLINDED } - } ?: continue - mappingDb.getBlindedIdMapping(blindedId).firstOrNull()?.let { - mappings[address] = it - } + + for ((address, _) in threadDatabase.allThreads) { + val blindedId = when { + address.isGroupOrCommunity -> null + address.isCommunityInbox -> GroupUtil.getDecodedOpenGroupInboxAccountId(address.toString()) + else -> address.address.takeIf { AccountId.fromStringOrNull(it)?.prefix == IdPrefix.BLINDED } + } ?: continue + + mappingDb.getBlindedIdMapping(blindedId).firstOrNull()?.let { + mappings[address.address] = it } } + for (mapping in mappings) { if (!BlindKeyAPI.sessionIdMatchesBlindedId( sessionId = senderPublicKey, diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/model/SearchResult.java b/app/src/main/java/org/thoughtcrime/securesms/search/model/SearchResult.java index e865961545..30284b710b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/model/SearchResult.java +++ b/app/src/main/java/org/thoughtcrime/securesms/search/model/SearchResult.java @@ -58,7 +58,6 @@ public boolean isEmpty() { } public void close() { - conversations.close(); messages.close(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java index 8fe5d030a5..67afa37337 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java @@ -3,29 +3,31 @@ import android.content.ComponentName; import android.content.IntentFilter; -import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Icon; import android.os.Bundle; -import android.os.Parcel; import android.service.chooser.ChooserTarget; import android.service.chooser.ChooserTargetService; import androidx.annotation.NonNull; +import com.bumptech.glide.Glide; + +import org.jetbrains.annotations.NotNull; +import org.session.libsession.database.StorageProtocol; +import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.ShareActivity; +import org.thoughtcrime.securesms.contacts.ShareContactListLoader; import org.thoughtcrime.securesms.database.RecipientRepository; import org.thoughtcrime.securesms.database.ThreadDatabase; -import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; -import com.bumptech.glide.Glide; import org.thoughtcrime.securesms.util.BitmapUtil; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; @@ -38,6 +40,11 @@ public class DirectShareService extends ChooserTargetService { @Inject RecipientRepository recipientRepository; + @Inject + LegacyGroupDeprecationManager legacyGroupDeprecationManager; + + @Inject + StorageProtocol storage; private static final String TAG = DirectShareService.class.getSimpleName(); @@ -45,53 +52,40 @@ public class DirectShareService extends ChooserTargetService { public List onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) { - List results = new LinkedList<>(); + List results = new ArrayList<>(); ComponentName componentName = new ComponentName(this, ShareActivity.class); ThreadDatabase threadDatabase = DatabaseComponent.get(this).threadDatabase(); - try (Cursor cursor = threadDatabase.getDirectShareList()) { - ThreadDatabase.Reader reader = threadDatabase.readerFor(cursor); - ThreadRecord record; - - while ((record = reader.getNext()) != null && results.size() < 10) { - Recipient recipient = record.getRecipient(); - String name = recipient.getDisplayName(); - - Bitmap avatar; - - if (recipient.getAvatar() != null) { - try { - avatar = Glide.with(this) - .asBitmap() - .load(recipient.getAvatar()) - .circleCrop() - .submit(getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width), - getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width)) - .get(); - } catch (InterruptedException | ExecutionException e) { - Log.w(TAG, e); - avatar = getFallbackDrawable(recipient); - } - } else { - avatar = getFallbackDrawable(recipient); - } - - Parcel parcel = Parcel.obtain(); - parcel.writeParcelable(recipient.getAddress(), 0); - - Bundle bundle = new Bundle(); - bundle.putLong(ShareActivity.EXTRA_THREAD_ID, record.getThreadId()); - bundle.putByteArray(ShareActivity.EXTRA_ADDRESS_MARSHALLED, parcel.marshall()); - bundle.putInt(ShareActivity.EXTRA_DISTRIBUTION_TYPE, record.getDistributionType()); - bundle.setClassLoader(getClassLoader()); - - results.add(new ChooserTarget(name, Icon.createWithBitmap(avatar), 1.0f, componentName, bundle)); - parcel.recycle(); - - } - - return results; - } + List<@NotNull Recipient> items = new ShareContactListLoader(this, null, legacyGroupDeprecationManager, threadDatabase, storage).loadInBackground(); + + for (final Recipient recipient : items) { + Bitmap avatar; + + if (recipient.getAvatar() != null) { + try { + avatar = Glide.with(this) + .asBitmap() + .load(recipient.getAvatar()) + .circleCrop() + .submit(getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width), + getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width)) + .get(); + } catch (InterruptedException | ExecutionException e) { + Log.w(TAG, e); + avatar = getFallbackDrawable(recipient); + } + } else { + avatar = getFallbackDrawable(recipient); + } + + Bundle bundle = new Bundle(1); + bundle.putParcelable(ShareActivity.EXTRA_ADDRESS, recipient.getAddress()); + bundle.setClassLoader(getClassLoader()); + + results.add(new ChooserTarget(recipient.getDisplayName(), Icon.createWithBitmap(avatar), 1.0f, componentName, bundle)); + } + + return results; } private Bitmap getFallbackDrawable(@NonNull Recipient recipient) { From efce12f7f28f4af75aed6dae24047872ec9a5f61 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:02:22 +1000 Subject: [PATCH 27/52] Tidy up reactivity logic in convo screen --- .../utilities/ConfigFactoryProtocol.kt | 10 +- .../conversation/v2/ConversationActivityV2.kt | 134 +++-- .../conversation/v2/ConversationViewModel.kt | 460 +++++++----------- .../conversation/v2/input_bar/InputBar.kt | 11 +- .../settings/ConversationSettingsViewModel.kt | 1 - .../securesms/database/LokiThreadDatabase.kt | 26 + .../securesms/groups/OpenGroupManager.kt | 14 - .../thoughtcrime/securesms/util/FlowUtils.kt | 22 + 8 files changed, 335 insertions(+), 343 deletions(-) diff --git a/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt b/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt index 77479d1b96..964e38ab5c 100644 --- a/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt +++ b/app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt @@ -1,6 +1,7 @@ package org.session.libsession.utilities import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.onStart @@ -127,8 +128,15 @@ fun ConfigFactoryProtocol.getGroup(groupId: AccountId): GroupInfo.ClosedGroupInf /** * Flow that emits when the user configs are modified or merged. */ -fun ConfigFactoryProtocol.userConfigsChanged(): Flow<*> = +fun ConfigFactoryProtocol.userConfigsChanged(debounceMills: Long = 0L): Flow<*> = configUpdateNotifications.filter { it is ConfigUpdateNotification.UserConfigsModified || it is ConfigUpdateNotification.UserConfigsMerged } + .let { flow -> + if (debounceMills > 0) { + flow.debounce(debounceMills) + } else { + flow + } + } /** * Wait until all configs of given group are pushed to the server. diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 11bed0b4ea..9e513473a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -66,13 +66,16 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.scan +import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import network.loki.messenger.R @@ -87,6 +90,7 @@ import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage +import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.attachments.Attachment @@ -105,6 +109,7 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED import org.session.libsession.utilities.concurrent.SimpleTask import org.session.libsession.utilities.getColorFromAttr +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.MnemonicCodec import org.session.libsignal.utilities.ListenableFuture import org.session.libsignal.utilities.Log @@ -326,6 +331,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, private var conversationLoadAnimationJob: Job? = null + private val layoutManager: LinearLayoutManager? get() { return binding.conversationRecyclerView.layoutManager as LinearLayoutManager? } @@ -342,7 +348,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, MnemonicCodec(loadFileContents).encode(hexEncodedSeed, MnemonicCodec.Language.Configuration.english) } - private var firstCursorLoad = false + private val firstCursorLoad = MutableStateFlow(false) private val adapter by lazy { val adapter = ConversationAdapter( @@ -544,13 +550,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, // messageIdToScroll messageToScrollTimestamp.set(intent.getLongExtra(SCROLL_MESSAGE_ID, -1)) messageToScrollAuthor.set(intent.getParcelableExtra(SCROLL_MESSAGE_AUTHOR)) - val recipient = viewModel.recipient - val openGroup = recipient.let { viewModel.openGroup } - if (recipient == null || (recipient.isCommunityRecipient && openGroup == null)) { - Toast.makeText(this, getString(R.string.conversationsDeleted), Toast.LENGTH_LONG).show() - return finish() - } - setUpToolBar() setUpInputBar() setUpLinkPreviewObserver() @@ -721,8 +720,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, true, screenshotObserver ) - - viewModel.onResume() } override fun onPause() { @@ -753,7 +750,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, adapter.changeCursor(cursor) if (cursor != null) { - firstCursorLoad = true + firstCursorLoad.value = true val messageTimestamp = messageToScrollTimestamp.getAndSet(-1) val author = messageToScrollAuthor.getAndSet(null) val initialUnreadCount = mmsSmsDb.getUnreadCount(viewModel.threadId) @@ -773,8 +770,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, if (firstLoad.getAndSet(false)) scrollToFirstUnreadMessageOrBottom() handleRecyclerViewScrolled() } - - updatePlaceholder() } stopConversationLoader() @@ -1026,9 +1021,8 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, // Observe toast messages lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.uiState - .mapNotNull { it.uiMessages.firstOrNull() } - .distinctUntilChanged() + viewModel.uiMessages + .mapNotNull { it.firstOrNull() } .collect { msg -> Toast.makeText(this@ConversationActivityV2, msg.message, Toast.LENGTH_LONG).show() viewModel.messageShown(msg.id) @@ -1040,29 +1034,88 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { // Wait for `shouldExit == true` then finish the activity - viewModel.uiState - .filter { it.shouldExit } - .first() + viewModel.shouldExit + .scan(null to null) { acc: Pair, current -> + acc.second to current + } + .mapNotNull { (prev, curr) -> + // If shouldExit(curr) is true, we will always finish the activity, + // but we will show a toast on the way out only if we used to have a conversation, + // this is a way to detect the deletion of the conversation. + val shouldShowToast = if (curr == true) { + prev == false + } else { + return@mapNotNull null + } - if (!isFinishing) { - finish() - } + shouldShowToast + } + .collect { shouldShowToast -> + if (shouldShowToast) { + Toast.makeText( + this@ConversationActivityV2, + getString(R.string.conversationsDeleted), + Toast.LENGTH_LONG + ).show() + } + + if (!isFinishing) { + finish() + } + } } } - // Observe the rest misc "simple" state change. They are bundled in one big - // state observing as these changes are relatively cheap to perform even redundantly. + // React to input bar state changes lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.uiState.collect { state -> - binding.inputBar.setState(state.inputBarState) + viewModel.inputBarState.collectLatest(binding.inputBar::setState) + } + } - binding.root.requestApplyInsets() + // React to input bar state changes + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.charLimitState.collectLatest(binding.inputBar::setCharLimitState) + } + } - // show or hide loading indicator - binding.loader.isVisible = state.showLoader - updatePlaceholder() + // React to loader visibility changes + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.showLoader + .collectLatest { show -> + binding.loader.isVisible = show + } + } + } + + // React to placeholder related changes + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + data class PlaceholderData( + val recipient: Recipient, + val openGroup: OpenGroup?, + val groupThreadStatus: GroupThreadStatus, + val firstLoad: Boolean + ) + + combine( + viewModel.recipientFlow.filterNotNull(), + viewModel.openGroupFlow, + viewModel.groupV2ThreadState, + firstCursorLoad, + ::PlaceholderData, + ).collectLatest { (r, og, groupState, firstLoad) -> + updatePlaceholder( + recipient = r, + blindedRecipient = viewModel.blindedRecipient, + openGroup = og, + groupThreadStatus = groupState, + firstLoad + + ) } } } @@ -1155,9 +1208,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.uiState - .map { it.messageRequestState } - .distinctUntilChanged() + viewModel.messageRequestState .collectLatest { state -> binding.messageRequestBar.root.isVisible = state is MessageRequestUiState.Visible @@ -1304,13 +1355,12 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } // Update placeholder / control messages in a conversation - private fun updatePlaceholder() { - if(!firstCursorLoad) return - val recipient = viewModel.recipient ?: return Log.w("Loki", "recipient was null in placeholder update") - val blindedRecipient = viewModel.blindedRecipient - val openGroup = viewModel.openGroup - - val groupThreadStatus = viewModel.groupV2ThreadState + private fun updatePlaceholder(recipient: Recipient, + blindedRecipient: Recipient?, + openGroup: OpenGroup?, + groupThreadStatus: GroupThreadStatus, + isFirstCursorLoad: Boolean) { + if(!isFirstCursorLoad) return // Special state handling for kicked/destroyed groups if (groupThreadStatus != GroupThreadStatus.None) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 4e26ee370c..d4bb90307a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -25,6 +25,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -42,7 +43,6 @@ import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAt import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.ExpirationUtil -import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY import org.session.libsession.utilities.StringSubstitutionConstants.DATE_KEY import org.session.libsession.utilities.StringSubstitutionConstants.LIMIT_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY @@ -50,8 +50,8 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.recipients.MessageType import org.session.libsession.utilities.recipients.Recipient -import org.session.libsession.utilities.recipients.displayNameOrFallback import org.session.libsession.utilities.recipients.getType +import org.session.libsession.utilities.userConfigsChanged import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix @@ -60,6 +60,7 @@ import org.thoughtcrime.securesms.audio.AudioSlidePlayer import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.LokiAPIDatabase import org.thoughtcrime.securesms.database.LokiMessageDatabase +import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.database.ReactionDatabase import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.RecipientRepository @@ -81,8 +82,9 @@ import org.thoughtcrime.securesms.ui.getSubbedString import org.thoughtcrime.securesms.util.AvatarUIData import org.thoughtcrime.securesms.util.AvatarUtils import org.thoughtcrime.securesms.util.DateUtils -import org.thoughtcrime.securesms.util.RecipientChangeSource import org.thoughtcrime.securesms.util.avatarOptions +import org.thoughtcrime.securesms.util.mapStateFlow +import org.thoughtcrime.securesms.util.mapToStateFlow import org.thoughtcrime.securesms.webrtc.CallManager import org.thoughtcrime.securesms.webrtc.data.State import java.time.ZoneId @@ -111,10 +113,10 @@ class ConversationViewModel @AssistedInject constructor( val dateUtils: DateUtils, private val expiredGroupManager: ExpiredGroupManager, private val avatarUtils: AvatarUtils, - private val recipientChangeSource: RecipientChangeSource, private val openGroupManager: OpenGroupManager, private val proStatusManager: ProStatusManager, private val recipientRepository: RecipientRepository, + private val lokiThreadDatabase: LokiThreadDatabase, ) : ViewModel() { val threadId: Long = threadDb.getThreadIdIfExistsFor(address) @@ -129,75 +131,65 @@ class ConversationViewModel @AssistedInject constructor( (getBlindedRecipient(recipient)?.acceptsCommunityMessageRequests == true || isContactRecipient) && !isLocalNumber && !approvedMe } ?: false - private val _uiState = MutableStateFlow(ConversationUiState()) - val uiState: StateFlow get() = _uiState - private val _uiEvents = MutableSharedFlow(extraBufferCapacity = 1) val uiEvents: SharedFlow get() = _uiEvents private val _dialogsState = MutableStateFlow(DialogsState()) val dialogsState: StateFlow = _dialogsState - private val _isAdmin = MutableStateFlow(false) - val isAdmin: StateFlow = _isAdmin - - // all the data we need for the conversation app bar - private val _appBarData = MutableStateFlow(ConversationAppBarData( - title = "", - pagerData = emptyList(), - showCall = false, - showAvatar = false, - avatarUIData = AvatarUIData( - elements = emptyList() - ) - )) - val appBarData: StateFlow = _appBarData - - private var _recipient: RetrieveOnce = RetrieveOnce { - val conversation = repository.maybeGetRecipientForThreadId(threadId)?.let(recipientRepository::getRecipientSync) - - // set admin from current conversation - val conversationType = conversation?.getType() - // Determining is the current user is an admin will depend on the kind of conversation we are in - _isAdmin.value = when(conversationType) { - // for Groups V2 - MessageType.GROUPS_V2 -> { - configFactory.getGroup(AccountId(conversation.address.toString()))?.hasAdminKey() == true - } - - // for legacy groups, check if the user created the group - MessageType.LEGACY_GROUP -> { - // for legacy groups, we check if the current user is the one who created the group - run { - val localUserAddress = - textSecurePreferences.getLocalNumber() ?: return@run false - val group = storage.getGroup(conversation.address.toGroupString()) - group?.admins?.contains(fromSerialized(localUserAddress)) ?: false - } - } + val recipientFlow: StateFlow = recipientRepository.observeRecipient(address) + .filterNotNull() + .mapToStateFlow(viewModelScope, recipientRepository.getRecipientSync(address)) { it } - // for communities the the `isUserModerator` field - MessageType.COMMUNITY -> isUserCommunityManager() + val openGroupFlow: StateFlow = + lokiThreadDatabase.retrieveAndObserveOpenGroup(viewModelScope, threadId) ?: MutableStateFlow(null) - // false in other cases - else -> false - } + val openGroup: OpenGroup? + get() = openGroupFlow.value + + val isAdmin: StateFlow = when { + address.isCommunity -> openGroupFlow.mapStateFlow(viewModelScope) { og -> isUserCommunityManager(og) } + address.isGroupV2 -> configFactory.userConfigsChanged(500) + .onStart { emit(Unit) } + .mapToStateFlow(viewModelScope, initialData = null) { + configFactory.getGroup(AccountId(address.address))?.hasAdminKey() == true + } - updateAppBarData(conversation) + address.isLegacyGroup -> textSecurePreferences.watchLocalNumber() + .filterNotNull() + .mapToStateFlow(viewModelScope, initialData = textSecurePreferences.getLocalNumber()) { myAddress -> + myAddress != null && storage.getGroup(address.toGroupString()) + ?.admins?.contains(fromSerialized(myAddress)) == true + } + .stateIn(viewModelScope, SharingStarted.Eagerly, false) - conversation + else -> MutableStateFlow(false) } + private val _searchOpened = MutableStateFlow(false) + + val appBarData: StateFlow = combine( + recipientFlow.filterNotNull(), + openGroupFlow, + _searchOpened, + ::getAppBarData + ).stateIn(viewModelScope, SharingStarted.Eagerly, ConversationAppBarData( + title = "", + pagerData = emptyList(), + showCall = false, + showAvatar = false, + showSearch = false, + avatarUIData = AvatarUIData(emptyList()) + )) + val recipient: Recipient? - get() = _recipient.value + get() = recipientFlow.value val blindedRecipient: Recipient? - get() = _recipient.value?.let { recipient -> + get() = recipient?.let { recipient -> getBlindedRecipient(recipient) } - private var currentAppBarNotificationState: String? = null - private fun getBlindedRecipient(recipient: Recipient?): Recipient? = when { recipient?.isCommunityOutboxRecipient == true -> recipient @@ -220,27 +212,21 @@ class ConversationViewModel @AssistedInject constructor( return repository.getInvitingAdmin(threadId)?.let(recipientRepository::getRecipientSync) } - val groupV2ThreadState: GroupThreadStatus - get() { - val recipient = recipient ?: return GroupThreadStatus.None - if (!recipient.isGroupV2Recipient) return GroupThreadStatus.None - - return configFactory.getGroup(AccountId(recipient.address.toString())).let { group -> - when { - group?.destroyed == true -> GroupThreadStatus.Destroyed - group?.kicked == true -> GroupThreadStatus.Kicked - else -> GroupThreadStatus.None + val groupV2ThreadState: Flow get() = when { + !address.isGroupV2 -> flowOf( GroupThreadStatus.None) + else -> configFactory.userConfigsChanged(500) + .onStart { emit(Unit) } + .map { + configFactory.getGroup(AccountId(address.toString())).let { group -> + when { + group?.destroyed == true -> GroupThreadStatus.Destroyed + group?.kicked == true -> GroupThreadStatus.Kicked + else -> GroupThreadStatus.None + } } } - } - - private val _openGroup: MutableStateFlow by lazy { - MutableStateFlow(storage.getOpenGroup(threadId)) } - val openGroup: OpenGroup? - get() = _openGroup.value - val serverCapabilities: List get() = openGroup?.let { storage.getServerCapabilities(it.server) } ?: listOf() @@ -259,6 +245,33 @@ class ConversationViewModel @AssistedInject constructor( return !recipient.isLocalNumber && !recipient.isLegacyGroupRecipient && !recipient.isCommunityRecipient && !recipient.approved } + private val _showLoader = MutableStateFlow(false) + val showLoader: StateFlow get() = _showLoader + + val shouldExit: Flow get() = recipientFlow.map { it == null } + + val inputBarState: StateFlow = combine( + recipientFlow, openGroupFlow, legacyGroupDeprecationManager.deprecationState, + this::getInputBarState + ).stateIn(viewModelScope, SharingStarted.Eagerly, InputBarState()) + + + private val _acceptingMessageRequest = MutableStateFlow(false) + val messageRequestState: StateFlow = combine( + recipientFlow, + _acceptingMessageRequest, + ) { r, accepting -> + if (accepting) MessageRequestUiState.Pending + else buildMessageRequestState(r) + }.stateIn(viewModelScope, SharingStarted.Eagerly, MessageRequestUiState.Invisible) + + + private val _uiMessages = MutableStateFlow>(emptyList()) + val uiMessages: StateFlow> get() = _uiMessages + + private val _charLimitState = MutableStateFlow(null) + val charLimitState: StateFlow get() = _charLimitState + /** * returns true for outgoing message request, whether they are for 1 on 1 conversations or community outgoing MR */ @@ -337,55 +350,6 @@ class ConversationViewModel @AssistedInject constructor( val lastSeenMessageId: Flow get() = repository.getLastSentMessageID(threadId) - init { - viewModelScope.launch(Dispatchers.Default) { - combine( - recipientRepository.observeRecipient(repository.maybeGetRecipientForThreadId(threadId)!!), - _openGroup, - legacyGroupDeprecationManager.deprecationState, - ::Triple - ).collect { (recipient, community, deprecationState) -> - _uiState.update { - it.copy( - shouldExit = recipient == null, - inputBarState = getInputBarState(recipient, community, deprecationState), - messageRequestState = buildMessageRequestState(recipient), - ) - } - } - } - - // update state on recipient changes - viewModelScope.launch(Dispatchers.Default) { - recipientChangeSource.changes().collect { - updateAppBarData(recipient) - _uiState.update { - it.copy( - shouldExit = recipient == null, - inputBarState = getInputBarState(recipient, _openGroup.value, legacyGroupDeprecationManager.deprecationState.value), - ) - } - } - } - - // Listen for changes in the open group's write access - viewModelScope.launch { - openGroupManager.getCommunitiesWriteAccessFlow() - .map { - withContext(Dispatchers.Default) { - if (openGroup?.groupId != null) - it[openGroup?.groupId] - else null - } - } - .filterNotNull() - .collect{ - // update our community object - _openGroup.value = openGroup?.copy(canWrite = it) - } - } - } - private fun getInputBarState( recipient: Recipient?, community: OpenGroup?, @@ -393,7 +357,7 @@ class ConversationViewModel @AssistedInject constructor( ): InputBarState { return when { // prioritise cases that demand the input to be hidden - !shouldShowInput(recipient, community, deprecationState) -> InputBarState( + !shouldShowInput(recipient, deprecationState) -> InputBarState( contentState = InputBarContentState.Hidden, enableAttachMediaControls = false ) @@ -411,7 +375,7 @@ class ConversationViewModel @AssistedInject constructor( ) // the user does not have write access in the community - openGroup?.canWrite == false -> InputBarState( + community?.canWrite == false -> InputBarState( contentState = InputBarContentState.Disabled( text = application.getString(R.string.permissionsWriteCommunity), ), @@ -421,84 +385,79 @@ class ConversationViewModel @AssistedInject constructor( // other cases the input is visible, and the buttons might be disabled based on some criteria else -> InputBarState( contentState = InputBarContentState.Visible, - enableAttachMediaControls = shouldEnableInputMediaControls(recipient) + enableAttachMediaControls = shouldEnableInputMediaControls(recipient, community) ) } } - private fun updateAppBarData(conversation: Recipient?) { - viewModelScope.launch { - // sort out the pager data, if any - val pagerData: MutableList = mutableListOf() - if (conversation != null) { - // Specify the disappearing messages subtitle if we should - val expiryMode = conversation.expiryMode - if (expiryMode.expiryMillis > 0) { - // Get the type of disappearing message and the abbreviated duration.. - val dmTypeString = when (expiryMode) { - is ExpiryMode.AfterRead -> R.string.disappearingMessagesDisappearAfterReadState - else -> R.string.disappearingMessagesDisappearAfterSendState - } - val durationAbbreviated = ExpirationUtil.getExpirationAbbreviatedDisplayValue(expiryMode.expirySeconds) + private suspend fun getAppBarData(conversation: Recipient, community: OpenGroup?, showSearch: Boolean): ConversationAppBarData { + // sort out the pager data, if any + val pagerData: MutableList = mutableListOf() + // Specify the disappearing messages subtitle if we should + val expiryMode = conversation.expiryMode + if (expiryMode.expiryMillis > 0) { + // Get the type of disappearing message and the abbreviated duration.. + val dmTypeString = when (expiryMode) { + is ExpiryMode.AfterRead -> R.string.disappearingMessagesDisappearAfterReadState + else -> R.string.disappearingMessagesDisappearAfterSendState + } + val durationAbbreviated = ExpirationUtil.getExpirationAbbreviatedDisplayValue(expiryMode.expirySeconds) - // ..then substitute into the string.. - val subtitleTxt = application.getSubbedString(dmTypeString, - TIME_KEY to durationAbbreviated - ) + // ..then substitute into the string.. + val subtitleTxt = application.getSubbedString(dmTypeString, + TIME_KEY to durationAbbreviated + ) - // .. and apply to the subtitle. - pagerData += ConversationAppBarPagerData( - title = subtitleTxt, - action = { - showDisappearingMessages() - }, - icon = R.drawable.ic_clock_11, - qaTag = application.resources.getString(R.string.AccessibilityId_disappearingMessagesDisappear) - ) - } + // .. and apply to the subtitle. + pagerData += ConversationAppBarPagerData( + title = subtitleTxt, + action = { + showDisappearingMessages() + }, + icon = R.drawable.ic_clock_11, + qaTag = application.resources.getString(R.string.AccessibilityId_disappearingMessagesDisappear) + ) + } - currentAppBarNotificationState = null - if (conversation.isMuted() || conversation.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS) { - currentAppBarNotificationState = getNotificationStatusTitle(conversation) - pagerData += ConversationAppBarPagerData( - title = currentAppBarNotificationState!!, - action = { - showNotificationSettings() - } - ) + if (conversation.isMuted() || conversation.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS) { + pagerData += ConversationAppBarPagerData( + title = getNotificationStatusTitle(conversation), + action = { + showNotificationSettings() } + ) + } - if (conversation.isGroupOrCommunityRecipient && conversation.approved) { - val title = if (conversation.isCommunityRecipient) { - val userCount = openGroup?.let { lokiAPIDb.getUserCount(it.room, it.server) } ?: 0 - application.resources.getQuantityString(R.plurals.membersActive, userCount, userCount) - } else { - val userCount = if (conversation.isGroupV2Recipient) { - storage.getMembers(conversation.address.toString()).size - } else { // legacy closed groups - groupDb.getGroupMemberAddresses(conversation.address.toGroupString(), true).size - } - application.resources.getQuantityString(R.plurals.members, userCount, userCount) - } - pagerData += ConversationAppBarPagerData( - title = title, - action = { - showGroupMembers() - }, - ) + if (conversation.isGroupOrCommunityRecipient && conversation.approved) { + val title = if (conversation.isCommunityRecipient) { + val userCount = community?.let { lokiAPIDb.getUserCount(it.room, it.server) } ?: 0 + application.resources.getQuantityString(R.plurals.membersActive, userCount, userCount) + } else { + val userCount = if (conversation.isGroupV2Recipient) { + storage.getMembers(conversation.address.toString()).size + } else { // legacy closed groups + groupDb.getGroupMemberAddresses(conversation.address.toGroupString(), true).size } + application.resources.getQuantityString(R.plurals.members, userCount, userCount) } - - // calculate the main app bar data - val avatarData = avatarUtils.getUIDataFromRecipient(conversation) - _appBarData.value = ConversationAppBarData( - title = conversation.takeUnless { it?.isLocalNumber == true }?.displayName ?: application.getString(R.string.noteToSelf), - pagerData = pagerData, - showCall = conversation?.showCallMenu ?: false, - showAvatar = showOptionsMenu, - showSearch = _appBarData.value.showSearch, - avatarUIData = avatarData + pagerData += ConversationAppBarPagerData( + title = title, + action = { + showGroupMembers() + }, ) + } + + // calculate the main app bar data + val avatarData = avatarUtils.getUIDataFromRecipient(conversation) + return ConversationAppBarData( + title = conversation.takeUnless { it.isLocalNumber }?.displayName ?: application.getString(R.string.noteToSelf), + pagerData = pagerData, + showCall = conversation.showCallMenu, + showAvatar = showOptionsMenu, + showSearch = showSearch, + avatarUIData = avatarData + ).also { // also preload the larger version of the avatar in case the user goes to the settings avatarData.elements.mapNotNull { it.contactPhoto }.forEach { val loadSize = application.resources.getDimensionPixelSize(R.dimen.large_profile_picture_size) @@ -509,7 +468,7 @@ class ConversationViewModel @AssistedInject constructor( } } - private fun getNotificationStatusTitle(conversation: Recipient): String{ + private fun getNotificationStatusTitle(conversation: Recipient): String { return if(conversation.isMuted()) application.getString(R.string.notificationsHeaderMute) else application.getString(R.string.notificationsHeaderMentionsOnly) } @@ -521,7 +480,7 @@ class ConversationViewModel @AssistedInject constructor( * 1. First time we send message to a person. * Since we haven't been approved by them, we can't send them any media, only text */ - private fun shouldEnableInputMediaControls(recipient: Recipient?): Boolean { + private fun shouldEnableInputMediaControls(recipient: Recipient?, openGroup: OpenGroup?): Boolean { // Specifically disallow multimedia if we don't have a recipient to send anything to if (recipient == null) { @@ -564,9 +523,9 @@ class ConversationViewModel @AssistedInject constructor( * 3. The legacy group is deprecated, OR * 4. Blinded recipient who have disabled message request from community members */ - private fun shouldShowInput(recipient: Recipient?, - community: OpenGroup?, - deprecationState: LegacyGroupDeprecationManager.DeprecationState + private fun shouldShowInput( + recipient: Recipient?, + deprecationState: LegacyGroupDeprecationManager.DeprecationState ): Boolean { return when { recipient?.isGroupV2Recipient == true -> !repository.isGroupReadOnly(recipient) @@ -815,7 +774,7 @@ class ConversationViewModel @AssistedInject constructor( viewModelScope.launch(Dispatchers.IO) { // show a loading indicator - _uiState.update { it.copy(showLoader = true) } + _showLoader.value = true // delete remotely try { @@ -854,7 +813,7 @@ class ConversationViewModel @AssistedInject constructor( } // hide loading indicator - _uiState.update { it.copy(showLoader = false) } + _showLoader.value = false } } @@ -863,7 +822,7 @@ class ConversationViewModel @AssistedInject constructor( viewModelScope.launch(Dispatchers.IO) { // show a loading indicator - _uiState.update { it.copy(showLoader = true) } + _showLoader.value = true // delete remotely try { @@ -905,7 +864,7 @@ class ConversationViewModel @AssistedInject constructor( } // hide loading indicator - _uiState.update { it.copy(showLoader = false) } + _showLoader.value = false } } @@ -955,7 +914,7 @@ class ConversationViewModel @AssistedInject constructor( private fun markAsDeletedForEveryoneGroupsV2(data: DeleteForEveryoneDialogData){ viewModelScope.launch(Dispatchers.Default) { // show a loading indicator - _uiState.update { it.copy(showLoader = true) } + _showLoader.value = true try { repository.deleteGroupV2MessagesRemotely(recipient!!.address, data.messages) @@ -997,14 +956,14 @@ class ConversationViewModel @AssistedInject constructor( } // hide loading indicator - _uiState.update { it.copy(showLoader = false) } + _showLoader.value = false } } private fun markAsDeletedForEveryoneCommunity(data: DeleteForEveryoneDialogData){ viewModelScope.launch(Dispatchers.IO) { // show a loading indicator - _uiState.update { it.copy(showLoader = true) } + _showLoader.value = true // delete remotely try { @@ -1043,11 +1002,11 @@ class ConversationViewModel @AssistedInject constructor( } // hide loading indicator - _uiState.update { it.copy(showLoader = false) } + _showLoader.value = false } } - private fun isUserCommunityManager() = openGroup?.let { openGroup -> + private fun isUserCommunityManager(openGroup: OpenGroup?) = openGroup?.let { openGroup -> val userPublicKey = textSecurePreferences.getLocalNumber() ?: return@let false openGroupManager.isUserModerator(openGroup.id, userPublicKey, blindedPublicKey) } ?: false @@ -1086,45 +1045,31 @@ class ConversationViewModel @AssistedInject constructor( fun acceptMessageRequest(): Job = viewModelScope.launch { val recipient = recipient ?: return@launch Log.w("Loki", "Recipient was null for accept message request action") - val currentState = _uiState.value.messageRequestState as? MessageRequestUiState.Visible + val currentState = messageRequestState.value as? MessageRequestUiState.Visible ?: return@launch Log.w("Loki", "Current state was not visible for accept message request action") - _uiState.update { - it.copy(messageRequestState = MessageRequestUiState.Pending(currentState)) - } + _acceptingMessageRequest.value = true repository.acceptMessageRequest(threadId, recipient.address) - .onSuccess { - _uiState.update { - it.copy(messageRequestState = MessageRequestUiState.Invisible) - } - } .onFailure { Log.w("Loki", "Couldn't accept message request due to error", it) - - _uiState.update { state -> - state.copy(messageRequestState = currentState) - } + _acceptingMessageRequest.value = false } } fun declineMessageRequest() = viewModelScope.launch { repository.declineMessageRequest(threadId, recipient!!.address) - .onSuccess { - _uiState.update { it.copy(shouldExit = true) } - } .onFailure { Log.w("Loki", "Couldn't decline message request due to error", it) } } private fun showMessage(message: String) { - _uiState.update { currentUiState -> - val messages = currentUiState.uiMessages + UiMessage( + _uiMessages.update { messages -> + messages + UiMessage( id = UUID.randomUUID().mostSignificantBits, message = message ) - currentUiState.copy(uiMessages = messages) } } @@ -1135,9 +1080,8 @@ class ConversationViewModel @AssistedInject constructor( } fun messageShown(messageId: Long) { - _uiState.update { currentUiState -> - val messages = currentUiState.uiMessages.filterNot { it.id == messageId } - currentUiState.copy(uiMessages = messages) + _uiMessages.update { messages -> + messages.filterNot { it.id == messageId } } } @@ -1165,7 +1109,7 @@ class ConversationViewModel @AssistedInject constructor( fun implicitlyApproveRecipient(): Job? { val recipient = recipient - if (uiState.value.messageRequestState is MessageRequestUiState.Visible) { + if (messageRequestState.value is MessageRequestUiState.Visible) { return acceptMessageRequest() } else if (recipient?.approved == false) { // edge case for new outgoing thread on new recipient without sending approval messages @@ -1266,7 +1210,7 @@ class ConversationViewModel @AssistedInject constructor( fun validateMessageLength(): Boolean { // the message is too long if we have a negative char left in the input state - val charsLeft = _uiState.value.inputBarState.charLimitState?.count ?: 0 + val charsLeft = charLimitState.value?.count ?: 0 return if(charsLeft < 0){ // the user is trying to send a message that is too long - we should display a dialog // we currently have different logic for PRE and POST Pro launch @@ -1284,7 +1228,7 @@ class ConversationViewModel @AssistedInject constructor( } private fun handleCharLimitTappedForProUser(){ - if((_uiState.value.inputBarState.charLimitState?.count ?: 0) < 0){ + if((charLimitState.value?.count ?: 0) < 0){ showMessageTooLongDialog() } else { showMessageLengthDialog() @@ -1297,7 +1241,7 @@ class ConversationViewModel @AssistedInject constructor( fun showMessageLengthDialog(){ _dialogsState.update { - val charsLeft = _uiState.value.inputBarState.charLimitState?.count ?: 0 + val charsLeft = charLimitState.value?.count ?: 0 it.copy( showSimpleDialog = SimpleDialogData( title = application.getString(R.string.modalMessageCharacterDisplayTitle), @@ -1381,12 +1325,12 @@ class ConversationViewModel @AssistedInject constructor( fun getUsername(accountId: String) = recipientRepository .getRecipientDisplayNameSync(fromSerialized(accountId)) - fun onSearchOpened(){ - _appBarData.update { _appBarData.value.copy(showSearch = true) } + fun onSearchOpened() { + _searchOpened.value = true } fun onSearchClosed(){ - _appBarData.update { _appBarData.value.copy(showSearch = false) } + _searchOpened.value = false } private fun showDisappearingMessages() { @@ -1413,15 +1357,6 @@ class ConversationViewModel @AssistedInject constructor( _uiEvents.tryEmit(ConversationUiEvent.ShowNotificationSettings(address)) } - fun onResume() { - // when resuming we want to check if the app bar has notification status data, if so update it if it has changed - if(currentAppBarNotificationState != null && recipient!= null){ - val newAppBarNotificationState = getNotificationStatusTitle(recipient!!) - if(currentAppBarNotificationState != newAppBarNotificationState){ - updateAppBarData(recipient) - } - } - } fun onTextChanged(text: CharSequence) { // check the character limit @@ -1439,13 +1374,7 @@ class ConversationViewModel @AssistedInject constructor( null } - _uiState.update { - it.copy( - inputBarState = it.inputBarState.copy( - charLimitState = charLimitState - ) - ) - } + _charLimitState.value = charLimitState } @AssistedFactory @@ -1504,21 +1433,12 @@ class ConversationViewModel @AssistedInject constructor( data class UiMessage(val id: Long, val message: String) -data class ConversationUiState( - val uiMessages: List = emptyList(), - val messageRequestState: MessageRequestUiState = MessageRequestUiState.Invisible, - val shouldExit: Boolean = false, - val inputBarState: InputBarState = InputBarState(), - val showLoader: Boolean = false, -) - data class InputBarState( val contentState: InputBarContentState = InputBarContentState.Visible, // Note: These input media controls are with regard to whether the user can attach multimedia files // or record voice messages to be sent to a recipient - they are NOT things like video or audio // playback controls. val enableAttachMediaControls: Boolean = true, - val charLimitState: InputBarCharLimitState? = null, ) data class InputBarCharLimitState( @@ -1545,33 +1465,11 @@ sealed interface ConversationUiEvent { sealed interface MessageRequestUiState { data object Invisible : MessageRequestUiState - data class Pending(val prevState: Visible) : MessageRequestUiState + data object Pending : MessageRequestUiState data class Visible( - @StringRes val acceptButtonText: Int, + @param:StringRes val acceptButtonText: Int, // If null, the block button shall not be shown val blockButtonText: String? = null ) : MessageRequestUiState } - -data class RetrieveOnce(val retrieval: () -> T?) { - private var triedToRetrieve: Boolean = false - private var _value: T? = null - - val value: T? - get() { - synchronized(this) { - if (triedToRetrieve) { - return _value - } - - triedToRetrieve = true - _value = retrieval() - return _value - } - } - - fun updateTo(value: T?) { - _value = value - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index 5e23fca1f6..bb6287394a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -22,6 +22,7 @@ import org.session.libsession.messaging.sending_receiving.link_preview.LinkPrevi import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.recipients.Recipient +import org.thoughtcrime.securesms.conversation.v2.InputBarCharLimitState import org.thoughtcrime.securesms.conversation.v2.InputBarContentState import org.thoughtcrime.securesms.conversation.v2.InputBarState import org.thoughtcrime.securesms.conversation.v2.components.LinkPreviewDraftView @@ -315,16 +316,18 @@ class InputBar @JvmOverloads constructor( // handle buttons state allowAttachMultimediaButtons = state.enableAttachMediaControls + } + fun setCharLimitState(state: InputBarCharLimitState?) { // handle char limit - if(state.charLimitState != null){ - binding.characterLimitText.text = state.charLimitState.count.toString() - binding.characterLimitText.setTextColor(if(state.charLimitState.danger) dangerColor else textColor) + if(state != null){ + binding.characterLimitText.text = state.count.toString() + binding.characterLimitText.setTextColor(if(state.danger) dangerColor else textColor) binding.characterLimitContainer.setOnClickListener { delegate?.onCharLimitTapped() } - binding.badgePro.isVisible = state.charLimitState.showProBadge + binding.badgePro.isVisible = state.showProBadge binding.characterLimitContainer.isVisible = true } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt index 649d280a4a..ba01f7d2cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt @@ -57,7 +57,6 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.textSi import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.RecipientRepository -import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.dependencies.ConfigFactory.Companion.MAX_GROUP_DESCRIPTION_BYTES import org.thoughtcrime.securesms.dependencies.ConfigFactory.Companion.MAX_NAME_BYTES import org.thoughtcrime.securesms.groups.OpenGroupManager diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt index 2049a0885b..571b36c7c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt @@ -5,8 +5,17 @@ import android.content.Context import android.database.Cursor import androidx.collection.LruCache import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.takeWhile import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsignal.utilities.JsonUtil import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper @@ -112,4 +121,21 @@ class LokiThreadDatabase @Inject constructor( mutableChangeNotification.tryEmit(threadID) } + /** + * Retrieves the OpenGroup for the given threadID and observes changes to it. + * + * If in the beginning the OpenGroup is not available, it returns null. + * If in the middle of the flow the OpenGroup is deleted, it will stop emitting updates. + */ + fun retrieveAndObserveOpenGroup(scope: CoroutineScope, threadID: Long): StateFlow? { + val initialOpenGroup = getOpenGroupChat(threadID) ?: return null + + return mutableChangeNotification + .filter { it == threadID } + .map { getOpenGroupChat(threadID) } + .takeWhile { it != null } + .filterNotNull() + .stateIn(scope, SharingStarted.Eagerly, initialOpenGroup) + } + } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt index f46216678f..e18708cade 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt @@ -3,8 +3,6 @@ package org.thoughtcrime.securesms.groups import android.content.Context import android.widget.Toast import com.squareup.phrase.Phrase -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import network.loki.messenger.R import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.session.libsession.database.StorageProtocol @@ -34,13 +32,6 @@ class OpenGroupManager @Inject constructor( private val groupMemberDatabase: GroupMemberDatabase, private val pollerManager: OpenGroupPollerManager, ) { - - // flow holding information on write access for our current communities - private val _communityWriteAccess: MutableStateFlow> = MutableStateFlow(emptyMap()) - - - fun getCommunitiesWriteAccessFlow() = _communityWriteAccess.asStateFlow() - suspend fun add(server: String, room: String, publicKey: String, context: Context) { val openGroupID = "$server.$room" val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context) @@ -117,11 +108,6 @@ class OpenGroupManager @Inject constructor( fun updateOpenGroup(openGroup: OpenGroup, context: Context) { val threadID = GroupManager.getOpenGroupThreadID(openGroup.groupId, context) lokiThreadDB.setOpenGroupChat(openGroup, threadID) - - // update write access for this community - val writeAccesses = _communityWriteAccess.value.toMutableMap() - writeAccesses[openGroup.groupId] = openGroup.canWrite - _communityWriteAccess.value = writeAccesses } fun isUserModerator( diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FlowUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/FlowUtils.kt index e5b35b8931..131b6504c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FlowUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FlowUtils.kt @@ -1,12 +1,17 @@ package org.thoughtcrime.securesms.util +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn /** * Buffers items from the flow and emits them in batches. The batch will have size [maxItems] and @@ -40,5 +45,22 @@ fun Flow.timedBuffer(timeoutMillis: Long, maxItems: Int): Flow> { } } +fun Flow.mapToStateFlow( + scope: CoroutineScope, + initialData: T, + valueGetter: (T) -> R +): StateFlow { + return map { valueGetter(it) } + .stateIn(scope, SharingStarted.Eagerly, valueGetter(initialData)) +} + +fun StateFlow.mapStateFlow( + scope: CoroutineScope, + valueGetter: (T) -> R +): StateFlow { + return map { valueGetter(it) } + .stateIn(scope, SharingStarted.Eagerly, valueGetter(value)) +} + @OptIn(ExperimentalCoroutinesApi::class) fun Flow>.flatten(): Flow = flatMapConcat { it.asFlow() } \ No newline at end of file From 25402f1d221908a38ab3fa6b8732383769f62109 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:17:14 +1000 Subject: [PATCH 28/52] Remove loader from message request activity --- .../MessageRequestsActivity.kt | 42 +++++++------------ .../messagerequests/MessageRequestsLoader.kt | 25 ----------- .../MessageRequestsViewModel.kt | 32 +++++++++++++- 3 files changed, 46 insertions(+), 53 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsLoader.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt index 78037a1216..3ecd05b523 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt @@ -3,12 +3,15 @@ package org.thoughtcrime.securesms.messagerequests import android.os.Bundle import androidx.activity.viewModels import androidx.core.view.isVisible -import androidx.loader.app.LoaderManager -import androidx.loader.content.Loader +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.bumptech.glide.Glide import com.bumptech.glide.RequestManager import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.ActivityMessageRequestsBinding import org.session.libsession.utilities.Address @@ -24,7 +27,7 @@ import org.thoughtcrime.securesms.util.push import javax.inject.Inject @AndroidEntryPoint -class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClickListener, LoaderManager.LoaderCallbacks> { +class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClickListener { private lateinit var binding: ActivityMessageRequestsBinding private lateinit var glide: RequestManager @@ -56,28 +59,16 @@ class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClick binding.root.applySafeInsetsPaddings( applyBottom = false, ) - } - - override fun onResume() { - super.onResume() - LoaderManager.getInstance(this).restartLoader(0, null, this) - } - - override fun onCreateLoader(id: Int, bundle: Bundle?): Loader> { - return MessageRequestsLoader(threadDb, this) - } - override fun onLoadFinished( - loader: Loader>, - data: List - ) { - adapter.conversations = data - updateEmptyState() - } - - override fun onLoaderReset(loader: Loader?>) { - adapter.conversations = emptyList() - updateEmptyState() + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.threads + .collectLatest { + adapter.conversations = it + updateEmptyState() + } + } + } } override fun onConversationClick(thread: ThreadRecord) { @@ -90,7 +81,6 @@ class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClick Address.fromSerialized(it) } ?: thread.recipient.address viewModel.blockMessageRequest(thread, recipient) - LoaderManager.getInstance(this).restartLoader(0, null, this) } showSessionDialog { @@ -108,7 +98,6 @@ class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClick override fun onDeleteConversationClick(thread: ThreadRecord) { fun doDecline() { viewModel.deleteMessageRequest(thread) - LoaderManager.getInstance(this).restartLoader(0, null, this) } showSessionDialog { @@ -128,7 +117,6 @@ class MessageRequestsActivity : ScreenLockActionBarActivity(), ConversationClick private fun deleteAll() { fun doDeleteAllAndBlock() { viewModel.clearAllMessageRequests(false) - LoaderManager.getInstance(this).restartLoader(0, null, this) } showSessionDialog { diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsLoader.kt deleted file mode 100644 index 5962c82564..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsLoader.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.thoughtcrime.securesms.messagerequests - -import android.content.Context -import androidx.loader.content.AsyncTaskLoader -import org.thoughtcrime.securesms.database.ThreadDatabase -import org.thoughtcrime.securesms.database.model.ThreadRecord - -class MessageRequestsLoader( - private val threadDatabase: ThreadDatabase, - context: Context -) : AsyncTaskLoader>(context) { - - - override fun loadInBackground(): List { - val list = threadDatabase.unapprovedConversationList - list.sortWith(UNAPPROVED_THREAD_COMPARATOR) - return list - } - - companion object { - private val UNAPPROVED_THREAD_COMPARATOR = compareByDescending { it.lastMessage?.timestamp ?: 0 } - .thenByDescending { it.date } - .thenBy { it.recipient.displayName } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsViewModel.kt index c7ab032e76..5a98b54e45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsViewModel.kt @@ -3,29 +3,59 @@ package org.thoughtcrime.securesms.messagerequests import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.session.libsession.utilities.Address +import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.repository.ConversationRepository import javax.inject.Inject @HiltViewModel class MessageRequestsViewModel @Inject constructor( - private val repository: ConversationRepository + private val repository: ConversationRepository, + private val threadDatabase: ThreadDatabase ) : ViewModel() { + private val reloadTrigger = MutableSharedFlow(extraBufferCapacity = 1) + + val threads: StateFlow> = reloadTrigger + .onStart { emit(Unit) } + .map { + withContext(Dispatchers.Default) { + threadDatabase.unapprovedConversationList + .apply { sortWith(COMPARATOR) } + } + } + .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) + // We assume thread.recipient is a contact or thread.invitingAdmin is not null fun blockMessageRequest(thread: ThreadRecord, blockRecipient: Address) = viewModelScope.launch { repository.setBlocked(blockRecipient, true) deleteMessageRequest(thread) + reloadTrigger.emit(Unit) } fun deleteMessageRequest(thread: ThreadRecord) = viewModelScope.launch { repository.deleteMessageRequest(thread) + reloadTrigger.emit(Unit) } fun clearAllMessageRequests(block: Boolean) = viewModelScope.launch { repository.clearAllMessageRequests(block) + reloadTrigger.emit(Unit) } + companion object { + private val COMPARATOR get() = compareByDescending { it.lastMessage?.timestamp ?: 0 } + .thenByDescending { it.date } + .thenBy { it.recipient.displayName } + } } From 78807900b75173c699fd1a43ce5af764995fe193 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Fri, 4 Jul 2025 16:59:51 +1000 Subject: [PATCH 29/52] Tidy up --- .../securesms/database/RecipientRepository.kt | 6 +- .../securesms/database/ThreadDatabase.java | 42 ++++-- .../securesms/home/HomeViewModel.kt | 123 ++++++------------ .../securesms/mediasend/MediaSendActivity.kt | 2 + 4 files changed, 77 insertions(+), 96 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index e813b4e40f..78c65018f1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -379,14 +379,18 @@ class RecipientRepository @Inject constructor( ) } + val localNumber = preferences.getLocalNumber() + val ntsSequence = sequenceOf( - preferences.getLocalNumber() + localNumber ?.takeIf { shouldHaveNts } ?.let(Address::fromSerialized)) .filterNotNull() val contactsSequence = contacts.asSequence() .filter { it.priority >= 0 && contactFilter(it) } + // Exclude self in the contact if exists + .filterNot { it.id.equals(localNumber, ignoreCase = true) } .map { Address.fromSerialized(it.id) } val groupsSequence = groups.asSequence() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 74ad3292cf..33d005508e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -70,6 +70,10 @@ import dagger.Lazy; import kotlin.collections.CollectionsKt; +import kotlinx.coroutines.channels.BufferOverflow; +import kotlinx.coroutines.flow.Flow; +import kotlinx.coroutines.flow.MutableSharedFlow; +import kotlinx.coroutines.flow.SharedFlowKt; import network.loki.messenger.libsession_util.util.GroupInfo; @Singleton @@ -154,6 +158,7 @@ public static String getUnreadMentionCountCommand() { } private ConversationThreadUpdateListener updateListener; + private final MutableSharedFlow updateNotifications = SharedFlowKt.MutableSharedFlow(0, 256, BufferOverflow.DROP_OLDEST); private final Lazy recipientRepository; @@ -165,6 +170,11 @@ public ThreadDatabase(@dagger.hilt.android.qualifiers.ApplicationContext Context this.recipientRepository = recipientRepository; } + @NonNull + public Flow getUpdateNotifications() { + return updateNotifications; + } + public void setUpdateListener(ConversationThreadUpdateListener updateListener) { this.updateListener = updateListener; } @@ -203,7 +213,6 @@ private void updateThread(long threadId, long count, String body, @Nullable Uri SQLiteDatabase db = getWritableDatabase(); db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""}); - notifyConversationListListeners(); } public void clearSnippet(long threadId){ @@ -214,12 +223,14 @@ public void clearSnippet(long threadId){ SQLiteDatabase db = getWritableDatabase(); db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""}); notifyConversationListListeners(); + notifyThreadUpdated(threadId); } public void deleteThread(long threadId) { SQLiteDatabase db = getWritableDatabase(); db.delete(TABLE_NAME, ID_WHERE, new String[] {threadId + ""}); notifyConversationListListeners(); + notifyThreadUpdated(threadId); } public void trimThreadBefore(long threadId, long timestamp) { @@ -227,6 +238,7 @@ public void trimThreadBefore(long threadId, long timestamp) { DatabaseComponent.get(context).smsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp); DatabaseComponent.get(context).mmsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp, false); update(threadId, false); + notifyThreadUpdated(threadId); notifyConversationListeners(threadId); } @@ -246,6 +258,7 @@ public List setRead(long threadId, long lastReadTime) { SQLiteDatabase db = getWritableDatabase(); db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId+""}); + notifyThreadUpdated(threadId); notifyConversationListListeners(); return new LinkedList() {{ @@ -270,6 +283,7 @@ public List setRead(long threadId, boolean lastSeen) { final List smsRecords = DatabaseComponent.get(context).smsDatabase().setMessagesRead(threadId); final List mmsRecords = DatabaseComponent.get(context).mmsDatabase().setMessagesRead(threadId); + notifyThreadUpdated(threadId); notifyConversationListListeners(); return new LinkedList() {{ @@ -421,7 +435,7 @@ public ArrayList getApprovedConversationList() { // Approved conversations will come from two different sources: // 1. Config based conversations // 2. Blinded conversations stored in the database - final List
blindedConversations = getBlindedConversations(true, false); + final List
blindedConversations = getBlindedConversations(false, false); final List
configBasedConversations = recipientRepository.get().getAllConfigBasedApprovedConversations(); return getFilteredConversationList(CollectionsKt.plus(blindedConversations, configBasedConversations)); @@ -471,7 +485,7 @@ public boolean setLastSeen(long threadId, long timestamp) { db.execSQL(reflectUpdates, new Object[]{threadId}); db.setTransactionSuccessful(); db.endTransaction(); - notifyConversationListeners(threadId); + notifyThreadUpdated(threadId); notifyConversationListListeners(); return true; } @@ -591,6 +605,8 @@ public long getOrCreateThreadIdFor(Address address, int distributionType) { if (updateListener != null) { updateListener.threadCreated(address, threadId); } + + updateNotifications.tryEmit(threadId); } return threadId; @@ -614,13 +630,14 @@ public long getOrCreateThreadIdFor(Address address, int distributionType) { public void setHasSent(long threadId, boolean hasSent) { ContentValues contentValues = new ContentValues(1); - contentValues.put(HAS_SENT, hasSent ? 1 : 0); + final int hasSentValue = hasSent ? 1 : 0; + contentValues.put(HAS_SENT, hasSentValue); - getWritableDatabase().update(TABLE_NAME, contentValues, ID_WHERE, - new String[] {String.valueOf(threadId)}); - - notifyConversationListeners(threadId); - notifyConversationListListeners(); + if (getWritableDatabase().update(TABLE_NAME, contentValues, ID + " = ? AND " + HAS_SENT + " != ?", + new String[] {String.valueOf(threadId), String.valueOf(hasSentValue)}) > 0) { + notifyThreadUpdated(threadId); + notifyConversationListListeners(); + } } public boolean update(long threadId, boolean unarchive) { @@ -646,8 +663,8 @@ record = reader.getNext(); return false; } } finally { + notifyThreadUpdated(threadId); notifyConversationListListeners(); - notifyConversationListeners(threadId); } } @@ -707,10 +724,7 @@ public boolean markAllAsRead(long threadId, boolean isGroupRecipient, long lastS public void notifyThreadUpdated(long threadId) { notifyConversationListeners(threadId); - } - - public interface ProgressListener { - void onProgress(int complete, int total); + updateNotifications.tryEmit(threadId); } private class Reader implements Closeable { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index e24f1cda7d..8a0299c9d7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -8,7 +8,6 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -20,10 +19,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @@ -34,18 +30,15 @@ import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDD import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.utilities.Address -import org.session.libsession.utilities.ConfigUpdateNotification import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.currentUserName -import org.session.libsession.utilities.userConfigsChanged import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.database.DatabaseContentProviders +import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository -import org.thoughtcrime.securesms.util.observeChanges import org.thoughtcrime.securesms.webrtc.CallManager import org.thoughtcrime.securesms.webrtc.data.State import javax.inject.Inject @@ -60,7 +53,8 @@ class HomeViewModel @Inject constructor( private val configFactory: ConfigFactory, callManager: CallManager, private val storage: StorageProtocol, - private val groupManager: GroupManagerV2 + private val groupManager: GroupManagerV2, + private val recipientDatabase: RecipientDatabase, ) : ViewModel() { // SharedFlow that emits whenever the user asks us to reload the conversation private val manualReloadTrigger = MutableSharedFlow( @@ -86,92 +80,59 @@ class HomeViewModel @Inject constructor( * This flow will emit whenever the user asks us to reload the conversation list or * whenever the conversation list changes. */ - val data: StateFlow = combine( - observeConversationList(), + @Suppress("OPT_IN_USAGE") + val data: StateFlow = (combine( + // First flow: conversation list and unapproved conversation count + merge( + manualReloadTrigger, + threadDb.updateNotifications, + configFactory.configUpdateNotifications, + recipientDatabase.updateNotifications, + ) + .debounce(CHANGE_NOTIFICATION_DEBOUNCE_MILLS) + .onStart { emit(Unit) } + .map { + withContext(Dispatchers.Default) { + threadDb.unapprovedConversationList.size to + threadDb.approvedConversationList.apply { + sortWith(CONVERSATION_COMPARATOR) + } + } + }, + + // Second flow: typing status of threads observeTypingStatus(), - messageRequests(), - hasHiddenNoteToSelf() - ) { threads, typingStatus, messageRequests, hideNoteToSelf -> + + // Third flow: whether the user has marked message requests as hidden + (TextSecurePreferences.events.filter { it == TextSecurePreferences.HAS_HIDDEN_MESSAGE_REQUESTS } as Flow<*>) + .onStart { emit(Unit) } + .map { prefs.hasHiddenMessageRequests() } + ) { (unapproveConvoCount, convoList), typingStatus, hiddenMessageRequest -> Data( items = buildList { - messageRequests?.let { add(it) } - - threads.mapNotNullTo(this) { thread -> - // if the note to self is marked as hidden, - // or if the contact is blocked, do not add it - if ( - thread.recipient.isLocalNumber && hideNoteToSelf || - thread.recipient.blocked - ) { - return@mapNotNullTo null - } + if (unapproveConvoCount > 0 && !hiddenMessageRequest) { + add(Item.MessageRequests(unapproveConvoCount)) + } + convoList.mapTo(this) { thread -> Item.Thread( thread = thread, isTyping = typingStatus.contains(thread.threadId), ) } } - ) as? Data? - }.catch { err -> + ) + } as Flow).catch { err -> Log.e("HomeViewModel", "Error loading conversation list", err) emit(null) }.stateIn(viewModelScope, SharingStarted.Eagerly, null) - private fun hasHiddenMessageRequests() = TextSecurePreferences.events - .filter { it == TextSecurePreferences.HAS_HIDDEN_MESSAGE_REQUESTS } - .map { prefs.hasHiddenMessageRequests() } - .onStart { emit(prefs.hasHiddenMessageRequests()) } - - private fun hasHiddenNoteToSelf(): Flow = - configFactory.userConfigsChanged() - .debounce(1000L) - .onStart { emit(Unit) } - .map { configFactory.withUserConfigs { it.userProfile.getNtsPriority() == PRIORITY_HIDDEN } } - - private fun observeTypingStatus(): Flow> = typingStatusRepository .typingThreads .asFlow() .onStart { emit(emptySet()) } .distinctUntilChanged() - private fun messageRequests() = combine( - unapprovedConversationCount(), - hasHiddenMessageRequests(), - ::createMessageRequests - ).flowOn(Dispatchers.Default) - - private fun unapprovedConversationCount() = reloadTriggersAndContentChanges() - .map { threadDb.unapprovedConversationList.size } - - @Suppress("OPT_IN_USAGE") - private fun observeConversationList(): Flow> = reloadTriggersAndContentChanges() - .mapLatest { _ -> - withContext(Dispatchers.Default) { - val records = threadDb.approvedConversationList - - // Sort the threads by priority and last message timestamp - records.sortWith(threadRecordComparator) - - records - } - } - - private val threadRecordComparator = compareByDescending { it.recipient.isPinned } - .thenByDescending { it.recipient.priority } - .thenByDescending { it.lastMessage?.timestamp ?: 0L } - .thenByDescending { it.date } - .thenBy { it.recipient.displayName } - - @OptIn(FlowPreview::class) - private fun reloadTriggersAndContentChanges(): Flow<*> = merge( - manualReloadTrigger, - contentResolver.observeChanges(DatabaseContentProviders.ConversationList.CONTENT_URI), - configFactory.configUpdateNotifications.filterIsInstance() - ) - .debounce(CHANGE_NOTIFICATION_DEBOUNCE_MILLS) - .onStart { emit(Unit) } fun tryReload() = manualReloadTrigger.tryEmit(Unit) @@ -205,12 +166,6 @@ class HomeViewModel @Inject constructor( data class MessageRequests(val count: Int) : Item } - private fun createMessageRequests( - count: Int, - hidden: Boolean - ) = if (count > 0 && !hidden) Item.MessageRequests(count) else null - - fun hideNoteToSelf() { configFactory.withMutableUserConfigs { it.userProfile.setNtsPriority(PRIORITY_HIDDEN) @@ -239,5 +194,11 @@ class HomeViewModel @Inject constructor( companion object { private const val CHANGE_NOTIFICATION_DEBOUNCE_MILLS = 100L + + private val CONVERSATION_COMPARATOR = compareByDescending { it.recipient.isPinned } + .thenByDescending { it.recipient.priority } + .thenByDescending { it.lastMessage?.timestamp ?: 0L } + .thenByDescending { it.date } + .thenBy { it.recipient.displayName } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt index 4fa027e673..46d4d733a7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt @@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.scribbles.ImageEditorFragment import org.thoughtcrime.securesms.util.FilenameUtils.constructPhotoFilename import org.thoughtcrime.securesms.util.applySafeInsetsPaddings +import javax.inject.Inject /** * Encompasses the entire flow of sending media, starting from the selection process to the actual @@ -55,6 +56,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme private lateinit var binding: MediasendActivityBinding + @Inject lateinit var recipientRepository: RecipientRepository From 2010892d52b598e0f622b2f39ce52350a903c780 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Fri, 4 Jul 2025 18:19:51 +1000 Subject: [PATCH 30/52] Shuffling code --- .../contacts/ShareContactListLoader.kt | 9 +- .../securesms/database/RecipientRepository.kt | 69 ------ .../securesms/database/ThreadDatabase.java | 218 ++++++++++-------- .../database/model/ThreadRecord.java | 29 +-- .../securesms/debugmenu/DebugMenuViewModel.kt | 5 +- .../securesms/groups/GroupManager.java | 2 +- .../securesms/home/HomeDiffUtil.kt | 6 +- .../securesms/home/HomeViewModel.kt | 22 +- .../MessageRequestsViewModel.kt | 15 +- .../repository/ConversationRepository.kt | 131 ++++++++++- .../securesms/search/SearchRepository.kt | 8 +- 11 files changed, 284 insertions(+), 230 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt index 5f359b8733..9050af882a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListLoader.kt @@ -1,11 +1,14 @@ package org.thoughtcrime.securesms.contacts import android.content.Context +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord +import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.util.AsyncLoader @@ -15,10 +18,14 @@ class ShareContactListLoader( private val deprecationManager: LegacyGroupDeprecationManager, private val threadDatabase: ThreadDatabase, private val storage: StorageProtocol, + private val repo: ConversationRepository, ) : AsyncLoader>(context) { override fun loadInBackground(): List { - val threads = threadDatabase.approvedConversationList + val threads = runBlocking { + repo.observeConversationList(approved = true) + .first() + } .asSequence() .filter { thread -> val recipient = thread.recipient diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index 78c65018f1..5fb501d03c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -18,8 +18,6 @@ import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.withContext import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.ReadableGroupInfoConfig -import network.loki.messenger.libsession_util.ReadableUserProfile -import network.loki.messenger.libsession_util.util.Contact import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.GroupInfo import org.session.libsession.database.StorageProtocol @@ -347,73 +345,6 @@ class RecipientRepository @Inject constructor( return getRecipient(address) ?: empty(address) } - fun getAllConfigBasedUnapprovedConversations(): List
{ - return getConfigBasedConversations( - nts = { false }, - contactFilter = { !it.approved && it.approvedMe && !it.blocked }, - groupFilter = { it.invited }, - communityFilter = { false }, - legacyFilter = { false }, - ) - } - - fun getAllConfigBasedApprovedConversations(): List
{ - return getConfigBasedConversations( - contactFilter = { it.approved && !it.blocked }, - groupFilter = { !it.invited } - ) - } - - fun getConfigBasedConversations( - nts: (ReadableUserProfile) -> Boolean = { true }, - contactFilter: (Contact) -> Boolean = { true }, - groupFilter: (GroupInfo.ClosedGroupInfo) -> Boolean = { true }, - legacyFilter: (GroupInfo.LegacyGroupInfo) -> Boolean = { true }, - communityFilter: (GroupInfo.CommunityGroupInfo) -> Boolean = { true } - ): List
{ - val (shouldHaveNts, contacts, groups) = configFactory.withUserConfigs { configs -> - Triple( - configs.userProfile.getNtsPriority() >= 0 && nts(configs.userProfile), - configs.contacts.all(), - configs.userGroups.all(), - ) - } - - val localNumber = preferences.getLocalNumber() - - val ntsSequence = sequenceOf( - localNumber - ?.takeIf { shouldHaveNts } - ?.let(Address::fromSerialized)) - .filterNotNull() - - val contactsSequence = contacts.asSequence() - .filter { it.priority >= 0 && contactFilter(it) } - // Exclude self in the contact if exists - .filterNot { it.id.equals(localNumber, ignoreCase = true) } - .map { Address.fromSerialized(it.id) } - - val groupsSequence = groups.asSequence() - .filterIsInstance() - .filter { it.priority >= 0 && groupFilter(it) } - .map { Address.fromSerialized(it.groupAccountId) } - - val legacyGroupsSequence = groups.asSequence() - .filterIsInstance() - .filter { it.priority >= 0 && legacyFilter(it) } - .map { Address.fromSerialized(GroupUtil.doubleEncodeGroupID(it.accountId)) } - - val communityGroupsSequence = groups.asSequence() - .filterIsInstance() - .filter(communityFilter) - .map { Address.fromSerialized(GroupUtil.getEncodedOpenGroupID(it.groupId.toByteArray())) } - - return (ntsSequence + contactsSequence + groupsSequence + legacyGroupsSequence + communityGroupsSequence).toList() - } - - private val GroupInfo.CommunityGroupInfo.groupId: String - get() = "${community.baseUrl}.${community.room}" - companion object { private const val TAG = "RecipientRepository" diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 33d005508e..f101ef251e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -25,17 +25,17 @@ import android.database.Cursor; import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import com.annimon.stream.Stream; import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.json.JSONArray; -import org.session.libsession.messaging.MessagingModuleConfiguration; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.snode.SnodeAPI; import org.session.libsession.utilities.Address; +import org.session.libsession.utilities.ConfigFactoryProtocol; import org.session.libsession.utilities.ConfigFactoryProtocolKt; import org.session.libsession.utilities.DistributionTypes; import org.session.libsession.utilities.TextSecurePreferences; @@ -45,14 +45,12 @@ import org.session.libsignal.utilities.IdPrefix; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.Pair; -import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.model.GroupThreadStatus; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.ThreadRecord; -import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.notifications.MarkReadReceiver; @@ -60,8 +58,10 @@ import java.io.Closeable; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import javax.inject.Inject; @@ -97,14 +97,18 @@ public interface ConversationThreadUpdateListener { public static final String READ = "read"; public static final String UNREAD_COUNT = "unread_count"; public static final String UNREAD_MENTION_COUNT = "unread_mention_count"; + @Deprecated(forRemoval = true) public static final String DISTRIBUTION_TYPE = "type"; // See: DistributionTypes.kt private static final String ERROR = "error"; public static final String SNIPPET_TYPE = "snippet_type"; + @Deprecated(forRemoval = true) public static final String SNIPPET_URI = "snippet_uri"; + @Deprecated(forRemoval = true) public static final String ARCHIVED = "archived"; public static final String STATUS = "status"; public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count"; public static final String READ_RECEIPT_COUNT = "read_receipt_count"; + @Deprecated(forRemoval = true) public static final String EXPIRES_IN = "expires_in"; public static final String LAST_SEEN = "last_seen"; public static final String HAS_SENT = "has_sent"; @@ -160,14 +164,29 @@ public static String getUnreadMentionCountCommand() { private ConversationThreadUpdateListener updateListener; private final MutableSharedFlow updateNotifications = SharedFlowKt.MutableSharedFlow(0, 256, BufferOverflow.DROP_OLDEST); - private final Lazy recipientRepository; + private final Lazy<@NonNull RecipientRepository> recipientRepository; + private final Lazy<@NonNull MmsSmsDatabase> mmsSmsDatabase; + private final Lazy<@NonNull ConfigFactoryProtocol> configFactory; + private final Lazy<@NonNull MessageNotifier> messageNotifier; + private final Lazy<@NonNull MmsDatabase> mmsDatabase; + private final Lazy<@NonNull SmsDatabase> smsDatabase; @Inject public ThreadDatabase(@dagger.hilt.android.qualifiers.ApplicationContext Context context, Provider databaseHelper, - Lazy recipientRepository) { + Lazy<@NonNull RecipientRepository> recipientRepository, + Lazy<@NonNull MmsSmsDatabase> mmsSmsDatabase, + Lazy<@NonNull ConfigFactoryProtocol> configFactory, + Lazy<@NonNull MessageNotifier> messageNotifier, + Lazy<@NonNull MmsDatabase> mmsDatabase, + Lazy<@NonNull SmsDatabase> smsDatabase) { super(context, databaseHelper); this.recipientRepository = recipientRepository; + this.mmsSmsDatabase = mmsSmsDatabase; + this.configFactory = configFactory; + this.messageNotifier = messageNotifier; + this.mmsDatabase = mmsDatabase; + this.smsDatabase = smsDatabase; } @NonNull @@ -179,13 +198,10 @@ public void setUpdateListener(ConversationThreadUpdateListener updateListener) { this.updateListener = updateListener; } - private long createThreadForRecipient(Address address, boolean group, int distributionType) { - ContentValues contentValues = new ContentValues(4); + private long createThreadForRecipient(Address address) { + ContentValues contentValues = new ContentValues(2); contentValues.put(ADDRESS, address.toString()); - - if (group) contentValues.put(DISTRIBUTION_TYPE, distributionType); - contentValues.put(MESSAGE_COUNT, 0); SQLiteDatabase db = getWritableDatabase(); @@ -228,15 +244,16 @@ public void clearSnippet(long threadId){ public void deleteThread(long threadId) { SQLiteDatabase db = getWritableDatabase(); - db.delete(TABLE_NAME, ID_WHERE, new String[] {threadId + ""}); - notifyConversationListListeners(); - notifyThreadUpdated(threadId); + if (db.delete(TABLE_NAME, ID_WHERE, new String[] {threadId + ""}) > 0) { + notifyConversationListListeners(); + notifyThreadUpdated(threadId); + } } public void trimThreadBefore(long threadId, long timestamp) { Log.i("ThreadDatabase", "Trimming thread: " + threadId + " before :"+timestamp); - DatabaseComponent.get(context).smsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp); - DatabaseComponent.get(context).mmsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp, false); + smsDatabase.get().deleteMessagesInThreadBeforeDate(threadId, timestamp); + mmsDatabase.get().deleteMessagesInThreadBeforeDate(threadId, timestamp, false); update(threadId, false); notifyThreadUpdated(threadId); notifyConversationListeners(threadId); @@ -244,8 +261,8 @@ public void trimThreadBefore(long threadId, long timestamp) { public List setRead(long threadId, long lastReadTime) { - final List smsRecords = DatabaseComponent.get(context).smsDatabase().setMessagesRead(threadId, lastReadTime); - final List mmsRecords = DatabaseComponent.get(context).mmsDatabase().setMessagesRead(threadId, lastReadTime); + final List smsRecords = smsDatabase.get().setMessagesRead(threadId, lastReadTime); + final List mmsRecords = mmsDatabase.get().setMessagesRead(threadId, lastReadTime); if (smsRecords.isEmpty() && mmsRecords.isEmpty()) { return Collections.emptyList(); @@ -280,8 +297,8 @@ public List setRead(long threadId, boolean lastSeen) { SQLiteDatabase db = getWritableDatabase(); db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId+""}); - final List smsRecords = DatabaseComponent.get(context).smsDatabase().setMessagesRead(threadId); - final List mmsRecords = DatabaseComponent.get(context).mmsDatabase().setMessagesRead(threadId); + final List smsRecords = smsDatabase.get().setMessagesRead(threadId); + final List mmsRecords = mmsDatabase.get().setMessagesRead(threadId); notifyThreadUpdated(threadId); notifyConversationListListeners(); @@ -377,12 +394,18 @@ public ArrayList getFilteredConversationList(@Nullable List getBlindedConversations(@Nullable Boolean approved, @Nullable Boolean blocked) { + /** + * @param approved If true, only returns approved conversations, otherwise returns unapproved conversations. + * Null means return all conversations. + * @return All blinded conversations in the database, with their blinded address. + */ + @NonNull + public List
getBlindedConversations(@Nullable Boolean approved) { String query = "SELECT " + TABLE_NAME + "." + ADDRESS + " FROM " + TABLE_NAME + " INNER JOIN " + RecipientDatabase.TABLE_NAME + " ON " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.ADDRESS + " = " + TABLE_NAME + "." + ADDRESS + " WHERE " + TABLE_NAME + "." + ADDRESS + " LIKE '" + IdPrefix.BLINDED.getValue() + "%'"; @@ -391,10 +414,6 @@ private List
getBlindedConversations(@Nullable Boolean approved, @Nulla query += " AND " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.APPROVED + " = " + (approved ? 1 : 0); } - if (blocked != null) { - query += " AND " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.BLOCK + " = " + (blocked ? 1 : 0); - } - try(final Cursor cursor = getReadableDatabase().rawQuery(query)) { final ArrayList
addresses = new ArrayList<>(cursor.getCount()); while (cursor.moveToNext()) { @@ -415,8 +434,11 @@ private List
getBlindedConversations(@Nullable Boolean approved, @Nulla * in the database, normally this is useful only for data integrity purposes. */ public List> getAllThreads() { - SQLiteDatabase db = getReadableDatabase(); - String query = "SELECT " + ID + ", " + ADDRESS + " FROM " + TABLE_NAME + " WHERE nullif(" + ADDRESS + ", '') IS NOT NULL"; + return getAllThreads(getReadableDatabase()); + } + + private List> getAllThreads(SQLiteDatabase db) { + final String query = "SELECT " + ID + ", " + ADDRESS + " FROM " + TABLE_NAME + " WHERE nullif(" + ADDRESS + ", '') IS NOT NULL"; try (Cursor cursor = db.rawQuery(query, null)) { List> threads = new ArrayList<>(cursor.getCount()); while (cursor.moveToNext()) { @@ -430,28 +452,6 @@ public List> getAllThreads() { } } - @NonNull - public ArrayList getApprovedConversationList() { - // Approved conversations will come from two different sources: - // 1. Config based conversations - // 2. Blinded conversations stored in the database - final List
blindedConversations = getBlindedConversations(false, false); - final List
configBasedConversations = recipientRepository.get().getAllConfigBasedApprovedConversations(); - - return getFilteredConversationList(CollectionsKt.plus(blindedConversations, configBasedConversations)); - } - - @NonNull - public ArrayList getUnapprovedConversationList() { - // Unapproved conversations will come from two different sources: - // 1. Config based conversations - // 2. Blinded conversations stored in the database - final List
blindedConversations = getBlindedConversations(false, false); - final List
configBasedConversations = recipientRepository.get().getAllConfigBasedUnapprovedConversations(); - - return getFilteredConversationList(CollectionsKt.plus(blindedConversations, configBasedConversations)); - } - /** * @param threadId * @param timestamp @@ -459,9 +459,8 @@ public ArrayList getUnapprovedConversationList() { */ public boolean setLastSeen(long threadId, long timestamp) { // edge case where we set the last seen time for a conversation before it loads messages (joining community for example) - MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase(); Address forThreadId = getRecipientForThreadId(threadId); - if (mmsSmsDatabase.getConversationCount(threadId) <= 0 && forThreadId != null && forThreadId.isCommunity()) return false; + if (mmsSmsDatabase.get().getConversationCount(threadId) <= 0 && forThreadId != null && forThreadId.isCommunity()) return false; SQLiteDatabase db = getWritableDatabase(); @@ -565,21 +564,6 @@ public long getThreadIdIfExistsFor(Address address) { } public long getOrCreateThreadIdFor(Address address) { - return getOrCreateThreadIdFor(address, DistributionTypes.DEFAULT); - } - - public void setThreadArchived(long threadId) { - ContentValues contentValues = new ContentValues(1); - contentValues.put(ARCHIVED, 1); - - getWritableDatabase().update(TABLE_NAME, contentValues, ID_WHERE, - new String[] {String.valueOf(threadId)}); - - notifyConversationListListeners(); - notifyConversationListeners(threadId); - } - - public long getOrCreateThreadIdFor(Address address, int distributionType) { SQLiteDatabase db = getReadableDatabase(); String where = ADDRESS + " = ?"; String[] recipientsArg = new String[]{address.toString()}; @@ -596,7 +580,7 @@ public long getOrCreateThreadIdFor(Address address, int distributionType) { if (cursor != null && cursor.moveToFirst()) { threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); } else { - threadId = createThreadForRecipient(address, address.isGroupOrCommunity(), distributionType); + threadId = createThreadForRecipient(address); created = true; } } @@ -640,11 +624,67 @@ public void setHasSent(long threadId, boolean hasSent) { } } + /** + * Synchronizes the threads with the given addresses. This will delete any threads that are not in the + * given addresses set, and will create threads for any addresses that do not have a thread yet. + * + * @return The deleted thread IDs. + */ + @NonNull + public List<@NonNull Long> syncThreadWithAddresses(@NonNull Set<@NonNull Address> addresses) { + final SQLiteDatabase db = getWritableDatabase(); + final List deletedThreadIds = new ArrayList<>(); + + db.beginTransaction(); + try { + // Get all thread IDs to start with + Map allThreads = CollectionsKt.associateTo( + getAllThreads(db), + new HashMap<>(), + (p) -> p + ); + + // Delete all threads that are not in the addresses set + for (Map.Entry entry : allThreads.entrySet()) { + Address address = entry.getKey(); + long threadId = entry.getValue(); + + if (!addresses.contains(address)) { + // This thread is not in the addresses set, delete it + db.delete(TABLE_NAME, ID_WHERE, new String[]{String.valueOf(threadId)}); + deletedThreadIds.add(threadId); + } + } + + // Create threads for any addresses that do not have a thread yet + for (Address address : addresses) { + if (!allThreads.containsKey(address)) { + // This address does not have a thread, create it + long threadId = createThreadForRecipient(address); + if (updateListener != null) { + updateListener.threadCreated(address, threadId); + } + updateNotifications.tryEmit(threadId); + } + } + + db.setTransactionSuccessful(); + + for (Long deletedThreadId : deletedThreadIds) { + notifyThreadUpdated(deletedThreadId); + } + notifyConversationListListeners(); + + return deletedThreadIds; + } finally { + db.endTransaction(); + } + } + public boolean update(long threadId, boolean unarchive) { - MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase(); - long count = mmsSmsDatabase.getConversationCount(threadId); + long count = mmsSmsDatabase.get().getConversationCount(threadId); - try (MmsSmsDatabase.Reader reader = mmsSmsDatabase.readerFor(mmsSmsDatabase.getConversationSnippet(threadId))) { + try (MmsSmsDatabase.Reader reader = mmsSmsDatabase.get().readerFor(mmsSmsDatabase.get().getConversationSnippet(threadId))) { MessageRecord record = null; if (reader != null) { record = reader.getNext(); @@ -675,11 +715,10 @@ record = reader.getNext(); * @return true if we have set the last seen for the thread, false if there were no messages in the thread */ public boolean markAllAsRead(long threadId, boolean isGroupRecipient, long lastSeenTime, boolean force) { - MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase(); - if (mmsSmsDatabase.getConversationCount(threadId) <= 0 && !force) return false; + if (mmsSmsDatabase.get().getConversationCount(threadId) <= 0 && !force) return false; List messages = setRead(threadId, lastSeenTime); MarkReadReceiver.process(context, messages); - ApplicationContext.getInstance(context).getMessageNotifier().updateNotification(context, threadId); + messageNotifier.get().updateNotification(context, threadId); return setLastSeen(threadId, lastSeenTime); } @@ -748,7 +787,6 @@ public ThreadRecord getNext() { public ThreadRecord getCurrent() { long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID)); - int distributionType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.DISTRIBUTION_TYPE)); Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.ADDRESS))); Recipient recipient = recipientRepository.get().getRecipientSyncOrEmpty(address); @@ -758,14 +796,10 @@ public ThreadRecord getCurrent() { int unreadCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_COUNT)); int unreadMentionCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_MENTION_COUNT)); long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE)); - boolean archived = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.ARCHIVED)) != 0; int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS)); int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.DELIVERY_RECEIPT_COUNT)); int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.READ_RECEIPT_COUNT)); - long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRES_IN)); long lastSeen = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.LAST_SEEN)); - Uri snippetUri = getSnippetUri(cursor); - boolean pinned = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.IS_PINNED)) != 0; String invitingAdmin = cursor.getString(cursor.getColumnIndexOrThrow(LokiMessageDatabase.invitingSessionId)); if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { @@ -775,14 +809,13 @@ public ThreadRecord getCurrent() { MessageRecord lastMessage = null; if (count > 0) { - MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase(); - lastMessage = mmsSmsDatabase.getLastMessage(threadId); + lastMessage = mmsSmsDatabase.get().getLastMessage(threadId); } final GroupThreadStatus groupThreadStatus; if (recipient.isGroupV2Recipient()) { GroupInfo.ClosedGroupInfo group = ConfigFactoryProtocolKt.getGroup( - MessagingModuleConfiguration.getShared().getConfigFactory(), + configFactory.get(), new AccountId(recipient.getAddress().toString()) ); if (group != null && group.getDestroyed()) { @@ -796,22 +829,9 @@ public ThreadRecord getCurrent() { groupThreadStatus = GroupThreadStatus.None; } - return new ThreadRecord(body, snippetUri, lastMessage, recipient, date, count, + return new ThreadRecord(body, lastMessage, recipient, date, count, unreadCount, unreadMentionCount, threadId, deliveryReceiptCount, status, type, - distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned, invitingAdmin, groupThreadStatus); - } - - private @Nullable Uri getSnippetUri(Cursor cursor) { - if (cursor.isNull(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_URI))) { - return null; - } - - try { - return Uri.parse(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_URI))); - } catch (IllegalArgumentException e) { - Log.w(TAG, e); - return null; - } + lastSeen, readReceiptCount, invitingAdmin, groupThreadStatus); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 3b658d69b5..7770032d05 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -24,7 +24,6 @@ import static org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY; import android.content.Context; -import android.net.Uri; import android.text.SpannableString; import android.text.TextUtils; import androidx.annotation.NonNull; @@ -47,44 +46,30 @@ */ public class ThreadRecord extends DisplayRecord { - private @Nullable final Uri snippetUri; public @Nullable final MessageRecord lastMessage; private final long count; private final int unreadCount; private final int unreadMentionCount; - private final int distributionType; - private final boolean archived; - private final long expiresIn; private final long lastSeen; - private final boolean pinned; - private final int initialRecipientHash; private final String invitingAdminId; - private final long dateSent; - @NonNull + @NonNull private final GroupThreadStatus groupThreadStatus; - public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri, + public ThreadRecord(@NonNull String body, @Nullable MessageRecord lastMessage, @NonNull Recipient recipient, long date, long count, int unreadCount, int unreadMentionCount, long threadId, int deliveryReceiptCount, int status, - long snippetType, int distributionType, boolean archived, long expiresIn, - long lastSeen, int readReceiptCount, boolean pinned, String invitingAdminId, + long snippetType, + long lastSeen, int readReceiptCount, String invitingAdminId, @NonNull GroupThreadStatus groupThreadStatus) { super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount); - this.snippetUri = snippetUri; this.lastMessage = lastMessage; this.count = count; this.unreadCount = unreadCount; this.unreadMentionCount = unreadMentionCount; - this.distributionType = distributionType; - this.archived = archived; - this.expiresIn = expiresIn; this.lastSeen = lastSeen; - this.pinned = pinned; - this.initialRecipientHash = recipient.hashCode(); this.invitingAdminId = invitingAdminId; - this.dateSent = date; this.groupThreadStatus = groupThreadStatus; } @@ -230,16 +215,10 @@ public boolean isGroupUpdateMessage() { public long getDate() { return getDateReceived(); } - public int getDistributionType() { return distributionType; } - - public long getExpiresIn() { return expiresIn; } - public long getLastSeen() { return lastSeen; } public boolean isPinned() { return getRecipient().isPinned(); } - public int getInitialRecipientHash() { return initialRecipientHash; } - public String getInvitingAdminId() { return invitingAdminId; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt index d7eb78a13a..f242620fcb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -34,6 +35,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.dependencies.ConfigFactory +import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.tokenpage.TokenPageNotificationManager import org.thoughtcrime.securesms.util.ClearDataUtils import java.time.ZonedDateTime @@ -52,6 +54,7 @@ class DebugMenuViewModel @Inject constructor( private val threadDb: ThreadDatabase, private val recipientDatabase: RecipientDatabase, private val attachmentDatabase: AttachmentDatabase, + private val conversationRepository: ConversationRepository, ) : ViewModel() { private val TAG = "DebugMenu" @@ -282,7 +285,7 @@ class DebugMenuViewModel @Inject constructor( // clear trusted downloads for all recipients viewModelScope.launch { - val conversations: List = threadDb.approvedConversationList + val conversations: List = conversationRepository.observeConversationList(approved = true).first() conversations.filter { !it.recipient.isLocalNumber }.forEach { recipientDatabase.setAutoDownloadAttachments(it.recipient.address, false) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java index 3e294f572f..8d92644cc2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java @@ -56,7 +56,7 @@ public static long getThreadIDFromGroupID(String groupID, @NonNull Context cont Address groupRecipient = Address.fromSerialized(groupId); long threadID = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor( - groupRecipient, DistributionTypes.CONVERSATION); + groupRecipient); return new GroupActionResult(groupRecipient, threadID); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt index 52cc772424..79afd341b4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt @@ -57,11 +57,7 @@ class HomeDiffUtil( if (isSameItem) { isSameItem = (oldItem.isPinned == newItem.isPinned) } if (isSameItem) { isSameItem = (oldItem.isRead == newItem.isRead) } - // The recipient is passed as a reference and changes to recipients update the reference so we - // need to cache the hashCode for the recipient and use that for diffing - unfortunately - // recipient data is also loaded asyncronously which means every thread will refresh at least - // once when the initial recipient data is loaded - if (isSameItem) { isSameItem = (oldItem.initialRecipientHash == newItem.initialRecipientHash) } + if (isSameItem) { isSameItem = (oldItem.recipient == newItem.recipient) } // Note: Two instances of 'SpannableString' may not equate even though their content matches if (isSameItem) { isSameItem = (oldItem.getDisplayBody(context).toString() == newItem.getDisplayBody(context).toString()) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index 8a0299c9d7..66e2ab89ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart @@ -38,6 +39,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.dependencies.ConfigFactory +import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository import org.thoughtcrime.securesms.webrtc.CallManager import org.thoughtcrime.securesms.webrtc.data.State @@ -55,6 +57,7 @@ class HomeViewModel @Inject constructor( private val storage: StorageProtocol, private val groupManager: GroupManagerV2, private val recipientDatabase: RecipientDatabase, + private val conversationRepository: ConversationRepository, ) : ViewModel() { // SharedFlow that emits whenever the user asks us to reload the conversation private val manualReloadTrigger = MutableSharedFlow( @@ -83,21 +86,12 @@ class HomeViewModel @Inject constructor( @Suppress("OPT_IN_USAGE") val data: StateFlow = (combine( // First flow: conversation list and unapproved conversation count - merge( - manualReloadTrigger, - threadDb.updateNotifications, - configFactory.configUpdateNotifications, - recipientDatabase.updateNotifications, - ) - .debounce(CHANGE_NOTIFICATION_DEBOUNCE_MILLS) + manualReloadTrigger .onStart { emit(Unit) } - .map { - withContext(Dispatchers.Default) { - threadDb.unapprovedConversationList.size to - threadDb.approvedConversationList.apply { - sortWith(CONVERSATION_COMPARATOR) - } - } + .flatMapLatest { conversationRepository.observeConversationList() } + .map { convos -> + val (approved, unapproved) = convos.partition { it.recipient.approved } + unapproved.size to approved.sortedWith(CONVERSATION_COMPARATOR) }, // Second flow: typing status of threads diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsViewModel.kt index 5a98b54e45..015caba958 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsViewModel.kt @@ -3,17 +3,16 @@ package org.thoughtcrime.securesms.messagerequests import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.session.libsession.utilities.Address -import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.repository.ConversationRepository import javax.inject.Inject @@ -21,17 +20,15 @@ import javax.inject.Inject @HiltViewModel class MessageRequestsViewModel @Inject constructor( private val repository: ConversationRepository, - private val threadDatabase: ThreadDatabase ) : ViewModel() { private val reloadTrigger = MutableSharedFlow(extraBufferCapacity = 1) + @OptIn(ExperimentalCoroutinesApi::class) val threads: StateFlow> = reloadTrigger .onStart { emit(Unit) } - .map { - withContext(Dispatchers.Default) { - threadDatabase.unapprovedConversationList - .apply { sortWith(COMPARATOR) } - } + .flatMapLatest { + repository.observeConversationList(approved = false) + .map { it.sortedWith(COMPARATOR) } } .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index 557f06561e..17b6dc54a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -4,11 +4,23 @@ import android.content.ContentResolver import app.cash.copper.Query import app.cash.copper.flow.observeQuery import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withContext +import network.loki.messenger.libsession_util.ReadableUserProfile +import network.loki.messenger.libsession_util.util.Contact import network.loki.messenger.libsession_util.util.ExpiryMode +import network.loki.messenger.libsession_util.util.GroupInfo import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.userAuth import org.session.libsession.messaging.groups.GroupManagerV2 @@ -28,6 +40,7 @@ import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.upsertContact +import org.session.libsession.utilities.userConfigsChanged import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log @@ -36,6 +49,7 @@ import org.thoughtcrime.securesms.database.DraftDatabase import org.thoughtcrime.securesms.database.LokiMessageDatabase import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.database.MmsSmsDatabase +import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.SessionJobDatabase import org.thoughtcrime.securesms.database.SmsDatabase import org.thoughtcrime.securesms.database.Storage @@ -46,8 +60,21 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.util.observeChanges import javax.inject.Inject +import javax.inject.Singleton +import kotlin.sequences.filter +import kotlin.sequences.map interface ConversationRepository { + fun observeConversationList(approved: Boolean? = null): Flow> + + fun getConfigBasedConversations( + nts: (ReadableUserProfile) -> Boolean = { false }, + contactFilter: (Contact) -> Boolean = { false }, + groupFilter: (GroupInfo.ClosedGroupInfo) -> Boolean = { false }, + legacyFilter: (GroupInfo.LegacyGroupInfo) -> Boolean = { false }, + communityFilter: (GroupInfo.CommunityGroupInfo) -> Boolean = { false } + ): List
+ fun maybeGetRecipientForThreadId(threadId: Long): Address? fun maybeGetBlindedRecipient(address: Address): Address? fun changes(threadId: Long): Flow @@ -102,6 +129,7 @@ interface ConversationRepository { suspend fun clearAllMessages(threadId: Long, groupId: AccountId?): Int } +@Singleton class DefaultConversationRepository @Inject constructor( private val textSecurePreferences: TextSecurePreferences, private val messageDataProvider: MessageDataProvider, @@ -117,8 +145,105 @@ class DefaultConversationRepository @Inject constructor( private val contentResolver: ContentResolver, private val groupManager: GroupManagerV2, private val clock: SnodeClock, + private val preferences: TextSecurePreferences, + private val recipientDatabase: RecipientDatabase, ) : ConversationRepository { + override fun getConfigBasedConversations( + nts: (ReadableUserProfile) -> Boolean, + contactFilter: (Contact) -> Boolean, + groupFilter: (GroupInfo.ClosedGroupInfo) -> Boolean, + legacyFilter: (GroupInfo.LegacyGroupInfo) -> Boolean, + communityFilter: (GroupInfo.CommunityGroupInfo) -> Boolean + ): List
{ + val (shouldHaveNts, contacts, groups) = configFactory.withUserConfigs { configs -> + Triple( + configs.userProfile.getNtsPriority() >= 0 && nts(configs.userProfile), + configs.contacts.all(), + configs.userGroups.all(), + ) + } + + val localNumber = preferences.getLocalNumber() + + val ntsSequence = sequenceOf( + localNumber + ?.takeIf { shouldHaveNts } + ?.let(Address::fromSerialized)) + .filterNotNull() + + val contactsSequence = contacts.asSequence() + .filter { it.priority >= 0 && contactFilter(it) } + // Exclude self in the contact if exists + .filterNot { it.id.equals(localNumber, ignoreCase = true) } + .map { Address.fromSerialized(it.id) } + + val groupsSequence = groups.asSequence() + .filterIsInstance() + .filter { it.priority >= 0 && groupFilter(it) } + .map { Address.fromSerialized(it.groupAccountId) } + + val legacyGroupsSequence = groups.asSequence() + .filterIsInstance() + .filter { it.priority >= 0 && legacyFilter(it) } + .map { Address.fromSerialized(GroupUtil.doubleEncodeGroupID(it.accountId)) } + + val communityGroupsSequence = groups.asSequence() + .filterIsInstance() + .filter(communityFilter) + .map { Address.fromSerialized(GroupUtil.getEncodedOpenGroupID(it.groupId.toByteArray())) } + + return (ntsSequence + contactsSequence + groupsSequence + legacyGroupsSequence + communityGroupsSequence).toList() + } + + private val GroupInfo.CommunityGroupInfo.groupId: String + get() = "${community.baseUrl}.${community.room}" + + private val GroupInfo.ClosedGroupInfo.approved: Boolean + get() = !invited + + @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) + override fun observeConversationList(approved: Boolean?): Flow> { + val configBasedFlow = configFactory.userConfigsChanged(200) + .onStart { emit(Unit) } + .map { + getConfigBasedConversations( + nts = { approved == null || approved }, + contactFilter = { + !it.blocked && (approved == null || it.approved == approved) + }, + groupFilter = { + approved == null || it.approved == approved + }, + legacyFilter = { approved != false }, // Legacy groups are always approved + communityFilter = { approved != false } // Communities are always approved + ) + } + .distinctUntilChanged() + + val blindConvoFlow = (threadDb.updateNotifications + .debounce(500) as Flow<*>) + .onStart { emit(Unit) } + .map { threadDb.getBlindedConversations(approved) } + .distinctUntilChanged() + + return combine(configBasedFlow, blindConvoFlow) { configBased, blinded -> + configBased + blinded + }.flatMapLatest { allAddresses -> + merge( + configFactory.configUpdateNotifications, + recipientDatabase.updateNotifications, + threadDb.updateNotifications + ).debounce(500) + .onStart { emit(Unit) } + .mapLatest { + withContext(Dispatchers.Default) { + threadDb.getFilteredConversationList(allAddresses) + } + } + } + } + override fun maybeGetRecipientForThreadId(threadId: Long): Address? { return threadDb.getRecipientForThreadId(threadId) } @@ -406,15 +531,15 @@ class DefaultConversationRepository @Inject constructor( = declineMessageRequest(thread.threadId, thread.recipient.address) override suspend fun clearAllMessageRequests(block: Boolean) = runCatching { - withContext(Dispatchers.Default) { - threadDb.unapprovedConversationList.forEach { record -> + observeConversationList(approved = false) + .first() + .forEach { record -> deleteMessageRequest(record) val recipient = record.recipient if (block && !recipient.isGroupV2Recipient) { setBlocked(recipient.address, true) } } - } } override suspend fun clearAllMessages(threadId: Long, groupId: AccountId?): Int { diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt index 9a8c5aa599..1944972e1d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.kt @@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.database.MmsSmsColumns import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.SearchDatabase import org.thoughtcrime.securesms.database.ThreadDatabase +import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.search.model.MessageResult import org.thoughtcrime.securesms.search.model.SearchResult import org.thoughtcrime.securesms.util.Stopwatch @@ -24,12 +25,13 @@ import javax.inject.Singleton // Class to manage data retrieval for search @Singleton class SearchRepository @Inject constructor( - @ApplicationContext private val context: Context, + @param:ApplicationContext private val context: Context, private val searchDatabase: SearchDatabase, private val threadDatabase: ThreadDatabase, private val groupDatabase: GroupDatabase, private val contactAccessor: ContactAccessor, private val recipientRepository: RecipientRepository, + private val conversationRepository: ConversationRepository, ) { private val executor = SignalExecutors.SERIAL @@ -80,7 +82,7 @@ class SearchRepository @Inject constructor( } private fun getBlockedContacts(): Set { - return recipientRepository.getConfigBasedConversations( + return conversationRepository.getConfigBasedConversations( nts = { false }, contactFilter = { it.blocked }, groupFilter = { false }, @@ -90,7 +92,7 @@ class SearchRepository @Inject constructor( } fun queryContacts(searchName: String? = null): List { - return recipientRepository.getConfigBasedConversations( + return conversationRepository.getConfigBasedConversations( nts = { false }, contactFilter = { !it.blocked && From 8a52e1703827f2860ab2479026e61fd12ab54238 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Mon, 7 Jul 2025 10:30:22 +1000 Subject: [PATCH 31/52] WIP --- .../securesms/contacts/ShareContactListFragment.kt | 8 ++++---- .../securesms/contacts/ShareContactListLoader.kt | 1 - .../securesms/service/DirectShareService.java | 7 +++++-- .../securesms/tokenpage/TokenPageViewModel.kt | 12 ++++++------ 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt index f16dcab10b..7b345ee36f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ShareContactListFragment.kt @@ -18,7 +18,7 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.database.ThreadDatabase +import org.thoughtcrime.securesms.repository.ConversationRepository import javax.inject.Inject @AndroidEntryPoint @@ -34,7 +34,7 @@ class ShareContactListFragment : Fragment(), LoaderManager.LoaderCallbacks>(context) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java index 67afa37337..3beee5407d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java @@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.database.RecipientRepository; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; +import org.thoughtcrime.securesms.repository.ConversationRepository; import org.thoughtcrime.securesms.util.BitmapUtil; import java.util.ArrayList; @@ -46,6 +47,9 @@ public class DirectShareService extends ChooserTargetService { @Inject StorageProtocol storage; + @Inject + ConversationRepository conversationRepository; + private static final String TAG = DirectShareService.class.getSimpleName(); @Override @@ -54,9 +58,8 @@ public List onGetChooserTargets(ComponentName targetActivityName, { List results = new ArrayList<>(); ComponentName componentName = new ComponentName(this, ShareActivity.class); - ThreadDatabase threadDatabase = DatabaseComponent.get(this).threadDatabase(); - List<@NotNull Recipient> items = new ShareContactListLoader(this, null, legacyGroupDeprecationManager, threadDatabase, storage).loadInBackground(); + List<@NotNull Recipient> items = new ShareContactListLoader(this, null, legacyGroupDeprecationManager, storage, conversationRepository).loadInBackground(); for (final Recipient recipient : items) { Bitmap avatar; diff --git a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt index b35f726b3d..9a2b57d255 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenPageViewModel.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -29,7 +30,7 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Snode -import org.thoughtcrime.securesms.dependencies.DatabaseComponent +import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.NetworkConnectivity import org.thoughtcrime.securesms.util.NumberUtil.formatAbbreviated @@ -41,11 +42,11 @@ import kotlin.time.Duration.Companion.minutes @HiltViewModel class TokenPageViewModel @Inject constructor( - @ApplicationContext val context: Context, - private val tokenRepository: TokenRepository, + @param:ApplicationContext val context: Context, private val tokenDataManager: TokenDataManager, private val dateUtils: DateUtils, - private val prefs: TextSecurePreferences + private val prefs: TextSecurePreferences, + private val conversationRepository: ConversationRepository, ) : ViewModel() { private val TAG = "TokenPageVM" @@ -254,8 +255,7 @@ class TokenPageViewModel @Inject constructor( var numGroupV2Convos = 0 // Grab the database and reader details we need to count the conversations / groups - val threadDatabase = DatabaseComponent.get(context).threadDatabase() - val convoList = threadDatabase.approvedConversationList + val convoList = conversationRepository.observeConversationList(approved = true).first() val result = mutableSetOf() // Look through the database to build up our conversation & group counts (still on Dispatchers.IO not the main thread) From 741e92bf4186f722cd38348b4056736b8258e341 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Mon, 7 Jul 2025 11:08:28 +1000 Subject: [PATCH 32/52] MediaPreview flow and autodownload issue --- .../securesms/MediaPreviewActivity.kt | 39 +++++++++++++++---- .../securesms/database/RecipientDatabase.java | 13 ++----- .../securesms/home/HomeViewModel.kt | 4 +- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt index 3d4d09f7f7..8727effd5b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.kt @@ -44,7 +44,10 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding +import androidx.lifecycle.Lifecycle import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.loader.app.LoaderManager import androidx.loader.content.Loader import androidx.recyclerview.widget.LinearLayoutManager @@ -54,6 +57,15 @@ import com.bumptech.glide.Glide import com.bumptech.glide.RequestManager import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.MediaPreviewActivityBinding import network.loki.messenger.databinding.MediaViewPageBinding @@ -109,6 +121,8 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), private var leftIsRecent = false private val viewModel: MediaPreviewViewModel by viewModels() private var viewPagerListener: ViewPagerListener? = null + + private val currentSelectedRecipient = MutableStateFlow(null) @Inject lateinit var deprecationManager: LegacyGroupDeprecationManager @@ -130,6 +144,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), private var windowInsetBottom = 0 private var railHeight = 0 + @OptIn(ExperimentalCoroutinesApi::class) override fun onCreate(bundle: Bundle?, ready: Boolean) { binding = MediaPreviewActivityBinding.inflate( layoutInflater @@ -170,6 +185,17 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), windowInsets.inset(insets) } + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + currentSelectedRecipient + .flatMapLatest { address -> + address?.let(recipientRepository::observeRecipient) ?: flowOf(null) + } + .collectLatest { recipient -> + updateActionBar(currentMediaItem, recipient) + } + } + } // Set up system UI visibility listener window.decorView.setOnSystemUiVisibilityChangeListener { visibility -> @@ -262,9 +288,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), throw UnsupportedOperationException("Callback unsupported.") } - private fun updateActionBar() { - val mediaItem = currentMediaItem - + private fun updateActionBar(mediaItem: MediaItem?, recipient: Recipient?) { if (mediaItem != null) { val relativeTimeSpan: CharSequence = if (mediaItem.date > 0) { dateUtils.getDisplayFormattedTimeSpanString(mediaItem.date) @@ -273,8 +297,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), } if (mediaItem.outgoing) supportActionBar?.title = getString(R.string.you) - else if (mediaItem.recipient != null) supportActionBar?.title = - mediaItem.recipient.displayName + else if (recipient != null) supportActionBar?.title = recipient.displayName else supportActionBar?.title = "" supportActionBar?.subtitle = relativeTimeSpan @@ -644,7 +667,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), try { val item = adapter!!.getMediaItemFor(position) viewModel.setActiveAlbumRailItem(this@MediaPreviewActivity, position) - updateActionBar() + currentSelectedRecipient.value = item.recipientAddress } catch (e: Exception){ finish() } @@ -741,7 +764,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), if (mediaRecord.attachment.dataUri == null) throw AssertionError() return MediaItem( - address?.let(recipientRepository::getRecipientSync), + address, mediaRecord.attachment, mediaRecord.attachment.dataUri!!, mediaRecord.contentType, @@ -779,7 +802,7 @@ class MediaPreviewActivity : ScreenLockActionBarActivity(), } class MediaItem( - val recipient: Recipient?, + val recipientAddress: Address?, val attachment: DatabaseAttachment?, val uri: Uri, val mimeType: String, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index cb1e186d5a..681ce07016 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -332,16 +332,9 @@ public void deleteRecipient(@NonNull String recipientAddress) { } public void setAutoDownloadAttachments(@NonNull Address recipient, boolean shouldAutoDownloadAttachments) { - SQLiteDatabase db = getWritableDatabase(); - db.beginTransaction(); - try { - ContentValues values = new ContentValues(); - values.put(AUTO_DOWNLOAD, shouldAutoDownloadAttachments ? 1 : 0); - db.update(TABLE_NAME, values, ADDRESS+ " = ?", new String[]{recipient.toString()}); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } + ContentValues values = new ContentValues(1); + values.put(AUTO_DOWNLOAD, shouldAutoDownloadAttachments ? 1 : 0); + updateOrInsert(recipient, values); invalidateCache(recipient); notifyRecipientListeners(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index 66e2ab89ee..3d6afb740a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -88,7 +88,9 @@ class HomeViewModel @Inject constructor( // First flow: conversation list and unapproved conversation count manualReloadTrigger .onStart { emit(Unit) } - .flatMapLatest { conversationRepository.observeConversationList() } + .flatMapLatest { + conversationRepository.observeConversationList() + } .map { convos -> val (approved, unapproved) = convos.partition { it.recipient.approved } unapproved.size to approved.sortedWith(CONVERSATION_COMPARATOR) From e1348eb315ee4fd9c293208aa7ba4c3a4860904e Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Wed, 9 Jul 2025 09:24:19 +1000 Subject: [PATCH 33/52] Tidy up --- .../org/thoughtcrime/securesms/home/HomeViewModel.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index 3d6afb740a..3833a6952d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.home -import android.content.ContentResolver import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.asFlow @@ -16,16 +15,13 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import network.loki.messenger.R import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN import org.session.libsession.database.StorageProtocol @@ -35,8 +31,6 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.currentUserName import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.database.RecipientDatabase -import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.repository.ConversationRepository @@ -48,15 +42,12 @@ import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( @param:ApplicationContext private val context: Context, - private val threadDb: ThreadDatabase, - private val contentResolver: ContentResolver, private val prefs: TextSecurePreferences, private val typingStatusRepository: TypingStatusRepository, private val configFactory: ConfigFactory, callManager: CallManager, private val storage: StorageProtocol, private val groupManager: GroupManagerV2, - private val recipientDatabase: RecipientDatabase, private val conversationRepository: ConversationRepository, ) : ViewModel() { // SharedFlow that emits whenever the user asks us to reload the conversation @@ -189,8 +180,6 @@ class HomeViewModel @Inject constructor( } companion object { - private const val CHANGE_NOTIFICATION_DEBOUNCE_MILLS = 100L - private val CONVERSATION_COMPARATOR = compareByDescending { it.recipient.isPinned } .thenByDescending { it.recipient.priority } .thenByDescending { it.lastMessage?.timestamp ?: 0L } From ae025bc8f3a9bb8b17edb6873efcbfe8acace03b Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Wed, 9 Jul 2025 09:30:25 +1000 Subject: [PATCH 34/52] Latest crowdin strings --- app/src/main/res/values-b+ar+SA/strings.xml | 9 + app/src/main/res/values-b+ca+ES/strings.xml | 168 +++++++++++++++- app/src/main/res/values-b+cs+CZ/strings.xml | 68 ++++++- app/src/main/res/values-b+de+DE/strings.xml | 1 + app/src/main/res/values-b+eo+UY/strings.xml | 125 ++++++++++++ app/src/main/res/values-b+es+419/strings.xml | 10 + app/src/main/res/values-b+es+ES/strings.xml | 10 + app/src/main/res/values-b+fr+FR/strings.xml | 14 ++ app/src/main/res/values-b+hi+IN/strings.xml | 81 ++++++++ app/src/main/res/values-b+hu+HU/strings.xml | 192 +++++++++++++++++-- app/src/main/res/values-b+id+ID/strings.xml | 44 ++++- app/src/main/res/values-b+ja+JP/strings.xml | 32 ++++ app/src/main/res/values-b+ka+GE/strings.xml | 51 ++++- app/src/main/res/values-b+ko+KR/strings.xml | 178 ++++++++++++++--- app/src/main/res/values-b+nl+NL/strings.xml | 58 +++++- app/src/main/res/values-b+pl+PL/strings.xml | 23 +++ app/src/main/res/values-b+ro+RO/strings.xml | 40 ++++ app/src/main/res/values-b+uk+UA/strings.xml | 53 +++++ app/src/main/res/values-b+zh+CN/strings.xml | 80 ++++++++ app/src/main/res/values/strings.xml | 11 +- 20 files changed, 1178 insertions(+), 70 deletions(-) diff --git a/app/src/main/res/values-b+ar+SA/strings.xml b/app/src/main/res/values-b+ar+SA/strings.xml index 6de4036c7b..cd324e649a 100644 --- a/app/src/main/res/values-b+ar+SA/strings.xml +++ b/app/src/main/res/values-b+ar+SA/strings.xml @@ -3,6 +3,7 @@ حول قبول نسخ مُعرف الحساب + معرف الحساب تم نسخ معرف الحساب نسخ مُعرف حسابك ثم شاركه مع أصدقائك حتى يتمكنوا من مراسلتك. أدخل معرف الحساب @@ -51,6 +52,9 @@ +{count} مجهول أيقونة التطبيق + تغيير اسم و أيقونة التطبيق + يتطلب تغيير أيقونة التطبيق واسمه إغلاق {app_name}. سوف تستمر الإشعارات في استخدام أيقونة واسم {app_name} الافتراضي. + يتم عرض أيقونة التطبيق المحدد والاسم على الشاشة الرئيسة و درج التطبيقات. الأيقونة والاسم استخدم أيقونة البديلة للتطبيق استخدام أيقونة واسم بديل للتطبيق @@ -215,6 +219,7 @@ هل أنت متأكد من حذف كافة الرسائل {group_name}؟ من جهازك. هل أنت متأكد من مسح كافة رسائل \"ملاحظة لنفسي\" من جهازك؟ غلق + إغلاق التطبيق غلق النافذة إيداع التجزئة: {hash} سيؤدي هذا إلى حظر المستخدم المحدد من هذه المجتمع وحذف جميع رسائلهم. هل أنت متأكد أنك تريد المتابعة؟ @@ -674,6 +679,7 @@ لديك %1$d رسائل جديدة. الرد على + لا يمكنك إرسال رسائل صوتية حتى يتم قَبُول طلب الرسالة الخاص بك {name} دعاك للانضمام إلى {group_name}. بإرسال رسالة إلى هذه المجموعة سوف يقبل تلقائيًا دعوة المجموعة. طلب رسالتك قيد الانتظار. @@ -740,6 +746,7 @@ صامت لمدة {time_large} إلغاء الكتم كتم + كتم حتى {date_time} الوضع البطيئ {app_name} سيتحقق بشكل دوري من وجود رسائل جديدة في الخلفية. صوت @@ -910,6 +917,7 @@ جاري البحث... تحديد تحديد الكل + حدد أيقونة التطبيق إرسل إرسال إرسال طلب للمكالمة @@ -920,6 +928,7 @@ المساعدة دعوة صديق طلبات المُراسلة + يتم إرسال الرسائل باستخدام {network_name}. تتكون الشبكة من عقد محفزة مع {token_name_long}، الذي يحافظ على {app_name} لامركزي وآمن. اعرف المزيد {icon} الإشعارات الصلاحيات الخصوصية diff --git a/app/src/main/res/values-b+ca+ES/strings.xml b/app/src/main/res/values-b+ca+ES/strings.xml index e56330dcfa..5dfb911306 100644 --- a/app/src/main/res/values-b+ca+ES/strings.xml +++ b/app/src/main/res/values-b+ca+ES/strings.xml @@ -3,6 +3,7 @@ Quant a Accepta Copiar ID del compte + Identificador del compte ID del compte copiat Copia el teu ID de compte i comparteix-lo amb els teus amics perquè et puguin enviar missatges. Introdueix Account ID @@ -15,6 +16,7 @@ Mida actual Afegir Afegiu administradors + Introdueix l\'ID del compte de l\'usuari que estàs promocionant a administrador.\n\nPer afegir diversos usuaris, introdueix cada ID del compte separat per una coma. Es poden especificar fins a 20 identificadors de compte alhora. Els administradors no es poden eliminar. {name} i {count} altres han estat ascendits a Admin. Promociona Administradors @@ -27,7 +29,9 @@ Error de promoció de {name} a administrador de {group_name} Error de promoció de {name} i {count} altres a administrador de {group_name} Error de promoció de {name} i {other_name} a administrador de {group_name} + No s\'ha enviat la promoció S\'ha enviat la promoció de l\'admin + Estat de promoció desconegut Suprimir Administradors Suprimeix com a Admin No hi ha administradors en aquesta Comunitat. @@ -37,12 +41,19 @@ {name} ha estat eliminat com a Admin. {name} i {count} altres han estat eliminats com a administradors. {name} i {other_name} han estat eliminats com a administradors. + + Enviant promoció d\'administració + Enviar promocions d\'administració + Configuració d\'Admins {name} i {other_name} han estat ascendits a Admin. +{count} Anònim Icona de l\'Aplicació + Canvia la icona i el nom de l\'aplicació + Canviar la icona i el nom de l\'aplicació requereix que es tanqui {app_name}. Les notificacions continuaran utilitzant la icona i nom de l\'aplicació {app_name}. La icona i nom alternatiu es mostraran a la pantalla d\'inici i calaix d\'aplicacions. + La icona i el nom de l\'aplicació seleccionada es mostren a la pantalla d\'inici i al calaix de l\'aplicació. Icona i nom La icona alternativa es mostrarà a la pantalla d\'inici i calaix d\'aplicacions. El nom mostrat continuarà essent \'{app_name}\'. Ús d\'una icona alternativa @@ -50,8 +61,11 @@ Trieu una icona alternativa Icona Calculadora + MeetingSE Notícies Notes + Estocks + Temps Mode fosc automàtic Amaga la barra de menú Llengua @@ -131,10 +145,13 @@ Bloquejar usuari fallit Desbloquejament fallit Desbloquejar usuari + Introdueix l\'ID del compte de l\'usuari que estàs desenganxant Usuari desbloquejat Bloquejar usuari Usuari exclòs + Entra el Compte ID de l\'usuari que estàs prohibint Bloqueu + Desbloca aquest contacte per a enviar-li un missatge No hi ha contactes bloquejats Bloquejat {name} Esteu segur que voleu blocar {name}? Els usuaris bloquejats no us poden enviar sol·licituds de missatges, invitacions de grups ni trucar-vos. @@ -154,11 +171,13 @@ Telefonada en procés Trucada entrant de {name} Telefonada entrant + Has perdut una trucada de {name} perquè no vas concedir accés al micròfon. Trucada perduda Trucada perduda de {name} Les telefonades de veu i de vídeo requereixen que les notificacions estiguin activades a la configuració del sistema del dispositiu. Permís de trucada necessari Podeu habilitar el permís de \'Telefonades de veu i vídeo\' a la configuració de Privadesa. + Pots habilitar el permís \"Trucades de veu i vídeo\" en configuració de permisos. S\'està tornant a connectar… Està sonant... Trucada {app_name} @@ -189,18 +208,27 @@ S\'ha produït un error desconegut i les vostres dades no s\'han suprimit. Voleu suprimir les dades només d\'aquest dispositiu? Esborrar el dispositiu Esborreu el dispositiu i la xarxa - Esteu segur que voleu suprimir les vostres dades de la xarxa? Si continueu, no podreu restaurar els vostres missatges o contactes. + Estàs segur que vols suprimir les teves dades de la xarxa? Si continues, no pots restaurar els teus missatges o contactes. Esteu segur que voleu esborrar el dispositiu? Esborreu només el dispositiu + Esborrar el dispositiu i reiniciar + Esborrar el dispositiu i restaurar Esborra tots els missatges Esteu segur que voleu esborrar tots els missatges de la vostra conversa amb {name}? del vostre dispositiu? + Estàs segur que vols esborrar tots els missatges de la teva conversa amb {name} en aquest dispositiu? Esteu segur que voleu esborrar tots els missatges de {community_name}? del vostre dispositiu? + Estàs segur que vols esborrar tots els missatges de {community_name} en aquest dispositiu? Esborra per a tothom Esborra per a mi Esteu segur que voleu esborrar tots els missatges de {group_name}?? + Estàs segur que vols esborrar tots els missatges de {group_name}? Esteu segur que voleu esborrar tots els missatges de {group_name}? del vostre dispositiu? + Estàs segur que vols esborrar tots els missatges de {group_name} en aquest dispositiu? Esteu segur que voleu esborrar tots els missatges de Nota Personal del vostre dispositiu? + Estàs segur que vols esborrar tots els Missatges a Si Mateix en aquest dispositiu? + Esborra en aquest dispositiu Tancar + Tanca l\'aplicació Tanca la finestra Hash del Commit: {hash} Això prohibirà l\'usuari seleccionat d\'aquesta Community i eliminarà tots els seus missatges. Esteu segur que voleu continuar? @@ -265,7 +293,11 @@ S\'ha copiat Copiar Crear + Creant Trucada Retalla + Estàs segur que vols suprimir tots els missatges, fitxers adjunts i dades del compte d\'aquest dispositiu i crear un compte nou? + S\'ha produït un error de base de dades.\n \n Exporta els registres de l\'aplicació per compartir i resoldre problemes. Si això no té èxit, reinstal·la {app_name} i restaura el teu compte. + Estàs segur que vols suprimir tots els missatges, fitxers adjunts i dades del compte d\'aquest dispositiu i restaurar el teu compte de la xarxa? Hem notat que {app_name} està trigant molt a començar.\n\nPodeu continuar esperant, exportar els registres del dispositiu per compartir-los per solucionar problemes, o intentar reiniciar {app_name}. La base de dades de la vostra aplicació no és compatible amb aquesta versió de {app_name}. Reinstal·leu l\'aplicació i restaureu el vostre compte per generar una nova base de dades i continuar utilitzant {app_name}.\n\nAvís: Això donarà lloc a la pèrdua de tots els missatges i adjunts anteriors a dues setmanes. Optimitzant la base de dades @@ -287,16 +319,26 @@ Espereu mentre es crea el grup... No s\'ha pogut actualitzar el grup No teniu autorització per esborrar els missatges d\'altres + Estàs segur que vols suprimir {name} dels teus contactes?\n\n Això suprimirà la teva conversa, inclosos tots els missatges i fitxers adjunts. Els missatges futurs de {name} apareixeran com una sol·licitud de missatge. + Estàs segur que vols suprimir la teva conversa amb {name} ? \n Això eliminarà definitivament tots els missatges i fitxers adjunts. Suprimeix el missatge Suprimeix els missatges + + Estàs segur que vols suprimir aquest missatge? + Estàs segur que vols suprimir aquests missatges? + Missatge suprimit Missatges suprimits Aquest missatge s\'ha suprimit Aquest missatge s\'ha suprimit en aquest dispositiu + + Estàs segur que vols suprimir aquest missatge només d\'aquest dispositiu? + Estàs segur que vols suprimir aquests missatges només d\'aquest dispositiu? + Esteu segur que voleu suprimir aquest missatge per a tothom? Suprimeix només en aquest dispositiu Suprimeix als meus dispositius @@ -305,6 +347,14 @@ Error en eliminar el missatge Error en eliminar els missatges + + Aquest missatge no es pot suprimir de tots els teus dispositius + Alguns dels missatges que has seleccionat no es poden suprimir de tots els teus dispositius + + + Aquest missatge no es pot suprimir per a tothom + Alguns dels missatges que has seleccionat no es poden suprimir per a tothom + Esteu segur que voleu suprimir aquests missatges per a tothom? Suprimint Activa o desactiva les eines de desenvolupament @@ -336,7 +386,7 @@ Temporitzador {name} ha desactivat els missatges efímers. Els missatges que enviïn ja no desapareixeran. {name} ha desactivat els missatges que desapareixen. - Heu desactivat els missatges que desapareixen. Els missatges que envieu ja no desapareixeran. + Has desactivat els missatges que desapareixen. Els missatges que envies ja no desapareixeran. Heu desactivat els missatges que desapareixen. llegit enviat @@ -351,7 +401,9 @@ Esccull un nou nom a mostrar Tria el nom que es mostrarà Definiu el nom a mostrar + El teu nom de pantalla és visible per als usuaris, grups i comunitats amb els que interactues. Document + Doneu Fet Descarregar Descarregant... @@ -359,19 +411,19 @@ Editar Emoticones &amp; Símbols Activitats - Animals &amp; Natura + Animals i Natura Banderes Menjar &amp; Beguda Objectes Usat recentment Emoticones &amp; Gent Símbols - Viatjar &amp; Llocs + Viatjar i Llocs Esteu segur que voleu esborrar tots els {emoji}? Vés a poc a poc! Has enviat massa reaccions d\'emoji. Torna-ho a provar aviat - I %1$d un altre ha reaccionat %2$s a aquest missatge. - I %1$d uns altres han reaccionat %2$s a aquest missatge. + I %1$d altre ha reaccionat %2$s a aquest missatge. + I %1$d altres han reaccionat %2$s a aquest missatge. {name} ha reaccionat amb {emoji_name} {name} i {other_name} han reaccionat amb {emoji_name} @@ -381,7 +433,7 @@ Vós i {name} han reaccionat amb {emoji_name} Va reaccionar al teu missatge {emoji} Habiliteu - Comprova si estas en línia i torneu-ho a provar. + Comprova si estàs en línia i torneu-ho a provar. Copia l\'error i surt Error de la base de dades Quelcom ha anat malament. Torneu a provar més tard. @@ -401,6 +453,9 @@ Creeu un grup Com a mínim, trieu 1 membre per al grup, per favor. Esborra el grup + Estàs segur que vols suprimir {group_name} ? \n \n Això eliminarà tots els membres i suprimirà tot el contingut del grup. + Estàs segur que vols suprimir {group_name}? + {group_name} has estat suprimit per un administrador del grup. No pots enviar cap missatge més. Introdueix una descripció del grup La imatge de perfil del grup ha estat actualitzada. Edita el grup @@ -414,6 +469,12 @@ Error al convidar {name} i {other_name} a {group_name} Error al convidar {name} a {group_name} Invitació no enviada + {name} et van convidar a unir-te a {group_name}, on ets un administrador. + Vas ser convidat a unir-te a {group_name}, on ets un administrador. + + Enviant invitació + Enviant invitacions + Invitació enviada Estat desconegut de la invitació Invitació al grup amb èxit @@ -421,6 +482,7 @@ Tu has estat convidat a unir-te al grup. Tu i {count} altres heu estat convidats a unir-vos al grup. Tu i {other_name} heu estat convidats a unir-vos al grup. + Tu vas ser convidat a unir-te al grup. Es va compartir l\'historial de xat. Marxar del grup Esteu segur que voleu abandonar {group_name}? Esteu segur que voleu deixar {group_name}?\n\nAixò eliminarà tots els membres i suprimirà tot el contingut del grup. @@ -435,6 +497,7 @@ {name} i {count} altres han estat convidats a unir-se al grup. {name} i {other_name} han estat convidats a unir-se al grup. Tu i {count} altres heu estat convidats a unir-vos al grup. S\'ha compartit l\'historial de la conversa. + Tu i {other_name} vas ser convidat a unir-se al grup. Es va compartir l\'historial de xat. Tu has abandonat el grup. Membres del Grup No hi ha altres membres en aquest grup. @@ -444,10 +507,14 @@ Introdueix un nom de grup mes curt. El nom del grup ara és «{group_name}». Nom del grup actualitzat. + El nom del grup és visible per a tots els membres del grup. No teniu missatges de {group_name}. Envieu un missatge per a encetar una conversa! + Aquest grup no s\'ha actualitzat en més de 30 dies. Pots experimentar problemes enviant missatges o mirar informació del grup. Vós sou l\'únic administradór de {group_name}.\n\nEls membres del grup i les configuracions no poden ser canviats sense un administradór. + Pendent d\'eliminació Tu has estat ascendit a administrador. Tu i {count} altres heu estat ascendits a administrador. + Tu i {other_name} es van promoure a l\'administrador. Voleu eliminar {name} de {group_name}? Voleu eliminar {name} i {count} altres de {group_name}? Voleu eliminar {name} i {other_name} de {group_name}? @@ -463,11 +530,13 @@ {name} i {count} altres han estat eliminats del grup. {name} i {other_name} han estat eliminats del grup. Heu estat expulsat de {group_name}. + Va ser retirat del grup. Tu i {count} altres heu estat eliminats del grup. Tu i {other_name} heu estat eliminats del grup. Definiu la imatge del grup Grup desconegut Grup actualitzat + Gestionar Candidats de Connexió PMF Ajuda\'ns a traduir {app_name} Informeu d\'un error @@ -480,6 +549,7 @@ Ens agradaria molt de rebre els vostres comentaris Amaga Commuta la visibilitat de la barra de menú del sistema + Estàs segur que vols amagar Nota a Si Mateix de la teva llista de converses? Amaga les altres Imatge imatges @@ -487,11 +557,24 @@ Sol·liciteu el mode incògnit si està disponible. Segons el teclat que feu servir, el vostre teclat pot ignorar aquesta sol·licitud. Info Drecera no vàlida + + Invitació ha fallat + Invitacions han fallat + + + La invitació no es va poder enviar. Vols tornar-ho a provar? + Les invitacions no es va poder enviar. Vols tornar-ho a provar? + Uneix-t\'hi Després Més informació Marxar Sortint... + Aquest grup ara és de només-llegir. Recrea aquest grup per seguir xerrant. + Aquest grup ara és de només-llegir. Demana a l\'administrador del grup a recrear aquest grup per seguir xerrant. + Els grups s\'han actualitzat! Recrea aquest grup per millorar la fiabilitat. Aquest grup es convertirà a només-llegir a {date}. + S\'ha actualitzat els grups! Demana a l\'administrador del grup per recrear aquest grup per millorar la fiabilitat. Aquest grup es convertirà en només-llegir a {date}. + L\'historial de xat no es transferirà al nou grup. Encara pots veure tota la història del xat del teu grup antic. {name} s\'ha unit al grup. {name} {count} altres s\'han unit al grup. Tu i {count} altres us heu unit al grup. @@ -544,6 +627,7 @@ Desa només nous missatges Convida Missatge + Llegiu més Aquest missatge és buit. Error en el lliurament del missatge Límit del missatge assolit @@ -553,8 +637,8 @@ Marcar com a llegit Marcar com a no llegit - Nou missatge - Nous missatges + Missatge nou + Missatges nous Comença una conversa nova introduint la ID del compte o el ONS del teu amic. Comença una conversa nova introduint la ID del compte, el ONS o escanejant el seu codi QR. @@ -562,7 +646,13 @@ Tens un missatge nou. Tens %1$d missatges nous. + + Tens un missatge nou a %1$s. + Tens %1$d nous missatges a %2$s. + Responent a + No pots enviar fitxers adjunts fins que no s\'accepti la sol·licitud de missatge + No pots enviar missatges de veu fins que no s\'accepti la sol·licitud de missatge {name} t\'ha convidat a unir-te a {group_name}. Enviant un missatge a aquest grup acceptarà automàticament la invitació al grup. La teva sol·licitud de missatge està pendent actualment. @@ -573,6 +663,7 @@ Esteu segur que voleu esborrar totes les sol·licituds de missatges i invitacions de grup? Sol·licituds de missatges de la comunitat Permeteu sol·licitud de missatges de les converses de la comunitat. + Estàs segur que vols suprimir aquesta sol·licitud de missatge i el contacte associat? Esteu segur que voleu suprimir aquesta sol·licitud de missatge? Teniu una sol·licitud de missatge nova No hi ha sol·licituds de missatges pendents @@ -590,9 +681,19 @@ {author}: {emoji} Missatge de veu Missatges Minimitza + + Els missatges tenen un límit de %1$s caràcter. Teniu %2$d caràcter que queda + Els missatges tenen un límit de %1$s caràcters. Teniu %2$d caràcters que queden + + Longitud del missatge + Heu superat el límit de caràcters per a aquest missatge. Si us plau, escurceu el vostre missatge a {limit} caràcters o menys. + Missatge massa llarg + Si us plau, escurceu el vostre missatge als {limit} caràcters o menys. + Missatge massa llarg Següent Trieu un sobrenom per a {name}. Això us apareixerà a les vostres converses individuals i de grup. Introdueix una sobrenom + Introdueix un sobrenom més curt Elimina sobrenom Definir sobrenom No @@ -627,6 +728,8 @@ Silencia per {time_large} Treure el silenci Silenciat + Silenciat per {time_large} + Silenciat fins a {date_time} Mode lent {app_name} ocasionalment comprovarà si hi ha nous missatges en segon pla. So @@ -655,7 +758,7 @@ Condicions del servei En utilitzar aquest servei, accepteu els nostres Termes de Servei i la nostra Política de Privadesa Ruta - {app_name} amaga la vostra IP redirigint els vostres missatges a través de múltiples Service Nodes a la xarxa descentralitzada de {app_name}. Aquest és el vostre camí actual: + {app_name} amaga la vostra IP redirigint els vostres missatges a través de múltiples nodes de servei a la xarxa descentralitzada de {app_name}. Aquest és el vostre camí actual: Destinació Node d\'entrada Service Node @@ -718,11 +821,17 @@ {app_name} necessita accés a l\'emmagatzematge per desar els fitxers adjunts i els suports. {app_name} necessita accés a l\'emmagatzematge per desar fotografies i vídeos, però s\'ha denegat permanentment. Per favor, continueu cap al menú de configuració de l\'aplicació, seleccioneu Permisos i habiliteu-hi l\'Emmagatzematge. {app_name} necessita accés a l\'emmagatzematge per enviar fotografies i vídeos. + No tens permís d\'escriptura en aquesta comunitat Ancoreu Ancoreu la conversa Desancoreu Desancorar la conversa Vista prèvia + Voleu enviar missatges més llargs? Envia més text i desbloqueja funcions premium amb Session Pro + Xateja de grups més grans fins a 300 membres + Plus carrega funcions més exclusives + Missatges de fins a 10,000 caràcters + Envia més amb Perfil Imatge de perfil Error en eliminar la imatge de perfil. @@ -730,6 +839,14 @@ Si us plau, selecciona un fitxer més petit. No s\'ha pogut actualitzar el perfil. Promou + + La promoció ha fallat + Les promocions han fallat + + + La promoció no es va poder aplicar. Vols tornar-ho a provar? + Les promocions no es va poder aplicar. Vols tornar-ho a provar? + Codi QR Aquest codi QR no conté un ID de compte Aquest codi QR no conté una contrasenya de recuperació @@ -742,11 +859,15 @@ Confirmacions de Lectura Mostreu les confirmacions de lectura per a tots els missatges que envieu i rebeu. Rebut: + Resposta rebuda + Rebre Oferta de Trucada + Rebent Oferta Prèvia Recomanat Desa la teva contrasenya de recuperació per assegurar-te de no perdre accés al teu compte. Desa la teva contrasenya de recuperació Utilitza la teva contrasenya de recuperació per carregar el teu compte en nous dispositius.\n\n No es pot recuperar el teu compte sense la teva contrasenya de recuperació. Assegura\'t que està emmagatzemada en un lloc segur i no la comparteixis amb ningú. Introdueix la teva contrasenya de recuperació + S\'ha produït un error en intentar carregar la contrasenya de recuperació.\n\n Exporta els teus registres i, a continuació, envia el fitxer a través de l\'assistència {app_name} per ajudar a resoldre aquest problema. Si us plau, comprova la teva contrasenya de recuperació i torna-ho a intentar. Algunes de les paraules de la vostra contrasenya de recuperació són incorrectes. Comproveu-ho i torneu-ho a provar. La contrasenya de recuperació que heu introduït no és prou llarga. Comproveu-ho i torneu-ho a provar. @@ -760,7 +881,13 @@ Introdueix la teva contrasenya de recuperació per carregar el teu compte. Si no l\'has guardat, la pots trobar a la configuració de l\'aplicació. Mostra la contrasenya Aquesta és la vostra contrasenya de recuperació. Si l\'envieu a algú, tindrà accés complet al vostre compte. + Recrea grup Refés + Reduir la longitud del missatge per {count} + + %1$d caràcter que queda + %1$d caràcters que queden + Suprimeix No s\'ha pogut eliminar la contrasenya Responeu @@ -792,8 +919,11 @@ Cercant... Seleccioneu Selecciona-ho tot + Selecciona la icona de l\'aplicació Enviar Enviant + Enviant Oferta de Trucada + Enviant Candidats de Connexió Enviat: Aparença Esborrar les dades @@ -801,6 +931,16 @@ Ajuda Convideu un amic Sol·licituds de missatges + Actual {token_name_short} preu + Els missatges s\'envien mitjançant el nom de xarxa {network_name}. La xarxa està formada per nodes incentivats amb {token_name_long}, que manté {app_name} descentralitzat i segur. Aprendre Més {icon} + Aprèn sobre participar + Cap de mercat + {app_name} Nodes que asseguren Els teus missatges + {app_name} Nodes al teu eixam + {token_name_long} És viu! Explora la nova secció {network_name} en Configuració per conèixer com {token_name_long} potencia Session. + Xarxa assegurada per + Quan fas participar {token_name_long} per assegurar la xarxa, obtindries recompenses a {token_name_short} del {staking_reward_pool}. + Nou Notificacions Permisos Privadesa @@ -818,6 +958,8 @@ Mostra Mostra-ho tot Mostreu menys + Mostra la Nota a Si Mateix + Estàs segur que vols mostrar Nota a Si Mateix a la teva llista de converses? Adhesius Vés a la pàgina de suport Informació del sistema: {information} @@ -836,10 +978,16 @@ S\'està baixant l\'actualització: {percent_loader}% No es pot actualitzar Ha fallat l\'actualització de {app_name}. Aneu a {session_download_url} i instal·leu la nova versió manualment, llavors contacteu amb el nostre Centre de Suport per a informar sobre aquest problema. + Actualitza el Grup Informació + El nom i la descripció del grup són visibles per a tots els membres del grup. + Si us plau, entra una descripció de grup més curta Hi ha disponible una nova versió de {app_name}. Toqueu per actualitzar-lo + Hi ha disponible una nova versió ({version}) de {app_name}. Vés a les notes de versió Actualització de {app_name} Versió {version} + Darrera actualització fa {relative_time} + Actualitzeu a Pujant Copiar URL Obriu l\'URL diff --git a/app/src/main/res/values-b+cs+CZ/strings.xml b/app/src/main/res/values-b+cs+CZ/strings.xml index 8531ee36c0..cd60d2590f 100644 --- a/app/src/main/res/values-b+cs+CZ/strings.xml +++ b/app/src/main/res/values-b+cs+CZ/strings.xml @@ -141,7 +141,7 @@ Ověření se nezdařilo Příliš mnoho neúspěšných pokusů o ověření. Zkuste to prosím později. K ověření nebylo možné přistoupit. - Ověření pro otevření {app_name}. + Ověřte pro otevření {app_name}. Zpět Zablokovat a smazat vše Zablokování selhalo @@ -153,6 +153,7 @@ Uživatel zablokován Zadejte ID účtu uživatele, kterého chcete blokovat Blokovat + Pro odeslání zprávy tento kontakt odblokujte. Žádné blokované kontakty Blokovat {name} Jste si jisti, že chcete zablokovat {name}? Zablokovaní uživatelé vám nemohou posílat žádosti o komunikaci, pozvánky do skupin ani vám nemohou volat. @@ -203,7 +204,7 @@ Tímto trvale smažete vaše zprávy a kontakty. Chcete smazat data pouze na tomto zařízení, nebo smazat také data ze sítě? Data nebyla odstraněna - Data nebyla odstraněna %1$d Service Node. ID Service Node: %2$s. + Data nebyla smazána %1$d servery služby. ID serveru služby: %2$s. Data nebyla smazána %1$d provozními uzly. ID provozních uzlů: %2$s. Data nebyla smazána %1$d provozními uzly. ID provozních uzlů: %2$s. Data nebyla smazána %1$d provozními uzly. ID provozních uzlů: %2$s. @@ -322,6 +323,7 @@ Počkejte prosím, než se skupina vytvoří... Aktualizace skupiny selhala Nemáte oprávnění k mazání zpráv ostatních + Opravdu chcete smazat {name} z vašich kontaktů?\n\nTím smažete vaše konverzace, včetně všech zpráv a příloh. Budoucí zprávy od {name} se zobrazí jako žádost o komunikaci. Opracdu chcete smazat konverzaci s {name}?\nTím trvale smažete všechny zprávy a přílohy. Smazat zprávu @@ -419,6 +421,7 @@ Nastavte zobrazované jméno Vaše zobrazované jméno je viditelné pro uživatele, skupiny a komunity, se kterými jste ve spojení. Dokument + Darovat Hotovo Stáhnout Stahování... @@ -449,10 +452,11 @@ Vy a {count} dalších reagovalo {emoji_name} Vy a {name} reagovali {emoji_name} Reagoval(a) na vaši zprávu {emoji} - Zapnout + Povolit Zkontrolujte prosím připojení k internetu a zkuste to znovu. Zkopírovat chybu a ukončit Chyba databáze + Něco se pokazilo. Zkuste to prosím později. Došlo k neznámé chybě. Stahování selhalo Chyby @@ -659,6 +663,7 @@ Sdílet pouze nové zprávy Pozvat Zpráva + Více Tato zpráva je prázdná. Selhalo doručení zprávy Dosažen limit zprávy @@ -688,8 +693,8 @@ Ve skupině %1$s máte %2$d nových zpráv. Odpovědět na - Dokud není vaše žádost o zprávu přijata, nemůžete posílat přílohy - Dokud není vaše žádost o zprávu přijata, nemůžete posílat hlasové zprávy + Dokud není vaše žádost o komunikaci přijata, nemůžete posílat přílohy + Dokud není vaše žádost o komunikaci přijata, nemůžete posílat hlasové zprávy {name} vás pozval(a) do skupiny {group_name}. Odesláním zprávy do této skupiny automaticky přijmete pozvánku do skupiny. Vaše žádost o komunikaci teď čeká na vyřízení. @@ -700,6 +705,7 @@ Jste si jisti, že chcete smazat všechny žádosti o komunikaci a pozvánky do skupin? Žádosti o komunikaci z komunit Povolit žádosti o komunikaci zahájené prostřednictvím komunit. + Opravdu chcete smazat tuto žádost o komunikaci a s ní spojený kontakt? Jste si jisti, že chcete smazat tuto žádost o komunikaci? Máte novou žádost o komunikaci Žádné nevyřízené žádosti o komunikaci @@ -717,6 +723,17 @@ {author}: {emoji} hlasová zpráva Zprávy Minimalizovat + + Zprávy mají limit %1$s znaků. Zbývá vám %2$d znak + Zprávy mají limit %1$s znaků. Zbývají vám %2$d znaky + Zprávy mají limit %1$s znaků. Zbývá vám %2$d znaků + Zprávy mají limit %1$s znaků. Zbývá vám %2$d znaků + + Délka zprávy + Překročili jste limit počtu znaků pro tuto zprávu. Zkraťte prosím svou zprávu na {limit} znaků nebo méně. + Zpráva je příliš dlouhá + Zkraťte prosím svou zprávu na {limit} znaků nebo méně. + Zpráva je příliš dlouhá Další Vyberte přezdívku pro {name}. Zobrazí se ve vašich konverzacích jeden na jednoho a ve skupinových. Zadejte přezdívku @@ -785,9 +802,10 @@ Podmínky služby Používáním této služby souhlasíte s našimi Podmínkami služby a Zásadami ochrany osobních údajů Trasa + {app_name} skrývá vaši IP adresu tím, že směruje vaše zprávy přes několik serverů služby v decentralizované síti {app_name}. Toto je vaše aktuální trasa: Cíl - Vstupní uzel - Service Node + Vstupní server + Server služby Neznámá země Nepodařilo se rozpoznat tento ONS. Zkontrolujte ho a zkuste to znovu. Nepodařilo se vyhledat tento ONS. Zkuste to prosím později. @@ -847,11 +865,27 @@ {app_name} potřebuje přístup k úložišti pro ukládání příloh a médií. {app_name} potřebuje přístup k úložišti pro ukládání fotografií a videí, ale byl trvale zakázán. Prosím, pokračujte do nastavení aplikací, vyberte \"Oprávnění\" a povolte \"Úložiště\". {app_name} potřebuje přístup k úložišti pro odesílání fotografií a videí. + V této komunitě nemáte oprávnění k zápisu Připnout Připnout konverzaci Odepnout Odepnout konverzaci Náhled + Aktivováno + Už máte + Jako váš zobrazovaný profilový obrázek nastavte animovaný GIF nebo WebP! + Získejte možnost nahrát animovaný zobrazovaný obrázek profilu a další prémiové funkce se Session Pro + uživatelé mohou nahrávat GIFy + Nahrajte GIFy se + Chcete posílat delší zprávy? Posílejte více textu odemknutím prémiových funkcí Session Pro + Chcete více připnutí? Organizujte své chaty a odemkněte prémiové funkce pomocí Session Pro + Chcete více než 5 připnutí? Organizujte své chaty a odemkněte prémiové funkce pomocí Session Pro + Nahrajte GIF a WebP jako zobrazovaný obrázek + Větší soukromé skupiny až 300 členů + A další exkluzivních funkce + Zprávy až do 10000 znaků + Připněte neomezený počet konverzací + Posílejte více se Profil Zobrazovaný obrázek Chyba při odstraňování zobrazovaného obrázku. @@ -907,6 +941,13 @@ Toto je vaše heslo pro obnovení. Pokud ho někomu pošlete, bude mít plný přístup k vašemu účtu. Znovu vytvořit skupinu Znovu + Zkraťte délku zprávy o {count} + + Zbývá %1$d znak + Zbývají %1$d znaky + Zbývá %1$d znaků + Zbývá %1$d znaků + Odstranit Odebrání hesla selhalo Odpovědět @@ -952,6 +993,16 @@ Nápověda Pozvat přátele Žádosti o komunikaci + Aktuální cena {token_name_short} + Zprávy se odesílají pomocí {network_name}. Síť se skládá ze serverů, jejichž provozovatelé jsou odměňováni pomocí {token_name_long}, což zajišťuje decentralizaci a bezpečnost {app_name}. Další informace {icon} + Přečtěte si o stakingu + Tržní kapitalizace + {app_name} servery zabezpečující vaše zprávy + {app_name} servery ve vašem swarmu + {token_name_long} funguje! Prozkoumejte novou sekci {network_name} v Nastavení a zjistěte, jak {token_name_long} zabezpečuje funkčnost Session. + Síť zabezpečuje + {token_name_long} staking znamená zvyšovat bezpečnost sítě a získávat odměny v podobě {token_name_short} ze {staking_reward_pool}. + Nové Upozornění Oprávnění Soukromí @@ -981,6 +1032,7 @@ Zkusit znovu Indikátory psaní Zobrazit a sdílet indikátor probíhajícího psaní. + Nedostupné Vrátit zpět Neznámé Aktualizace aplikace @@ -996,6 +1048,8 @@ Přejít na poznámky k vydání Aktualizace {app_name} Verze {version} + Naposledy aktualizováno před {relative_time} + Navýšit na Nahrávání Zkopírovat URL Otevřít odkaz diff --git a/app/src/main/res/values-b+de+DE/strings.xml b/app/src/main/res/values-b+de+DE/strings.xml index 07487e139d..13ff76ddb2 100644 --- a/app/src/main/res/values-b+de+DE/strings.xml +++ b/app/src/main/res/values-b+de+DE/strings.xml @@ -276,6 +276,7 @@ Erstellen Anruf wird erstellt Ausschneiden + Ein Datenbankfehler ist aufgetreten.\n\n Exportiere deine App-Logs, um diese für eine Fehleranalyse zu teilen. Wenn dies nicht erfolgreich ist, installiere die {app_name} neu und stelle deinen Account wieder her. Wir haben bemerkt, dass {app_name} lange zum Starten braucht.\n\nDu kannst weiter warten, deine Geräteprotokolle zur Fehlerbehebung exportieren oder versuchen, {app_name} neu zu starten. Deine App-Datenbank ist mit dieser Version von {app_name} nicht kompatibel. Installiere die App neu und stelle deinen Account wieder her, um eine neue Datenbank zu erstellen und {app_name} weiter zu verwenden.\n\nWarnung: Dadurch gehen alle Nachrichten und Anhänge verloren, die älter als zwei Wochen sind. Optimiere Datenbank diff --git a/app/src/main/res/values-b+eo+UY/strings.xml b/app/src/main/res/values-b+eo+UY/strings.xml index d66ffff4ed..aaf30d0abe 100644 --- a/app/src/main/res/values-b+eo+UY/strings.xml +++ b/app/src/main/res/values-b+eo+UY/strings.xml @@ -3,6 +3,7 @@ Pri Akcepti Kopii identigilon de konto + Konta ID Konto-identigilo kopiita Kopiu vian Konto-ID-on kaj dividu ĝin kun viaj amikoj tiel ke ili povas mesaĝi al vi. Enigi Account ID @@ -14,6 +15,7 @@ Ĉi tio estas via Konto IDENT. Aliaj uzantoj povas skani ĝin por komenci konversacion kun vi. Efektiva grandeco Aldoni + Aldoni administrantojn Administrantoj ne povas esti forigitaj. {name} kaj {count} aliaj estis promociitaj al Admin. Promocii administrantojn @@ -26,7 +28,9 @@ Malsukcesis promocii {name} en {group_name} Malsukcesis promocii {name} kaj {count} aliajn en {group_name} Malsukcesis promocii {name} kaj {other_name} en {group_name} + Promocio ne sendita Pli altnivela administranto sendita + Stato de promocio estas nekonata Forigi Administrantojn Forigi kiel Administranto Ne estas Dummy-antoj en ĉi tiu Komunumo. @@ -36,10 +40,27 @@ {name} estis deprenita kiel Admin. {name} kaj {count} aliaj estis forigitaj kiel Admoj. {name} kaj {other_name} estis forigitaj kiel Admoj. + + Sendante administran promocion + Sendante administrajn promociojn + Administraj Agordoj {name} kaj {other_name} estis promociitaj al Admin. +{count} Anonima + Piktogramo de aplikaĵo + Ŝanĝi piktogramon de aplikaĵo kaj nomon + Piktogramo kaj nomo + Uzi alternativan piktogramon de aplikaĵo + Uzi alternativan piktogramon de aplikaĵo kaj nomon + Elektu alternativan piktogramon de aplikaĵo + Piktogramo + Kalkulilo + KunvenoSE + Novaĵoj + Notoj + Akcioj + Vetero Aŭtomata malhela reĝimo Kaŝi Menuan Stangon Lingvo @@ -57,6 +78,7 @@ Alzomi Elzomi Kunsendaĵo + Alfiksitaĵoj Aldoni kunsendaĵon Sen nomita Albumo Aŭtomata Elŝuto de Aldonaĵoj @@ -118,9 +140,11 @@ Malpermeso malsukcesis La malpermesado malsukcesis Repermesi al uzanto + Enigu ID de la konto de la uzanto, kiun vi malblokas Uzanto repermisita Forbari uzanton Uzanto forigita + Enigu ID de la konto de la uzanto, kiun vi blokas Bloki Neniu blokata kontakto Blokis {name} @@ -141,11 +165,13 @@ Voko farata Envena alvoko el {name} Envena alvoko + Vi maltrafis vokon de {name}, ĉar vi ne ebligis aliron al la mikrofono. Maltrafita Alvoko Maltrafita alvoko el {name} Voĉaj kaj Videaj Vokoj bezonas ebligi notifikojn en viaj sistema agordoj. Vokaj Permesoj Bezonataj Vi povas ebligi la permeson \"Voĉaj kaj Video Alvokoj\" en privatecaj agordoj. + Vi povas ebligi la permeson \"Voĉaj kaj Video Alvokoj\" en permesaj agordoj. Rekonektante… Sonorante... {app_name} Voko @@ -187,7 +213,9 @@ Ĉu vi certas, ke vi volas forigi ĉiujn mesaĝojn de {group_name}? Ĉu vi certas, ke vi volas forigi ĉiujn mesaĝojn de {group_name} el via aparato? Ĉu vi certas, ke vi volas forigi ĉiujn mesaĝojn al Memo al Mi mem el via aparato? + Forigi sur ĉi tiu aparato Fermi + Fermi aplikaĵon Fermi fenestron Komitiba Hash: {hash} This will ban the selected user from this Community and delete all their messages. Are you sure you want to continue? @@ -252,6 +280,7 @@ Kopiite Kopii Krei + Kreante vokon Eltondi Ni rimarkis ke {app_name} bezonas longe por komenci.\n\nVi povas daŭrigi atendadon, eksporti viajn aparato-protokolojn por dividi por cimo-serĉado, aŭ reprovi relanĉi {app_name}. Via aplikaĵa datumbazo ne kongruas kun ĉi tiu versio de {app_name}. Reinstalu la aplikaĵon kaj restarigu vian konton por generi novan datumbazon kaj daŭrigi la uzadon de {app_name}.\n\nAverto: Ĉi tio rezultos en la perdo de ĉiuj mesaĝoj kaj aldonaĵoj pli aĝaj ol du semajnoj. @@ -278,12 +307,20 @@ Forigi mesaĝon Forigi mesaĝojn + + Ĉu vi certas, ke vi volas forigi ĉi tiun mesaĝon? + Ĉu vi certas, ke vi volas forigi ĉi tiujn mesaĝojn? + Mesaĝo forigita Mesaĝoj forigitaj Ĉi tiu mesaĝo forigitis Ĉi tiu mesaĝo estis forigita sur ĉi tiu aparato + + Ĉu vi certas, ke vi volas forigi ĉi tiun mesaĝon nur de ĉi tiu aparato? + Ĉu vi certas, ke vi volas forigi ĉi tiujn mesaĝojn nur de ĉi tiu aparato? + Ĉu vi certas, ke vi volas forigi ĉi tiun mesaĝon por ĉiuj? Forigi sole sur ĉi tiu aparato Forigi sur ĉiuj miaj aparatoj @@ -292,6 +329,14 @@ Malsukcesis forigi mesaĝon Malsukcesis forigi mesaĝojn + + Tiu ĉi mesaĝo ne povas esti forigita el ĉiuj viaj aparatoj + Kelkaj el la mesaĝoj, kiujn vi elektis, ne povas esti forigitaj el ĉiuj viaj aparatoj + + + Tiu ĉi mesaĝo ne povas esti forigita por ĉiu + Kelkaj el la mesaĝoj, kiujn vi elektis, ne povas esti forigitaj por ĉiu + Ĉu vi certas, ke vi volas forigi ĉi tiujn mesaĝojn por ĉiuj? Forviŝante Baskuligi programistajn ilojn @@ -338,7 +383,9 @@ Elektu novan vidigan nomon Elektu vian vidigan nomon Agordi Montratan Nomon + Via montra nomo estas videbla al uzantoj, grupoj kaj komunumojl, kun kiuj vi interagas. Dokumento + Donaci Farite Elŝuti Elŝutante... @@ -371,11 +418,14 @@ Bonvolu kontroli vian interretan konekton kaj reprovu. Kopii la eraron kaj eliri Datumbaza eraro + Io misfunkciis. Bonvolu reprovi poste. Nekonata eraro okazis. + Elŝutado fiaskis Malsukcesoj Dosiero Dosieroj Sekvi sistemajn agordojn + Por ĉiam El: Baskuligi plenekranan reĝimon GIF @@ -385,6 +435,9 @@ Krei Grupon Bonvolu elekti almenaŭ unu grupanon. Forigi grupon + Ĉu vi certas, ke vi volas forigi {group_name}?\n\nTio forigos ĉiujn membrojn kaj forigos la tutan enhavon de la grupo. + Ĉu vi certas, ke vi volas forigi {group_name}? + {group_name} estis forigita de administranto de la grupo. Vi ne plu povos sendi mesaĝojn. Enigu gruppriskribon Grupa bildo ĝisdatigis. Redakti Grupon @@ -397,12 +450,21 @@ Malsukcesis inviti {name} kaj {count} aliajn al {group_name} Malsukcesis inviti {name} kaj {other_name} al {group_name} Malsukcesis inviti {name} al {group_name} + Invitilo ne sendita + {name} invitis vin por aliĝi {group_name}, kie vi estas Administranto. + Vi estis invitita por realiĝi {group_name}, kie vi estas Administranto. + + Sendante invitilon + Sendante invitilojn + Invito sendita + Stato de invitilo estas nekonata Invito de grupo sukcesis Uzantoj devas havi la plej novan version por ricevi invitojn Vi estis invitita aniĝi al la grupo. Vi kaj {count} aliaj estis invititaj aniĝi al la grupo. Vi kaj {other_name} estis invititaj aniĝi al la grupo. + Vi estis invitita por aliĝi la grupon. Babileja historio estis diskonigita. Forlasi Grupon Ĉu vi certas, ke vi volas forlasi {group_name}? Ĉu vi certas, ke vi volas foriri el {group_name}?\n\nĈi tio forigos ĉiujn membrojn kaj forigos ĉiun grupon enhavo. @@ -417,6 +479,7 @@ {name} kaj {count} aliaj estis invititaj aniĝi al la grupo. {name} kaj {other_name} estis invititaj aniĝi al la grupo. Vi kaj {count} aliaj estis invititaj aniĝi al la grupo. Babilhistorio estis dividita. + Vi kaj {other_name} estis invititaj por aliĝi al la grupo. Historio de la babilejo estis diskonigita. Vi forlasis la grupon. Grupanoj Ne estas aliaj membroj en ĉi tiu grupo. @@ -426,10 +489,13 @@ Bonvolu entajpi plej mallongan grupnomon. La grupnomo estas de nun „{group_name}“. Grupa nomo ĝisdatigite. + Nomo de la grupo estas videbla al ĉiuj membroj de la grupo. Vi ne havas mesaĝojn de {group_name}. Sendu mesaĝon por komenci la konversacion! + Tiu ĉi grupo ne estis ĝisdatigita pli ol 30 tagojn. Vi povas trafi problemojn pri sendi mesaĝojn aŭ vidi informon de la grupo. Vi estas la sola admin en {group_name}.\n\nGrupanoj kaj agordoj ne povas esti ŝanĝitaj sen admin. Vi estis promociita al Admin. Vi kaj {count} aliaj estis promociitaj al Admin. + Vi kaj {other_name} estis promociitaj al Administranto. Ĉu vi ŝatus forigi {name} de {group_name}? Ĉu vi ŝatus forigi {name} kaj {count} aliajn de {group_name}? Ĉu vi ŝatus forigi {name} kaj {other_name} de {group_name}? @@ -445,6 +511,7 @@ {name} kaj {count} aliaj estis forigitaj de la grupo. {name} kaj {other_name} estis forigitaj de la grupo. Vi estis forigita el {group_name}. + Vi estis forigita el la grupo. Vi kaj {count} aliaj estis forigitaj de la grupo. Vi kaj {other_name} estis forigitaj de la grupo. Agordi Grupbildon @@ -464,15 +531,29 @@ Ŝalti la videblecon de la sistemmenuo Kaŝi aliajn Bildo + bildoj Inkognita klavaro Peti inkognitan reĝimon se disponebla. Depende de la klavaro kiun vi uzas, via klavaro eble ignoros ĉi tiun peton. Info Nevalida ŝparvojo + + Invitado fiaskis + Invitadoj fiaskis + + + La invitilo ne povas esti sendita. Ĉu vi volas reprovi? + La invitiloj ne povas esti senditaj. Ĉu vi volas reprovi? + Aliĝi Poste Lerni pli Forlasi Forlasante... + Tiu ĉi grupo nun estas nurlega. Rekreu tiun ĉi grupon por daŭrigi babiladon. + Tiu ĉi grupo nun estas nurlega. Petu administranton de la grupo rekrei tiun ĉi grupon por daŭrigi babiladon. + Grupoj estis plibonigitaj! Rekreu tiun ĉi grupon por plibonigi la fidindecon. Tiu ĉi grupo fariĝos nurlega je {date}. + Grupoj estis plibonigitaj! Petu administranton de la grupo rekrei la grupon por plibonigi la fidindecon. Tiu ĉi grupo fariĝos nurlega je {date}. + Historio de babilejo ne estos transportita al la nova grupo. Vi ankoraŭ povas vidi tutan historion de la babilado en via malnova grupo. {name} grupaniĝis. {name} kaj {count} aliaj grupaniĝis. Vi kaj {count} aliaj aniĝis al la grupo. @@ -501,6 +582,7 @@ Ŝlosostato Tuŝetu por Malŝlosi {app_name} estas malŝlosita + Administri membrojn Maksimuma Aŭdvidaĵo @@ -542,6 +624,10 @@ Vi ricevis novan mesaĝon. Vi ricevis %1$d novajn mesaĝojn. + + Vi ricevis novan mesaĝon en %1$s. + Vi ricevis %1$d novajn mesaĝojn en %2$s. + Respondi al {name} invitis vin aniĝi al {group_name} . Sendi mesaĝon al ĉi tiu grupo aŭtomate akceptos la grupinvitaĵon. @@ -553,6 +639,7 @@ Ĉu vi certas, ke vi volas forigi ĉiujn mesaĝpetojn kaj grup-invitojn? Mesaĝaj Petoj de la Komunumo Permesi mesaĝajn petojn de Comunitat konversacioj. + Ĉu vi certas, ke vi volas forigi ĉi tiun mesaĝpeton kaj la asociitan kontakton? Ĉu vi certas, ke vi volas forigi ĉi tiun mesaĝpeton? Vi havas novan mesaĝan peton Neniuj pendaj message requests @@ -573,6 +660,7 @@ Sekva Elektu kromnomon por {name}. Ĉi tio aperos al vi en viaj unu-kontraŭ-unu kaj grupaj konversacioj. Entajpu alnomon + Bonvolu enigi pli mallongan kaŝnomon Forigi alnomon Agordi Kromnomon Ne @@ -606,6 +694,8 @@ Silentigi por {time_large} Malsilentigi Silentigite + Silentigita por {time_large} + Silentigita ĝis {date_time} Malrapida Reĝimo {app_name} foje serĉos novajn mesaĝojn en la fono. Sono @@ -664,20 +754,26 @@ Agordi Pasvorton Via pasvorto estas agordita. Bonvolu konservi ĝin sekura. Alglui + Ŝanĝi permesojn {app_name} needs music and audio access in order to send files, music and audio, but it has been permanently denied. Tap Settings → Permissions, and turn \"Music and audio\" on. {app_name} bezonas uzi Apple Music por ludi aŭdvidaĵojn. Aŭtomata Ĝisdatigo Aŭtomate serĉi ĝisdatigojn kiam komenciĝas {app_name} needs camera access to take photos and videos, but it has been permanently denied. Tap Settings → Permissions, and turn \"Camera\" on. + Permesu aliron al kamerao por video vokoj. La ŝlosila ekrano en {app_name} uzas Vizaĝo-ID. Tenadi en Sistemo Plej {app_name} daŭrigas funkcii en la fono kiam vi fermas la fenestron {app_name} bezonas aliron al foto-biblioteko por daŭrigi. Vi povas ŝalti aliron en la agordoj de iOS. + {app_name} bezonas aliron al loka reto por fari voĉajn kaj video vokojn. + Permesu aliron al loka reto por faciligi voĉajn kaj video vokojn. + Loka reto Mikrofono {app_name} bezonas aliron al la mikrofono por fari vokojn kaj sendi aŭdajn mesaĝojn, sed ĝi estis porĉiame malakceptita. Alklaku agordojn - Permesoj, kaj ŝaltu \"Mikrofono\". Vi povas ebligi mikrofonan aliron en la privatecaj agordoj de {app_name} {app_name} bezonas mikrofonan aliron por fari vokojn kaj registri aŭdajn mesaĝojn. Permesi uzi la mikrofonon. + Permesu aliron al mikrofono por voĉaj vokoj kaj aŭdiomesaĝoj. {app_name} needs music and audio access in order to send files, music and audio. Permeso bezonata {app_name} needs photo library access so you can send photos and videos, but it has been permanently denied. Tap Settings → Permissions, and turn \"Photos and videos\" on. @@ -685,6 +781,7 @@ {app_name} bezonas aliron al memoro por konservi aldonaĵojn kaj aŭdvidaĵojn. {app_name} bezonas aliron al memoro por konservi bildojn kaj videojn, sed ĝi estis porĉiame malakceptita. Bonvolu iri al la aplikaĵaj agordoj, elekti \"Permesoj\", kaj ŝalti \"Memoro\". {app_name} bezonas aliron al memoro por sendi bildojn kaj videojn. + Vi ne havas permesojn por skribi en tiu ĉi komunumo Alpingli Alpingli Interparolon Depingli @@ -697,6 +794,14 @@ Bonvolu elekti plej malgrandan dosieron. Malsukcesis ĝisdatigi profilon. Promocii + + Promocio fiaskis + Promocioj fiaskis + + + La promocio ne povas esti aplikita. Ĉu vi volas reprovi? + La promocioj ne povas esti aplikitaj. Ĉu vi volas reprovi? + QR-Kodo Ĉi tiu QR-kodo ne enhavas Konto IDENT. Ĉi tiu QR-kodo ne enhavas riparan pasvorton @@ -709,11 +814,13 @@ Legokonfirmoj Montri legitajn kvitancojn por ĉiuj mesaĝoj, kiujn vi sendas kaj ricevas. Ricevita: + Ricevita respondo Rekomendate Konservu vian riparan pasvorton por certigi, ke vi ne perdu aliron al via konto. Konservu vian riparan pasvorton Uzu vian recuđer passvorton por ŝarĝi vian konton sur novaj aparatoj.\n\nVia konto ne povas esti rekuperita sen via recuđer passvorto. Certigu, ke ĝi estas stokita ien sekura kaj ne dividas ĝin kun iu ajn. Enigu vian riparan pasvorton + Okazis eraro dum provi ŝarĝi vian reakiran pasvorton.\n\nBonvolu eksporti viajn protokolojn, poste alŝutu la dosieron tra helplabortablo de {app_name} por helpi solvi tiun ĉi problemon. Bonvolu kontroli vian riparan pasvorton kaj reprovu. Iuj vortoj en via Ripara Pasvorto estas malĝustaj. Bonvolu kontroli kaj provi denove. La ripara pasvorto, kiun vi enmetis, ne estas sufiĉe longa. Bonvolu kontroli kaj reprovi. @@ -727,6 +834,7 @@ Enigu vian riparan pasvorton por ŝargi vian konton. Se vi ne konservis ĝin, vi povas trovi ĝin en viaj aplikaĵa agordoj. Vidi Pasvorton Ĉi tio estas via ripara pasvorto. Se vi sendas ĝin al iu, tiam tiu havos tutan aliron al via konto. + Rekrei grupon Refari Forigi Malsukcesis forigi pasvorton @@ -759,6 +867,7 @@ Serĉante... Elekti Elekti ĉion + Elektu piktogramon de aplikaĵo Sendi Sendante Sendita: @@ -768,30 +877,39 @@ Helpo Inviti Amikon Mesaĝpetoj + Nuna prezo de {token_name_short} + {token_name_long} estas viva! Esploru la novan sekcion {network_name} en Agordoj por lerni kiel {token_name_long} funkciigas Session. + Reto sekurigita de + Nova Sciigoj Permesoj Privateco Ripara Pasvorto Agordoj Fiksu + Agordi bildon de la komunumo Vi devas rekomenci {app_name} por apliki viajn novajn agordojn. Kunhavigi Invitu vian amikon babili kun vi en {app_name} dividante vian Konta Identigilo kun ili. Kunhavigu kun viaj amikoj kien ajn vi kutime parolas kun ili — tiam movu la konversacion ĉi tie. Okazis problemo dum malfermo de la datumbazo. Bonvolu restartigi la aplikon kaj reprovi. + Ups! Ŝajnas ke vi ankoraŭ ne havas {app_name} konton\n\nVi bezonos krei ĝin en la {app_name} antaŭ ol vi povos diskonigi. Kunhavigi al {app_name} Montri Montri ĉiujn Montri malpli + Montri noton al mi mem Glumarkoj Iri al helppaĝo Sistemajn Informojn: {information} + Frapetu por reprovi Daŭrigi Defaŭlta Eraro Provi Denove Tajpantaj indikiloj Vidi kaj kunhavigi tajp-indikilojn. + Ne disponebla Malfari Nekonata Aplikaĵaj ĝisdatigoj @@ -799,10 +917,15 @@ Elŝutante ĝisdatigon: {percent_loader}% Neaktualebligas {app_name} malsukcesis ĝisdatigi. Bonvolu iri al {session_download_url} kaj instali la novan version permane, poste kontaktu nian HelpoCentron por sciigi nin pri tiu ĉi problemo. + Ĝisdatigi informon de la grupo + Grupa nomo kaj priskribo estas videbla al ĉiuj membroj de la grupo. + Bonvolu enigi pli mallongan priskribon de la grupo Nova versio de {app_name} disponeblas, tuŝu por ĝisdatigi + Nova versio ({version}) de {app_name} estas disponebla. Iri al eldonaj notoj Ĝisdatigo de {app_name} Versio {version} + Laste ĝisdatigita antaŭ {relative_time} Alŝutante Kopii URL-on Ĉu Malfermi Retadreson @@ -812,6 +935,8 @@ Videaĵo Ne eblas ludi videaĵon. Vidi + Vidi malpli + Vidi pli Ĉi tio povas daŭri kelkajn minutojn. Momenton, Bonvolu... Averto diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 9cc5b12ec7..1e4ba25891 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -153,6 +153,7 @@ Las llamadas de voz y video requieren que las notificaciones estén habilitadas en la configuración de su dispositivo. Se requieren permisos de llamada Puedes activar el permiso de \"Llamadas de voz y video\" en los ajustes de Privacidad. + Puedes activar el permiso para \"Llamadas de voz y vídeo\" en los ajustes de Privacidad. Reconectando... Llamando... Llamada de {app_name} @@ -261,6 +262,7 @@ Crear Creando llamada Cortar + Ocurrió un error en la base de datos.\n\nExporta tus registros de aplicación para compartirlos con fines de resolución de problemas. Si esto no funciona, reinstala {app_name} y restaura tu cuenta. Hemos notado que {app_name} está tardando mucho en arrancar.\n\nPuedes esperar, exportar los registros de tu dispositivo para compartirlos para la resolución de problemas, o intentar reiniciar {app_name}. Tu base de datos de la app es incompatible con esta versión de {app_name}. Reinstala la app y restaura tu cuenta para generar una nueva base de datos y continuar usando {app_name}.\n\nAdvertencia: Esto resultará en la pérdida de todos los mensajes y archivos adjuntos mayores a dos semanas. Optimizando base de datos @@ -410,7 +412,9 @@ Crear Grupo Por favor, elige al menos un otro miembro del grupo. Eliminar grupo + ¿Estás seguro de que deseas eliminar {group_name}?\n\n Esto borrará a todos los miembros y eliminará todo el contenido del grupo. ¿Seguro que quieres eliminar {group_name}? + Un administrador de grupo ha eliminado {group_name}. No podrás enviar más mensajes. Introduce la descripción del grupo Foto de grupo actualizada. Editar grupo @@ -463,9 +467,12 @@ Nombre del grupo actualizado. El nombre del grupo es visible para todos los miembros del grupo. No tienes mensajes de {group_name}. ¡Envía un mensaje para iniciar la conversación! + Este grupo no ha sido actualizado en más de 30 días. Puede que experimentes problemas al enviar mensajes o ver la información del grupo. Eres el único administrador en {group_name}.\n\nLos miembros del grupo y la configuración no se pueden cambiar sin un administrador. + Pendiente de eliminación fuiste promovido a Admin. y {count} más fueron promovidos a Admin. + y {other_name} fueron promovidos a Administradores. ¿Te gustaría eliminar a {name} de {group_name}? ¿Te gustaría eliminar a {name} y a {count} otros de {group_name}? ¿Te gustaría eliminar a {name} y a {other_name} de {group_name}? @@ -774,6 +781,7 @@ Guarde su clave de recuperación Utilice su contraseña de recuperación para cargar su cuenta en nuevos dispositivos.\n\nSu cuenta no se puede recuperar sin su contraseña de recuperación. Asegúrese de guardarla en un lugar seguro — y no la comparta con nadie. Escriba su clave de recuperación + Ocurrió un error al intentar cargar tu contraseña de recuperación.\n\nPor favor exporta tus registros, y luego sube el archivo a través del Help Desk {app_name} para ayudar a resolver este problema. Por favor, comprueba tu clave de recuperación y vuelve a intentarlo. Algunas de las palabras en tu Recovery Password son incorrectas. Por favor verifica y vuelve a intentarlo. La Recuperación Password que ingresaste no es lo suficientemente larga. Por favor verifica e inténtalo de nuevo. @@ -839,6 +847,7 @@ Invita a tu amigo a chatear contigo en {app_name} compartiendo tu ID de cuenta con ellos. Comparte con tus amigos dondequiera que hables con ellos — luego mueve la conversación aquí. Hay un problema al abrir la base de datos. Por favor reinicia la aplicación y vuelve a intentarlo. + ¡Ups! Parece que no tienes una cuenta de {app_name} todavía.\n\nTendrás que crear una en la aplicación de {app_name} antes de que puedas compartir. Compartir en {app_name} Mostrar Mostrar todo @@ -860,6 +869,7 @@ No se pudo actualizar {app_name} no se pudo actualizar. Por favor, dirígete a {session_download_url} e instala la nueva versión manualmente. Al finalizar, avísanos en el Centro de Ayuda sobre este problema. Hay una nueva versión de {app_name} disponible, toca para actualizar + Hay disponible una nueva versión ({version}) de {app_name}. Ir a las notas de versión Actualización de {app_name} Versión {version} diff --git a/app/src/main/res/values-b+es+ES/strings.xml b/app/src/main/res/values-b+es+ES/strings.xml index 033734c65e..65e2d06eef 100644 --- a/app/src/main/res/values-b+es+ES/strings.xml +++ b/app/src/main/res/values-b+es+ES/strings.xml @@ -153,6 +153,7 @@ Las llamadas de voz y video requieren que las notificaciones estén habilitadas en la configuración del sistema de tu dispositivo. Se requieren permisos de llamada Puedes habilitar los permisos de \"Llamadas de Voz y Video\" en la Configuración de Privacidad. + Puedes activar el permiso para \"Llamadas de voz y vídeo\" en los ajustes de Privacidad. Reconectando... Llamando... Llamada de {app_name} @@ -261,6 +262,7 @@ Crear Creando llamada Cortar + Ocurrió un error en la base de datos.\n\nExporta tus registros de aplicación para compartirlos con fines de resolución de problemas. Si esto no funciona, reinstala {app_name} y restaura tu cuenta. Hemos notado que {app_name} está tardando mucho en iniciar.\n\nPuedes seguir esperando, exportar los registros de tu dispositivo para compartirlos y solucionar problemas, o intentar reiniciar {app_name}. Su base de datos de la aplicación no es compatible con esta versión de {app_name}. Reinstala la aplicación y restaura tu cuenta para generar una nueva base de datos y continuar usando {app_name}.\n\nAdvertencia: Esto resultará en la pérdida de todos los mensajes y archivos adjuntos anteriores a dos semanas. Optimizando base de datos @@ -410,7 +412,9 @@ Crear grupo Por favor, elige al menos un miembro del grupo. Borrar Grupo + ¿Estás seguro de que deseas eliminar {group_name}?\n\n Esto borrará a todos los miembros y eliminará todo el contenido del grupo. ¿Seguro que quieres eliminar {group_name}? + Un administrador de grupo ha eliminado {group_name}. No podrás enviar más mensajes. Ingrese una descripción del grupo La imagen del grupo se actualizó. Editar grupo @@ -463,9 +467,12 @@ Nombre del grupo actualizadó. El nombre del grupo es visible para todos los miembros del grupo. No tienes mensajes de {group_name}. ¡Envía un mensaje para iniciar la conversación! + Este grupo no ha sido actualizado en más de 30 días. Puede que experimentes problemas al enviar mensajes o ver la información del grupo. Eres el único admin en {group_name}.\n\nLos miembros y la configuración del grupo no pueden ser modificados sin un admin. + Pendiente de eliminación fuiste promovido a Administrador. y {count} más fueron promovidos a Administradores. + y {other_name} fueron promovidos a Administradores. ¿Te gustaría eliminar a {name} de {group_name}? ¿Te gustaría eliminar a {name} y a {count} otros de {group_name}? ¿Te gustaría eliminar a {name} y a {other_name} de {group_name}? @@ -774,6 +781,7 @@ Guarde su clave de recuperación Usa tu contraseña de recuperación para cargar tu cuenta en nuevos dispositivos.\n\nTu cuenta no se puede recuperar sin tu contraseña de recuperación. Asegúrate de que esté guardada en un lugar seguro y no la compartas con nadie. Ingrese su clave de recuperación + Ocurrió un error al intentar cargar tu contraseña de recuperación.\n\nPor favor exporta tus registros, y luego sube el archivo a través del Help Desk {app_name} para ayudar a resolver este problema. Por favor, comprueba tu clave de recuperación y vuelve a intentarlo. Algunas de las palabras en tu clave de recuperación son incorrectas. Por favor verifica e inténtalo de nuevo. La contraseña de recuperación que ingresaste no es lo suficientemente larga. Por favor verifica e intenta nuevamente. @@ -839,6 +847,7 @@ Invita a tu amigo a hablar contigo en {app_name} compartiendo tu ID de Cuenta con él. Comparte con tus amigos dondequiera que hables con ellos — luego mueve la conversación aquí. Hay un problema al abrir la base de datos. Por favor, reinicia la aplicación e inténtalo de nuevo. + ¡Ups! Parece que no tienes una cuenta de {app_name} todavía.\n\nTendrás que crear una en la aplicación de {app_name} antes de que puedas compartir. Compartir en {app_name} Mostrar Mostrar todas @@ -860,6 +869,7 @@ No se pudo actualizar {app_name} falló al actualizarse. Por favor ve a {session_download_url} e instala la nueva versión manualmente, después contacta con nuestro Centro de Ayuda para informarnos sobre este problema. Hay una nueva versión de {app_name} disponible, toca para actualizar + Hay disponible una nueva versión ({version}) de {app_name}. Ir a las notas de versión Actualización de {app_name} Versión {version} diff --git a/app/src/main/res/values-b+fr+FR/strings.xml b/app/src/main/res/values-b+fr+FR/strings.xml index 9296265708..f3441c1c56 100644 --- a/app/src/main/res/values-b+fr+FR/strings.xml +++ b/app/src/main/res/values-b+fr+FR/strings.xml @@ -402,6 +402,7 @@ Définir un nom d\'affichage Votre nom d\'affichage est visible par les utilisateurs, les groupes et les communautés avec lesquels vous interagissez. Document + Faire un don Terminé Télécharger Téléchargement… @@ -434,6 +435,7 @@ Veuillez vérifier votre connexion internet et réessayer. Copier l\'erreur et quitter Erreur de base de données + Une erreur s\'est produite. Veuillez réessayer plus tard. Une erreur inconnue est survenue. Échec du téléchargement Échecs @@ -906,6 +908,16 @@ Aide Inviter un ami Demandes de message + Prix actuel du {token_name_short} + Les messages sont envoyés en utilisant l\'{network_name}. Le réseau est composé de noeuds stimulés par {token_name_long}, qui permet à {app_name} d\'être décentralisée et sécurisée. En savoir plus {icon} + En savoir plus sur Staking + Capitalisation du marché + {app_name} Nodes sécurisant vos messages + {app_name} Nodes dans votre essaim + Le {token_name_long} est en ligne ! Explorez la nouvelle section {network_name} dans les paramètres pour apprendre comment le {token_name_long} alimente Session. + Réseau sécurisé par + Lorsque vous stakez du {token_name_long} pour sécuriser le réseau, vous gagnez des récompenses en {token_name_short} de l’ {staking_reward_pool}. + Nouveau Notifications Permissions Confidentialité @@ -935,6 +947,7 @@ Réessayer Indicateurs de saisie Voir et partager les indicateurs de saisie. + Indisponible Annuler Inconnu Mises à jour de l’application @@ -950,6 +963,7 @@ Accéder aux notes de mise à jour Mise à jour de {app_name} Version {version} + Dernière mise à jour il y a {relative_time} Téléversement Copier l\'adresse URL Ouvrir l\'URL diff --git a/app/src/main/res/values-b+hi+IN/strings.xml b/app/src/main/res/values-b+hi+IN/strings.xml index 6c53edd2b8..7b5e1764d1 100644 --- a/app/src/main/res/values-b+hi+IN/strings.xml +++ b/app/src/main/res/values-b+hi+IN/strings.xml @@ -26,7 +26,9 @@ {name} को {group_name} में पदोन्नत करने में विफल {name} और {count} अन्य को {group_name} में पदोन्नत करने में विफल {name} और {other_name} को {group_name} में पदोन्नत करने में विफल + प्रमोशन नहीं भेजा गया एडमिन पदोन्नति भेजी गई + पदोन्नति की स्थिति अज्ञात एडमिन को हटाएं एडमिन के रूप में हटाएं इस Community में कोई Admins नहीं हैं। @@ -36,14 +38,24 @@ {name} को एडमिन से हटा दिया गया। {name} और {count} अन्य को व्यवस्थापक पद से हटा दिया गया | {name} and {other_name} को व्यवस्थापक पद से हटा दिया गया | + + एडमिन प्रमोशन भेजा जा रहा है + एडमिन प्रमोशन भेजे जा रहे हैं + एडमिन सेटिंग्स {name} और {other_name} को Admin बनाया गया। +{count} गुमनाम ऐप आइकॉन + होम स्क्रीन और ऐप ड्रॉअर पर वैकल्पिक ऐप आइकन और नाम प्रदर्शित होता है। आइकॉन और नाम + होम स्क्रीन और ऐप लाइब्रेरी पर वैकल्पिक ऐप आइकन प्रदर्शित होता है। ऐप का नाम अभी भी \'{app_name}\' के रूप में दिखाई देगा। + वैकल्पिक ऐप आइकन का उपयोग करें + वैकल्पिक ऐप आइकन और नाम का उपयोग करें + वैकल्पिक ऐप आइकन चुनें आइकॉन कैलकुलेटर + मीटिंगएसई समाचार नोट्स शेयरों @@ -155,6 +167,7 @@ वॉइस और वीडियो कॉल के लिए आपके डिवाइस सिस्टम सेटिंग्स में नोटिफिकेशन सक्षम होना आवश्यक है। कॉल अनुमतियाँ आवश्यक हैं आप गोपनीयता सेटिंग्स में \"वॉइस और वीडियो कॉल्स\" अनुमति सक्षम कर सकते हैं। + आप अनुमति सेटिंग में \"वॉयस और वीडियो कॉल\" अनुमति सक्षम कर सकते हैं। पुन: कनेक्ट हो रहा है… बज रहा है... {app_name} कॉल करें @@ -261,7 +274,9 @@ कॉपी किया गया! कॉपी करें बनाएं + कॉल बनाया जा रहा है कट + डेटाबेस त्रुटि हुई है।\n\nसमस्या निवारण के लिए अपने एप्लिकेशन लॉग्स को शेयर करने के लिए निर्यात करें। यदि यह असफल रहता है, तो {app_name} को फिर से इंस्टॉल करें और अपना खाता पुनः प्राप्त करें। हमने देखा कि {app_name} प्रारंभ होने में बहुत समय ले रहा है।\n\nआप प्रतीक्षा करना जारी रख सकते हैं, अपने डिवाइस लॉग को निर्यात कर सकते हैं ताकि समस्या निवारण के लिए साझा कर सकें, या {app_name} पुनरारंभ करने का प्रयास कर सकते हैं। {app_name} का यह संस्करण आपके ऐप डेटाबेस के साथ असंगत है। ऐप को पुन: स्थापित करें और अपना खाता पुनर्स्थापित करें ताकि नया डेटाबेस उत्पन्न हो सके और {app_name} का उपयोग जारी रख सकें।\n\nचेतावनी: इससे दो सप्ताह से पुराने सभी संदेश और संलग्नक खो जाएंगे। डाटाबेस का अनुकूलन @@ -287,12 +302,20 @@ संदेश मिटाएं संदेश मिटाएं + + क्या आप वाकई इस संदेश को हटाना चाहते हैं? + क्या आप वाकई इन संदेशों को हटाना चाहते हैं? + संदेश मिटाया गया संदेश मिटाये गए यह संदेश हटा दिया गया है यह संदेश इस डिवाइस पर हटा दिया गया है + + क्या आप वाकई केवल इस डिवाइस से इस संदेश को हटाना चाहते हैं? + क्या आप वाकई इन संदेशों को केवल इसी डिवाइस से हटाना चाहते हैं? + क्या आप वाकई इस संदेश को सभी के लिए हटाना चाहते हैं? केवल इस डिवाइस पर मिटाएँ सभी उपकरणों पर मिटाएँ @@ -390,6 +413,7 @@ त्रुटि कॉपी करें और छोड़ दें डेटाबेस त्रुटि एक अज्ञात त्रुटि हुई। + डाउनलोड विफल विफलतायें फ़ाइल फ़ाइलें @@ -403,6 +427,9 @@ समूह बनाएँ कृपया कम से कम एक ग्रुप सदस्य चुनें ग्रुप डिलीट करें + क्या आप वाकई {group_name} को हटाना चाहते हैं?\n\nइससे सभी सदस्य हट जाएंगे और समूह की सारी सामग्री भी मिट जाएगी। + क्या आप वाकई {group_name} को हटाना चाहते हैं? + {group_name} को ग्रुप एडमिन ने हटा दिया है। अब आप कोई और संदेश नहीं भेज पाएंगे। ग्रुप विवरण दर्ज करें समूह डिस्प्ले चित्र अपडेट किया गया। समूह संपादित करें @@ -418,7 +445,12 @@ आमंत्रण नहीं भेजा गया {name} ने आपको {group_name} में फिर से शामिल होने के लिए आमंत्रित किया है, जहां आप एक व्यवस्थापक हैं। आपको {group_name} में फिर से जुड़ने के लिए आमंत्रित किया गया है, जहाँ आप एक एडमिन हैं। + + निमंत्रण भेजा जा रहा है + निमंत्रण भेजे जा रहे हैं + आमंत्रण भेजा गया + आमंत्रण स्थिति अज्ञात समूह आमंत्रण सफल निमंत्रण प्राप्त करने के लिए उपयोगकर्ताओं के पास नवीनतम रिलीज़ होनी चाहिए आप को समूह में शामिल होने के लिए आमंत्रित किया गया था। @@ -439,6 +471,7 @@ {name} और {count} अन्य को समूह में शामिल होने के लिए आमंत्रित किया गया। {name} और {other_name} को समूह में शामिल होने के लिए आमंत्रित किया गया था। आप और {count} अन्य को समूह में शामिल होने के लिए आमंत्रित किया गया। चैट इतिहास साझा किया गया। + आप और {other_name} को समूह में शामिल होने के लिए आमंत्रित किया गया। चैट इतिहास साझा किया गया। आप ने समूह छोड़ दिया। समूह के सदस्य इस समूह में कोई अन्य सदस्य नहीं है। @@ -450,10 +483,12 @@ समूह का नाम अपडेट किया गया। समूह का नाम सभी समूह के सदस्यों के लिए दृश्यमान है। आपके पास {group_name} से कोई संदेश नहीं हैं। वार्तालाप शुरू करने के लिए एक संदेश भेजें! + इस समूह को 30 दिनों से अधिक समय से अपडेट नहीं किया गया है। आपको संदेश भेजने या समूह की जानकारी देखने में समस्या आ सकती है। आप {group_name} में अकेले व्यवस्थापक हैं।\n\nसमूह सदस्य और सेटिंग्स बिना व्यवस्थापक के बदले नहीं जा सकते। लंबित हटाना आप को Admin बनाया गया। आप और {count} अन्य को Admin बनाया गया। + आपको और {other_name} को व्यवस्थापक के पद पर पदोन्नत किया गया है। क्या आप {name} को {group_name} से निकालना चाहेंगे? क्या आप {name} और {count} अन्य को {group_name} से निकालना चाहेंगे? क्या आप {name} और {other_name} को {group_name} से निकालना चाहेंगे? @@ -475,6 +510,7 @@ समूह डिस्प्ले तस्वीर सेट करें अनजान समूह समूह अपडेट किया गया + कनेक्शन उम्मीदवारों को संभाला जा रहा है अकसर किये गए सवाल हमें {app_name} का अनुवाद करने में मदद करें बग सूचित करें @@ -489,16 +525,28 @@ सिस्टम मेनू बार दृश्यता टॉगल करें बाकियों को छुपाएं तस्वीर + इमेजिस गुप्त कीबोर्ड संवेदनशील मोड उपलब्ध होने पर अनुरोध करें। आप जिस कीबोर्ड का उपयोग कर रहे हैं, उसके आधार पर आपका कीबोर्ड इस अनुरोध को अनदेखा कर सकता है। जानकारी अमान्य शॉर्टकट + + निमंत्रण विफल + निमंत्रण विफल + + + आमंत्रण नहीं भेजा जा सका। क्या आप फिर से प्रयास करना चाहेंगे? + आमंत्रण नहीं भेजे जा सके। क्या आप फिर से प्रयास करना चाहेंगे? + से जुड़ें बाद में अधिक जानें छोड़ें छोड़ना... यह समूह अब केवल पढ़ने के लिए है। चैटिंग जारी रखने के लिए इस समूह को पुनः बनाएँ। + यह ग्रुप अब केवल पढ़ने के लिए है। चैटिंग जारी रखने के लिए ग्रुप एडमिन से इस ग्रुप को फिर से बनाने के लिए कहें। + समूहों को अपग्रेड कर दिया गया है! बेहतर विश्वसनीयता के लिए इस समूह को फिर से बनाएँ। यह समूह {date} को केवल पढ़ने के लिए उपलब्ध हो जाएगा। + ग्रुप को अपग्रेड कर दिया गया है! बेहतर विश्वसनीयता के लिए ग्रुप एडमिन से इस ग्रुप को फिर से बनाने के लिए कहें। यह ग्रुप {date} को केवल पढ़ने के लिए उपलब्ध हो जाएगा। बातचीत इतिहास का हस्तांतरण नए समूह में नहीं हो सकता है। आप बातचीत इतिहास पुराने समूह में देख सकते हैं। {name} समूह में शामिल हुए {name} और {count} अन्य समूह में शामिल हुए। @@ -569,6 +617,10 @@ आपको एक नया संदेश मिला है। आपको %1$d नए संदेश मिले हैं। + + आपको %1$s में एक नया संदेश मिला है। + आपको %1$s में %2$d नए संदेश मिले हैं। + को जवाब दे रहे हैं {name} ने आपको {group_name} से जुड़ने के लिए आमंत्रित किया है। इस समूह को संदेश भेजना स्वचालित रूप से समूह निमंत्रण को स्वीकार करेगा। @@ -619,6 +671,7 @@ कोई नाम या सामग्री नहीं तीव्र विधा | आपको नई सूचनाओं के बारे में Google के नोटीफिकेशन servers से तत्काल सूचित किया जाएगा। + Huawei के अधिसूचना सर्वर का उपयोग करके आपको नए संदेशों की विश्वसनीय और तुरंत सूचना दी जाएगी। आपको नई सूचनाओं के बारे में Apple के नोटीफिकेशन servers से तत्काल सूचित किया जाएगा। डिवाइस अधिसूचना सेटिंग्स पर जाएं सूचनाएं - सभी @@ -692,20 +745,31 @@ पासवर्ड सेट करें आपका पासवर्ड सेट कर दिया गया है। कृपया इसे सुरक्षित रखें। पेस्ट करें + अनुमति परिवर्तन {app_name} को संगीत और ऑडियो एक्सेस की आवश्यकता है ताकि फ़ाइलें, संगीत और ऑडियो भेजी जा सकें, लेकिन इसे स्थायी रूप से अस्वीकार कर दिया गया है। सेटिंग्स पर टैप करें → अनुमतियां, और \"संगीत और ऑडियो\" चालू करें। मीडिया संलग्नक बजाने के लिए {app_name} को Apple Music के उपयोग की आवश्यकता है। स्वयमेव अद्यतन हो जाना स्टार्टअप पर स्वचालित रूप से अद्यतन जांचें + वीडियो कॉल करने के लिए कैमरा एक्सेस की आवश्यकता होती है। जारी रखने के लिए सेटिंग में \"कैमरा\" अनुमति टॉगल करें। + कैमरा एक्सेस वर्तमान में सक्षम है। इसे अक्षम करने के लिए, सेटिंग्स में \"कैमरा\" अनुमति टॉगल करें। {app_name} को फ़ोटो और वीडियो लेने के लिए कैमरा अनुमति की आवश्यकता होती है, लेकिन इसे स्थायी रूप से मना कर दिया गया है। सेटिंग्स → अनुमतियां पर टैप करें और \"कैमरा\" चालू करें। + वीडियो कॉल के लिए कैमरे तक पहुंच की अनुमति दें. {app_name} पर स्क्रीन लॉक फीचर Face ID का उपयोग करता है। सिस्टम ट्रे में रखें जब आप विंडो बंद करते हैं तो {app_name} पृष्ठभूमि में चलता रहता है {app_name} को जारी रखने के लिए फ़ोटो लाइब्रेरी पहुंच की आवश्यकता है। आप iOS सेटिंग्स में पहुंच सक्षम कर सकते हैं। + कॉल की सुविधा के लिए स्थानीय नेटवर्क एक्सेस की आवश्यकता होती है। जारी रखने के लिए सेटिंग्स में \"स्थानीय नेटवर्क\" अनुमति टॉगल करें। + {app_name} को वॉयस और वीडियो कॉल करने के लिए स्थानीय नेटवर्क तक पहुंच की आवश्यकता है। + स्थानीय नेटवर्क एक्सेस वर्तमान में सक्षम है। इसे अक्षम करने के लिए, सेटिंग्स में \"स्थानीय नेटवर्क\" अनुमति टॉगल करें। + आवाज और वीडियो कॉल की सुविधा के लिए स्थानीय नेटवर्क तक पहुंच की अनुमति दें। + लोकल नेटवर्क माइक्रोफ़ोन {app_name} को कॉल करने और ऑडियो संदेश भेजने के लिए माइक्रोफ़ोन अनुमति की आवश्यकता है, लेकिन इसे स्थायी रूप से मना कर दिया गया है। सेटिंग्स → अनुमतियां पर टैप करें, और \"माइक्रोफ़ोन\" चालू करें। + कॉल करने और ऑडियो संदेश रिकॉर्ड करने के लिए माइक्रोफ़ोन एक्सेस की आवश्यकता होती है। जारी रखने के लिए सेटिंग में \"माइक्रोफ़ोन\" अनुमति टॉगल करें। आप {app_name} की गोपनीयता सेटिंग्स में माइक्रोफ़ोन एक्सेस सक्षम कर सकते हैं कॉल करने और ऑडियो संदेश रिकॉर्ड करने के लिए {app_name} को माइक्रोफोन एक्सेस की आवश्यकता है। माइक्रोफ़ोन तक पहुंच की अनुमति दें। + वोइस कॉल और ऑडियो संदेशों के लिए माइक्रोफ़ोन तक पहुंच की अनुमति दे | {app_name} को फ़ाइलें, संगीत और ऑडियो भेजने के लिए संगीत और ऑडियो एक्सेस की आवश्यकता है। अनुमति की आवश्यकता {app_name} को फ़ोटो और वीडियो भेजने के लिए फोटो लाइब्रेरी अनुमति की आवश्यकता है, लेकिन इसे स्थायी रूप से मना कर दिया गया है। सेटिंग्स → अनुमतियां पर टैप करें, और \"फ़ोटो और वीडियो\" चालू करें। @@ -725,6 +789,14 @@ Please pick a smaller file. प्रोफ़ाइल अपडेट करने में विफल। पदोन्नत करें + + प्रमोशन विफल + प्रमोशन विफल + + + प्रमोशन लागू नहीं किया जा सका। क्या आप फिर से प्रयास करना चाहेंगे? + प्रमोशन लागू नहीं किए जा सके। क्या आप फिर से प्रयास करना चाहेंगे? + QR कोड इस QR कोड में Account ID नहीं है इस QR कोड में Recovery Password नहीं है @@ -737,11 +809,15 @@ पठित स्थिति प्रमाणपत्र आपके द्वारा भेजे और प्राप्त किए गए सभी संदेशों के लिए पढ़ने की रसीदें दिखाएं। प्राप्त किया: + उत्तर प्राप्त हुआ + कॉल ऑफर प्राप्त हो रहा है + प्री ऑफर प्राप्त हो रहा है संस्तुत अपना रिकवरी पासवर्ड सहेजें ताकि आप अपने खाते तक पहुँच न खोएं। अपना रिकवरी पासवर्ड सहेजें अपना अकाउंट नए डिवाइस पर लोड करने के लिए अपने रिकवरी पासवर्ड का उपयोग करें।\n\nआपका अकाउंट आपके रिकवरी पासवर्ड के बिना पुनर्प्राप्त नहीं किया जा सकता है। सुनिश्चित करें कि इसे कहीं सुरक्षित रखें और इसे किसी के साथ साझा न करें। अपना पुनर्प्राप्ति पासवर्ड दर्ज करें + आपका पुनर्प्राप्ति पासवर्ड लोड करने का प्रयास करते समय एक त्रुटि हुई।\n\nकृपया अपने लॉग निर्यात करें, फिर इस समस्या को हल करने में सहायता के लिए {app_name} सहायता डेस्क के माध्यम से फ़ाइल अपलोड करें। Please check your recovery password and try again. आपके पुनर्प्राप्ति पासवर्ड के कुछ शब्द गलत हैं। कृपया जांच कर पुनः प्रयास करें। The Recovery Password you entered is not long enough. Please check and try again. @@ -755,6 +831,7 @@ अपना खाता लोड करने के लिए अपना पुनर्प्राप्ति पासवर्ड दर्ज करें। यदि आपने इसे सहेजा नहीं है, तो आप इसे अपनी ऐप सेटिंग में पा सकते हैं। पासवर्ड देखें यह आपका पुनर्प्राप्ति वाक्यांश है। अगर आप इसे किसी को भेजते हैं तो उनके पास आपके खाते की पूरी पहुंच होगी। + समूह पुनः बनाएँ फिर से करें हटा दें पासवर्ड हटाने में विफल @@ -789,6 +866,8 @@ सभी को चुन लो स भेजें भेजा जा रहा है + कॉल ऑफर भेजा जा रहा है + कनेक्शन उम्मीदवार भेजे जा रहे हैं भेजा गया: दिखावट डेटा हटाएं @@ -807,6 +886,7 @@ अपने मित्र को अपने साथ {app_name} पर चैट करने के लिए Account ID साझा करके आमंत्रित करें। अपने दोस्तों के साथ वहां साझा करें जहां आप आमतौर पर उनसे बात करते हैं - फिर यहां बातचीत को स्थानांतरित करें। डेटाबेस खोलने में समस्या हो रही है। कृपया एप्लिकेशन को पुनः प्रारंभ करें और फिर से कोशिश करें। + ओह! ऐसा लगता है कि आपके पास अभी तक {app_name} खाता नहीं है।\n\nशेयर करने से पहले आपको {app_name} ऐप में एक खाता बनाना होगा। {app_name} को साझा करें दिखाएं सभी को दिखाएं @@ -829,6 +909,7 @@ अद्यतन नहीं हो रहा {app_name} अपडेट होने में विफल. कृपया {session_download_url} पर जाएं और नए संस्करण को मैन्युअल रूप से इंस्टॉल करें, फिर इस समस्या के बारे में हमें हमारे सहायता केंद्र से संपर्क करें | {app_name} का एक नया संस्करण उपलब्ध है, अपडेट करने के लिए टैप करें + {app_name} का एक नया संस्करण ({version}) उपलब्ध है। रिलीज़ नोट्स पे जाइए {app_name} अपडेट वर्ज़न {version} diff --git a/app/src/main/res/values-b+hu+HU/strings.xml b/app/src/main/res/values-b+hu+HU/strings.xml index 7864372910..05d96f0ca3 100644 --- a/app/src/main/res/values-b+hu+HU/strings.xml +++ b/app/src/main/res/values-b+hu+HU/strings.xml @@ -3,6 +3,7 @@ Névjegy Elfogadás Felhasználó ID másolása + Fiókazonosító Felhasználó ID másolva Másold ki a Felhasználó ID-dat, majd oszd meg barátaiddal, hogy üzenetet küldhessenek neked. Add meg a Felhasználó ID-t @@ -14,6 +15,8 @@ Ez a te Felhasználó ID-d. Más felhasználók beszkennelhetik, hogy egy beszélgetést indítsanak el veled. Eredeti méret Hozzáadás + Adminisztrátorok hozzáadása + Adja meg a felhasználó fiókazonosítóját, akit adminisztrátorrá kíván kinevezni.\n\nEgyszerre több felhasználó hozzáadásához adja meg az egyes fiókazonosítókat vesszővel elválasztva. Egyszerre legfeljebb 20 fiókazonosító adható meg. Adminokat nem lehet eltávolítani. {name} és {count} másik személy adminisztrátorrá lettek előléptetve. Adminisztrátorok előléptetése @@ -26,7 +29,9 @@ Nem sikerült {name}-t adminisztrátorrá léptetni a {group_name} csoportban Nem sikerült {name}-t és {count} másik személyt adminisztrátorrá léptetni a {group_name} csoportban Nem sikerült {name}-t és {other_name}-t adminisztrátorrá léptetni a {group_name} csoportban + Az előléptetés nem lett elküldve Adminisztrátori előléptetés elküldve + Az előléptetés állapota ismeretlen Adminok eltávolítása Admin jogosultság eltávolítása Nincsenek adminisztrátorok ebben a közösségben. @@ -36,10 +41,31 @@ {name} el lett távolítva mint adminisztrátor. {name} és {count} másik személy el lettek távolítva adminisztrátorként. {name} és {other_name} el lettek távolítva adminisztrátorként. + + Adminisztrátori kinevezés küldése + Adminisztrátori kinevezés küldése + Adminisztrátor beállítások - {name} és {other_name} adminisztrátorrá lettek előléptetve. + {name} és {other_name} adminisztrátorrá lett előléptetve. +{count} Névtelen + Alkalmazásikon + Az alkalmazás ikonjának és nevének módosítása + Az alkalmazás ikonjának és nevének módosításához be kell zárni a Session alkalmazást. Az értesítések továbbra is az alapértelmezett Session ikont és nevet fogják használni. + Az alternatív alkalmazásikon és név megjelenik a kezdőképernyőn és az alkalmazásfiókban. + A kiválasztott alkalmazás ikonja és neve megjelenik a kezdőképernyőn és az alkalmazásfiókban. + Ikon és név + Az alternatív alkalmazásikon megjelenik a kezdőképernyőn és az alkalmazáskönyvtárban. Az alkalmazás neve továbbra is „{app_name}” néven jelenik meg. + Alternatív alkalmazásikon használata + Alternatív alkalmazásikon és -név használata + Válasszon alternatív alkalmazásikont + Ikon + Számológép + Találkozók + Hírek + Jegyzetek + Részvények + Időjárás Automatikus sötét mód Menüsor elrejtése Nyelv @@ -57,6 +83,7 @@ Nagyítás növelése Nagyítás csökkentése Melléklet + Mellékletek Melléklet hozzáadása Névtelen album Mellékletek automatikus letöltése @@ -118,9 +145,11 @@ A kitiltás sikertelen A kitiltás feloldása sikertelen Felhasználó kitiltásának feloldása + Adja meg annak a felhasználónak a fiókazonosítóját, amelyiknek a kitiltását fel akarja oldani Kitiltás feloldva Felhasználó kitiltása Felhasználó kitiltva + Adja meg annak a felhasználónak a fiókazonosítóját, amelyiket ki akarja tiltani Letiltás Nincsenek blokkolt kontaktok {name} letiltva @@ -147,6 +176,7 @@ A hang- és videóhívásokhoz szükséges, hogy az értesítések engedélyezve legyenek az eszköz rendszerbeállításaiban. Hívási engedély szükséges A hang és videó hívásokat az adatvédelmi beállításokban engedélyezheted. + A „Hang- és videóhívások” engedélyt a Jogosultságbeállításokban adhatja meg. Újracsatlakozás… Kicseng... {app_name} Hívás @@ -180,15 +210,24 @@ Biztos, hogy törölni szeretnéd az adataidat a hálózatról? Ezután nem fogod tudni visszaállítani az üzeneteidet vagy kapcsolataidat. Biztos, hogy törölni szeretnéd a készülékedet? Törlés csak az eszközről + Eszköz törlése és újraindítás + Eszköz törlése és visszaállítás Összes üzenet törlése Biztos, hogy az összes {name} üzenetet törölni szeretnéd az eszközödről? + Biztosan törli az összes üzenetet a(z) {name} nevű partnerével való beszélgetésből ezen az eszközön? Biztos, hogy az összes {community_name} üzenetet törölni szeretnéd az eszközödről? + Biztosan törli a(z) {community_name} összes üzenetét ezen az eszközön? Törlés mindenkinek Törlés nekem Biztos, hogy az összes {group_name} üzenetet törölni szeretnéd? + Biztosan törli a(z) {group_name} összes üzenetét? Biztos, hogy az összes {group_name} üzenetet törölni szeretnéd az eszközödről? + Biztosan törli a(z) {group_name} összes üzenetét ezen az eszközön? Biztos, hogy az összes \'Privát feljegyzés\' üzenetet törölni szeretnéd az eszközödről? + Biztosan törli az összes Jegyzet magamnak üzenetet ezen az eszközön? + Törlés ezen az eszközön Bezárás + Alkalmazás bezárása Ablak bezárása Commit Hash: {hash} Ez ki fogja tiltani a kiválasztott felhasználót ebből a közösségből és törölni fogja az összes üzenetét. Biztosan folytatni akarod? @@ -254,6 +293,8 @@ Másolás Létrehozás Kivágás + Biztosan törli az összes üzenetet, mellékletet és fiókadatot erről az eszközről, és új fiókot hoz létre? + Biztosan törli az összes üzenetet, mellékletet és fiókadatot erről az eszközről, és vissza állítja a fiókját a hálózatról? Észrevettük, hogy {app_name} indítása sokáig tart.\n\nTovábbra is várhatsz, exportálhatod az eszköz naplóit a hibaelhárításhoz, vagy megpróbálhatod újraindítani {app_name}-t. Az alkalmazás adatbázisa nem kompatibilis a {app_name} jelenlegi verziójával. Telepítsd újra az alkalmazást, és állítsd vissza fiókját egy új adatbázis létrehozásához és a {app_name} további használathoz.\n\nFigyelmeztetés: Ez minden két hétnél régebbi üzenet és melléklet elvesztését eredményezi. Adatbázis optimalizálása @@ -275,16 +316,26 @@ Várj amíg a csoport elkészül... Nem sikerült frissíteni a csoportot Nincs jogod mások üzeneteit törölni + Biztosan törli a névjegyek közül a következőt: {name}?\n\nEzzel törli a beszélgetést, beleértve az összes üzenetet és mellékletet. A jövőben a(z) {name} nevű partnerétől érkező üzenetek üzenetkérésként fognak megjelenni. + Biztosan törli a következő partnerével folytatott beszélgetést: {name}?\nEz véglegesen törli az összes üzenetet és mellékletet. Üzenet törlése Üzenetek törlése + + Biztosan törölni akarja ezt az üzenetet? + Biztosan törölni akarja ezeket az üzeneteket? + Üzenet törölve Üzenetek törölve Ez az üzenet törölve lett Ez az üzenet törölve lett ezen az eszközön + + Biztosan csak erről az eszközről szeretné törölni ezt az üzenetet? + Biztosan csak erről az eszközről szeretné törölni ezeket az üzeneteket? + Biztos, hogy törölni szeretnéd ezt az üzenetet mindenki számára? Törlés csak ezen az eszközön Törlés minden eszközömön @@ -293,6 +344,14 @@ Nem sikerült az üzenet törlése Nem sikerült az üzenetek törlése + + Ez az üzenet nem törölhető az összes eszközéről + A kiválasztott üzenetek közül néhány nem törölhető az összes eszközéről + + + Ez az üzenet nem törölhető mindenki számára + A kiválasztott üzenetek közül néhányat nem lehet mindenki számára törölni + Biztos, hogy törölni szeretnéd ezeket az üzeneteket mindenki számára? Törlés Fejlesztői eszközök be-/kikapcsolása @@ -322,7 +381,7 @@ {name} beállította, hogy az üzenetek eltűnjenek {time} után, miután el lettek {disappearing_messages_type}. Te beállítottad, hogy az üzenetek eltűnjenek {time} után, miután el lettek {disappearing_messages_type}. Időzítő - {name} kikapcsolta az eltűnő üzeneteket. Az általuk küldött üzenetek többé nem fognak eltűnni. + {name} kikapcsolta az eltűnő üzeneteket. Az általa küldött üzenetek többé nem tűnnek el. {name} kikapcsolta az eltűnő üzeneteket. Te kikapcsoltad az eltűnő üzeneteket. Az általad küldött üzenetek többé nem fognak eltűnni. Te kikapcsoltad az eltűnő üzeneteket. @@ -339,7 +398,9 @@ Válassz új felhasználónevet Válassz egy felhasználónevet Megjelenítendő név beállítása + Az Ön megjelenítendő neve látható a felhaszálók, a csoportok és a közösségek számára, amelyekkel interakcióba lép. Dokumentum + Adományozás Kész Letöltés Letöltés... @@ -372,11 +433,14 @@ Ellenőrizd az internetkapcsolatot, majd próbáld újra. Hiba másolása és kilépés Adatbázishiba + Valami hiba történt. Próbálja meg később újra. Ismeretlen hiba történt. + Sikertelen letöltés Hibák Fájl Fájlok Rendszerbeállítások követése + Örökre Feladó: Teljes képernyő be-/kikapcsolása GIF @@ -386,6 +450,8 @@ Csoport létrehozása Válassz legalább 1 csoporttagot. Csoport törlése + Biztosan törölni szeretné a(z) {group_name} nevű csoportot? + A(z) {group_name} nevű csoportot egy csoport-adminisztrátor törölte. További üzeneteket már nem küldhet. Adj meg egy csoportleírást A csoport képe frissítve lett. Csoport szerkesztése @@ -398,19 +464,28 @@ Nem sikerült meghívni {name}-t és {count} másik személyt a {group_name} csoportba Nem sikerült meghívni {name}-t és {other_name}-t a {group_name} csoportba Nem sikerült meghívni {name}-t a {group_name} csoportba + A meghívó nem lett elküldve + {name} meghívta Önt, hogy csatlakozzon újra a(z) {group_name} nevű csoporthoz, ahol Ön adminisztrátor. + Ön meghívást kapott, hogy csatlakozzon a(z) {group_name} nevű csoporthoz, ahol Ön adminisztrátor. + + Meghívó küldése + Meghívók küldése + Meghívás elküldve + A meghívó állapota ismeretlen A meghívás sikeres volt. A felhasználóknak a legújabb verzióval kell rendelkezniük, hogy meghívásokat kaphassanak Te meghívást kaptál a csoportba. Te és {count} másik személy meghívást kaptatok a csoportba. Te és {other_name} meghívást kaptatok a csoportba. + Önt meghívták a csoportba. A csevegési előzmények meg lettek osztva. Kilépés a csoportból Biztos, hogy ki akarsz lépni a(z) {group_name} csoportból? Biztos, hogy ki akarsz lépni a {group_name} csoportból?\n\nEz az összes tag eltávolításával és a csoport teljes tartalmának törlésével jár. Nem sikerült kilépni a {group_name} csoportból - {name} kilépett a csoportból. + {name} elhagyta a csoportot. {name} és {count} másik személy kiléptek a csoportból. - {name} és {other_name} kiléptek a csoportból. + {name} és {other_name} kilépett a csoportból. {name} meghívást kapott a csoportba. {name} meg lett hívva a csoportba. A beszélgetési előzményeket megosztottuk. {name} és {count} másik személy meg lettek hívva a csoportba. A beszélgetési előzményeket megosztottuk. @@ -418,7 +493,8 @@ {name} és {count} másik személy meghívást kaptak a csoportba. {name} és {other_name} meghívást kaptak a csoportba. Te és {count} másik személy meg lettetek hívva a csoportba. A beszélgetési előzményeket megosztottuk. - Te kiléptél a csoportból. + Önt és {other_name}-t meghívták a csoportba. A csevegési előzmények meg lettek osztva. + Ön elhagyta a csoportot. Csoporttagok Nincsenek más tagok ebben a csoportban. Csoport neve @@ -427,10 +503,14 @@ Adj meg egy rövidebb csoportnevet. A csoport neve mostantól {group_name}. Csoport neve frissítve lett. + A csoport neve minden csoporttag számára látható. Nincsenek üzenetek a {group_name} csoportban. Küldj egy üzenetet a beszélgetés megkezdéséhez! + A csoportot több mint 30 napja nem frissítették. Előfordulhat, hogy problémák merülnek fel az üzenetek küldésével vagy a csoportinformációk megtekintésével kapcsolatban. Te vagy az egyetlen adminisztrátor a {group_name} csoportban.\n\nA csoporttagok és beállítások nem változtathatók adminisztrátor nélkül. + Eltávolításra vár Te adminisztrátorrá lettél előléptetve. Te és {count} másik személy adminisztrátorrá lettetek előléptetve. + Ön és {other_name} elő lett léptetve adminisztrátorrá. El szeretnéd távolítani {name}-t a {group_name} csoportból? El szeretnéd távolítani {name}-t és {count} másik személyt a {group_name} csoportból? El szeretnéd távolítani {name}-t és {other_name}-t a {group_name} csoportból? @@ -443,9 +523,10 @@ Felhasználók eltávolítása {name} el lett távolítva a csoportból. - {name} és {count} mások el lettek távolítva a csoportból. - {name} és {other_name} el lettek távolítva a csoportból. + {name} és {count} másik személy el lett távolítva a csoportból. + {name} és {other_name} el lett távolítva a csoportból. El lettél távolítva {group_name} csoportból. + Ön el lett távolítva a csoportból. Te és {count} másik személy el lettetek távolítva a csoportból. Te és {other_name} el lettetek távolítva a csoportból. Csoportprofilkép beállítása @@ -463,22 +544,37 @@ Örülnénk a visszajelzésednek Elrejtés A rendszer menüsor láthatóságának be-/kikapcsolása + Biztosan el akarja rejteni a Jegyzet magamnak jegyzetet a beszélgetési listából? Többi elrejtése Kép + képek Inkognitó billentyűzet Inkognitó mód igénylése, ha elérhető. A használt billentyűzettől függően előfordulhat, hogy a billentyűzet figyelmen kívül hagyja ezt a kérést. Üzenet adatai Érvénytelen parancsikon + + Sikertelen meghívás + Sikertelen meghívások + + + A meghívót nem lehetett elküldeni. Szeretné újra megpróbálni? + A meghívókat nem lehetett elküldeni. Szeretné újra megpróbálni? + Csatlakozás Később További információ Kilépés Kilépés... + Ez a csoport most csak olvasható. A csevegés folytatásához hozza létre újra ezt a csoportot. + Ez a csoport most csak olvasható. Kérje meg a csoport adminisztrátorát, hogy hozza létre újra ezt a csoportot a csevegés folytatásához. + A csoportok fejlesztése megtörtént! A nagyobb megbízhatóság érdekében hozza létre újra ezt a csoportot. Ez a csoport csak-olvashatóvá válik ekkor: {date}. + A csoportok fejlesztése megtörtént! Kérje meg a csoport adminisztrátorát, hogy hozza létre újra ezt a csoportot a nagyobb megbízhatóság érdekében. Ez a csoport csak-olvashatóvá válik ekkor: {date}. + A csevegési előzmények nem kerülnek át az új csoportba. Továbbra is megtekintheti az előzményeket a régi csoportban. {name} csatlakozott a csoporthoz. {name} és {count} másik személy csatlakozott a csoporthoz. Te és {count} másik személy csatlakoztatok a csoporthoz. Te és {other_name} csatlakoztatok a csoporthoz. - {name} és {other_name} csatlakoztak a csoporthoz. + {name} és {other_name} csatlakozott a csoporthoz. Te csatlakoztál a csoporthoz. Linkelőnézetek Hivatkozás előnézet készítése támogatott URL-ekhez. @@ -502,6 +598,7 @@ Zár státusza Koppintson a feloldáshoz {app_name} feloldva + Tagok kezelése Maximum Média @@ -525,6 +622,7 @@ Csak új üzenetek megosztása Meghívás Üzenet + Tudjon meg többet Ez az üzenet üres. Üzenet kézbesítése sikertelen Üzenethossz-korlát elérve @@ -540,8 +638,12 @@ Új beszélgetés indítása az ismerősöd Felhasználó ID-jának megadásával. Új beszélgetés indítása úgy, hogy beírja ismerősöd Felhasználó ID-ját, ONS-t vagy beolvassa a QR kódját. - Új üzeneted érkezett. - %1$d új üzeneted érkezett. + Új üzenete érkezett + %1$d új üzenete érkezett. + + + Egy új üzenet érkezett a(z) %1$s nevű csoportban. + %1$d új üzenet érkezett a(z) %2$s nevű csoportban. Válasz erre Addig nem küldhet mellékleteket, amíg az üzenetkérelmét el nem fogadják @@ -556,10 +658,11 @@ Biztos, hogy törölni szeretnéd az összes üzenetkérelmet és csoportmeghívót? Közösségi üzenetkérelmek A közösségi beszélgetésekből származó üzenetek engedélyezése. + Biztosan törölni szeretné ezt az üzenetkérést és a hozzá tartozó kapcsolatot? Biztos, hogy törölni szeretnéd ezt az üzenetkérelmet? Új üzeneted érkezett Nincsenek függőben lévő üzenetkérelmek - {name} kikapcsolta a Közösségi üzenetkérelmek funkciót, így nem küldhetsz neki üzenetet. + {name} kikapcsolta az üzenetkéréseket a közösségi beszélgetéseknél, ezért nem küldhet neki üzenetet. Üzenet kiválasztása {author}: {message_snippet} Küldés sikertelen @@ -573,9 +676,19 @@ {author}: {emoji} Hangüzenet Üzenetek Minimalizálás + + Az üzenetek karakterkorlátja %1$s karakter. %2$d maradt + Az üzenetek karakterkorlátja %1$s karakter. %2$d maradt + + Üzenet hossza + Az üzenet karakterszáma túllépte a megadott. Rövidítse le az üzenetet {limit} karakterekre vagy kevesebbre. + Az üzenet túl hosszú + Rövidítse le az üzenetét {limit} karakterekre vagy kevesebbre. + Az üzenet túl hosszú Tovább Válasszon becenevet {name} számára. Ez fog megjelenni az egyéni és csoportos beszélgetésekben. Add meg a becenevet + Adjon meg egy rövidebb becenevet Becenév eltávolítása Becenév beállítása Nem @@ -594,6 +707,7 @@ Se név, se üzenet Gyors mód A Google értesítési szerverein keresztül megbízhatóan és azonnal értesítést kapsz az új üzenetekről. + A Huawei értesítési kiszolgálóinak segítségével megbízhatóan és azonnal értesítést fog kapni az új üzenetekről. Az Apple értesítési szerverein keresztül megbízhatóan és azonnal értesítést kapsz az új üzenetekről. Rendszerbeállítások megnyitása Értesítések - Összes @@ -609,6 +723,8 @@ Némítás: {time_large} Némítás feloldása Némítva + Elnémítva: {time_large} + Némítva eddig: {date_time} Lassított mód {app_name} időnként a háttérben ellenőrizni fogja, hogy vannak-e új üzenetek. Hang @@ -667,20 +783,29 @@ Jelszó beállítása A jelszó be lett állítva. Tartsd biztonságos helyen! Beillesztés + Engedélyváltozás {app_name} alkalmazásnak zene és hang-hozzáférésre van szüksége a fájlok és zenék küldéséhez, de ez nem lett megadva. Kérlek, lépj tovább az alkalmazás beállításokhoz, válaszd az \"Engedélyek\" lehetőséget, majd engedélyezd a \"Zene és hang\" hozzáférést. {app_name}-nak szüksége van az Apple Music használatára a média mellékletek lejátszásához. Automatikus frissítés Indításkor automatikusan ellenőrzi a frissítéseket. + A videohívások indításához kamerához való hozzáférés szükséges. Kapcsolja be a „Kamera” engedélyt a Beállításokban a folytatáshoz. {app_name} alkalmazásnak kamera-hozzáférésre van szüksége képek és videók felvételéhez, de ez nem lett megadva. Kérlek, lépj tovább az alkalmazás beállításokhoz, válaszd az \"Engedélyek\" lehetőséget, majd engedélyezd a \"Kamera\" hozzáférést. + Engedélyezze a kamerához való hozzáférést videohívásokhoz. A {app_name} képernyőzár funkciója Face ID-t használ. Rendszertálcán tartás - {app_name} továbbra is fut a háttérben, amikor bezárja az ablakot + A(z) {app_name} az ablak bezárása után is tovább fut a háttérben {app_name} alkalmazásnak fotótár-hozzáférésre van szüksége a folytatáshoz. A hozzáférést az iOS beállításokban engedélyezheted. + A hívások lehetővé tételéhez szükséges a helyi hálózathoz való hozzáférés. Kapcsolja be a „Helyi hálózat” engedélyt a Beállításokban a folytatáshoz. + A(z) {app_name} alkalmazásnak hozzáférésre van szüksége a helyi hálózathoz a hang- és videohívások indításához. + Engedélyezze a helyi hálózathoz való hozzáférést a hang- és videohívások lehetővé tételéhez. + Helyi hálózat Mikrofon {app_name} alkalmazásnak mikrofon-hozzáférésre van szüksége hívások bonyolítására és hangüzeneteket rögzítésére, de ez nem lett megadva. Koppints a Beállítások → Engedélyek lehetőségre, és kapcsold be a \"Mikrofon\" lehetőséget. + A hívások indításához és hangüzenetek rögzítéséhez mikrofonhoz való hozzáférés szükséges. Kapcsolja be a „Mikrofon” engedélyt a Beállításokban a folytatáshoz. A mikrofon engedélyeinek beállításait a {app_name} adatvédelmi beállításaiban engedélyezheted {app_name} alkalmazásnak mikrofon-hozzáférésre van szüksége hívások bonyolítására és hangüzeneteket rögzítésére. Mikrofon hozzáférés engedélyezése. + Engedélyezze a mikrofonhoz való hozzáférést hanghívások és hangüzenetek küldéséhez. {app_name} alkalmazásnak zene és hang-hozzáférésre van szüksége a fájlok és zenék küldéséhez. Engedély szükséges {app_name} alkalmazásnak fotótár-hozzáférésre van szüksége a képek és videók küldéséhez, de ez nem lett megadva. Kérlek, lépj tovább az alkalmazás beállításokhoz, válaszd az \"Engedélyek\" lehetőséget, majd engedélyezd a \"Fotók és videók\" hozzáférést. @@ -688,11 +813,17 @@ {app_name} alkalmazásnak tárhely-hozzáférésre van szüksége a mellékletek és médiák mentéséhez. {app_name} alkalmazásnak tárhely-hozzáférésre van szüksége a fotók és videók mentéséhez, de ez nem lett megadva. Kérlek, lépj tovább az alkalmazás beállításokhoz, válaszd az \"Engedélyek\" lehetőséget, majd engedélyezd a \"Tárhely\" hozzáférést. {app_name} alkalmazásnak tárhely-hozzáférésre van szüksége a fotók és videók elküldéséhez. + Nincs írási jogosultsága ebben a közösségben Kitűzés Beszélgetés kitűzése Kitűzés eltávolítása Beszélgetés kitűzésének eltávolítása Előnézet + Szeretne hosszabb üzeneteket küldeni? Küldjön több szöveget és oldja fel a prémium funkciókat a Session Pro szolgáltatással + Nagyobb csoportos beszélgetések akár 300 taggal + Plusz még több exkluzív funkció + Legfeljebb 10 000 karakteres üzenetek + Küldjön többet ezzel: Profil Profilkép Nem sikerült törölni a profilképet. @@ -700,6 +831,14 @@ Válassz egy kisebb fájlt. Nem sikerült frissíteni a profilt. Előléptetés + + Sikertelen előléptetés + Sikertelen előléptetések + + + Az előléptetést nem lehetett alkalmazni. Szeretné újra megpróbálni? + Az előléptetést nem lehetett alkalmazni. Szeretné újra megpróbálni? + QR-kód Ez a QR-kód nem tartalmaz Felhasználó ID-t Ez a QR-kód nem tartalmaz visszaállítási jelszót @@ -717,6 +856,7 @@ Visszaállítási jelszó elmentése A visszaállítási jelszavaddal új eszközökön is betöltheted a felhasználódat.\n\nA felhasználód nem állítható vissza a visszaálltási jelszó nélkül. Gondoskodj róla, hogy biztonságos helyen tárolod — és ne oszd meg senkivel. Add meg a visszaálltási jelszavad + Hiba történt a helyreállítási jelszó betöltése közben.\n\nExportálja a naplófájlokat, majd töltse fel azokat a(z) {app_name} segítségével az ügyfélszolgálatnak a probléma megoldása érdekében. Ellenőrizd a visszaállítási jelszavad és próbáld újra. A visszaállítási jelszó néhány szava helytelenül lett megadva. Ellenőrizd őket és próbáld újra. A megadott visszaállítási jelszó nem elég hosszú. Ellenőrizd és próbáld újra. @@ -730,7 +870,13 @@ Írd be a visszaállítási jelszavad a fiókod betöltéséhez. Ha nem mentetted el, az alkalmazás beállításai között találhatod meg. Jelszó megtekintése Ez a visszaállítási jelszavad. Ha elküldöd valakinek, teljes hozzáférést kap a fiókodhoz. + Csoport újra-létrehozása Újra + Az üzenet hosszának csökkentése ennyivel: {count} + + %1$d karakter maradt + %1$d karakter maradt + Eltávolítás Jelszó eltávolítása sikertelen Válasz @@ -762,6 +908,7 @@ Keresés... Kiválasztás Összes kiválasztása + Alkalmazásikon kiválasztása Küldés Küldés Elküldve: @@ -771,30 +918,46 @@ Segítség Ismerős meghívása Üzenetkérelmek + Jelenlegi {token_name_short} ára + Az üzenetek küldése a(z) {network_name} hálózaton keresztül történik. A hálózat a(z) {token_name_long} tokennel ösztönzött csomópontokat tartalmaz, amelyek decentralizálttá és biztonságossá teszik a(z) {app_name} alkalmazást.Tudjon meg többet {icon} + Ismerje meg a lekötést + Piaci sapka + {app_name} csomópontok védik az Ön üzeneteit + {app_name} csomópontok vannak a saját bolyban + {token_name_long} aktív! Fedezze fel az új {network_name} menüpontot a beállításokban, hogy megtudja, hogyan működteti a(z) {token_name_long} a Sessiont. + A hálózat a következővel van biztosítva: + Amikor leköti a(z) {token_name_long} tokent, hogy biztosítsa a hálózatot, akkor {token_name_short} tokenben fog jutalmat kapni a(z) {staking_reward_pool} gyűjtőből. + Új Értesítések Engedélyek Adatvédelem Visszaállítási Jelszó Beállítások Beállít + Közösség megjelenítendő képének beállítása Újra kell indítanod a {app_name}-t a beállítások érvényesítéséhez. Megosztás Oszd meg a Felhasználó ID-dat ismerőseiddel, hogy a {app_name} alkalmazáson csevegjetek. Ossza meg barátaival ott, ahol általában beszélgetni szokott velük - majd helyezze át a beszélgetést ide. Hiba történt az adatbázis megnyitásakor. Indítsd újra az alkalmazást, majd próbáld újra. + Úgy tűnik, még nincs {app_name} fiókja.\n\nA megosztás előtt létre kell hoznia egyet a(z) {app_name} alkalmazásban. Megosztás {app_name}-en Megjelenítés Összes megjelenítése Kevesebb mutatása + „Jegyzet magamnak” megjelenítése + Biztosan meg akarja jeleníteni a Jegyzet magamnak jegyzetet a beszélgetési listában? Matricák Támogatási oldal megnyitása Rendszerinformáció: {information} + Érintse meg az újrapróbálkozáshoz Folytatás Alapértelmezett Hiba Próbáld újra Gépelés-indikátorok Gépelés-indikátorok küldése és fogadása. + Nem elérhető Visszavonás Ismeretlen App frissítések @@ -802,10 +965,15 @@ Frissítés letöltése: {percent_loader}% A frissítés sikertelen {app_name} frissítése sikertelen. Kérlek, látogass el a {session_download_url} oldalra és telepítsd manuálisan az új verziót, majd vedd fel a kapcsolatot az ügyfélszolgálatunkkal, hogy többet tudhassunk a problémáról. + Csoportinformációk frissítése + A csoport neve és leírása minden csoporttag számára látható. + Adjon meg egy rövidebb csoportleírást A(z) {app_name} új verziója elérhető, koppints a frissítéshez Verzióinformáció megnyitása {app_name} Frissítés Verzió {version} + Utoljára frissítve {relative_time} + Frissítés erre: Feltöltés URL másolása URL megnyitása diff --git a/app/src/main/res/values-b+id+ID/strings.xml b/app/src/main/res/values-b+id+ID/strings.xml index 315de5e346..4202b5e663 100644 --- a/app/src/main/res/values-b+id+ID/strings.xml +++ b/app/src/main/res/values-b+id+ID/strings.xml @@ -3,6 +3,7 @@ Tentang Terima Salin ID Akun + ID Akun ID Akun Disalin Salin ID Akun Anda lalu bagikan dengan teman Anda sehingga mereka dapat mengirim Anda pesan. Masukkan Account ID @@ -14,6 +15,7 @@ Ini adalah ID akun anda. Pengguna lain bisa memindainya untuk memulai percakapan dengan anda. Ukuran Sebenarnya Tambahkan + Tambah Admin Admin tidak dapat dihapus. {name} dan {count} lainnya dipromosikan menjadi Admin. Promosikan Admin @@ -45,7 +47,17 @@ {name} dan {other_name} dipromosikan menjadi Admin. +{count} Anonim - Selamat malam + Ikon Aplikasi + Ikon dan nama + Gunakan ikon aplikasi alternatif + Gunakan ikon aplikasi alternatif dan nama + Pilih ikon aplikasi alternatif + Ikon + Kalkulator + Berita + Catatan + Bursa Saham + Cuaca Mode gelap otomatis Sembunyikan Meny Bar Bahasa @@ -63,6 +75,7 @@ Memperbesar Memperkecil Lampiran + Lampiran Tambah lampiran Album tanpa nama Unduh Lampiran Otomatis @@ -186,6 +199,8 @@ Apakah Anda yakin ingin menghapus data Anda dari jaringan? Jika Anda melanjutkan, Anda tidak akan dapat memulihkan pesan atau kontak Anda. Apakah Anda yakin ingin menghapus perangkat Anda? Hapus di Perangkat Saja + Hapus Perangkat dan Hidupkan Ulang + Hapus Perangkat dan Pulihkan Hapus Semua Pesan Anda yakin ingin menghapus semua pesan dari percakapan Anda dengan {name} dari perangkat Anda? Anda yakin ingin menghapus semua pesan {community_name} dari perangkat Anda? @@ -194,7 +209,9 @@ Anda yakin ingin menghapus semua pesan {group_name}? Anda yakin ingin menghapus semua pesan {group_name} dari perangkat Anda? Anda yakin ingin menghapus semua pesan Catatan Pribadi dari perangkat Anda? + Hapus dalam perangkat ini Tutup + Tutup Aplikasi Tutup Jendela Hash Commit: {hash} Tindakan ini akan memblokir pengguna yang dipilih dari Komunitas ini dan menghapus semua pesan mereka. Apakah Anda yakin ingin melanjutkan? @@ -389,10 +406,12 @@ Salin Galat dan Berhenti Kesalahan Database Terjadi kegagalan yang tidak diketahui. + Gagal mengunduh Gagal Berkas Berkas Sesuaikan dengan pengaturan sistem + Selamanya Dari: Toggle layar penuh GIF @@ -402,6 +421,7 @@ Buat Grup Pilih setidaknya satu anggota grup lainnya. Hapus Grup + Apakah Anda yakin ingin menghapus {group_name}?\n\nIni akan mengeluarkan semua anggota dan menghapus semua konten grup. Apakah Anda yakin ingin menghapus {group_name}? {group_name} telah dihapus oleh admin grup. Anda tidak akan dapat mengirim pesan lagi. Masukkan deskripsi grup @@ -495,6 +515,7 @@ Alihkan visibilitas bilah menu sistem Sembunyikan lainnya Gambar + gambar Papan Ketik Penyamaran Minta mode incognito jika tersedia. Tergantung pada keyboard yang Anda gunakan, keyboard Anda mungkin mengabaikan permintaan ini. Info @@ -543,6 +564,7 @@ Status kunci Ketuk untuk Membuka Kunci {app_name} tidak terkunci + Atur Anggota Maks Media @@ -647,6 +669,8 @@ Senyapkan selama {time_large} Batalkan senyap Disenyapkan + Senyapkan selama {time_large} + Senyapkan sampai {date_time} Slow Mode {app_name} sesekali akan memeriksa pesan baru di latar belakang. Suara @@ -705,19 +729,24 @@ Atur Kata Sandi Kata sandi anda telah disetel. Harap untuk menjaganya. Tempel + Persetujuan Diubah {app_name} memerlukan akses musik dan audio untuk mengirim file, musik, dan audio, tapi sudah ditolak secara permanen. Ketuk Setelan → Perizinan, dan aktifkan \"Musik dan audio\". {app_name} membutuhkan Apple Music untuk memutar lampiran media. Pembaruan Otomatis Secara otomatis cek pembaruan saat startup + Akses kamera saat ini diaktifkan. Untuk mematikan, pilih izin \"Kamera\" dalam Pengaturan. {app_name} memerlukan akses kamera untuk mengambil foto dan video, tapi telah ditolak secara permanen. Ketuk Setelan → Perizinan, dan aktifkan \"Kamera\". + Izinkan akses kamera untuk video call. Fitur kunci layar pada {app_name} menggunakan Face ID. Simpan di System Tray {app_name} bekerja di background ketika anda menutup jendela {app_name} membutuhkan akses perpustakaan foto untuk melanjutkan. Anda dapat mengaktifkan akses di pengaturan iOS. + Jaringan Lokal Mikrofon {app_name} memerlukan akses mikrofon untuk melakukan panggilan dan mengirim pesan audio, tetapi izin ini telah ditolak secara permanen. Ketuk setelan → Perizinan, dan aktifkan \"Mikrofon\". Anda dapat mengaktifkan akses mikrofon di pengaturan privasi {app_name} {app_name} membutuhkan akses mikrofon untuk melakukan panggilan dan merekam pesan audio. + Akses mikrofon saat ini diaktifkan. Untuk mematikan, pilih izin \"Mikrofon\" dalam Pengaturan. Izinkan akses ke mikrofon. {app_name} memerlukan akses musik dan audio untuk mengirim file, musik, dan audio. Izin dibutuhkan @@ -809,6 +838,7 @@ Mencari... Pilih Pilih Semua + Pilih ikon aplikasi Kirim Mengirim Mengirim Penawaran Panggilan @@ -820,12 +850,15 @@ Bantuan Undang Teman Permintaan Pesan + Harga {token_name_short} saat ini + Jaringan aman oleh Notifikasi Izin Privasi Kata Sandi Pemulihan Pengaturan Atur + Atur Tampilan Gambar Komunitas Anda harus memulai ulang {app_name} untuk menerapkan setelan baru. Bagikan Undang teman Anda untuk mengobrol dengan Anda di {app_name} dengan membagikan ID Akun Anda kepada mereka. @@ -836,15 +869,18 @@ Tampilkan Tampilkan Semua Tampilkan Sedikit + Lihat Catatan Pribadi Stiker Cek halaman bantuan Informasi Sistem: {information} + Ketuk untuk mencoba lagi Lanjutkan Bawaan Kesalahan Coba Lagi Indikator penulisan Lihat dan bagikan indikator mengetik. + Tidak tersedia Ulang Tidak dikenal Pembaruan aplikasi @@ -852,7 +888,11 @@ Mengunduh pembaruan: {percent_loader}% Pembeharuan Gagal {app_name} gagal diperbarui. Silahkan pergi ke {session_download_url} dan instal versi terbaru secara manual, kemudian hubungi Pusat Bantuan kami untuk memberitahu terkait masalah ini. + Perbaharui Informasi Grup + Nama grup dan deskripsi dapat dilihat oleh semua anggota grup. + Masukkan nama grup yang lebih pendek Versi terbaru dari {app_name} telah tersedia, ketuk untuk memperbarui + Versi terbaru ({version}) dari {app_name} telah tersedia. Lihat Catatan Rilis Pembaruan {app_name} Versi {version} @@ -865,6 +905,8 @@ Video Tidak dapat memutar video. Lihat + Tampilkan Ringkas + Lihat Selengkapnya Ini akan memerlukan beberapa menit. Silakan menunggu... Peringatan diff --git a/app/src/main/res/values-b+ja+JP/strings.xml b/app/src/main/res/values-b+ja+JP/strings.xml index 10476aa420..e2666d9830 100644 --- a/app/src/main/res/values-b+ja+JP/strings.xml +++ b/app/src/main/res/values-b+ja+JP/strings.xml @@ -40,6 +40,7 @@ {name}{other_name} がアドミンに昇格しました +{count} 匿名 + アプリアイコン オートダークモード メニューバーを隠す 言語 @@ -147,6 +148,7 @@ 音声とビデオ通話を使用するには、デバイスのシステム設定で通知を有効にする必要があります。 通話の権限が必要です プライバシー設定で「音声とビデオ通話」の許可を有効にできます。 + 「音声とビデオ通話」はプライバシー設定で有効にできます。 再接続しています... 呼び出し中... {app_name} 電話 @@ -253,6 +255,8 @@ コピーする 作成する 切り取り + データベースエラーが発生しました。\n\n +トラブルシューティングのために、アプリのログをエクスポートして共有してください。この操作が失敗した場合は、{app_name} を再インストールし、アカウントを復元してください。 {app_name}が起動するのに時間がかかっていることを確認しました。\n\n引き続きお待ちいただくか、トラブルシューティングのためにデバイスログをエクスポートして共有するか、{app_name}を再起動してみてください。 お使いのアプリデータベースはこのバージョンの {app_name} と互換性がありません。アプリを再インストールしてアカウントを復元し、新しいデータベースを生成して {app_name} を使用し続けてください。\n\n警告: これにより、2週間以上前のすべてのメッセージと添付ファイルが失われます。 データベースを更新中 @@ -277,11 +281,17 @@ メッセージを削除 + + メッセージを削除します。本当によろしいいですか? + メッセージが削除されました このメッセージは削除されました このメッセージはこのデバイス上で削除されました + + このデバイスでのみ、メッセージを削除します。本当によろしいですか? + 本当にこのメッセージを全員に対して削除しますか? このデバイスのみを削除 すべてのデバイスから削除 @@ -388,6 +398,9 @@ グループを作成 他のグループメンバーも選択してください グループを削除 + 本当に{group_name}を削除してもよろしいですか?\n\nこれにより、すべてのメンバーが削除され、グループ内にある全コンテンツも削除されます。 + 本当に {group_name} を退出しますか? + {group_name} はグループの管理者によって削除されました。これ以上メッセージを送信することはできません。 グループ説明を入力してください グループ表示画像を更新しました グループを編集 @@ -423,6 +436,7 @@ {name}{count}名 がグループに招待されました。 {name}{other_name} がグループに招待されました。 あなた{count}名 がグループに招待されました。チャット履歴が共有されました。 + あなた{other_name} はグループに招待されました。チャット履歴が共有されました。 Youがグループを退会しました グループメンバー このグループには他のメンバーがいません。 @@ -437,6 +451,7 @@ あなたは{group_name}で唯一の管理者です。\n\n管理者がいないと、グループメンバーと設定は変更できません。 You はアドミンに昇格しました あなた{count}名 はAdminに昇格しました。 + あなた{other_name} は管理者になりました。 {group_name}から{name}を削除しますか? {group_name}から{name}{count}人を削除しますか? {group_name}から{name}{other_name}を削除しますか? @@ -479,6 +494,11 @@ 詳細を知る 抜ける 終了しています... + このグループは読み取り専用になりました。チャットを続けるには、このグループを再作成してください。 + このグループは読み取り専用になりました。チャットを続けるために、このグループを再作成するようグループ管理者に依頼してください。 + グループ機能はアップグレードされました!信頼性を向上させるために、このグループを再作成してください。このグループは {date} に読み取り専用となります。 + グループ機能はアップグレードされました!信頼性を向上させるために、グループ管理者にこのグループを再作成するよう依頼してください。グループは {date} に読み取り専用になります。 + 新しくグループにはチャットの履歴は転送されません。古いグループでチャット履歴を確認できます。 {name}がグループに加わりました {name}{count}人 がグループに加わりました あなた{count}名 がグループに加わりました。 @@ -543,6 +563,9 @@ %1$d 件の新規メッセージがあります + + %1$s から新しいメッセージが %2$d あります。 + 返信 {name} があなたを {group_name} に招待しました このグループにメッセージを送ると、グループ招待が自動的に受け入れられます。 @@ -571,6 +594,9 @@ {author}: {emoji} 音声メッセージ メッセージ 最小化する + + メッセージに入力できる最大文字数は %1$s 字までです。残り %2$d 字です + {name}のニックネームを選んでください。これが1対1およびグループ会話で表示されます。 ニックネームを入力してください @@ -716,6 +742,7 @@ リカバリーパスワードを保存してください リカバリパスワードを使用して、新しいデバイスでアカウントを読み込みます。\n\n リカバリパスワードがないとアカウントを復元できません。 安全な場所に保管し、他の人と共有しないでください。 リカバリーフレーズを入力してください + リカバリパスワードの読み込み中にエラーが発生しました。\n\nログをエクスポートし、 {app_name} のヘルプデスクにファイルをアップロードしてこの問題の解決に役立ててください。 リカバリーパスワードを確認してもう一度やり直してください いくつかのリカバリパスワードの単語が間違っています。確認して再試行してください。 入力したリカバリーパスワードが十分な長さではありません。確認して再試行してください。 @@ -730,6 +757,9 @@ パスワードを見る これはあなたのリカバリーパスワードです。誰かに送信すると、その人はあなたのアカウントにフルアクセスできます。 やり直す + + 残り %1$d 字 + 削除 パスワードを削除できませんでした 返信 @@ -780,6 +810,7 @@ 友達を{app_name} に招待して、チャットを始めましょう。アカウントIDを共有して招待できます。 いつでもどこでも友達と共有 — ここで会話を始めましょう データベースを開く際に問題が発生しました。アプリを再起動して再度お試しください。 + {app_name} のアカウントをまだお持ちでないようです。\n\n共有するには、 {app_name} アプリで作成する必要があります。 {app_name}に共有 表示 すべて表示 @@ -801,6 +832,7 @@ 更新できませんでした {app_name}の更新に失敗しました。{session_download_url}にアクセスして新しいバージョンを手動でインストールしてください。その後、この問題についてヘルプセンターにご連絡ください。 {app_name} のバージョンアップが利用可能です。タップすると更新します。 + {app_name}の新しいバージョン({version})が利用可能です。 リリースノートを閲覧 {app_name}アップデート バージョン {version} diff --git a/app/src/main/res/values-b+ka+GE/strings.xml b/app/src/main/res/values-b+ka+GE/strings.xml index 497fb5c2ab..aefba3ff98 100644 --- a/app/src/main/res/values-b+ka+GE/strings.xml +++ b/app/src/main/res/values-b+ka+GE/strings.xml @@ -5,9 +5,9 @@ აკაუნტის ID-ის დაკოპირება ანგარიშის ID დაკოპირდა დააკოპირეთ თქვენი Account ID და გაუზიარეთ თქვენს მეგობრებს, რომ მათ შეძლონ გაცნობოთ. - შეიყვანეთ ანგარიში ID + შეიყვანეთ ანგარიშის ID ეს ანგარიშის ID არასწორია. გთხოვთ გადაამოწმეთ და სცადეთ თავიდან. - შეიყვანეთ ანგარიში ID ან ONS + შეიყვანეთ ანგარიშის ID ან ONS მოწვიე Account ID ან ONS Hey, I\'ve been using {app_name} to chat with complete privacy and security. Come join me! My Account ID is\n\n{account_id}\n\nDownload it at {session_download_url} თქვენი Account ID @@ -40,6 +40,7 @@ {name}ს და {other_name}ს მიენიჭათ ადმინისტრატორის როლი. +{count} ანონიმური + აპის ხატულა ავტომატიკური დაბნელების რეჟიმი მენიუს ზოლში დამალვა ენა @@ -68,7 +69,7 @@ დააწკაპუნეთ რომ გადმოწეროთ {file_type} შესავსობი ოფციების დამალვა შესავსობების შეგროვება... - მიმაგრებული ფაილის გადმოტვირთვა + მიმაგრებული ფაილის ჩამოტვირთვა ხანგრძლივობა: შეცდომა ფაილის მიმაგრებისას ვერ ავირჩიე მიმაგრებული ფაილი @@ -252,6 +253,7 @@ დაკოპირებულია დაკოპირება შექმნა + ირეკება ამოჭრა გავიგეთ {app_name}-ის გაშვება დიდ დროს იკავებს.\n\nთქვენ შეგიძლიათ დაელოდოთ, ექსპორტირდეთ თქვენი მოწყობილობის ჟურნალები რათა გაუზიაროთ პრობლემების დიაგნოსტირებისთვის, ან სცადოთ {app_name}-ის გადატვირთვა. თქვენი აპლიკაციის მონაცემთა ბაზა არ შეესაბამება {app_name}-ის ამ ვერსიას. ხელახლა დააინსტალირეთ აპლიკაცია და აღადგინეთ თქვენი ანგარიში ახალი მონაცემთა ბაზის შესაქმნელად და {app_name} გამოყენების გაგრძელებისთვის.\n\nგაფრთხილება: ეს გამოიწვევს ყველა მესიჯის და ფაილის დაკარგვას, რომლებიც ორ კვირაზე მეტი ხნისაა. @@ -278,12 +280,20 @@ შეტყობინების წაშლა შეტყობინებების წაშლა + + დარწმუნებული ხართ რომ ამ შეტყობინების წაშლა გსურთ? + დარწმუნებული ხართ რომ ამ შეწყობინებების წაშლა გსურთ? + შეტყობინება წაშლილია შეტყობინებები წაშლილია ეს შეტყობინება წაშლილია ეს შეტყობინება წაშლილია ამ მოწყობილობაზე + + დარწმუნებული ხართ რომ ამ შეწყობინების მხოლოდ ამ მოწყობილობიდან წაშლა გსურთ? + დარწმუნებული ხართ რომ ამ შეტყობინებების მხოლოდ ამ მოწყობილობიდან წაშლა გსურთ? + დარწმუნებული ხართ, რომ გსურთ ამ შეტყობინების წაშლა ყველასთვის? მხოლოდ ამ მოწყობილობაზე წაშლა წაშლა ყველა ჩემს მოწყობილობაზე @@ -292,8 +302,16 @@ შეტყობინების წაშლა ვერ მოხერხდა შეტყობინებების წაშლა ვერ მოხერხდა + + ეს შეტყობინება ვერ წაიშლება თქვენი ყველა მოწყობილობიდან + თქვენს მიერ არჩეული ზოგიერთი შეტყობინება ვერ წაიშლება თქვენი ყველა მოწყობილობიდან + + + ეს შეტყობინება ყველასთვის ვერ წაიშლება + თქვენს მიერ არჩეული ზოგიერთი შეტყობინება ყველასთვის ვერ წაიშლება + დარწმუნებული ხართ, რომ გსურთ ამ შეტყობინებების წაშლა ყველასთვის? - წაშლა მიმდინარეობს + იშლება დეველოპერის ხელსაწყოების გადართვა დიქტაციის დაწყება... გაუჩინარებადი შეტყობინებები @@ -340,7 +358,7 @@ ხელის შეწყვილება დოკუმენტი შესრულებულია - გადმოტვირთვა + ჩამოტვირთვა იტვირთება... წერილობითი ვარიანტი რედაქტირება @@ -385,6 +403,7 @@ ჯგუფის შექმნა გთხოვთ აირჩიოთ მინიმუმ ერთი სხვა ჯგუფის წევრი. ჯგუფის წაშლა + დარწმუნებული ხართ რომ {group_name}-ის წაშლა გსურთ?\n\n ეს ამოშლის ჯგუფის ყველა წევრს და შიგთავსს. შეიყვანეთ ჯგუფის აღწერა ჯგუფის სურათი განახლდა. ჯგუფის რედაქტირება @@ -397,6 +416,10 @@ ვერ შევძელიში {name} და {count} სხვა პირი ჯგუფში {group_name} მიიწვია ვერ შევძელიში {name} და {other_name} ჯგუფში {group_name} მიიწვია ვერ შევძელიში {name} ჯგუფში {group_name} მიიწვია + + იგზავნება მოსაწვევი + იგზავნება მისაწვევები + მოწვევა გაგზავნილია ჯგუფის მოწვევა წარმატებული იყო მომხმარებელმა უნდა ჰქონდეს უახლესი ვერსია მიწვევების მისაღებად @@ -468,6 +491,14 @@ თხოვა ინკოგნიტო რეჟიმი თუ შესაძლებელი. დამოკიდებულია იმ კლავიატურაზე, რომელიც თქვენ იყენებთ, თქვენი კლავიატურა შეიძლება იგნორიროს ეს თხოვნა. ინფორმაცია არასწორი მალსახმობი + + მოწვევა ვერ მოხერხდა + მოწვევები ვერ მოხერხდა + + + მოსაწვევი ვერ გაიგზავნა. გსურთ თავიდან ცდა? + მოსაწვევები ვერ გაიგზავნა. გსურთ თავიდან ცდა? + შეუერთდით მოგვიანებით გაიგე მეტი @@ -511,7 +542,7 @@ %1$d აქტიური წევრი %1$d აქტიური წევრი - ანგარიშის ID ან ONS-ის დამატება + ანგარიშის ID-ის ან ONS-ის დამატება კონტაქტების მოწვევა გამოაგზავნეთ მოწვევა @@ -542,6 +573,10 @@ თქვენ გაქვთ ახალი შეტყობინება. თქვენ გაქვთ %1$d ახალი შეტყობინება. + + თქვენ მიიღეთ ახალი შეტყობინება %1$s-ში. + თქვენ მიიღეთ %1$d ახალი შეტყობინება %2$s-ში. + Პასუხობთ {name}ს მოპატიჟა {group_name}-ში. გაუგზავნეთ ამ ჯგუფს შეტყობინება, რაც ავტომატურად დაადასტურებს ჯგუფის მოწვევას. @@ -709,6 +744,7 @@ წაკითხვის ქვითრები პრევიუების ჩვენება, რომლებიც მიიღებენ და გაგზავნიან ყველა შეტყობინებას. მიღებული: + მიღებულია პასუხი გირჩევთ შეინახე შენი recovery password, რათა დარწმუნდე, რომ არ დაკარგავ ანგარიშზე წვდომას. შეინახე შენი recovery password @@ -796,10 +832,11 @@ უცნობი აპლიკაციის განახლება განახლება დაინსტალირებულია, დაწკაპეთ გადასატვირთად - განახლების გადმოტვირთვა: {percent_loader}% + განახლება იწერება: {percent_loader}% ვერ განახლდა {app_name} ვერ განახლდა. გთხოვთ გადადით {session_download_url} და ხელით დააყენეთ ახალი ვერსია, შემდეგ კი დაგვიკავშირდით დახმარების ცენტრში ამ პრობლემის შესახებ. {app_name}-ის ახალი ვერსია ხელმისაწვდომია, დააჭირეთ განახლებისთვის + ხელმისაწვდომია {app_name}-ის ახალი ვერსია ({version}). გადადით გამოშვების ჩანაწერებზე {app_name} განახლება Ვერსია {version} diff --git a/app/src/main/res/values-b+ko+KR/strings.xml b/app/src/main/res/values-b+ko+KR/strings.xml index 82455d002e..65edbc94bd 100644 --- a/app/src/main/res/values-b+ko+KR/strings.xml +++ b/app/src/main/res/values-b+ko+KR/strings.xml @@ -3,6 +3,7 @@ 정보 수락 계정 ID 복사 + 계정 ID 계정 ID 복사됨 계정 ID를 복사한 후 친구들에게 공유하여 메시지를 받을 수 있도록 하세요. Account ID 입력 @@ -14,7 +15,9 @@ 이는 당신의 계정 ID입니다. 다른 사용자가 이를 스캔하여 당신과 대화를 시작할 수 있습니다. 실제 크기 추가 - Admins cannot be removed. + 관리자 추가 + 관리자로 승격할 유저의 계정 ID를 입력하세요.\n\n한번에 여러 유저를 추가하려면 각 계정 ID를 쉼표로 구분하여 최대 20명까지 추가할 수 있습니다. + 관리자는 추방될 수 없습니다. {name}님{count}명이 관리자(Admin)로 승격되었습니다. 관리자 승격 정말 {name}를 관리자(Admin)로 승격하시겠습니까? 관리자는 제거될 수 없습니다. @@ -22,18 +25,18 @@ 관리자로 승격 정말 {name}{other_name}를 관리자(Admin)로 승격하시겠습니까? 관리자는 제거될 수 없습니다. {name}님이 관리자(Admin)로 승격되었습니다. - Admin promotion failed + 관리자 승격 실패 {name}님을 {group_name}의 관리자로 승격하지 못했습니다. {name}님 및 {count}명의 다른 사람들을 {group_name}의 관리자로 승격하지 못했습니다. {name}님과 {other_name}님을 {group_name}의 관리자로 승격하지 못했습니다. 권한 부여가 되지 않았습니다 - Admin promotion sent + 관리자 승격 전송됨 관리자 권한 부여 상태를 알 수 없음 관리자 제거 관리자에서 제거 이 커뮤니티에 관리자가 없습니다. {name}님을 관리자로 제거하지 못했습니다. - {name} 님과 {count} 명의 사람들의 관리자 직책을 제거하지 못했습니다. + {name} 님과 {count} 명의 사람들의 관리자 직책을 제거하지 못했습니다. {name}님과 {other_name}님의 관리자 직책을 제거하지 못했습니다. {name}님이 관리자에서 제거되었습니다. {name}님과 {count} 명의 사람들의 관리자 직책이 제거되었습니다 @@ -41,10 +44,27 @@ 관리자 권한 부여 중 - Admin Settings + 관리자 설정 {name}님{other_name}님이 관리자(Admin)로 승격되었습니다. +{count} 익명 + 앱 아이콘 + 앱 아이콘 및 이름 변경 + 앱 아이콘 및 이름을 변경하려면 {app_name}의 재시작이 필요합니다. 알림은 {app_name}의 기본 아이콘 및 이름으로 표시됩니다. + 대체 앱 아이콘 및 이름이 홈 화면과 앱 서랍에 표시됩니다. + 선택된 앱 아이콘 및 이름이 홈 화면과 앱 서랍에 표시됩니다. + 아이콘 및 이름 + 대체 앱 아이콘이 홈 화면 및 앱 보관함에 표시됩니다. 앱 이름은 여전히 \'{app_name}\'으로 표시됩니다. + 대체 앱 아이콘 사용 + 대체 앱 아이콘 및 이름 사용 + 대체 앱 아이콘을 선택 + 아이콘 + 계산기 + 미팅 + 뉴스 + 노트 + 주식 + 날씨 자동 다크 모드 메뉴 바 숨기기 언어 @@ -62,6 +82,7 @@ 확대 축소 첨부파일 + 첨부 파일 첨부 이름 없는 앨범 첨부파일 자동 다운로드 @@ -123,13 +144,16 @@ 차단 실패 차단 해제 실패 사용자 금지 해제 + 밴을 해제할 유저의 계정 ID를 입력하세요 사용자 금지 해제됨 사용자 차단 사용자 금지됨 + 밴할 유저의 계정 ID를 입력하세요 차단 + 이 사용자에게 메시지를 보내려면 먼저 차단을 해제하세요 차단된 연락처가 없습니다. {name} 차단됨 - Are you sure you want to block {name}? Blocked users cannot send you message requests, group invites or call you. + 정말로 {name}님을 차단하시겠습니까? 차단된 유저는 당신에게 메시지를 보내거나 그룹 초대, 전화를 할 수 없습니다. 차단 해제 정말 {name}의 차단을 해제할까요? 정말 {name}{count}명의 차단을 해제하시겠습니까? @@ -179,21 +203,30 @@ %1$d Service Node가 데이터를 삭제하지 않았습니다. Service Node ID: %2$s. - An unknown error occurred and your data was not deleted. Do you want to delete your data from just this device instead? + 데이터를 삭제하는 도중 알 수 없는 에러가 발생했습니다. 이 기기에서만 데이터를 삭제하시겠습니까? 디바이스 삭제 디바이스와 네트워크 삭제 정말 네트워크에서 데이터를 삭제하시겠습니까? 계속하면 메시지나 연락처를 복원할 수 없습니다. Are you sure you want to clear your device? 디바이스만 삭제 + 기기 초기화 및 재시작 + 디바이스 삭제 및 복원 모든 메시지 삭제 Are you sure you want to clear all messages from your conversation with {name} from your device? - Are you sure you want to clear all {community_name} messages from your device? + 정말로 이 기기에서 {name}와의 대화 메시지를 모두 삭제하시겠습니까? + 정말 디바이스에 있는 {community_name}의 모든 메시지를 지우시겠습니까? + 정말로 {community_name}의 모든 메시지를 이 기기에서 삭제하시겠습니까? 모두에게서 삭제 내 게서만 삭제 Are you sure you want to clear all {group_name} messages? - Are you sure you want to clear all {group_name} messages from your device? + 정말로 {group_name}의 모든 메시지를 삭제하시겠습니까? + 정말 디바이스에 있는 {group_name} 그룹의 모든 메시지를 지우시겠습니까? + 정말로 이 기기에서 {group_name}의 모든 매세지를 삭제하시겠습니까? Are you sure you want to clear all Note to Self messages from your device? + 정말로 이 기기에서 모든 개인용 메모 매시지를 삭제하시겠습니까? + 이 기기에서 삭제 닫기 + 앱 닫기 닫기 커밋 해시: {hash} 선택한 사용자를 이 커뮤니티에서 차단하고 모든 메시지를 삭제합니다. 계속하시겠습니까? @@ -258,7 +291,11 @@ 복사됨 복사 만들기 + 통화 생성 중 잘라내기 + 정말로 이 기기에서 모든 메시지, 첨부 파일, 계정 데이터를 삭제하고 새 계정을 생성하시겠습니까? + 데이터베이스 오류가 발생했습니다.\n\n문제 해결을 위해 애플리케이션 로그를 내보내서 공유하십시오. 실패할 경우, {app_name}을 다시 설치하고 계정을 복원하십시오. + 정말로 이 기기에서 모든 메시지, 첨부 파일, 계정 데이터를 삭제하고 네트워크에서 계정을 복원하시겠습니까? {app_name} 이 오랜 시간동안 응답하지 않은 것으로 보입니다. \n\n계속 기다리거나, 기기의 로그를 내보내 도움을 요청하거나, {app_name} 을 재시작 해보세요. {app_name}의 이 버전은 앱 데이터베이스와 호환되지 않습니다. 앱을 재설치하고 계정을 복원하여 새 데이터베이스를 생성하고 {app_name}을 계속 사용하십시오.\n\n경고: 이렇게 하면 2주 이상 된 모든 메시지와 첨부 파일이 손실됩니다. 데이터베이스 최적화 @@ -280,6 +317,8 @@ 그룹 생성 중... 그룹 업데이트 실패 다른 사람의 메시지를 삭제할 수 있는 권한이 없습니다 + 연락처에서 {name}을(를) 삭제하시겠습니까?\n\n모든 대화 내역이 삭제되며, 이후에 오는 {name}의 메시지는 메시지 요청으로 오게 됩니다. + {name}간의 대화 내역을 삭제하시겠습니까?\n이 결정은 영구적이며 모든 메시지와 첨부 파일이 삭제됩니다. 메시지 삭제 @@ -287,7 +326,7 @@ 정말 해당 메시지들을 삭제하시겠습니까? - 메시지가 삭제되었습니다. + 메시지가 삭제되었습니다 이 메시지는 삭제되었습니다. 이 장치에서 이 메시지가 삭제되었습니다. @@ -330,7 +369,7 @@ 설정 따라가기 보내는 메시지가 더 이상 사라지지 않습니다. 소멸 메시지를 끄시겠습니까? 메시지를 {disappearing_messages_type}{time} 후에 사라지도록 설정하시겠습니까? - {name}가 구식 클라이언트를 사용하고 있습니다. 사라지는 메시지가 예상대로 작동하지 않을 수 있습니다. + {name} 님이 이전 버전의 클라이언트를 사용하고 있습니다. 사라지는 메시지가 예상대로 작동하지 않을 수 있습니다. 그룹 관리자만 이 설정을 변경할 수 있습니다. 보냄 {name}님이 메시지가 {disappearing_messages_type} 이후 {time} 후에 사라지는 설정을 했습니다. @@ -355,6 +394,7 @@ 표시 이름 설정 사용자 이름은 당신이 상호작용하는 사용자, 그룹 및 커뮤니티에 공개됩니다. 문서 + 후원하기 완료 다운로드 다운로드 중... @@ -362,7 +402,7 @@ 편집 이모지와 기호 활동 - 동물 &amp; 자연 + 동물과 자연 깃발 음식 & 음료 오브젝트 @@ -370,14 +410,14 @@ 스마일리 &amp; 사람들 기호 여행 & 장소 - Are you sure you want to clear all {emoji}? + 정말로 모든 {emoji}를 지우시겠습니까? 속도를 줄이세요! 너무 많은 이모지 반응을 보냈습니다. 잠시 후 다시 시도해 주세요 그리고 %1$d명이 이 메시지에 %2$s로 반응했습니다. - {name}이(가) {emoji_name}으로 반응했습니다 - {name} 및 {other_name}이(가) {emoji_name}으로 반응했습니다 - {name}과 {count}명이(가) {emoji_name}으로 반응했습니다 + {name} 님이 {emoji_name}으로 반응했습니다 + {name} 및 {other_name} 님이 {emoji_name}으로 반응했습니다 + {name} 님과 {count}명이 {emoji_name}으로 반응했습니다 사용자님이 {emoji_name}으로 반응했습니다 사용자님과 {count}명이(가) {emoji_name}으로 반응했습니다 사용자 및 {name}님이 {emoji_name}으로 반응했습니다 @@ -386,20 +426,24 @@ 인터넷 연결을 확인하시고 다시 시도해주세요. 복사 오류 및 종료 데이터베이스 오류 + 문제가 발생했습니다. 나중에 다시 시도해 주세요. 알 수 없는 오류가 발생했습니다. + 다운로드에 실패했습니다 실패 파일 파일들 시스템 설정 따르기 + 영원히 보낸 사람: Toggle Full Screen GIF Giphy - {app_name}는 검색 결과를 제공하기 위해 Giphy에 연결됩니다. GIF를 보낼 때 전체 메타데이터 보호가 제공되지 않습니다. + {app_name}은 검색 결과를 제공하기 위해 Giphy에 연결됩니다. GIF를 보낼 때 전체 메타데이터 보호가 제공되지 않습니다. 그룹은 최대 100명까지 멤버를 추가할 수 있습니다 그룹 만들기 적어도 한 명의 추가 그룹 멤버를 선택해 주세요. 그룹 삭제하기 + 정말로 {group_name}을 제거하시겠습니까?\n\n모든 멤버가 제거되고 모든 그룹 컨텐츠가 삭제됩니다. 정말로 {group_name} 그룹을 삭제 하시겠습니까? {group_name} 그룹은 관리자에 의해 삭제 되었습니다. 더 이상 메시지를 보낼 수 없습니다. 그룹 설명 입력 @@ -442,7 +486,6 @@ {name}님{count}명이 그룹에 초대되었습니다. {name}님{other_name}님이 그룹 초대를 받았습니다. 당신{count} 명의 사람들이 그룹으로 초대받았습니다. 대화 내역이 공개됩니다. - 당신{other_name}이 그룹에 초대 되었습니다. 대화 내용이 공유 되었습니다. 당신이 그룹을 나갔습니다. 그룹 멤버 이 그룹에 다른 멤버가 없습니다. @@ -454,10 +497,12 @@ 그룹 이름이 업데이트되었습니다. 그룹 이름은 모든 그룹 멤버에게 보입니다. {group_name}님으로부터 받은 메시지가 없습니다.대화를 시작하려면 메시지를 보내세요! + 이 그룹은 30일 이상 업데이트 되지 않았습니다. 메시지를 보내거나 그룹 정보를 보는데 문제가 있을 수 있습니다. 당신은 {group_name}의 유일한 관리자입니다.\n\n관리자가 없으면 그룹 구성원 및 설정을 변경할 수 없습니다. 추방 대기 중 당신이 관리자(Admin)로 승격되었습니다. 당신{count}명이 관리자(Admin)로 승격되었습니다. + 당신{other_name}님이 관리자로 승격되었습니다. {group_name}에서 {name}님을 제거하시겠습니까? {group_name}에서 {name}님과 {count}명을 제거하시겠습니까? {group_name}에서 {name}님과 {other_name}님을 제거하시겠습니까? @@ -477,6 +522,7 @@ 그룹 프로필 사진 설정 알 수 없는 그룹 그룹 정보가 업데이트되었습니다. + 연결 후보 처리 중 자주 묻는 질문 {app_name} 번역 돕기 버그 제보 @@ -489,17 +535,30 @@ 여러분의 의견을 기다리고 있습니다 숨기기 시스템 메뉴 바를 끄거나 켜기 + 정말로 대화 목록에서 개인용 메모를 숨기시겠습니까? 나머지 숨기기 이미지 + 이미지 사생활 키보드 사용 가능한 경우 시크릿 모드를 요청합니다. 사용 중인 키보드에 따라 이 요청을 무시할 수도 있습니다. 정보 잘못된 바로 가기 + + 초대 실패 + + + 초대를 보내지 못했습니다. 다시 시도하시겠습니까? + 가입 나중에 더 알아보기 나가기 떠나는 중... + 이 그룹은 이제 읽기 전용입니다. 계속 채팅하려면 이 그룹을 다시 생성하세요. + 이 그룹은 이제 읽기 전용입니다. 계속 채팅하려면 그룹 관리자에게 이 그룹을 다시 생성하도록 요청하세요. + 그룹이 업그레이드 되었습니다! 안정성을 위해 이 그룹을 다시 생성하세요. 이 그룹은 {date}에 읽기 전용으로 전환됩니다. + 그룹이 업그레이드 되었습니다! 안정성을 위해 그룹 관리자에게 이 그룹을 다시 생성하도록 요청하세요. 이 그룹은 {date}에 읽기 전용으로 전환됩니다. + 새 그룹으로 채팅 기록이 이전되지 않습니다. 이전 그룹에서 모든 채팅 기록을 계속 확인할 수 있습니다. {name}님이 그룹에 참여했습니다. {name}님님과 {count}명이 그룹에 참여했습니다. 당신님과 {count}명이 그룹에 참여했습니다. @@ -515,7 +574,7 @@ 링크 미리보기 보내기 링크 미리보기를 보낼 때 모든 메타데이터 보호가 이루어지지 않습니다. 링크 미리보기 꺼짐 - {app_name}는 송수신 링크 프리뷰를 생성하기 위해 연결된 웹사이트에 접속해야 합니다.\n\n{app_name}의 설정에서 이를 켤 수 있습니다. + {app_name}은 송수신 링크 프리뷰를 생성하기 위해 연결된 웹사이트에 접속해야 합니다.\n\n{app_name}의 설정에서 이를 켤 수 있습니다. 계정 불러오기 계정 불러오는 중 불러오는 중... @@ -523,20 +582,21 @@ {app_name} 을 열때마다 지문, PIN, 패턴 또는 비밀번호를 요구합니다 {app_name} 잠금 해제를 위해 Touch ID, Face ID 또는 비밀번호가 필요합니다. 화면 잠금을 사용하기 위해 iOS 설정에서 암호를 활성화해야 합니다. - {app_name}가 잠겼습니다 + {app_name}이 잠겼습니다 {app_name} 이 잠겨 있어 빠른 답변을 사용할 수 없습니다 잠금 상태 탭하여 잠금 해제 - {app_name}의 잠금을 해제했습니다 + {app_name}이 잠금 해제되었습니다. + 구성원 관리 최대 미디어 %1$d명의 멤버 - %1$d인 온라인 + %1$d명의 온라인 멤버 - Add Account ID or ONS + Account ID 또는 ONS로 초대 연락처에서 초대 초대 전송 @@ -562,9 +622,14 @@ 친구의 Account ID 또는 ONS를 입력하여 새로운 대화를 시작하세요. 친구의 Account ID, ONS를 입력하거나 QR 코드를 스캔하여 새 대화를 시작하십시오. - %1$d 님에게 새 메시지가 도착했습니다. + %1$d개의 새 메시지가 도착했습니다. + + + %1$s에 %2$d개의 새로운 메시지가 있습니다. 답장 중 + 메시지 요청이 수락되기 전까지는 첨부 파일을 보낼 수 없습니다 + 메시지 요청이 수락되기 전까지는 음성 메시지를 보낼 수 없습니다 {name}님{group_name}에 참여하도록 초대했습니다. 이 그룹에 메시지를 보내면 초대가 자동으로 수락됩니다. 당신의 메시지 요청이 현재 보류 중입니다. @@ -574,7 +639,8 @@ 당신의 메시지 요청이 수락되었습니다. 모든 메세지 요청과 그룹 초대를 삭제하시겠습니까? 커뮤니티 메시지 요청 - Allow message requests from Community conversations. + 커뮤니티 대화에서 메시지 요청 허용. + 정말로 이 메시지 요청과 연결된 연락처를 삭제하시겠습니까? 정말 이 메시지 요청을 삭제하시겠습니까? 새로운 메시지 요청이 있습니다. 보류중인 메세지 요청이 없습니다. @@ -606,7 +672,7 @@ 개인용 메모에 메시지가 없습니다. 개인용 메모 숨기기 정말 개인용 메모를 숨기시겠습니까? - All Messages + 모든 메시지 알림 내용 알림에서 표시되는 정보 이름 및 내용 @@ -614,12 +680,13 @@ 이름 또는 내용 없음 Fast 모드 Google의 알림 서버를 사용하여 새 메시지를 확실하고 즉각적으로 알려드립니다. + Huawei의 알림 서버를 사용하여 새 메시지를 확실하고 즉각적으로 알려드립니다. Apple의 알림 서버를 사용하여 새 메시지를 확실하고 즉각적으로 알려드립니다. 기기 알림 설정으로 이동 알림 - 전체 알림 - 멘션만 알림 - 음소거 - {name}이(가) {conversation_name}에 보냈습니다 + {name} 님이 {conversation_name}에 보냈습니다 {device}가 재시동되는 동안 메시지를 수신했을 수 있습니다. 알림등 색상 언급만 알림 @@ -629,6 +696,8 @@ {time_large} 동안 음소거 알림 켜기 알림 꺼짐 + {time_large} 동안 음소거됨 + {date_time}까지 음소거됨 Slow 모드 {app_name}은 때때로 백그라운드에서 새 메시지를 확인합니다. 소리 @@ -649,7 +718,7 @@ 계정 생성은 즉시, 무료이며 익명입니다 {emoji} 가입할 때 전화번호가 필요하지 않습니다. 당신의 주머니 속 개인정보 보호. - {app_name}는 개인 정보를 보호하기 위해 설계되었습니다. + {app_name}은 개인 정보를 보호하기 위해 설계되었습니다. {app_name}에 오신 것을 환영합니다 {emoji} 채팅을 시작하거나, 그룹을 생성하거나, 공식 커뮤니티에 참여하려면 플러스 버튼을 눌러보세요! {app_name}이 새 메시지를 알릴 수 있는 두 가지 방법이 있습니다. @@ -657,7 +726,7 @@ Terms of Service 서비스를 사용함으로써, 귀하는 우리의 서비스 약관개인정보 보호정책에 동의하게 됩니다. 경로 - {app_name}는 {app_name}의 탈중앙화된 네트워크에서 여러 서비스 노드를 통해 메시지를 라우팅하여 IP를 숨깁니다. 이것이 현재 경로입니다: + {app_name}은 {app_name}의 탈중앙화된 네트워크에서 여러 서비스 노드를 통해 메시지를 라우팅하여 IP를 숨깁니다. 이것이 현재 경로입니다: 목적지 출구 노드 Service Node @@ -687,20 +756,32 @@ 비밀번호 설정 비밀번호 설정이 완료되었습니다. 안전히 관리하시기 바랍니다. 붙여넣기 + 권한 변경 {app_name}은(는) 파일, 음악 및 오디오를 전송하기 위해 음악 및 오디오 접근이 필요하지만, 접근이 영구적으로 거부되었습니다. 설정 → 권한으로 이동하여 \"음악 및 오디오\"를 켜십시오. {app_name}은 미디어 첨부 파일을 재생하기 위해 Apple Music을 사용해야 합니다. 자동 업데이트 시작 시 자동으로 업데이트를 확인 + 영상 통화를 위해 “카메라” 접근이 필요합니다. 계속하려면 설정에서 “카메라” 권한을 켜세요. + 카메라 접근이 현재 활성화되어 있습니다. 비활성화하려면 설정에서 “카메라” 권한을 끄세요. {app_name}은 사진과 동영상을 찍기 위해 카메라 접근이 필요하지만 영구적으로 거부되었습니다. 설정 - 권한을 누르고 \'카메라\'를 켜십시오. + 영상 통화를 위해 카메라 접근을 허용하세요. {app_name}의 화면 잠금 기능은 Face ID를 사용합니다. 트레이 아이콘 유지 {app_name} 창을 닫아도 백그라운드에서 실행됩니다. {app_name}은 포토 라이브러리 접근권한이 필요합니다. iOS 설정에서 접근 권한을 허용할 수 있습니다. + 통화를 위해 “로컬 네트워크” 접근이 필요합니다. 계속하려면 설정에서 “로컬 네트워크” 권한을 켜세요. + {app_name}이 음성 및 영상 통화를 하기 위해 로컬 네트워크에 접근해야 합니다. + 로컬 네트워크 접근이 현재 활성화되어 있습니다. 비활성화하려면 설정에서 “로컬 네트워크” 권한을 끄세요. + 음성 및 영상 통화를 위해 로컬 네트워크 접근을 허용하세요. + 로컬 네트워크 마이크 {app_name}은 통화 및 음성 메시지를 보내기 위해 마이크 접근 권한이 필요하지만 영구적으로 거부되었습니다. 설정에서 \'권한\'으로 이동하여 \'마이크\'를 켜십시오. + 통화 및 오디오 메시지 녹음을 위해 “마이크” 접근이 필요합니다. 계속하려면 설정에서 “마이크” 권한을 켜세요. {app_name}의 프라이버시 설정에서 마이크 접근을 허용할 수 있습니다 {app_name}은 통화를 하고 음성 메시지를 녹음하기 위해 마이크 접근이 필요합니다. + 마이크 접근이 현재 활성화되어 있습니다. 비활성화하려면 설정에서 “마이크” 권한을 끄세요. 마이크 접근 권한을 허용해주세요. + 음성 통화 및 오디오 메시지를 위해 마이크 접근을 허용하세요. {app_name}는 파일, 음악 및 오디오를 전송하려면 음악 및 오디오 접근이 필요합니다. 권한 필요 {app_name}에서 사진과 동영상을 전송하려면 사진 보관함 접근 권한이 필요하지만 영구적으로 거부되었습니다. 설정 → 권한으로 이동하여 \"사진 및 동영상\"을 켜십시오. @@ -708,6 +789,7 @@ {app_name}은 첨부 파일과 미디어를 저장하기 위해 저장 공간 접근이 필요합니다. {app_name}은 사진과 동영상을 저장하기 위해 저장 공간 접근이 필요하지만 영구적으로 거부되었습니다. 앱 설정에서 \"권한\"을 선택하고, \"저장 공간\" 권한을 활성화하세요. {app_name}은 사진과 동영상을 전송하기 위해 저장공간 접근이 필요합니다. + 이 커뮤니티에서 작정 권한이 없습니다 고정 대화 고정하기 고정 해제 @@ -720,6 +802,12 @@ 더 작은 파일을 선택해 주세요. 프로필 업데이트 실패. 승격 + + 승격 실패 + + + 승격 시키지 못했습니다. 다시 시도하시겠습니까? + QR 코드 이 QR 코드는 계정 ID를 포함하고 있지 않습니다. 이 QR 코드는 복구 비밀번호를 포함하고 있지 않습니다. @@ -732,11 +820,15 @@ 읽음 표시 보낸 모든 메시지와 받는 모든 메시지에 읽음 확인을 표시합니다. 수신됨: + 응답 수신됨 + 통화 제안 받는 중 + 사전 제안 수신 중 권장 계정에 접근 권한을 잃지 않도록 회복 비밀번호를 저장하세요. 회복 비밀번호 저장 새 장치에서 계정을 로드하려면 복구 비밀번호를 사용하십시오.\n\n복구 비밀번호 없이는 계정을 복구할 수 없습니다. 안전하게 보관하고 타인과 공유하지 않도록 주의하세요. 복구 비밀번호 입력 + 복구 비밀번호를 불러오는 도중 오류가 발생했습니다.\n\n문제를 해결하기 위해 로그를 내보낸 후 {app_name} 고객 지원 센터에 첨부하여 문의 해주세요. 복구 비밀번호를 확인하시고 다시 시도해주세요. 복구 암호의 일부 단어가 잘못되었습니다. 확인하고 다시 시도해 주세요. 입력한 복구 비밀번호가 충분하지 않습니다. 확인 후 다시 시도해주세요. @@ -750,7 +842,9 @@ 계정을 로드하려면 복구 비밀번호를 입력하세요. 저장되지 않았다면, 앱 설정에서 찾을 수 있습니다. 비밀번호 보기 이것은 당신의 복구 비밀번호입니다. 다른 사람에게 보내면 그들이 당신의 계정에 완전히 접근할 수 있게 됩니다. + 그룹 다시 만들기 다시 실행 + {count}자 입력 가능 삭제 비밀번호를 제거하지 못했습니다 답장 @@ -781,8 +875,11 @@ 검색 중... 선택 Select All + 앱 아이콘 변경 전송 전송 중 + 통화 제안 전송 중 + 연결 후보 전송 중 보냄: 디자인 데이터 삭제 @@ -790,30 +887,46 @@ 도움말 친구 초대 메시지 요청 + 현재 {token_name_short} 가격 + 메시지는 {network_name}를 통해 전송됩니다. 네트워크는 {token_name_long}으로 인센티브를 받은 노드들로 구성되며, 이 노드들은 {app_name}을 분산되고 안전하게 유지합니다. 더 알아보기 {icon} + 스테이킹에 대해 알아보기 + 시가 총액 + 내 메시지를 보호하는 {app_name} 노드 + 내 스웜의 {app_name} 노드 + {token_name_long}이 출시되었습니다! 설정에서 새로 추가된 {network_name} 섹션을 확인하고, {token_name_long}이 Session을 어떻게 구동하는지 알아보세요. + 보호 중인 네트워크 수 + 네트워크 보안을 위해 {token_name_long}을 스테이킹 하면, {staking_reward_pool}에서 {token_name_short}로 보상을 받습니다. + 신규 알림 권한 개인정보 보호 Recovery Password 설정 설정 + 커뮤니티 프로필 사진 설정 새 설정을 적용하려면 {app_name}을 재시작해야 합니다. 공유 친구에게 계정 ID를 공유하고 {app_name} 에서 친구들과 대화하세요. 친구들이 자주 사용하는 곳에서 공유해 주세요 — 그러면 여기로 대화를 이동하십시오. 데이터베이스를 열 때 문제가 발생했습니다. 앱을 재시작하고 다시 시도해주세요. + 앗! 아직 {app_name} 계정이 없는 것 같아요.\n\n{app_name} 앱에서 계정을 생성해야 공유할 수 있습니다. {app_name}에 공유 보기 모두 보기 간략히 보기 + 개인용 메모 표시하기 + 개인용 메모를 대화 리스트에 표시하겠습니까? 스티커 지원 페이지로 이동 시스템 정보: {information} + 탭하여 재시도 계속 기본값 에러 다시 시도 입력 지시자 깜박임 입력 중 아이콘 보고 공유. + 사용 불가 실행 취소 알 수 없음 앱 업데이트 @@ -821,10 +934,15 @@ 업데이트 다운로드 중: {percent_loader}% 업데이트 불가능 {app_name} 업데이트에 실패했습니다. {session_download_url}에서 새 버전을 수동으로 설치한 후, 이 문제를 알려주시기 위해 고객 지원 센터에 연락해 주세요. + 그룹 정보 업데이트 + 그룹 이름과 설명은 모든 그룹 멤버에게 보입니다. + 더 짧은 그룹 설명을 입력해주세요. 새 버전의 {app_name}이 사용 가능합니다. 눌러서 업데이트 하세요. + {app_name}의 새 버전({version})을 사용할 수 있습니다. 패치 노트 보기 {app_name} 업데이트 버전 {version} + {relative_time} 전에 마지막 업데이트됨 업로드 중 URL 복사 URL 열기 @@ -834,6 +952,8 @@ 동영상 동영상을 재생할 수 없습니다. 보기 + 덜 보기 + 더 보기 이 작업은 몇 분 정도 소요될 수 있습니다. 잠시만 기다려주세요... 경고 diff --git a/app/src/main/res/values-b+nl+NL/strings.xml b/app/src/main/res/values-b+nl+NL/strings.xml index 46128b833d..bb49cb7f28 100644 --- a/app/src/main/res/values-b+nl+NL/strings.xml +++ b/app/src/main/res/values-b+nl+NL/strings.xml @@ -3,17 +3,20 @@ Over Accepteer Kopieer Account-ID + Gebruiker ID Account-ID gekopieerd Kopieer je Account-ID en deel het met je vrienden zodat ze je berichten kunnen sturen. Voer Account-ID in Dit Account-ID is ongeldig. Controleer en probeer het opnieuw. Voer Account-ID of ONS in Uitnodigen Account-ID of ONS - Hey, ik gebruik {app_name} om volledig privé en veilig te chatten. Doe mee! Mijn Account ID is\n\n{account_id}\n\nDownload het op {session_download_url} + Hoi, ik gebruik {app_name} om volledig privé en veilig te chatten. Doe mee! Mijn Gebruikers ID is\n\n{account_id}\n\nDownload het op {session_download_url} Uw Account-ID Dit is uw Account-ID. Andere gebruikers kunnen het scannen om een gesprek met u te beginnen. Werkelijke grootte Toevoegen + Beheerders toevoegen + Voer de account-ID in van de gebruiker die u promoot als admin.\n\nOm meerdere gebruikers toe te voegen, voer elk account-ID in, gescheiden door een komma. Tot 20 Account ID\'s kunnen tegelijkertijd worden opgegeven. Admins kunnen niet worden verwijderd. {name} en {count} anderen zijn gepromoveerd tot Admin. Beheerders promoveren @@ -47,7 +50,10 @@ +{count} Anoniem App icoon + Verander App Pictogram en Naam + Om het app icoon en de naam te wijzigen moet {app_name} afgesloten worden. Meldingen blijven het standaard {app_name} pictogram en naam gebruiken. Alternatieve app icoon en naam wordt weergegeven op het startscherm en de app lade. + Het geselecteerde app pictogram en de naam worden weergegeven in het startscherm en de app-lade. Icoon en naam Alternatieve app icoon wordt weergegeven op het startscherm en de app bibliotheek. De app naam wordt nog steeds weergegeven als \'{app_name}\'. Alternatief app icoon gebruiken @@ -77,6 +83,7 @@ Inzoomen Uitzoomen Bijlage + Bijlagen Bijlage toevoegen Naamloos Album Automatisch downloaden bijlagen @@ -138,9 +145,11 @@ Blokkeren mislukt Deblokkeren mislukt Gebruiker deblokkeren + Voer het account-ID van de gebruiker in die u weer toelaat Gebruiker gedeblokkeerd Gebruiker verbannen Gebruiker verbannen + Voer het account-ID van de gebruiker in die u buitensluit Blokkeren Geen geblokkeerde contactpersonen {name} geblokkeerd @@ -201,15 +210,24 @@ Weet u zeker dat u uw gegevens uit het netwerk wilt wissen? Als u doorgaat, kunt u uw berichten of contacten niet meer herstellen. Weet u zeker dat u uw apparaatgegevens wilt wissen? Alleen apparaatgegevens wissen + Apparaat wissen en herstarten + Apparaat wissen en herstellen Wis alle berichten Weet u zeker dat u alle berichten van uw gesprek met {name} van uw apparaat wilt wissen? + Weet u zeker dat u alle berichten van uw gesprek met {name} op dit apparaat wilt wissen? Weet u zeker dat u alle {community_name} berichten van uw apparaat wilt wissen? + Weet je zeker dat je alle berichten van {community_name} op dit apparaat wilt wissen? Wis voor iedereen Wis voor mij Weet je zeker dat je alle {group_name} berichten wilt wissen? + Weet je zeker dat je alle berichten van {group_name} wilt wissen? Weet u zeker dat u alle {group_name} berichten van uw apparaat wilt wissen? + Weet je zeker dat je alle berichten van {group_name} op dit apparaat wilt wissen? Weet u zeker dat u alle \"Notitie aan mijzelf\" berichten van uw apparaat wilt wissen? + Weet u zeker dat u alle {Bericht aan Jezelf} op dit apparaat wilt wissen? + Wissen op dit apparaat Sluiten + Applicatie sluiten Venster sluiten Bevestig Hash: {hash} Dit zal de geselecteerde gebruiker van deze Community verbannen en hen/haar/hem berichten verwijderen. Weet u zeker dat u door wilt gaan? @@ -276,6 +294,9 @@ Aanmaken Oproep starten Knippen + Weet u zeker dat u alle berichten, bijlagen en accountgegevens van dit apparaat wilt verwijderen en een nieuw account wilt aanmaken? + Er is een databasefout opgetreden.\n\nExporteer uw applicatie logs om te delen voor probleemoplossing. Als dit niet lukt, installeer {app_name} opnieuw en herstel uw account. + Weet u zeker dat u alle berichten, bijlagen en accountgegevens van dit apparaat wilt verwijderen en uw account wilt herstellen vanuit het netwerk? We hebben gemerkt dat {app_name} veel tijd nodig heeft om op te starten.\n\nU kunt doorgaan met wachten, uw apparaatlogs exporteren om te delen voor probleemoplossing, of proberen {app_name} opnieuw op te starten. Uw app-database is niet compatibel met deze versie van {app_name}. Installeer de app opnieuw en herstel uw account om een nieuwe database te genereren en {app_name} te blijven gebruiken.\n\nWaarschuwing: Dit leidt tot verlies van alle berichten en bijlagen ouder dan twee weken. Optimaliseer Database @@ -297,6 +318,8 @@ Een moment geduld, de groep wordt aangemaakt... Het is mislukt om de groep bij te werken Je hebt geen toestemming om andermans berichten te verwijderen + Weet u zeker dat u {name} wilt verwijderen uit uw contacten?\n\nDit zal je gesprek, inclusief alle berichten en bijlagen, verwijderen. Toekomstige berichten van {name} worden weergegeven als een berichtverzoek. + Weet u zeker dat u uw gesprek met {name}wilt verwijderen?\nAlle berichten en bijlagen worden permanent verwijderd. Verwijder bericht Verwijder berichten @@ -411,12 +434,14 @@ Controleer je internetverbinding en probeer het opnieuw. Foutmelding kopiëren en afsluiten Databasefout + Er ging iets mis. Probeer het later nog eens. Er is een onbekende fout opgetreden. Downloaden mislukt Mislukkingen Bestand Bestanden Systeeminstellingen volgen + Voor altijd Van: Volledigschermmodus GIF @@ -426,6 +451,7 @@ Groep aanmaken Kies ten minste één ander groepslid. Verwijder groep + Weet u zeker dat u de groep {group_name} wil verwijderen?\n\n Dit zal alle leden en de inhoud van de groep verwijderen. Weet u zeker dat u {group_name} wilt verwijderen? {group_name} is verwijderd door een groepsbeheerder. U kunt geen berichten meer versturen. Voer een groepsbeschrijving in @@ -481,6 +507,7 @@ Groepsnaam bijgewerkt. Groepsnaam is zichtbaar voor alle groepsleden. U heeft geen berichten van {group_name}. Stuur een bericht om het gesprek te starten! + De groep is niet bijgewerkt gedurende de afgelopen 30 dagen. U kunt problemen ervaren bij het verzenden van berichten of bekijken van de groepsinformatie. U bent de enige beheerder in {group_name}.\n\nGroepsleden en instellingen kunnen niet worden gewijzigd zonder een beheerder. In afwachting van verwijdering U bent gepromoveerd tot Admin. @@ -520,6 +547,7 @@ We hechten veel waarde aan uw feedback Verbergen Zichtbaarheid systeemmenubalk in-/uitschakelen + Ben je zeker dat je Bericht aan Jezelf in je conversatie lijst wilt verbergen? Anderen verbergen Afbeelding afbeeldingen @@ -573,6 +601,7 @@ Vergrendelingstoestand Tik om te ontgrendelen {app_name} is ontgrendeld + Leden beheren Maximaal Media @@ -619,6 +648,8 @@ U heeft %1$d nieuwe berichten in %2$s. Antwoord naar + U kunt geen bijlagen versturen totdat uw berichtaanvraag is geaccepteerd + U kunt geen spraakberichten verzenden totdat uw berichtaanvraag is geaccepteerd {name} heeft je uitgenodigd om lid te worden van {group_name}. Door een bericht te versturen naar deze groep accepteert u automatisch de groepsuitnodiging. Uw berichtverzoek is momenteel in behandeling. @@ -684,6 +715,8 @@ Dempen voor {time_large} Niet langer dempen Gedempt + Dempen voor {time_large} + Gedempt tot {date_time} Langzame modus {app_name} controleert af en toe nieuwe berichten op de achtergrond. Geluid @@ -734,7 +767,7 @@ Wachtwoord mag alleen letters, cijfers en symbolen bevatten Wachtwoord moet tussen de 6 en 64 tekens lang zijn Wachtwoorden komen niet overeen - Het instellen van wachtwoord mislukt + Instellen van wachtwoord mislukt Onjuist wachtwoord Wachtwoord verwijderen Verwijder het wachtwoord dat nodig is om {app_name} te ontgrendelen. @@ -862,6 +895,7 @@ Zoeken... Selecteren Alles selecteren + Selecteer app pictogram Verzenden Verzenden Oproepaanbod verzenden @@ -873,12 +907,23 @@ Help Nodig een vriend uit Berichtverzoeken + Huidige {token_name_short} prijs + Berichten worden verzonden via de {network_name}. Het netwerk bestaat uit nodes die bestaan uit {token_name_long}, wat {app_name} gedecentraliseerd en veilig houdt. Meer Informatie {icon} + Leer meer over Beleggen + Beurswaarde + {app_name} Nodes die je berichten beveiligen + {app_name} Nodes in jouw zwerm + {token_name_long} is actief! Ontdek de nieuwe {network_name} paragraaf in Instellingen om te leren hoe {token_name_long} Session aanstuurt. + Netwerk beveiligd door + Wanneer je {token_name_long} toepast om het netwerk te beveiligen, verdien je beloningen in {token_name_short} van de {staking_reward_pool}. + Nieuw Notificaties Toestemmingen Privacy Herstelwachtwoord Instellingen Instellen + Instellen Groeps afbeelding Uw moet {app_name} opnieuw starten om uw nieuwe instellingen toe te passen. Delen Nodig uw vriend uit om met u te chatten op {app_name} door uw Account ID met hen te delen. @@ -889,6 +934,8 @@ Tonen Alles tonen Minder tonen + Notitie aan mezelf tonen + Ben je zeker dat je Bericht aan Jezelf in je conversatie lijst wilt tonen? Stickers Ga naar ondersteuningspagina Systeeminformatie: {information} @@ -899,6 +946,7 @@ Probeer opnieuw Typindicatoren Bekijk en deel typ indicatoren. + Niet beschikbaar Ongedaan maken Onbekend Nieuwe versies van de app @@ -906,11 +954,15 @@ Update aan het downloaden: {percent_loader}% Kan niet bijwerken {app_name} kon niet worden bijgewerkt. Ga naar {session_download_url} en installeer handmatig de nieuwe versie, neem vervolgens contact op met ons Help Center om dit probleem te laten weten. + Groepsgegevens bijwerken + Groepsnaam en beschrijving zijn zichtbaar voor alle groepsleden. + Vul alstublieft een kortere groepsnaam in Er is een nieuwe versie van {app_name} beschikbaar, tik om bij te werken Versie ({version}) van {app_name} is beschikbaar. Ga naar Release Opmerkingen {app_name} Bijwerken Versie {version} + {relative_time} geleden voor het laatst bijgewerkt Uploaden Kopieer URL URL openen @@ -920,6 +972,8 @@ Video Kan video niet afspelen. Bekijken + Minder weergeven + Meer weergeven Dit kan een paar minuten duren. Een moment geduld aub... Waarschuwing diff --git a/app/src/main/res/values-b+pl+PL/strings.xml b/app/src/main/res/values-b+pl+PL/strings.xml index a6e90683da..4fef8f5015 100644 --- a/app/src/main/res/values-b+pl+PL/strings.xml +++ b/app/src/main/res/values-b+pl+PL/strings.xml @@ -3,6 +3,7 @@ O aplikacji Zaakceptuj Kopiuj identyfikator konta + Identyfikator konta Skopiowano identyfikator konta Skopiuj swój identyfikator konta, a następnie udostępnij go znajomym, aby mogli wysłać do Ciebie wiadomość. Wprowadź identyfikator konta @@ -15,6 +16,7 @@ Rzeczywisty rozmiar Dodaj Dodaj administratorów + Wprowadź identyfikator konta użytkownika, którego chcesz awansować na administratora.\n\nAby dodać wielu użytkowników, wpisz każdy identyfikator konta oddzielone przecinkiem. Można jednocześnie podać maksymalnie 20 identyfikatorów kont. Nie można usuwać administratorów. {name} i {count} innych zostali awansowani na administratów. Awansuj administratorów @@ -50,7 +52,10 @@ +{count} Anonim Ikona aplikacji + Zmień ikonę i nazwę aplikacji + Zmiana ikony i nazwy aplikacji wymaga zamknięcia aplikacji {app_name}. Powiadomienia nadal będą używać domyślnej ikony i nazwy {app_name}. Alternatywna ikona i nazwa aplikacji są wyświetlane na ekranie głównym i w szufladzie aplikacji. + Wybrana ikona i nazwa aplikacji będą wyświetlane na ekranie głównym oraz w szufladzie aplikacji. Ikona i nazwa Alternatywna ikona aplikacji jest wyświetlana na ekranie głównym i w bibliotece aplikacji. Nazwa aplikacji będzie nadal wyświetlana jako „{app_name}”. Użyj alternatywnej ikony aplikacji @@ -226,6 +231,7 @@ Czy na pewno chcesz usunąć z urządzenia wszystkie Moje notatki? Wyczyść na tym urządzeniu Zamknij + Zamknij aplikację Zamknij okno Hash zatwierdzenia: {hash} Działanie zablokuje wybranego użytkownika w społeczności i skasuje wszystkie jego wiadomości. Na pewno kontynuować? @@ -448,6 +454,7 @@ Sprawdź swoje połączenie z internetem i spróbuj ponownie. Skopiuj błąd i zakończ Błąd bazy danych + Coś poszło nie tak. Spróbuj ponownie później. Wystąpił nieznany błąd. Nie udało się pobrać Awarie @@ -695,6 +702,7 @@ Czy na pewno chcesz wyczyścić wszystkie prośby o wiadomość i zaproszenia do grupy? Prośby o wiadomość od społeczności Zezwalaj na prośby o wiadomość z rozmów społecznościowych. + Czy na pewno chcesz usunąć to żądanie wiadomości i powiązany z nim kontakt? Czy na pewno chcesz usunąć tę prośbę o wiadomość? Masz nową prośbę o wiadomość Brak oczekujących próśb o wiadomość @@ -751,6 +759,7 @@ Wyłącz wyciszenie Wyciszono Wyciszony przez {time_large} + Wyciszony do {date_time} Tryb wolny Aplikacja {app_name} będzie od czasu do czasu sprawdzać nowe wiadomości w tle. Dźwięk @@ -842,6 +851,7 @@ Aby zapisywać załączniki i multimedia, aplikacja {app_name} potrzebuje dostępu do pamięci. Aby zapisywać zdjęcia i filmy, aplikacja {app_name} potrzebuje dostępu do pamięci, jednak na stałe go odmówiono. Przejdź do ustawień aplikacji, wybierz „Uprawnienia” i włącz „Pamięć”. Aby wysyłać zdjęcia i filmy, aplikacja {app_name} potrzebuje dostępu do pamięci. + Nie masz uprawnień do zapisu w tej społeczności Przypnij Przypnij konwersację Odepnij @@ -935,6 +945,7 @@ Wyszukiwanie... Wybierz Zaznacz wszystko + Wybierz ikonę aplikacji Wyślij Wysyłanie Wysyłanie oferty połączenia @@ -946,6 +957,16 @@ Pomoc Zaproś znajomego Prośby o wiadomość + Aktualna {token_name_short} cena + Wiadomości są wysyłane za pośrednictwem sieci {network_name}. Sieć składa się z węzłów, które są motywowane tokenami {token_name_long}, co zapewnia, że {app_name} pozostaje zdecentralizowana i bezpieczna. Dowiedz się więcej {icon} + Dowiedz się więcej o stakingu + Kapitalizacja + {app_name} Węzły zabezpieczające twoje wiadomości + {app_name} Węzły w twoim roju + {token_name_long} jest już dostępny! Zapoznaj się z nową sekcją {network_name} w Ustawieniach, aby dowiedzieć się, jak {token_name_long} zasila Session. + Sieć zabezpieczona przez + Stawiając {token_name_long} w celu zabezpieczenia sieci, otrzymujesz nagrody w {token_name_short} z puli {staking_reward_pool}. + Nowy Powiadomienia Uprawnienia Prywatność @@ -975,6 +996,7 @@ Spróbuj ponownie Wskaźniki pisania Wyświetlaj i udostępniaj wskaźniki pisania. + Niedostępny Cofnij Nieznane Aktualizacje aplikacji @@ -990,6 +1012,7 @@ Przejdź do informacji o wersji Aktualizacja aplikacji {app_name} Wersja {version} + Ostatnia aktualizacja {relative_time} temu Przesyłanie Skopiuj adres URL Otwórz adres URL diff --git a/app/src/main/res/values-b+ro+RO/strings.xml b/app/src/main/res/values-b+ro+RO/strings.xml index 6af1b91c3e..29026d4269 100644 --- a/app/src/main/res/values-b+ro+RO/strings.xml +++ b/app/src/main/res/values-b+ro+RO/strings.xml @@ -255,6 +255,7 @@ Copiază Creează Decupează + A apărut o eroare în baza de date.\n\nExportați jurnalele aplicației pentru a le partaja în vederea depanării. Dacă nu reușiți, reinstalați {app_name} și restaurați-vă contul. Am observat că {app_name} durează mult timp să pornească.\n\nPuteți continua să așteptați, să exportați jurnalele dispozitivului pentru a le partaja pentru depanare sau să încercați să reporniți {app_name}. Baza de date a aplicației dumneavoastră este incompatibilă cu această versiune de {app_name}. Reinstalați aplicația și restaurați contul pentru a genera o nouă bază de date și a continua să folosiți {app_name}.\n\nAtenție: Aceasta va conduce la pierderea tuturor mesajelor și atașamentelor mai vechi de două săptămâni. Optimizare bază de date @@ -288,6 +289,11 @@ Acest mesaj a fost șters. Acest mesaj a fost șters pe acest dispozitiv. + + Sunteţi sigur că doriţi să ștergeți acest mesaj doar de pe acest dispozitiv? + Sunteţi sigur că doriţi să ștergeți acest mesaj doar de pe acest dispozitiv? + Sigur doriți să ștergeți acest mesaj numai de pe acest dispozitiv? + Ești sigur/ă că dorești să ștergi acest mesaj pentru toată lumea? Șterge doar pe acest dispozitiv Șterge pe toate dispozitivele mele @@ -402,6 +408,7 @@ Creează grup Vă rugăm să alegeți cel puțin un alt membru al grupului. Șterge grup + Sunteți sigur că vreți să ștergeți {group_name}?\n\nAceasta va elimina toți membrii și va șterge tot conținutul grupului. Introduceți o descriere a grupului Imaginea afișată de grup a fost actualizată. Editează grupul @@ -416,6 +423,11 @@ Nu s-a putut invita {name} la {group_name} {name} te-a invitat să te realături grupului {group_name}, unde ești Admin. Ai fost invitat să te alături din nou la {group_name}, unde ești administrator. + + Se trimite invitația + Se trimit invitațiile + Se trimit invitațiile + Invitația a fost trimisă Invitație grup reușită Utilizatorii trebuie să aibă versiunea cea mai recentă pentru a primi invitații @@ -437,6 +449,7 @@ {name} și alți {count} au fost invitați să se alăture grupului. {name} și {other_name} au fost invitați să se alăture grupului. Tu și alți {count} ați fost invitați să vă alăturați grupului. Istoricul conversațiilor a fost partajat. + Dumneavoastră și {other_name} ați fost invitați să vă alăturați grupului. Istoricul conversațiilor a fost partajat. Tu ai părăsit grupul. Membrii grupului Nu există alți membri în acest grup. @@ -451,6 +464,7 @@ Ești singurul administrator din {group_name}.\n\nMembrii și setările grupului nu pot fi modificate fără un administrator. Tu ai fost promovat/ă la nivel de administrator. Tu și alți {count} ați fost promovați la nivel de administrator. + Dumneavoastră și {other_name} ați fost promovați ca administratori. Doriți să eliminați pe {name} din {group_name}? Doriți să eliminați pe {name} și alți {count} din {group_name}? Doriți să eliminați pe {name} și {other_name} din {group_name}? @@ -492,6 +506,16 @@ Solicită modul incognito, dacă este disponibil. În funcție de tastatura pe care o folosești, este posibil ca tastatura ta să ignore această solicitare. Info Scurtătură incorectă + + Invitație eșuată + Invitații eșuate + Invitații eșuate + + + Invitația nu a putut fi trimisă. Doriți să încercați din nou? + Invitațiile nu au putut fi trimise. Doriți să încercați din nou? + Invitațiile nu au putut fi trimise. Doriți să încercați din nou? + Alătură-te Mai târziu Află mai mult @@ -571,6 +595,11 @@ Ai %1$d mesaje noi. Ai %1$d mesaje noi. + + Ați primit un mesaj nou în %1$s. + Ați primit %1$d mesaje noi în %2$s. + Ați primit %1$d mesaje noi în %2$s. + Răspunde la {name} te-a invitat să te alături grupului {group_name}. Trimiterea unui mesaj către acest grup va accepta automat invitația în grup. @@ -727,6 +756,16 @@ Vă rugăm alegeți un fișier mai mic. Eroare la actualizarea profilului. Promovează + + Promovare eșuată + Promovări eșuate + Promovări eșuate + + + Promovarea nu a putut fi aplicată. Doriți să încercați din nou? + Promovările nu au putut fi aplicate. Doriți să încercați din nou? + Promovările nu au putut fi aplicate. Doriți să încercați din nou? + Cod QR Acest cod QR nu conține un ID de cont Acest cod QR nu conține o parolă de recuperare @@ -831,6 +870,7 @@ Nu se poate actualiza {app_name} nu a reușit să se actualizeze. Vă rugăm să accesați {session_download_url} și să instalați manual noua versiune, apoi contactați Centrul de Asistență pentru a ne informa despre această problemă. Este disponibilă o nouă versiune de {app_name}, apăsați pentru actualizare + O nouă versiune ({version}) {app_name} este valabilă. Mergi la Notele de lansare Actualizare {app_name} Versiunea {version} diff --git a/app/src/main/res/values-b+uk+UA/strings.xml b/app/src/main/res/values-b+uk+UA/strings.xml index 885b8c19ea..e793fef0d1 100644 --- a/app/src/main/res/values-b+uk+UA/strings.xml +++ b/app/src/main/res/values-b+uk+UA/strings.xml @@ -16,6 +16,7 @@ Актуальний розмір Додати Додати адміністраторів + Введіть ідентифікатор облікового запису користувача, якого ви призначаєте адміністратором.\n\nЩоб додати кількох користувачів, введіть ідентифікатори їхніх облікових записів, розділяючи їх комами. Одночасно можна вказати до 20 ідентифікаторів облікових записів. Адміністратори не можуть бути видалені. {name} та ще {count} інших було підвищено до адміністраторів. Підвищити адміністратора @@ -146,9 +147,11 @@ Не вдалося додати до чорного списку Не вдалося видалити з чорного списку Видалити користувача з чорного списку + Введіть ідентифікатор облікового запису користувача, якого ви розблоковуєте Користувача розблоковано Додати користувача до чорного списку Користувач заблокований + Введіть ідентифікатор облікового запису користувача, якого ви блокуєте Заблокувати Немає заблокованих контактів Заблоковано {name} @@ -215,12 +218,18 @@ Очистити пристрій та відновити Очистити всі повідомлення Ви впевнені, що хочете стерти всі повідомлення з вашої розмови з {name} з вашого пристрою? + Ви впевнені, що хочете видалити всі повідомлення з вашої розмови з {name} на цьому пристрої? Ви впевнені, що хочете стерти всі повідомлення {community_name} з вашого пристрою? + Ви впевнені, що хочете видалити всі повідомлення від {community_name} на цьому пристрої? Очистити для всіх Очистити для мене Ви впевнені, що хочете стерти всі повідомлення {group_name}? + Ви впевнені, що хочете видалити всі повідомлення з {group_name}? Ви впевнені, що хочете стерти всі повідомлення {group_name} з вашого пристрою? + Ви впевнені, що хочете видалити всі повідомлення з {group_name} на цьому пристрої? Ви впевнені, що хочете стерти всі повідомлення Note to Self з вашого пристрою? + Ви впевнені, що хочете видалити всі повідомлення в Нотатці для себе на цьому пристрої? + Очистити на цьому пристрої Закрити Закрити застосунок Закрити вікно @@ -289,6 +298,9 @@ Створити Викликаємо Вирізати + Ви впевнені, що хочете видалити всі повідомлення, вкладення та дані облікового запису з цього пристрою та створити новий обліковий запис? + Сталася помилка бази даних.\n\nЕкспортуйте журнали програми, щоб надати їх для усунення несправностей. Якщо це не допоможе, перевстановіть {app_name} та відновіть свій обліковий запис. + Ви впевнені, що хочете видалити всі повідомлення, вкладення та дані облікового запису з цього пристрою та відновити свій обліковий запис із мережі? Ми помітили, що {app_name} довго запускається.\n\nВи можете продовжити чекати, експортувати журнали вашого пристрою для аналізу або спробувати перезапустити {app_name}. База даних вашого додатку несумісна з цією версією {app_name}. Перевстановіть додаток та відновіть свій обліковий запис, щоб створити нову базу даних і продовжувати користуватися {app_name}.\n\nУвага: Це призведе до втрати всіх повідомлень та вкладень, старших двох тижнів. Оптимізація бази даних @@ -310,6 +322,7 @@ Будь ласка, зачекайте поки створюється група... Не вдалося оновити групу У вас немає прав на видалення інших повідомлень + Ви впевнені, що хочете видалити {name} зі своїх контактів?\n\nЦе призведе до видалення вашої розмови, включно з усіма повідомленнями та вкладеннями. Майбутні повідомлення від {name} відображатимуться як запит на повідомлення. Ви дійсно хочете видалити розмову з {name}?\n Це назавжди видалить усі повідомлення та вкладення. Видалити повідомлення @@ -407,6 +420,7 @@ Застосувати ім\'я, що показуватиметься Ваше відображуване ім\'я видно користувачам, групам і спільнотам, з якими ви взаємодієте. Документ + Підтримати Готово Завантажити Завантаження... @@ -458,6 +472,7 @@ Створити групу Будь ласка, виберіть принаймні 1 учасника групи. Видалити групу + Ви впевнені, що хочете видалити {group_name}?\n\nЦе призведе до видалення всіх учасників та всього вмісту групи. Ви дійсно бажаєте видалити {group_name}? {group_name} видалено адміністратором групи. Ви більше не зможете надсилати повідомлення. Введіть опис групи @@ -515,6 +530,7 @@ Назву групи оновлено. Назва групи видима всім учасникам групи. У вас немає повідомлень від {group_name}. Надішліть повідомлення, щоб розпочати розмову! + Ця група не оновлювалася понад 30 днів. У вас можуть виникнути проблеми з надсиланням повідомлень або переглядом інформації про групу. Ви — єдиний адміністратор у {group_name}.\n\nУчасники групи та налаштування не можуть бути змінені без адміністратора. Очікує видалення Вас підвищили до адміністратора. @@ -558,6 +574,7 @@ Будемо раді, якщо залишите нам відгук Приховати Увімкнути або вимкнути видимість панелі меню + Ви впевнені, що хочете приховати Нотатку для себе зі свого списку розмов? Сховати інші Зображення зображення @@ -645,6 +662,7 @@ Ділитися тільки новими повідомленнями Запросити Повідомлення + Читати далі Це повідомлення порожнє. Збій доставки повідомлення Досягнуто ліміту повідомлення @@ -686,6 +704,7 @@ Ви впевнені, що бажаєте очистити всі запити на повідомлення та запрошення до груп? Запити на повідомлення зі спільнот Дозволити запити на повідомлення зі спільнот. + Ви дійсно хочете видалити цей запит на надіслання повідомлення та пов\'язаний з ним контакт? Ви дійсно бажаєте видалити цей запит? У вас є новий запит на повідомлення Немає запитів на повідомлення @@ -703,6 +722,11 @@ {author}: {emoji} Голосове повідомлення Повідомлення Згорнути в трей + Довжина повідомлення + Ви перевищили максимальну кількість символів для цього повідомлення. Будь ласка, скоротіть ваше повідомлення до {limit} символів або менше. + Задовге повідомлення + Будь ласка, скоротіть повідомлення до {limit} символів або менше. + Задовге повідомлення Далі Виберіть псевдонім для {name}. Він з\'являтиметься в особистих та групових розмовах. Введіть псевдонім @@ -834,11 +858,27 @@ {app_name} потребує доступу до сховища для збереження вкладень та медіа. {app_name} потребує дозволу \"Зберігання\", щоб зберігати файли, але наразі цей дозвіл ви постійно відхиляли. Будь ласка, перейдіть до налаштувань додатку, оберіть \"Дозволи\", та увімкніть \"Зберігання\". {app_name} потребує доступу до сховища для відправлення фотографій та відео. + Вам не надано дозвіл на дописування у цій спільноті Закріпити Закріпити розмову Відкріпити Відкріпити розмову Попередній перегляд + активовано + У вас вже є + Не зволікайте і завантажуйте GIF та анімовані WebP картинки для свого аватара! + Отримайте анімовані аватари та розблокуйте преміальні функції з Session Pro + користувачі можуть завантажувати GIF + Завантажувати GIF з + Хочете відправляти довші повідомлення? Надсилайте більше тексту та розблокуйте преміальні функції застосунку з Session Pro + Потрібно більше закріплених бесід? Впорядкуйте свої чати та розблокуйте преміальні функції з Session Pro + Потрібно понад 5 закріплених бесід? Впорядкуйте свої бесіди та розблокуйте преміальні функції з Session Pro + Завантажуйте GIF та WebP аватари + Більша кількість — до 300 учасників — групових чатів + Та велика кількість ексклюзивних можливостей + Повідомлення до 10 000 символів + Закріплюйте необмежену кількість бесід + Надсилайте довші повідомлення з Профіль Встановити зображення для показу Не вдалося видалити зображення профілю @@ -894,6 +934,8 @@ Це ваш Recovery password. Якщо ви надішлете його комусь, він матиме повний доступ до вашого облікового запису. Оновити групу Вперед + Скоротіть довжину повідомлення на {count} + {count} символів залишилось Видалити Не вдалося видалити пароль Відповісти @@ -939,7 +981,15 @@ Допомога Запросити друга Запити на повідомлення + Поточна ціна {token_name_short} + Повідомлення надсилаються за допомогою {network_name}. Мережа складається з вузлів, заохочуваних {token_name_long}, що забезпечує децентралізацію та безпеку {app_name}. Дізнатися більше {icon} + Дізнайтеся про стейкінг + Ринкова капіталізація + {app_name} Вузли, що захищають ваші повідомлення + {app_name} Вузли у вашому рої + {token_name_long} запущено! Ознайомтеся з новим розділом {network_name} у Налаштуваннях, щоб дізнатися, як {token_name_long} забезпечує роботу Session. Мережа захищена + Коли ви робите стейкінг {token_name_long}, щоб захистити мережу, ви заробляєте винагороди у {token_name_short} з {staking_reward_pool}. Сповіщення Дозволи Конфіденційність @@ -957,6 +1007,8 @@ Перегляд Показати все Показати менше + Показати нотатку для себе + Ви впевнені, що хочете показувати Нотатку для себе у вашому списку розмов? Стікери Перейти на сторінку підтримки Про систему: {information} @@ -984,6 +1036,7 @@ Оновлення {app_name} Версія {version} Останнє оновлення {relative_time} тому + Підвищити до Вивантаження Копіювати URL Відкрити URL-адресу diff --git a/app/src/main/res/values-b+zh+CN/strings.xml b/app/src/main/res/values-b+zh+CN/strings.xml index f749e34894..7a20c2cb89 100644 --- a/app/src/main/res/values-b+zh+CN/strings.xml +++ b/app/src/main/res/values-b+zh+CN/strings.xml @@ -14,6 +14,8 @@ 这是您的账户ID。其他用户可以扫描它来与您开始会话。 实际尺寸 添加 + 添加管理员 + 请输入您正在授权为管理员的用户的帐户 ID。\n\n要添加多个用户,请输入用逗号分隔的每个帐户 ID。一次最多可以指定20个帐户 ID。 管理员无法被移除。 {name}和其他{count}名成员被设置为管理员。 授权为管理员 @@ -26,7 +28,9 @@ 将{name}授权为{group_name}的管理员失败 将{name}和其他{count}人授权为{group_name}的管理员失败 将{name}和{other_name}授权为{group_name}的管理员失败 + 授权未发送 管理员授权已发送 + 授权状态未知 移除管理员 作为管理员移除 此社群没有管理员。 @@ -43,6 +47,20 @@ {name}{other_name}被设置为管理员。 +{count} 匿名用戶 + 应用图标 + 替代的应用图标与应用名会显示在主页和应用抽屉中。 + 图标与应用名 + 替代的应用图标会显示在主页和应用列表中。应用名仍会显示为“{app_name}”。 + 使用替代的应用图标 + 使用替代的应用图标与应用名 + 选择替代的应用图标 + 图标 + 计算器 + SE云会议 + 新闻 + 笔记 + 股票 + 天气 自动开启深色模式 隐藏菜单栏 语言 @@ -60,6 +78,7 @@ 放大 缩小 附件 + 附件 添加附件 未命名的相册 自动下载附件 @@ -121,10 +140,13 @@ 禁言失败 解封失败 解封用户 + 输入您想取消封禁的用户的帐户 ID 用户已被解封 禁言该用户 用户已被封禁 + 输入您想封禁的用户的帐户 ID 屏蔽 + 取消屏蔽此联系人以发送消息 没有屏蔽的联系人 已屏蔽{name} 您确定要屏蔽{name}吗?被屏蔽的用户将无法向您发送消息请求、群聊邀请或者语音通话。 @@ -256,7 +278,9 @@ 已复制 复制 创建 + 正在创建通话 剪切 + 发生数据库错误。\n\n请导出您的应用日志以进行故障排除。如果不成功,请重新安装{app_name}并恢复您的帐户。 我们注意到{app_name}启动时间过长。\n\n您可以选择继续等待,导出设备日志以分享故障排除,或尝试重新启动{app_name}。 您的应用数据库与此版本的{app_name}不兼容。请重新安装应用并恢复您的账户以生成新的数据库并继续使用{app_name}。\n\n警告:该操作将导致两周前的所有消息和附件丢失。 正在优化数据库 @@ -278,6 +302,8 @@ 正在创建群组,请稍候... 更新群组失败 您无权删除他人的消息 + 您确定要删除联系人{name}吗?\n\n该操作将删除你们的会话,包括所有消息和附件。来自{name}的新消息将被视为消息请求。 + 您确定要删除您与{name}的会话吗?\n该操作将永久删除所有消息和附件。 删除消息 @@ -385,10 +411,12 @@ 复制错误并退出 数据库错误 发生了未知错误。 + 下载失败 失败 文件 文件 匹配系统设置 + 永久 发送自: 全屏 GIF @@ -398,6 +426,7 @@ 创建群组 请至少选择一名群组成员。 删除群组 + 您确定要删除{group_name}吗?\n\n这将移除所有成员并删除所有群组内容。 你确定要删除群组 {group_name}吗? {group_name} 已被群组管理员删除。您将无法再发送任何信息。 输入群组描述 @@ -412,12 +441,14 @@ 邀请{name}和其他{count}位成员加入{group_name}失败 邀请{name}和{other_name}加入{group_name}失败 邀请{name}加入{group_name}失败 + 邀请未发送 {name}邀请您重新加入{group_name},您是该群组的管理员。 有人邀请您重新加入{group_name},您是该群的管理员。 正在发送邀请 邀请已发送 + 邀请状态未知 群组邀请成功 用户必须使用最新版本才能接受邀请 被邀请加入群组。 @@ -450,10 +481,12 @@ 群组名称已更新。 群组名称对所有群组成员可见。 您没有来自{group_name}的消息。发送一条消息开始会话! + 此群组已超过 30 天未更新。您可能在发送消息或查看群组信息时遇到问题。 您是{group_name}中唯一的管理员。\n\n没有管理员,群组成员和设置将无法被更改。 待移除 被设置为管理员。 和其他{count}人被授权为管理员。 + {other_name}已被授权为管理员。 你想将 {name}{group_name} 中移除吗? 您希望将{name}和其他{count}人{group_name}中移除吗? 您希望将{name}{other_name}{group_name}中移除吗? @@ -487,15 +520,24 @@ 切换系统菜单栏可见性 隐藏其它 图片 + 图片 无痕键盘 启用隐身模式(如果可用)。您正在使用的键盘可能会忽略此请求。 信息 无效的快捷方式 + + 无法发送邀请。您想重试吗? + 加入 稍后 了解更多 离开 退出中... + 此群组目前为只读状态。请重新创建此群组以继续聊天。 + 此群组目前为只读状态。请要求管理员重新创建此群组以继续聊天。 + 群组已升级!请重新创建此群组以提高可靠性。此群组将于{date}变为只读状态。 + 群组已升级!请要求群组管理员重新创建此群组以提高可靠性。此群组将于{date}变为只读状态。 + 聊天记录被不会转移到新群组。您仍然可以查看旧群组中的所有聊天记录。 {name}加入了群组。 {name}和其他{count}名成员加入了群组。 和其他{count}人加入了群组。 @@ -524,6 +566,7 @@ 锁定状态 点击解锁 {app_name}已解锁 + 管理成员 最大 媒体 @@ -560,6 +603,9 @@ 您有%1$d条新消息。 + + 您在%1$s中收到了%2$d条新消息。 + 回复 {name}邀请您加入{group_name} 向此群组发送消息将会自动接受群组邀请。 @@ -610,6 +656,7 @@ 不显示发送者和信息内容 高速模式 您将会收到由Google的通知服务器发出的即时可靠的新消息通知。 + 您将会收到由华为的通知服务器发出的即时可靠的新消息通知。 您将会收到由Apple的通知服务器发出的即时可靠的新消息通知。 跳转到设备通知设置 通知 - 所有 @@ -625,6 +672,7 @@ 免打扰{time_large} 关闭免打扰 免打扰 + 已设置免打扰{time_large} 慢速模式 {app_name}将不时在后台获取新消息。 提示音 @@ -683,20 +731,32 @@ 设置密码 您的密码已设定。请妥善保管。 粘帖 + 授权变更 {app_name}需要音乐和音频权限才能发送文件、音乐和音频,但该权限已被永久拒绝。请进入应用程序设置→权限,打开“音乐和音频”权限。 {app_name}需要使用Apple Music来播放媒体附件。 自动更新 启动时自动检查更新 + 需要摄像头访问权限才能进行通话。在设置中允许“摄像头”权限以继续。 + 摄像头访问权限已允许。如需禁用,请在设置中禁用“摄像头”权限。 {app_name}需要相机权限来拍摄照片和视频,但是该权限已被永久拒绝。请点击设置 → 权限,并启用\"相机\"。 + 请允许访问摄像头以进行视频通话。 {app_name}的屏幕锁功能使用 Face ID。 保留在系统托盘 {app_name}会在您关闭窗口后继续在后台运行 {app_name}需要照片库访问权限以继续。您可以在iOS设置中启用访问。 + 需要本地网络访问权限才能进行通话。在设置中允许“本地网络”权限以继续。 + {app_name}需要访问本地网络才能进行语音和视频通话。 + 本地网络访问权限已允许。如需禁用,请在设置中禁用“本地网络”权限。 + 请允许访问本地网络访问以进行语音和视频通话。 + 本地网络 麦克风 {app_name}需要麦克风权限来进行语音通话和发送语音消息,但是该权限已被永久拒绝。请点击设置 → 权限,并启用\"麦克风\"。 + 需要麦克风访问权限以进行通话和录制语音消息。在设置中打开“麦克风”权限以继续。 您可以在{app_name}的隐私设置中启用麦克风访问权限。 {app_name}需要麦克风访问权限来进行语音通话及录制语音消息。 + 麦克风访问权限已允许。如需禁用,请在设置中禁用“麦克风”权限。 允许访问麦克风。 + 请允许访问麦克风以进行语音通话和录制语音消息。 {app_name}需要音乐和音频访问才能发送文件、音乐和音频。 需要相应权限 {app_name}需要照片权限才能发送照片和视频,但该权限已被永久拒绝。请进入应用程序设置→权限,打开“照片与视频”权限。 @@ -716,6 +776,12 @@ 请选择一个更小的文件。 更新资料失败。 授权 + + 授权失败 + + + 无法授权。您想重试吗? + 二维码 此二维码不含账户ID 此二维码不含恢复密码 @@ -728,11 +794,14 @@ 已读回执 显示您发送和接收的所有消息的已读回执。 已接收: + 已收到应答 + 正在接收通话邀请 推荐选项 保存您的恢复密码以确保您不会失去对账户的访问权限。 保存您的恢复密码 使用您的恢复密码在新设备上加载您的帐户。\n\n没有您的恢复密码,您的帐户将无法恢复。请确保将它存储在安全的地方,并且不要与任何人分享。 输入您的恢复密码 + 尝试加载您的恢复密码时发生错误。\n\n请导出您的日志,然后通过{app_name}帮助服务台上传文件以帮助解决此问题。 请检查您的恢复密码并重试。 你的恢复密码中的一些词语不正确。请检查后重试。 您输入的恢复密码长度不够。请检查后重试。 @@ -746,6 +815,7 @@ 输入您的恢复密码以加载您的账户。如果您没有保存它,您可以在应用设置中找到该恢复密码。 查看密码 这是您的恢复密码。如果您发送给他人,他们将能够完全访问您的账户。 + 重新创建群组 重做 移除 移除密码失败 @@ -779,6 +849,7 @@ 全选 发送 发送中 + 正在发送通话邀请 发送: 外观 清除数据 @@ -792,11 +863,13 @@ 恢复密码 设置 设置 + 设置社群头像 您必须重新启动{app_name}以应用您的新设置。 分享 通过与好友分享您的账号ID来邀请他们与您在{app_name}上聊天 随时随地和朋友分享,并将会话移到此处。 打开数据库时出现问题。请重新启动应用程序并重试。 + 哎呀!您似乎还没有{app_name}帐户。\n\n您需要先在{app_name}应用中创建一个帐户,然后才能分享。 分享到{app_name} 显示 全部显示 @@ -804,6 +877,7 @@ 贴图 跳转到支持页面 系统信息:{information} + 点击以重试 继续 默认 错误 @@ -817,7 +891,11 @@ 正在下载更新:{percent_loader}% 无法更新 {app_name}更新失败。请返回{session_download_url}并手动安装新版本,并联系帮助中心反馈此问题。 + 更新群组信息 + 群组名称与描述对所有群组成员可见。 + 请输入更简短的群组描述 有新版本的{app_name}可用,点击更新 + {app_name}有新版本({version})可用。 跳转到版本信息 {app_name}更新 版本 {version} @@ -830,6 +908,8 @@ 视频 无法播放视频。 查看 + 查看更少 + 查看更多 这可能需要几分钟的时间。 请稍候... 警告 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b9e1588332..2cc5953c84 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -683,8 +683,8 @@ Messages Minimize - Messages have a character limit of %1$s characters. You have %2$d character remaining - Messages have a character limit of %1$s characters. You have %2$d characters remaining + Messages have a character limit of %1$s characters. You have %2$d character remaining. + Messages have a character limit of %1$s characters. You have %2$d characters remaining. Message Length You have exceeded the character limit for this message. Please shorten your message to {limit} characters or less. @@ -828,9 +828,16 @@ Unpin Unpin Conversation Preview + Activated + You’ve already got + Go ahead and upload GIFs and animated WebP images for your display picture! + Get animated display pictures and unlock premium features with Session Pro + users can upload GIFs + Upload GIFs with Want to send longer messages? Send more text and unlock premium features with Session Pro Want more pins? Organize your chats and unlock premium features with Session Pro Want more than 5 pins? Organize your chats and unlock premium features with Session Pro + Upload GIF and WebP display pictures Larger group chats up to 300 members Plus loads more exclusive features Messages up to 10,000 characters From 5351dfb8feeb1ae21832f150c98800013013d3cd Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Wed, 9 Jul 2025 10:13:59 +1000 Subject: [PATCH 35/52] Fixes conflict --- .../conversation/v2/ConversationActivityV2.kt | 8 +++---- .../conversation/v2/ConversationViewModel.kt | 6 ------ .../conversation/v2/input_bar/InputBar.kt | 2 +- .../securesms/mediasend/MediaSendActivity.kt | 2 +- .../securesms/mediasend/MediaSendFragment.kt | 21 ++++++++----------- .../notifications/MarkReadReceiver.kt | 1 - 6 files changed, 15 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 5e9b1df590..65cad83ebe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -880,7 +880,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, AttachmentManager.MediaType.VIDEO == mediaType) ) { val media = Media(mediaURI, filename, mimeType, 0, 0, 0, 0, null, null) - startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), viewModel.recipient!!.address, threadId, getMessageBody()), PICK_FROM_LIBRARY) + startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), viewModel.recipient!!.address, viewModel.threadId, getMessageBody()), PICK_FROM_LIBRARY) return } else { prepMediaForSending(mediaURI, mediaType).addListener(object : ListenableFuture.Listener { @@ -1999,7 +1999,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, val mimeType = MediaUtil.getMimeType(this, contentUri)!! val filename = FilenameUtils.getFilenameFromUri(this, contentUri, mimeType) val media = Media(contentUri, filename, mimeType, 0, 0, 0, 0, null, null) - startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), recipient.address, threadId, getMessageBody()), PICK_FROM_LIBRARY) + startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), recipient.address, viewModel.threadId, getMessageBody()), PICK_FROM_LIBRARY) } // If we previously approve this recipient, either implicitly or explicitly, we need to wait for @@ -2174,11 +2174,11 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, private fun pickFromLibrary() { val recipient = viewModel.recipient ?: return binding.inputBar.text?.trim()?.let { text -> - AttachmentManager.selectGallery(this, PICK_FROM_LIBRARY, recipient.address, threadId, getMessageBody()) + AttachmentManager.selectGallery(this, PICK_FROM_LIBRARY, recipient.address, viewModel.threadId, getMessageBody()) } } - private fun showCamera() { attachmentManager.capturePhoto(this, TAKE_PHOTO, viewModel.recipient?.address, threadId) } + private fun showCamera() { attachmentManager.capturePhoto(this, TAKE_PHOTO, viewModel.recipient?.address, viewModel.threadId) } override fun onAttachmentChanged() { /* Do nothing */ } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 2872ae522c..82a1fac6d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -249,12 +249,6 @@ class ConversationViewModel @AssistedInject constructor( val shouldExit: Flow get() = recipientFlow.map { it == null } - val inputBarState: StateFlow = combine( - recipientFlow, openGroupFlow, legacyGroupDeprecationManager.deprecationState, - this::getInputBarState - ).stateIn(viewModelScope, SharingStarted.Eagerly, InputBarState()) - - private val _acceptingMessageRequest = MutableStateFlow(false) val messageRequestState: StateFlow = combine( recipientFlow, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index d62e672dd1..b8dd0416f4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -348,7 +348,7 @@ class InputBar @JvmOverloads constructor( allowAttachMultimediaButtons = state.enableAttachMediaControls } - fun setCharLimitState(state: InputBarCharLimitState?) { + fun setCharLimitState(state: InputbarViewModel.InputBarCharLimitState?) { // handle char limit if(state != null){ binding.characterLimitText.text = state.count.toString() diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt index 3c23cf8cdc..c77ff0de17 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt @@ -533,7 +533,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme */ @JvmStatic fun buildCameraIntent(context: Context, recipient: Address, threadId: Long): Intent { - val intent = buildGalleryIntent(context, recipient, "") + val intent = buildGalleryIntent(context, recipient, threadId, "") intent.putExtra(KEY_IS_CAMERA, true) return intent } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt index a2199fb53e..7889e066ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt @@ -13,8 +13,7 @@ import android.widget.TextView import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat +import androidx.core.os.BundleCompat import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.fragment.app.Fragment @@ -27,10 +26,10 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.viewpager.widget.ViewPager.SimpleOnPageChangeListener import com.bumptech.glide.Glide import dagger.hilt.android.AndroidEntryPoint +import dagger.hilt.android.lifecycle.withCreationCallback import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.future.await import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope @@ -38,14 +37,10 @@ import kotlinx.coroutines.withContext import network.loki.messenger.databinding.MediasendFragmentBinding import org.session.libsession.utilities.Address import org.session.libsession.utilities.MediaTypes -import org.session.libsession.utilities.TextSecurePreferences.Companion.isEnterSendsEnabled -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.InputBarDialogs -import org.thoughtcrime.securesms.conversation.v2.ConversationV2Dialogs import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate import org.thoughtcrime.securesms.conversation.v2.mention.MentionViewModel -import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter.RailItemListener import org.thoughtcrime.securesms.providers.BlobUtils @@ -56,7 +51,6 @@ import org.thoughtcrime.securesms.util.hideKeyboard import java.io.File import java.io.FileInputStream import java.io.FileOutputStream -import javax.inject.Inject /** * Allows the user to edit and caption a set of media items before choosing to send them. @@ -77,10 +71,13 @@ class MediaSendFragment : Fragment(), RailItemListener, InputBarDelegate { private val threadId: Long get() = arguments?.getLong(KEY_THREADID) ?: -1L - @Inject lateinit var mentionViewModelFactory: MentionViewModel.AssistedFactory - private val mentionViewModel: MentionViewModel by viewModels { - mentionViewModelFactory.create(threadId) - } + private val mentionViewModel: MentionViewModel by viewModels(extrasProducer = { + defaultViewModelCreationExtras.withCreationCallback { + it.create( + requireNotNull(BundleCompat.getParcelable(requireArguments(), KEY_ADDRESS, Address::class.java)) + ) + } + }) override fun onAttach(context: Context) { super.onAttach(context) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt index 5a828f59e8..6e640b8d9c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt @@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.conversation.disappearingmessages.ExpiryType import org.thoughtcrime.securesms.database.ExpirationInfo import org.thoughtcrime.securesms.database.MarkedMessageInfo import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.util.SessionMetaProtocol.shouldSendReadReceipt import javax.inject.Inject @AndroidEntryPoint From c9a34de5dffec9d05780b63564d150d2fa8f19ff Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Wed, 9 Jul 2025 10:17:18 +1000 Subject: [PATCH 36/52] Adjust gradle memory (#1307) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ff6e0d83a7..3dfd62b432 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ # org.gradle.parallel=true #Mon Jun 26 09:56:43 AEST 2023 android.enableJetifier=false -org.gradle.jvmargs=-Xmx3072m +org.gradle.jvmargs=-Xmx2048m android.useAndroidX=true android.nonTransitiveRClass=false From 36b09f885e8acd205feeb0f746fa832fa071c79e Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Wed, 9 Jul 2025 10:53:08 +1000 Subject: [PATCH 37/52] Increase gradle memory --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 3dfd62b432..031fe5447f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ # org.gradle.parallel=true #Mon Jun 26 09:56:43 AEST 2023 android.enableJetifier=false -org.gradle.jvmargs=-Xmx2048m +org.gradle.jvmargs=-Xmx4096m android.useAndroidX=true android.nonTransitiveRClass=false From 1f62d4477ab36f9465cc3a2e5244f830d0eab520 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Wed, 9 Jul 2025 13:31:20 +1000 Subject: [PATCH 38/52] Tweak worker count and kotlin jvmargs --- gradle.properties | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gradle.properties b/gradle.properties index 031fe5447f..39447ad686 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,8 +12,15 @@ # org.gradle.parallel=true #Mon Jun 26 09:56:43 AEST 2023 android.enableJetifier=false + org.gradle.jvmargs=-Xmx4096m +# Limit the worker threads to avoid consuming too much memory +org.gradle.workers.max=4 +org.gradle.worker.heap.size=512m + +kotlin.daemon.jvmargs=-Xmx1024m + android.useAndroidX=true android.nonTransitiveRClass=false android.nonFinalResIds=false From 362ba123068fe6cd4e5ff1a068a74775c225159b Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Wed, 9 Jul 2025 13:31:20 +1000 Subject: [PATCH 39/52] Tweak worker count and kotlin jvmargs --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 39447ad686..5baed3a4eb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,13 +13,13 @@ #Mon Jun 26 09:56:43 AEST 2023 android.enableJetifier=false -org.gradle.jvmargs=-Xmx4096m +org.gradle.jvmargs=-Xmx3072m # Limit the worker threads to avoid consuming too much memory org.gradle.workers.max=4 org.gradle.worker.heap.size=512m -kotlin.daemon.jvmargs=-Xmx1024m +kotlin.daemon.jvmargs=-Xmx3072m android.useAndroidX=true android.nonTransitiveRClass=false From da533a1f1375af93800960ef485222270ca8445a Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Wed, 9 Jul 2025 17:09:05 +1000 Subject: [PATCH 40/52] Do not update teh recipient class for no reason. Also fixes SES-4182 about the lost char limit state --- .../sending_receiving/ReceivedMessageHandler.kt | 16 +++++++++------- .../utilities/recipients/Recipient.java | 3 +++ .../conversation/v2/ConversationActivityV2.kt | 2 -- .../conversation/v2/ConversationViewModel.kt | 13 +++++++++---- .../thoughtcrime/securesms/database/Storage.kt | 6 +++--- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 6be4745ed3..5d13006890 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -355,7 +355,7 @@ fun MessageReceiver.handleVisibleMessage( val isUserBlindedSender = messageSender == context.userBlindedKey if (profile != null && userPublicKey != messageSender && !isUserBlindedSender) { val name = profile.displayName!! - if (name.isNotEmpty()) { + if (name.isNotEmpty() && name != recipient.rawName) { context.profileManager.setName(context.context, recipient, name) } val newProfileKey = profile.profileKey @@ -371,7 +371,7 @@ fun MessageReceiver.handleVisibleMessage( } } - if (userPublicKey != messageSender && !isUserBlindedSender) { + if (userPublicKey != messageSender && !isUserBlindedSender && message.blocksMessageRequests != recipient.blocksCommunityMessageRequests) { context.storage.setBlocksCommunityMessageRequests(recipient, message.blocksMessageRequests) } @@ -380,11 +380,13 @@ fun MessageReceiver.handleVisibleMessage( proto.dataMessage.expireTimer > 0 && !proto.hasExpirationType() -> Recipient.DisappearingState.LEGACY else -> Recipient.DisappearingState.UPDATED } - context.storage.updateDisappearingState( - messageSender, - context.threadId, - disappearingState - ) + if(disappearingState != recipient.disappearingState) { + context.storage.updateDisappearingState( + messageSender, + context.threadId, + disappearingState + ) + } } // Handle group invite response if new closed group if (context.threadRecipient?.isGroupV2Recipient == true) { diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java index 71c0874477..61a6b806d7 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java +++ b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.java @@ -333,6 +333,9 @@ public void setContactUri(@Nullable Uri contactUri) { } } + //todo SESSIONHERO refactor: This can be removed + public synchronized String getRawName() { return name; } + public void setName(@Nullable String name) { boolean notify = false; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 48bb48970e..19d0c21118 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -1136,8 +1136,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, // region Animation & Updating override fun onModified(recipient: Recipient) { - viewModel.updateRecipient() - runOnUiThread { invalidateOptionsMenu() updateSendAfterApprovalText() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 53daf3a682..282e808249 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -390,11 +390,13 @@ class ConversationViewModel( community: OpenGroup?, deprecationState: LegacyGroupDeprecationManager.DeprecationState ): InputBarState { + val currentCharLimitState = _inputBarState.value.charLimitState return when { // prioritise cases that demand the input to be hidden !shouldShowInput(recipient, community, deprecationState) -> InputBarState( contentState = InputBarContentState.Hidden, - enableAttachMediaControls = false + enableAttachMediaControls = false, + charLimitState = currentCharLimitState ) // next are cases where the input is visible but disabled @@ -406,7 +408,8 @@ class ConversationViewModel( _uiEvents.tryEmit(ConversationUiEvent.ShowUnblockConfirmation) } ), - enableAttachMediaControls = false + enableAttachMediaControls = false, + charLimitState = currentCharLimitState ) // the user does not have write access in the community @@ -414,13 +417,15 @@ class ConversationViewModel( contentState = InputBarContentState.Disabled( text = application.getString(R.string.permissionsWriteCommunity), ), - enableAttachMediaControls = false + enableAttachMediaControls = false, + charLimitState = currentCharLimitState ) // other cases the input is visible, and the buttons might be disabled based on some criteria else -> InputBarState( contentState = InputBarContentState.Visible, - enableAttachMediaControls = shouldEnableInputMediaControls(recipient) + enableAttachMediaControls = shouldEnableInputMediaControls(recipient), + charLimitState = currentCharLimitState ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 995d01275c..c3dc4e4c62 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -400,10 +400,10 @@ open class Storage @Inject constructor( } val targetRecipient = Recipient.from(context, targetAddress, false) if (!targetRecipient.isGroupOrCommunityRecipient) { - if (isUserSender || isUserBlindedSender) { + if ((isUserSender || isUserBlindedSender) && !targetRecipient.isApproved) { setRecipientApproved(targetRecipient, true) - } else { - setRecipientApprovedMe(targetRecipient, true) + } else if(!targetRecipient.hasApprovedMe()){ + setRecipientApprovedMe(targetRecipient, true) } } if (message.threadID == null && !targetRecipient.isCommunityRecipient) { From bc1107f3ed7ce7b6fd38bf08b3d71f33be059d3c Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Wed, 9 Jul 2025 17:17:58 +1000 Subject: [PATCH 41/52] SES-4181 - Preserve message body when adding a photo --- .../conversation/v2/ConversationActivityV2.kt | 10 +++++++++- .../conversation/v2/utilities/AttachmentManager.java | 4 ++-- .../securesms/mediasend/MediaSendActivity.kt | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 19d0c21118..7f28201f21 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -2139,7 +2139,15 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, getMessageBody()) } - private fun showCamera() { attachmentManager.capturePhoto(this, TAKE_PHOTO, viewModel.recipient, threadId) } + private fun showCamera() { + attachmentManager.capturePhoto( + this, + TAKE_PHOTO, + viewModel.recipient, + threadId, + getMessageBody() + ) + } override fun onAttachmentChanged() { /* Do nothing */ } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java index 2547733536..5e92dd913f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java @@ -318,7 +318,7 @@ public static void selectGif(Activity activity, int requestCode) { return captureUri; } - public void capturePhoto(Activity activity, int requestCode, Recipient recipient, @NonNull long threadId) { + public void capturePhoto(Activity activity, int requestCode, Recipient recipient, @NonNull long threadId, @NonNull String body) { String cameraPermissionDeniedTxt = Phrase.from(context, R.string.permissionsCameraDenied) .put(APP_NAME_KEY, context.getString(R.string.app_name)) @@ -328,7 +328,7 @@ public void capturePhoto(Activity activity, int requestCode, Recipient recipient .request(Manifest.permission.CAMERA) .withPermanentDenialDialog(cameraPermissionDeniedTxt) .onAllGranted(() -> { - Intent captureIntent = MediaSendActivity.buildCameraIntent(activity, recipient, threadId); + Intent captureIntent = MediaSendActivity.buildCameraIntent(activity, recipient, threadId, body); if (captureIntent.resolveActivity(activity.getPackageManager()) != null) { activity.startActivityForResult(captureIntent, requestCode); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt index 82b7220274..7a2b925bdf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt @@ -531,8 +531,8 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme * Get an intent to launch the media send flow starting with the camera. */ @JvmStatic - fun buildCameraIntent(context: Context, recipient: Recipient, threadId: Long): Intent { - val intent = buildGalleryIntent(context, recipient, threadId, "") + fun buildCameraIntent(context: Context, recipient: Recipient, threadId: Long, body: String): Intent { + val intent = buildGalleryIntent(context, recipient, threadId, body) intent.putExtra(KEY_IS_CAMERA, true) return intent } From 44e0c9c669e1a8d5b3238b64f2c8c18a220194f5 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Thu, 10 Jul 2025 09:22:54 +1000 Subject: [PATCH 42/52] Fix reaction notification issues on restoring (#1308) --- .../org/thoughtcrime/securesms/database/MmsSmsDatabase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 333843241e..564ddd075c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -426,7 +426,7 @@ public Cursor getUnreadOrUnseenReactions() { "(" + READ + " = 0 AND " + NOTIFIED + " = 0 AND NOT (" + outgoingCondition + "))" + // A " OR (" + - ReactionDatabase.TABLE_NAME + "." + ReactionDatabase.DATE_RECEIVED + " > (" + lastSeenQuery +") AND (" + + ReactionDatabase.TABLE_NAME + "." + ReactionDatabase.DATE_SENT + " > (" + lastSeenQuery +") AND (" + outgoingCondition + "))"; // B String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " ASC"; From 1665fa6f5bf09d1fb5bbbc2e8a1921a19cf0a08c Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:45:09 +1000 Subject: [PATCH 43/52] BlindMapping WIP --- .../libsession/database/StorageProtocol.kt | 2 - .../libsession/messaging/BlindedIdMapping.kt | 8 -- .../messages/ProfileUpdateHandler.kt | 91 ++++++------------ .../messaging/open_groups/OpenGroup.kt | 25 +++++ .../ReceivedMessageHandler.kt | 4 +- .../pollers/OpenGroupPoller.kt | 17 ++-- .../session/libsession/utilities/Address.kt | 5 + .../utilities/recipients/Recipient.kt | 1 + .../conversation/v2/ConversationActivityV2.kt | 17 +++- .../v2/utilities/AttachmentManager.java | 4 +- .../database/BlindMappingRepository.kt | 94 +++++++++++++++++++ .../database/BlindedIdMappingDatabase.kt | 60 +----------- .../securesms/database/RecipientRepository.kt | 9 +- .../securesms/database/Storage.kt | 63 +------------ .../securesms/mediasend/MediaSendActivity.kt | 18 ++-- .../securesms/mediasend/MediaSendFragment.kt | 17 ++-- gradle/libs.versions.toml | 2 +- 17 files changed, 204 insertions(+), 233 deletions(-) delete mode 100644 app/src/main/java/org/session/libsession/messaging/BlindedIdMapping.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/BlindMappingRepository.kt diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 3460825009..e6bb8a74be 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -4,7 +4,6 @@ import android.content.Context import android.net.Uri import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.KeyPair -import org.session.libsession.messaging.BlindedIdMapping import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job @@ -228,7 +227,6 @@ interface StorageProtocol { fun getLastOutboxMessageId(server: String): Long? fun setLastOutboxMessageId(server: String, messageId: Long) fun removeLastOutboxMessageId(server: String) - fun getOrCreateBlindedIdMapping(blindedId: String, server: String, serverPublicKey: String, fromOutbox: Boolean = false): BlindedIdMapping /** * Add reaction to a message that has the timestamp given by [reaction]. This is less than diff --git a/app/src/main/java/org/session/libsession/messaging/BlindedIdMapping.kt b/app/src/main/java/org/session/libsession/messaging/BlindedIdMapping.kt deleted file mode 100644 index d00abd28c3..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/BlindedIdMapping.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.session.libsession.messaging - -data class BlindedIdMapping( - val blindedId: String, - val accountId: String?, - val serverUrl: String, - val serverId: String -) \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt b/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt index de39ee3c97..6b75e2f0b7 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt @@ -1,17 +1,16 @@ package org.session.libsession.messaging.messages -import dagger.Lazy -import network.loki.messenger.BuildConfig +import network.loki.messenger.libsession_util.util.BaseCommunityInfo import network.loki.messenger.libsession_util.util.UserPic -import org.session.libsession.database.StorageProtocol import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.database.BlindedIdMappingDatabase +import org.thoughtcrime.securesms.database.BlindMappingRepository import org.thoughtcrime.securesms.database.RecipientDatabase +import java.util.EnumSet import javax.inject.Inject import javax.inject.Singleton @@ -27,30 +26,11 @@ import javax.inject.Singleton class ProfileUpdateHandler @Inject constructor( private val configFactory: ConfigFactoryProtocol, private val recipientDatabase: RecipientDatabase, - private val blindedIdMappingDatabase: BlindedIdMappingDatabase, private val prefs: TextSecurePreferences, - private val storage: Lazy, + private val blindIdMappingRepository: BlindMappingRepository, ) { - fun handleProfileUpdate(sender: Address, updates: Updates, communityServerPubKey: String?) { - val senderAccountId = AccountId.fromStringOrNull(sender.address) - if (senderAccountId == null) { - Log.e(TAG, "Invalid sender address") - return - } - - handleProfileUpdate(senderAccountId, updates, communityServerPubKey) - } - - fun handleProfileUpdate(sender: AccountId, updates: Updates, communityServerPubKey: String?) { - if (sender.hexString == prefs.getLocalNumber() || - (communityServerPubKey != null && storage.get().getUserBlindedAccountId(communityServerPubKey) == sender) - ) { - Log.w(TAG, "Ignoring profile update for local number") - return - } - - + fun handleProfileUpdate(sender: Address, updates: Updates, fromCommunity: BaseCommunityInfo?) { if (updates.name.isNullOrBlank() && updates.pic == null && updates.acceptsCommunityRequests == null @@ -59,29 +39,32 @@ class ProfileUpdateHandler @Inject constructor( return } + // Unblind the sender if it's blinded and we have information to unblind it. + val actualSender = if (IdPrefix.fromValue(sender.address)?.isBlinded() == true && fromCommunity != null) { + blindIdMappingRepository.getMapping(fromCommunity.baseUrl, sender) + } else { + sender + } ?: sender - Log.d(TAG, "Handling profile update for $sender") + if (actualSender.address == prefs.getLocalNumber()) { + Log.w(TAG, "Ignoring profile update for local number") + return + } - // If the sender is blind, we need to figure out their real address first. - val actualSender = - if (sender.prefix == IdPrefix.BLINDED || sender.prefix == IdPrefix.BLINDEDV2) { - blindedIdMappingDatabase.getBlindedIdMapping(sender.hexString) - .firstNotNullOfOrNull { it.accountId } - ?.let(AccountId::fromStringOrNull) - ?: sender - } else { - sender - } + val actualSenderIdPrefix = IdPrefix.fromValue(actualSender.address) - if (actualSender.hexString == prefs.getLocalNumber()) { - Log.w(TAG, "Ignoring profile update for local number: $actualSender") + if (actualSenderIdPrefix == null || + actualSenderIdPrefix !in EnumSet.of(IdPrefix.STANDARD, IdPrefix.BLINDED, IdPrefix.BLINDEDV2)) { + Log.w(TAG, "Unsupported profile update for sender: $sender (actualSender: $actualSender)") return } + Log.d(TAG, "Handling profile update for $sender") + // First, if the user is a contact, update the config and that's all we need to do. - val isExistingContact = actualSender.prefix == IdPrefix.STANDARD && + val isExistingContact = actualSenderIdPrefix == IdPrefix.STANDARD && configFactory.withMutableUserConfigs { configs -> - val existingContact = configs.contacts.get(actualSender.hexString) + val existingContact = configs.contacts.get(actualSender.address) if (existingContact != null) { configs.contacts.set( existingContact.copy( @@ -100,27 +83,13 @@ class ProfileUpdateHandler @Inject constructor( return } - // If the actual sender is still blinded or unknown contact, we need to update their - // settings in the recipient database instead, as we don't have a place in the config - // for them. - if (actualSender.prefix == IdPrefix.BLINDED || actualSender.prefix == IdPrefix.BLINDEDV2 || - actualSender.prefix == IdPrefix.STANDARD - ) { - Log.d(TAG, "Updating recipient profile for $actualSender") - recipientDatabase.updateProfile( - Address.fromSerialized(actualSender.hexString), - updates.name, - updates.pic, - updates.acceptsCommunityRequests - ) - return - } - - if (BuildConfig.DEBUG) { - throw IllegalArgumentException("Unsupported profile update for sender: $sender") - } - - Log.e(TAG, "Unsupported profile updating for sender: $sender") + Log.d(TAG, "Updating recipient profile for $actualSender") + recipientDatabase.updateProfile( + actualSender, + updates.name, + updates.pic, + updates.acceptsCommunityRequests + ) } data class Updates( diff --git a/app/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt b/app/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt index 889bf125b9..43966f3517 100644 --- a/app/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt +++ b/app/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt @@ -1,7 +1,10 @@ package org.session.libsession.messaging.open_groups +import network.loki.messenger.libsession_util.util.BaseCommunityInfo import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.GroupUtil import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log import java.util.Locale @@ -65,6 +68,28 @@ data class OpenGroup( } return builder.build() } + + /** + * Returns the group ID for this community info. The group ID is the session android unique + * way of identifying a community. It itself isn't super useful but it's used to construct + * the [Address] for communities. + * + * See [toAddress] + */ + val BaseCommunityInfo.groupId: String + get() = "${baseUrl}.${room}" + + fun BaseCommunityInfo.toAddress(): Address { + return Address.fromSerialized(GroupUtil.getEncodedOpenGroupID(groupId.toByteArray())) + } + } + + fun toCommunityInfo(): BaseCommunityInfo { + return BaseCommunityInfo( + baseUrl = server, + room = room, + pubKeyHex = publicKey, + ) } fun toJson(): Map = mapOf( diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 8f29d9872d..e24354295b 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -337,7 +337,7 @@ fun MessageReceiver.handleVisibleMessage( picKey = message.profile?.profileKey, acceptsCommunityRequests = !message.blocksMessageRequests ), - communityServerPubKey = context.openGroup?.publicKey + fromCommunity = context.openGroup?.toCommunityInfo(), ) } // Handle group invite response if new closed group @@ -563,7 +563,7 @@ private fun MessageReceiver.handleGroupUpdated(message: GroupUpdated, closedGrou picKey = message.profile?.profileKey, acceptsCommunityRequests = null, ), - communityServerPubKey = null // Groupv2 is not a community + fromCommunity = null // Groupv2 is not a community ) when { diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index 4afa237c7a..ed712e9eb2 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -18,7 +18,6 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import nl.komponents.kovenant.functional.map import org.session.libsession.database.StorageProtocol -import org.session.libsession.messaging.BlindedIdMapping import org.session.libsession.messaging.jobs.BatchMessageReceiveJob import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob import org.session.libsession.messaging.jobs.JobQueue @@ -43,6 +42,7 @@ import org.session.libsession.utilities.GroupUtil import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.database.BlindMappingRepository import org.thoughtcrime.securesms.util.AppVisibilityManager import java.util.concurrent.TimeUnit @@ -59,6 +59,7 @@ private typealias ManualPollRequestToken = Channel> class OpenGroupPoller @AssistedInject constructor( private val storage: StorageProtocol, private val appVisibilityManager: AppVisibilityManager, + private val blindMappingRepository: BlindMappingRepository, @Assisted private val server: String, @Assisted private val scope: CoroutineScope, ) { @@ -303,7 +304,6 @@ class OpenGroupPoller @AssistedInject constructor( val serverPublicKey = storage.getOpenGroupPublicKey(server)!! val sortedMessages = messages.sortedBy { it.id } val lastMessageId = sortedMessages.last().id - val mappingCache = mutableMapOf() if (fromOutbox) { storage.setLastOutboxMessageId(server, lastMessageId) } else { @@ -327,19 +327,16 @@ class OpenGroupPoller @AssistedInject constructor( emptySet() // this shouldn't be necessary as we are polling open groups here ) if (fromOutbox) { - val mapping = mappingCache[it.recipient] ?: storage.getOrCreateBlindedIdMapping( - it.recipient, - server, - serverPublicKey, - true - ) - val syncTarget = mapping.accountId ?: it.recipient + val syncTarget = blindMappingRepository.getMapping( + serverUrl = server, + blindedAddress = Address.fromSerialized(it.recipient) + )?.address ?: it.recipient + if (message is VisibleMessage) { message.syncTarget = syncTarget } else if (message is ExpirationTimerUpdate) { message.syncTarget = syncTarget } - mappingCache[it.recipient] = mapping } val threadId = Message.getThreadId(message, null, storage, false) MessageReceiver.handle(message, proto, threadId ?: -1, null, null) diff --git a/app/src/main/java/org/session/libsession/utilities/Address.kt b/app/src/main/java/org/session/libsession/utilities/Address.kt index 9ddba07a26..38b929cc3b 100644 --- a/app/src/main/java/org/session/libsession/utilities/Address.kt +++ b/app/src/main/java/org/session/libsession/utilities/Address.kt @@ -2,6 +2,7 @@ package org.session.libsession.utilities import android.os.Parcelable import kotlinx.parcelize.Parcelize +import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Util import java.util.LinkedList @@ -71,6 +72,10 @@ data class Address private constructor(val address: String) : Parcelable, Compar } return Util.join(escapedAddresses, delimiter.toString() + "") } + + fun AccountId.toAddress(): Address { + return fromSerialized(hexString) + } } } \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt index 9b1bf14757..70c04ba23e 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt @@ -78,6 +78,7 @@ data class Recipient( autoDownloadAttachments = true, notifyType = RecipientDatabase.NOTIFY_TYPE_ALL, acceptsCommunityMessageRequests = false, + unblindedRecipientAddress = null, ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 65cad83ebe..7ddd8598ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -69,13 +69,10 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.scan -import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import network.loki.messenger.R @@ -880,7 +877,12 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, AttachmentManager.MediaType.VIDEO == mediaType) ) { val media = Media(mediaURI, filename, mimeType, 0, 0, 0, 0, null, null) - startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), viewModel.recipient!!.address, viewModel.threadId, getMessageBody()), PICK_FROM_LIBRARY) + startActivityForResult(MediaSendActivity.buildEditorIntent( + this, + listOf( media ), + viewModel.recipient!!.address, + getMessageBody() + ), PICK_FROM_LIBRARY) return } else { prepMediaForSending(mediaURI, mediaType).addListener(object : ListenableFuture.Listener { @@ -1999,7 +2001,12 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, val mimeType = MediaUtil.getMimeType(this, contentUri)!! val filename = FilenameUtils.getFilenameFromUri(this, contentUri, mimeType) val media = Media(contentUri, filename, mimeType, 0, 0, 0, 0, null, null) - startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), recipient.address, viewModel.threadId, getMessageBody()), PICK_FROM_LIBRARY) + startActivityForResult(MediaSendActivity.buildEditorIntent( + this, + listOf( media ), + recipient.address, + getMessageBody() + ), PICK_FROM_LIBRARY) } // If we previously approve this recipient, either implicitly or explicitly, we need to wait for diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java index 1b530949dd..66835e227c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java @@ -297,7 +297,7 @@ public static void selectGallery(Activity activity, int requestCode, @NonNull Ad ); } - builder.onAllGranted(() -> activity.startActivityForResult(MediaSendActivity.buildGalleryIntent(activity, recipient, threadId, body), requestCode)) + builder.onAllGranted(() -> activity.startActivityForResult(MediaSendActivity.buildGalleryIntent(activity, recipient, body), requestCode)) .execute(); } @@ -329,7 +329,7 @@ public void capturePhoto(Activity activity, int requestCode, Address recipient, .request(Manifest.permission.CAMERA) .withPermanentDenialDialog(cameraPermissionDeniedTxt) .onAllGranted(() -> { - Intent captureIntent = MediaSendActivity.buildCameraIntent(activity, recipient, threadId); + Intent captureIntent = MediaSendActivity.buildCameraIntent(activity, recipient); if (captureIntent.resolveActivity(activity.getPackageManager()) != null) { activity.startActivityForResult(captureIntent, requestCode); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/BlindMappingRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/BlindMappingRepository.kt new file mode 100644 index 0000000000..df3968ab1d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/BlindMappingRepository.kt @@ -0,0 +1,94 @@ +package org.thoughtcrime.securesms.database + +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn +import network.loki.messenger.libsession_util.util.BlindKeyAPI +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.userConfigsChanged +import org.thoughtcrime.securesms.dependencies.ConfigFactory +import javax.inject.Inject +import javax.inject.Singleton + +private typealias CommunityServerUrl = String +private typealias BlindedAddress = Address + +/** + * A class that handles the blind mappings of addresses reactively. + */ +@Singleton +class BlindMappingRepository @Inject constructor( + configFactory: ConfigFactory, + prefs: TextSecurePreferences, +) { + + /** + * A [StateFlow] that emits a map of community server URLs to their respective mappings of + * blinded addresses to 05 prefixed addresses + */ + @Suppress("OPT_IN_USAGE") + val mappings: StateFlow>> = prefs.watchLocalNumber() + .filterNotNull() + .flatMapLatest { localAddress -> + configFactory + .userConfigsChanged(200L) + .onStart { emit(Unit) } + .map { + configFactory.withUserConfigs { configs -> + Pair( + configs.userGroups.allCommunityInfo().map { it.community }, + configs.contacts.all().map { Address.fromSerialized(it.id) } + + Address.fromSerialized(localAddress) + ) + } + } + } + .distinctUntilChanged() + .map { (allCommunities, allContacts) -> + allContacts.asSequence() + .flatMap { contactAddress -> + allCommunities.asSequence() + .map { community -> + val allBlindIDs = BlindKeyAPI.blind15Ids( + sessionId = contactAddress.address, + serverPubKey = community.pubKeyHex + ).asSequence() + BlindKeyAPI.blind25Id( + sessionId = contactAddress.address, + serverPubKey = community.pubKeyHex + ) + + community.baseUrl to + allBlindIDs + .map(Address::fromSerialized) + .associateWith { contactAddress } + } + + } + .toMap() + } + .stateIn(GlobalScope, started = SharingStarted.Eagerly, initialValue = emptyMap()) + + fun getMapping( + serverUrl: CommunityServerUrl, + blindedAddress: BlindedAddress + ): Address? { + return mappings.value[serverUrl]?.get(blindedAddress) + } + + fun observeMapping( + communityAddress: CommunityServerUrl, + blindedAddress: BlindedAddress + ): StateFlow { + return mappings.map { it[communityAddress]?.get(blindedAddress) } + .distinctUntilChanged() + .stateIn(GlobalScope, started = SharingStarted.Eagerly, initialValue = getMapping(communityAddress, blindedAddress)) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/BlindedIdMappingDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/BlindedIdMappingDatabase.kt index e4a007d47b..465466bb2c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/BlindedIdMappingDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/BlindedIdMappingDatabase.kt @@ -1,13 +1,10 @@ package org.thoughtcrime.securesms.database -import android.content.ContentValues import android.content.Context -import android.database.Cursor -import androidx.core.database.getStringOrNull -import org.session.libsession.messaging.BlindedIdMapping import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import javax.inject.Provider +@Deprecated("Use the new BlindMappingRepository instead. This class will be removed in a future release.") class BlindedIdMappingDatabase(context: Context, helper: Provider) : Database(context, helper) { companion object { @@ -29,61 +26,6 @@ class BlindedIdMappingDatabase(context: Context, helper: Provider { - val query = "$BLINDED_PK = ?" - val args = arrayOf(blindedId) - - val mappings: MutableList = mutableListOf() - - readableDatabase.query(TABLE_NAME, null, query, args, null, null, null).use { cursor -> - while (cursor.moveToNext()) { - mappings += readBlindedIdMapping(cursor) - } - } - - return mappings - } - - fun addBlindedIdMapping(blindedIdMapping: BlindedIdMapping) { - writableDatabase.beginTransaction() - try { - val values = ContentValues().apply { - put(BLINDED_PK, blindedIdMapping.blindedId) - put(SERVER_PK, blindedIdMapping.accountId) - put(SERVER_URL, blindedIdMapping.serverUrl) - put(SERVER_PK, blindedIdMapping.serverId) - } - - writableDatabase.insert(TABLE_NAME, null, values) - writableDatabase.setTransactionSuccessful() - } finally { - writableDatabase.endTransaction() - } - } - - fun getBlindedIdMappingsExceptFor(server: String): List { - val query = "$SESSION_PK IS NOT NULL AND $SERVER_URL <> ?" - val args = arrayOf(server) - - val mappings: MutableList = mutableListOf() - - readableDatabase.query(TABLE_NAME, null, query, args, null, null, null).use { cursor -> - while (cursor.moveToNext()) { - mappings += readBlindedIdMapping(cursor) - } - } - - return mappings } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index 5fb501d03c..66f751ec5f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -203,7 +203,10 @@ class RecipientRepository @Inject constructor( } settings != null -> { - value = createGenericRecipient(address, settings) + value = createGenericRecipient( + address = address, + settings = settings + ) changeSource = recipientDatabase.updateNotifications.filter { it == address } } @@ -465,7 +468,7 @@ class RecipientRepository @Inject constructor( */ private fun createGenericRecipient( address: Address, - settings: RecipientSettings + settings: RecipientSettings, ): Recipient { return Recipient( basic = BasicRecipient.Generic( @@ -485,7 +488,7 @@ class RecipientRepository @Inject constructor( ?.let { ZonedDateTime.from(Instant.ofEpochMilli(it)) }, autoDownloadAttachments = settings.autoDownloadAttachments, notifyType = settings.notifyType, - acceptsCommunityMessageRequests = !settings.blocksCommunityMessagesRequests + acceptsCommunityMessageRequests = !settings.blocksCommunityMessagesRequests, ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 519550bd35..822af2f634 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -15,7 +15,6 @@ import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.KeyPair import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.StorageProtocol -import org.session.libsession.messaging.BlindedIdMapping import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob @@ -108,7 +107,7 @@ open class Storage @Inject constructor( private val mmsSmsDatabase: MmsSmsDatabase, private val mmsDatabase: MmsDatabase, private val smsDatabase: SmsDatabase, - private val blindedIdMappingDatabase: BlindedIdMappingDatabase, + private val blindedIdMappingRepository: BlindMappingRepository, private val groupMemberDatabase: GroupMemberDatabase, private val reactionDatabase: ReactionDatabase, private val lokiThreadDatabase: LokiThreadDatabase, @@ -1087,12 +1086,7 @@ open class Storage @Inject constructor( } override fun syncLibSessionContacts(contacts: List, timestamp: Long?) { - val mappingDb = blindedIdMappingDatabase - val moreContacts = contacts.filter { contact -> - val id = AccountId(contact.id) - id.prefix?.isBlinded() == false || mappingDb.getBlindedIdMapping(contact.id).none { it.accountId != null } - } - moreContacts.forEach { contact -> + contacts.forEach { contact -> val address = fromSerialized(contact.id) if (contact.priority == PRIORITY_HIDDEN) { @@ -1114,7 +1108,7 @@ open class Storage @Inject constructor( IdPrefix.fromValue(localContact.toString()) == IdPrefix.STANDARD && // only want standard address localContact.isContact && // only for conversations localContact.address != currentUserKey && // we don't want to remove ourselves (ie, our Note to Self) - moreContacts.none { it.id == localContact.address } // we don't want to remove contacts that are present in the config + contacts.none { it.id == localContact.address } // we don't want to remove contacts that are present in the config } removedContacts.forEach { deleteContact(it.address) @@ -1389,10 +1383,9 @@ open class Storage @Inject constructor( picKey = profile.profileKey, acceptsCommunityRequests = null ), - communityServerPubKey = null) + fromCommunity = null) } - val mappingDb = blindedIdMappingDatabase val mappings = mutableMapOf() for ((address, _) in threadDatabase.allThreads) { @@ -1401,12 +1394,9 @@ open class Storage @Inject constructor( address.isCommunityInbox -> GroupUtil.getDecodedOpenGroupInboxAccountId(address.toString()) else -> address.address.takeIf { AccountId.fromStringOrNull(it)?.prefix == IdPrefix.BLINDED } } ?: continue - - mappingDb.getBlindedIdMapping(blindedId).firstOrNull()?.let { - mappings[address.address] = it - } } + // TODO: Actually move the conversation from blind to normal for (mapping in mappings) { if (!BlindKeyAPI.sessionIdMatchesBlindedId( sessionId = senderPublicKey, @@ -1416,7 +1406,6 @@ open class Storage @Inject constructor( ) { continue } - mappingDb.addBlindedIdMapping(mapping.value.copy(accountId = senderPublicKey)) val blindedThreadId = threadDatabase.getOrCreateThreadIdFor(fromSerialized(mapping.key)) mmsDatabase.updateThreadId(blindedThreadId, threadId) @@ -1535,48 +1524,6 @@ open class Storage @Inject constructor( lokiAPIDatabase.removeLastOutboxMessageId(server) } - override fun getOrCreateBlindedIdMapping( - blindedId: String, - server: String, - serverPublicKey: String, - fromOutbox: Boolean - ): BlindedIdMapping { - val db = blindedIdMappingDatabase - val mapping = db.getBlindedIdMapping(blindedId).firstOrNull() ?: BlindedIdMapping(blindedId, null, server, serverPublicKey) - if (mapping.accountId != null) { - return mapping - } - - configFactory.withUserConfigs { it.contacts.all() } - .forEach { contact -> - val accountId = AccountId(contact.id) - if (accountId.prefix == IdPrefix.STANDARD && BlindKeyAPI.sessionIdMatchesBlindedId( - sessionId = accountId.hexString, - blindedId = blindedId, - serverPubKey = serverPublicKey - ) - ) { - val contactMapping = mapping.copy(accountId = accountId.hexString) - db.addBlindedIdMapping(contactMapping) - return contactMapping - } - } - db.getBlindedIdMappingsExceptFor(server).forEach { - if (BlindKeyAPI.sessionIdMatchesBlindedId( - sessionId = it.accountId!!, - blindedId = blindedId, - serverPubKey = serverPublicKey - ) - ) { - val otherMapping = mapping.copy(accountId = it.accountId) - db.addBlindedIdMapping(otherMapping) - return otherMapping - } - } - db.addBlindedIdMapping(mapping) - return mapping - } - override fun addReaction( threadId: Long, reaction: Reaction, diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt index c77ff0de17..adf803157e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt @@ -59,9 +59,6 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme @Inject lateinit var recipientRepository: RecipientRepository - private val threadId: Long by lazy { - intent.getLongExtra(KEY_THREADID, 0L) - } override val applyDefaultWindowInsets: Boolean get() = false // we want to handle window insets manually here for fullscreen fragments like the camera screen @@ -103,7 +100,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme } else if (!isEmpty(media)) { viewModel.onSelectedMediaChanged(this, media!!) - val fragment: Fragment = MediaSendFragment.newInstance(recipient!!.address, threadId) + val fragment: Fragment = MediaSendFragment.newInstance(recipient!!.address) supportFragmentManager.beginTransaction() .replace(R.id.mediasend_fragment_container, fragment, TAG_SEND) @@ -339,7 +336,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme } private fun navigateToMediaSend(recipient: Address) { - val fragment = MediaSendFragment.newInstance(recipient, threadId) + val fragment = MediaSendFragment.newInstance(recipient) var backstackTag: String? = null if (supportFragmentManager.findFragmentByTag(TAG_SEND) != null) { @@ -506,7 +503,6 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme const val EXTRA_MESSAGE: String = "message" private const val KEY_ADDRESS = "address" - private const val KEY_THREADID = "threadid" private const val KEY_BODY = "body" private const val KEY_MEDIA = "media" private const val KEY_IS_CAMERA = "is_camera" @@ -520,11 +516,10 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme * Get an intent to launch the media send flow starting with the picker. */ @JvmStatic - fun buildGalleryIntent(context: Context, recipient: Address, threadId: Long, body: String): Intent { + fun buildGalleryIntent(context: Context, recipient: Address, body: String): Intent { val intent = Intent(context, MediaSendActivity::class.java) intent.putExtra(KEY_ADDRESS, recipient.toString()) intent.putExtra(KEY_BODY, body) - intent.putExtra(KEY_THREADID, threadId) return intent } @@ -532,8 +527,8 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme * Get an intent to launch the media send flow starting with the camera. */ @JvmStatic - fun buildCameraIntent(context: Context, recipient: Address, threadId: Long): Intent { - val intent = buildGalleryIntent(context, recipient, threadId, "") + fun buildCameraIntent(context: Context, recipient: Address): Intent { + val intent = buildGalleryIntent(context, recipient, "") intent.putExtra(KEY_IS_CAMERA, true) return intent } @@ -546,10 +541,9 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme context: Context, media: List, recipient: Address, - threadId: Long, body: String ): Intent { - val intent = buildGalleryIntent(context, recipient, threadId, body) + val intent = buildGalleryIntent(context, recipient, body) intent.putParcelableArrayListExtra(KEY_MEDIA, ArrayList(media)) return intent } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt index 7889e066ee..b40a57811e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt @@ -67,15 +67,14 @@ class MediaSendFragment : Fragment(), RailItemListener, InputBarDelegate { private val controller: Controller get() = (parentFragment as? Controller) ?: requireActivity() as Controller - // Mentions - private val threadId: Long - get() = arguments?.getLong(KEY_THREADID) ?: -1L + private val address: Address + get() = requireNotNull(BundleCompat.getParcelable(requireArguments(), KEY_ADDRESS, Address::class.java)) { + "Address is not provided in arguments" + } private val mentionViewModel: MentionViewModel by viewModels(extrasProducer = { defaultViewModelCreationExtras.withCreationCallback { - it.create( - requireNotNull(BundleCompat.getParcelable(requireArguments(), KEY_ADDRESS, Address::class.java)) - ) + it.create(address) } }) @@ -409,12 +408,10 @@ class MediaSendFragment : Fragment(), RailItemListener, InputBarDelegate { private val TAG: String = MediaSendFragment::class.java.simpleName private const val KEY_ADDRESS = "address" - private const val KEY_THREADID = "threadid" - fun newInstance(address: Address, threadId: Long): MediaSendFragment { - val args = Bundle() + fun newInstance(address: Address): MediaSendFragment { + val args = Bundle(1) args.putParcelable(KEY_ADDRESS, address) - args.putLong(KEY_THREADID, threadId) val fragment = MediaSendFragment() fragment.arguments = args diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 36481e044e..ad2c44379e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,7 +34,7 @@ kotlinxDatetimeVersion = "0.6.0" kryoVersion = "5.1.1" kspVersion = "2.2.0-2.0.2" legacySupportV13Version = "1.0.0" -libsessionUtilAndroidVersion = "1.0.5-2-gae785f1" +libsessionUtilAndroidVersion = "1.0.5-3-g76cd4a1" media3ExoplayerVersion = "1.7.1" mockitoCoreVersion = "5.18.0" navVersion = "2.9.0" From 4031cbd4f517131611316fe540f18657a18a0aa7 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Fri, 11 Jul 2025 15:22:29 +1000 Subject: [PATCH 44/52] SES-SES-4180 - Make sure we preserve the typed text before going out to pick new media --- .../thoughtcrime/securesms/mediasend/MediaSendFragment.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt index 591e873b06..f2b122fdf7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.kt @@ -247,7 +247,12 @@ class MediaSendFragment : Fragment(), RailItemListener, InputBarDelegate { viewModel.getBucketId().observe(this) { bucketId: String? -> if (bucketId == null) return@observe - mediaRailAdapter!!.setAddButtonListener { controller.onAddMediaClicked(bucketId) } + mediaRailAdapter!!.setAddButtonListener { + // save existing text in VM + viewModel.onBodyChanged(mentionViewModel.deconstructMessageMentions()) + + controller.onAddMediaClicked(bucketId) + } } } From dbab8c284da7e957b7aaac9eeeeca34985e22c60 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Mon, 14 Jul 2025 13:56:20 +1000 Subject: [PATCH 45/52] Truncate at 10K pre pro launch --- .../java/org/thoughtcrime/securesms/pro/ProStatusManager.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt index ecf594f4d9..6410e693d1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt @@ -9,7 +9,7 @@ import javax.inject.Singleton class ProStatusManager @Inject constructor( private val prefs: TextSecurePreferences ){ - private val MAX_CHARACTER_PRO = 10000 // max characters in a message for pro users + val MAX_CHARACTER_PRO = 10000 // max characters in a message for pro users private val MAX_CHARACTER_REGULAR = 2000 // max characters in a message for non pro users private val MAX_PIN_REGULAR = 5 // max pinned conversation for non pro users @@ -29,7 +29,7 @@ class ProStatusManager @Inject constructor( if (prefs.forceIncomingMessagesAsPro()) return MAX_CHARACTER_PRO // otherwise return the true value - return MAX_CHARACTER_REGULAR //todo PRO implement real logic once it's in + return if(isPostPro()) MAX_CHARACTER_REGULAR else MAX_CHARACTER_PRO //todo PRO implement real logic once it's in } // Temporary method and concept that we should remove once Pro is out From 54c24cf960235fd729b24be8eaeb4c0a9d1069d8 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Mon, 14 Jul 2025 16:36:23 +1000 Subject: [PATCH 46/52] New feedback survey url --- .../thoughtcrime/securesms/preferences/HelpSettingsActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt index 87d0b46fd6..d51eb88af8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt @@ -44,7 +44,7 @@ class HelpSettingsFragment: CorrectedPreferenceFragment() { private const val FAQ = "faq" private const val SUPPORT = "support" private const val CROWDIN_URL = "https://getsession.org/translate" - private const val FEEDBACK_URL = "https://getsession.org/survey" + private const val FEEDBACK_URL = "https://www.surveymonkey.com/r/Q7C8GJS" private const val FAQ_URL = "https://getsession.org/faq" private const val SUPPORT_URL = "https://sessionapp.zendesk.com/hc/en-us" } From 4f982f1b09969492522cacbf6a452ba2d042bc53 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Mon, 14 Jul 2025 16:46:58 +1000 Subject: [PATCH 47/52] Fixed CTA icon color --- app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt index e565ccad3d..9e224c9eb8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -828,7 +828,7 @@ fun ProCTAFeature( is CTAFeature.Icon -> { Image( painter = painterResource(id = data.icon), - colorFilter = ColorFilter.tint(LocalColors.current.accent), + colorFilter = ColorFilter.tint(LocalColors.current.accentText ), contentDescription = null, modifier = Modifier.align(Alignment.CenterVertically) ) From 97378e9ec18772ec97869220ca995550b839d354 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Mon, 14 Jul 2025 16:56:37 +1000 Subject: [PATCH 48/52] Reverting feedback url --- .../thoughtcrime/securesms/preferences/HelpSettingsActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt index d51eb88af8..87d0b46fd6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt @@ -44,7 +44,7 @@ class HelpSettingsFragment: CorrectedPreferenceFragment() { private const val FAQ = "faq" private const val SUPPORT = "support" private const val CROWDIN_URL = "https://getsession.org/translate" - private const val FEEDBACK_URL = "https://www.surveymonkey.com/r/Q7C8GJS" + private const val FEEDBACK_URL = "https://getsession.org/survey" private const val FAQ_URL = "https://getsession.org/faq" private const val SUPPORT_URL = "https://sessionapp.zendesk.com/hc/en-us" } From d7ff363b21a31b6bf156199642ed1a3c436a1f24 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Tue, 15 Jul 2025 14:41:47 +1000 Subject: [PATCH 49/52] BlindMapping WIP --- .../database/BlindMappingRepository.kt | 9 ++++ .../securesms/database/Storage.kt | 54 ++++++++++--------- .../securesms/database/ThreadDatabase.java | 14 +++++ 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/BlindMappingRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/BlindMappingRepository.kt index df3968ab1d..1dc3f67352 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/BlindMappingRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/BlindMappingRepository.kt @@ -83,6 +83,15 @@ class BlindMappingRepository @Inject constructor( return mappings.value[serverUrl]?.get(blindedAddress) } + fun getReverseMappings( + contactAddress: Address, + ): List> { + return mappings.value.flatMap { (communityUrl, mapping) -> + mapping.filter { it.value == contactAddress } + .map { (blindedAddress, _) -> communityUrl to blindedAddress } + } + } + fun observeMapping( communityAddress: CommunityServerUrl, blindedAddress: BlindedAddress diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 822af2f634..2cba59f3e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1385,33 +1385,35 @@ open class Storage @Inject constructor( ), fromCommunity = null) } - - val mappings = mutableMapOf() - - for ((address, _) in threadDatabase.allThreads) { - val blindedId = when { - address.isGroupOrCommunity -> null - address.isCommunityInbox -> GroupUtil.getDecodedOpenGroupInboxAccountId(address.toString()) - else -> address.address.takeIf { AccountId.fromStringOrNull(it)?.prefix == IdPrefix.BLINDED } - } ?: continue - } - - // TODO: Actually move the conversation from blind to normal - for (mapping in mappings) { - if (!BlindKeyAPI.sessionIdMatchesBlindedId( - sessionId = senderPublicKey, - blindedId = mapping.value.blindedId, - serverPubKey = mapping.value.serverId - ) - ) { - continue - } - val blindedThreadId = threadDatabase.getOrCreateThreadIdFor(fromSerialized(mapping.key)) - mmsDatabase.updateThreadId(blindedThreadId, threadId) - smsDatabase.updateThreadId(blindedThreadId, threadId) - deleteConversation(blindedThreadId) - } +// blindedIdMappingRepository.getReverseMappings(sender) +// +// val mappings = mutableMapOf() +// +// for ((address, threadId) in threadDatabase.allThreads) { +// val blindedId = when { +// address.isGroupOrCommunity -> null +// address.isCommunityInbox -> GroupUtil.getDecodedOpenGroupInboxAccountId(address.toString()) +// else -> address.address.takeIf { AccountId.fromStringOrNull(it)?.prefix == IdPrefix.BLINDED } +// } ?: continue +// } +// +// // TODO: Actually move the conversation from blind to normal +// for (mapping in mappings) { +// if (!BlindKeyAPI.sessionIdMatchesBlindedId( +// sessionId = senderPublicKey, +// blindedId = mapping.value.blindedId, +// serverPubKey = mapping.value.serverId +// ) +// ) { +// continue +// } +// +// val blindedThreadId = threadDatabase.getOrCreateThreadIdFor(fromSerialized(mapping.key)) +// mmsDatabase.updateThreadId(blindedThreadId, threadId) +// smsDatabase.updateThreadId(blindedThreadId, threadId) +// deleteConversation(blindedThreadId) +// } var alreadyApprovedMe = false diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 8949d7290c..d11a59b78b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -527,6 +527,20 @@ public int getMessageCount(long threadId) { } } + public List getThreadIDsFor(List addresses) { + final String where = ADDRESS + " IN (SELECT value FROM json_each(?))"; + final String whereArg = new JSONArray(addresses).toString(); + + try (final Cursor cursor = getReadableDatabase().query(TABLE_NAME, new String[]{ID}, where, + new String[]{whereArg}, null, null, null)) { + List threadIds = new ArrayList<>(cursor.getCount()); + while (cursor.moveToNext()) { + threadIds.add(cursor.getLong(cursor.getColumnIndexOrThrow(ID))); + } + return threadIds; + } + } + public long getThreadIdIfExistsFor(String address) { SQLiteDatabase db = getReadableDatabase(); String where = ADDRESS + " = ?"; From bab253c1e8d0f5dd1cfaca8041a26dae16dc1c14 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Tue, 15 Jul 2025 14:45:30 +1000 Subject: [PATCH 50/52] [SES-4285] - Simplify expiriation logic (#1312) --- .../libsession/database/StorageProtocol.kt | 3 +- .../messages/signal/IncomingMediaMessage.java | 21 +- .../OutgoingExpirationUpdateMessage.java | 35 --- .../signal/OutgoingGroupMediaMessage.java | 6 +- .../messages/signal/OutgoingMediaMessage.java | 24 +- .../signal/OutgoingSecureMediaMessage.java | 6 +- .../sending_receiving/MessageSender.kt | 5 +- .../ReceivedMessageHandler.kt | 5 +- .../utilities/UpdateMessageBuilder.kt | 52 ++-- .../libsession/utilities/ExpirationUtil.kt | 10 +- .../libsession/utilities/SSKEnvironment.kt | 7 - .../utilities/TextSecurePreferences.kt | 6 + .../DisappearingMessages.kt | 75 ++++-- .../conversation/v2/ConversationActivityV2.kt | 5 +- .../v2/messages/ControlMessageView.kt | 17 +- .../database/DatabaseContentProviders.java | 6 +- .../securesms/database/MessagingDatabase.java | 4 + .../securesms/database/MmsDatabase.kt | 165 +++++++++---- .../securesms/database/MmsSmsColumns.java | 5 +- .../securesms/database/MmsSmsDatabase.java | 11 +- .../securesms/database/SmsDatabase.java | 84 ++++--- .../securesms/database/Storage.kt | 65 ++--- .../securesms/database/ThreadDatabase.java | 82 ++++-- .../database/helpers/SQLCipherOpenHelper.java | 11 +- .../database/model/DisplayRecord.java | 21 +- .../database/model/MediaMmsMessageRecord.java | 6 +- .../database/model/MessageRecord.java | 29 ++- .../database/model/MmsMessageRecord.java | 18 +- .../database/model/SmsMessageRecord.java | 2 +- .../database/model/ThreadRecord.java | 13 +- .../content/DisappearingMessageUpdate.kt | 54 ++++ .../database/model/content/MessageContent.kt | 27 ++ .../model/content/UnknownMessageContent.kt | 10 + .../securesms/dependencies/AppModule.kt | 12 + .../securesms/dependencies/DatabaseModule.kt | 7 - .../notifications/MarkReadReceiver.kt | 53 +--- .../securesms/notifications/PushReceiver.kt | 4 +- .../service/ExpiringMessageManager.kt | 233 ++++++++---------- .../securesms/tokenpage/TokenRepository.kt | 7 +- .../securesms/util/ProtobufEnumSerializer.kt | 32 +++ 40 files changed, 730 insertions(+), 508 deletions(-) delete mode 100644 app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/model/content/DisappearingMessageUpdate.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/model/content/MessageContent.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/model/content/UnknownMessageContent.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/ProtobufEnumSerializer.kt diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 16504ca582..77e2a36685 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -116,7 +116,7 @@ interface StorageProtocol { fun removeReceivedMessageTimestamps(timestamps: Set) fun getAttachmentsForMessage(mmsMessageId: Long): List fun getMessageBy(timestamp: Long, author: String): MessageRecord? - fun updateSentTimestamp(messageId: MessageId, openGroupSentTimestamp: Long, threadId: Long) + fun updateSentTimestamp(messageId: MessageId, newTimestamp: Long) fun markAsResyncing(messageId: MessageId) fun markAsSyncing(messageId: MessageId) fun markAsSending(messageId: MessageId) @@ -274,7 +274,6 @@ interface StorageProtocol { fun blockedContacts(): List fun getExpirationConfiguration(threadId: Long): ExpirationConfiguration? fun setExpirationConfiguration(config: ExpirationConfiguration) - fun getExpiringMessages(messageIds: List = emptyList()): List> fun updateDisappearingState( messageSender: String, threadID: Long, diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java index d0789a67e9..c35c6c7c38 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java @@ -1,5 +1,6 @@ package org.session.libsession.messaging.messages.signal; +import org.jspecify.annotations.Nullable; import org.session.libsession.messaging.messages.visible.VisibleMessage; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment; @@ -13,6 +14,7 @@ import org.session.libsignal.messages.SignalServiceGroup; import org.session.libsignal.utilities.Hex; import org.session.libsignal.utilities.guava.Optional; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import java.util.Collections; import java.util.LinkedList; @@ -28,9 +30,10 @@ public class IncomingMediaMessage { private final int subscriptionId; private final long expiresIn; private final long expireStartedAt; - private final boolean expirationUpdate; private final boolean messageRequestResponse; private final boolean hasMention; + @Nullable + private final MessageContent messageContent; private final DataExtractionNotificationInfoMessage dataExtractionNotification; private final QuoteModel quote; @@ -44,17 +47,18 @@ public IncomingMediaMessage(Address from, int subscriptionId, long expiresIn, long expireStartedAt, - boolean expirationUpdate, boolean messageRequestResponse, boolean hasMention, Optional body, Optional group, Optional> attachments, + @Nullable MessageContent messageContent, Optional quote, Optional> sharedContacts, Optional> linkPreviews, Optional dataExtractionNotification) { + this.messageContent = messageContent; this.push = true; this.from = from; this.sentTimeMillis = sentTimeMillis; @@ -62,7 +66,6 @@ public IncomingMediaMessage(Address from, this.subscriptionId = subscriptionId; this.expiresIn = expiresIn; this.expireStartedAt = expireStartedAt; - this.expirationUpdate = expirationUpdate; this.dataExtractionNotification = dataExtractionNotification.orNull(); this.quote = quote.orNull(); this.messageRequestResponse = messageRequestResponse; @@ -96,8 +99,8 @@ public static IncomingMediaMessage from(VisibleMessage message, Optional> linkPreviews) { return new IncomingMediaMessage(from, message.getSentTimestamp(), -1, expiresIn, expireStartedAt, - false, false, message.getHasMention(), Optional.fromNullable(message.getText()), - group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews, Optional.absent()); + false, message.getHasMention(), Optional.fromNullable(message.getText()), + group, Optional.fromNullable(attachments), null, quote, Optional.absent(), linkPreviews, Optional.absent()); } public int getSubscriptionId() { @@ -120,12 +123,12 @@ public Address getGroupId() { return groupId; } - public boolean isPushMessage() { - return push; + public @Nullable MessageContent getMessageContent() { + return messageContent; } - public boolean isExpirationUpdate() { - return expirationUpdate; + public boolean isPushMessage() { + return push; } public long getSentTimeMillis() { diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java deleted file mode 100644 index b42d6d1bde..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.session.libsession.messaging.messages.signal; - -import org.session.libsession.messaging.sending_receiving.attachments.Attachment; -import org.session.libsession.utilities.DistributionTypes; -import org.session.libsession.utilities.recipients.Recipient; - -import java.util.Collections; -import java.util.LinkedList; - -public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage { - - private final String groupId; - - public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn, long expireStartedAt, String groupId) { - super(recipient, "", new LinkedList(), sentTimeMillis, - DistributionTypes.CONVERSATION, expiresIn, expireStartedAt, null, Collections.emptyList(), - Collections.emptyList()); - this.groupId = groupId; - } - - @Override - public boolean isExpirationUpdate() { - return true; - } - - @Override - public boolean isGroup() { - return groupId != null; - } - - public String getGroupId() { - return groupId; - } - -} diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java index 86db70f435..8c30df5866 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java @@ -9,6 +9,7 @@ import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; import org.session.libsession.utilities.recipients.Recipient; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import java.util.LinkedList; import java.util.List; @@ -28,12 +29,13 @@ public OutgoingGroupMediaMessage(@NonNull Recipient recipient, boolean updateMessage, @Nullable QuoteModel quote, @NonNull List contacts, - @NonNull List previews) + @NonNull List previews, + @Nullable MessageContent messageContent) { super(recipient, body, new LinkedList() {{if (avatar != null) add(avatar);}}, sentTime, - DistributionTypes.CONVERSATION, expireIn, expireStartedAt, quote, contacts, previews); + DistributionTypes.CONVERSATION, expireIn, expireStartedAt, quote, contacts, previews, messageContent); this.groupID = groupId; this.isUpdateMessage = updateMessage; diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java index 85dc0cc6a2..0655855df1 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java @@ -12,11 +12,19 @@ import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.NetworkFailure; import org.session.libsession.utilities.recipients.Recipient; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import java.util.Collections; import java.util.LinkedList; import java.util.List; +/** + * Represents an outgoing mms message. Note this class is only used for saving messages + * into the database. We will still use {@link org.session.libsession.messaging.messages.Message} + * as a model when sending the message to the network. + *
+ * See {@link OutgoingTextMessage} for the sms table counterpart. + */ public class OutgoingMediaMessage { private final Recipient recipient; @@ -28,6 +36,8 @@ public class OutgoingMediaMessage { private final long expiresIn; private final long expireStartedAt; private final QuoteModel outgoingQuote; + @Nullable + private final MessageContent messageContent; private final List networkFailures = new LinkedList<>(); private final List identityKeyMismatches = new LinkedList<>(); @@ -42,7 +52,8 @@ public OutgoingMediaMessage(Recipient recipient, String message, @NonNull List contacts, @NonNull List linkPreviews, @NonNull List networkFailures, - @NonNull List identityKeyMismatches) + @NonNull List identityKeyMismatches, + @Nullable MessageContent messageContent) { this.recipient = recipient; this.body = message; @@ -53,6 +64,7 @@ public OutgoingMediaMessage(Recipient recipient, String message, this.expiresIn = expiresIn; this.expireStartedAt = expireStartedAt; this.outgoingQuote = outgoingQuote; + this.messageContent = messageContent; this.contacts.addAll(contacts); this.linkPreviews.addAll(linkPreviews); @@ -70,6 +82,7 @@ public OutgoingMediaMessage(OutgoingMediaMessage that) { this.expiresIn = that.expiresIn; this.expireStartedAt = that.expireStartedAt; this.outgoingQuote = that.outgoingQuote; + this.messageContent = that.messageContent; this.identityKeyMismatches.addAll(that.identityKeyMismatches); this.networkFailures.addAll(that.networkFailures); @@ -91,7 +104,12 @@ public static OutgoingMediaMessage from(VisibleMessage message, } return new OutgoingMediaMessage(recipient, message.getText(), attachments, message.getSentTimestamp(), -1, expiresInMillis, expireStartedAt, DistributionTypes.DEFAULT, outgoingQuote, - Collections.emptyList(), previews, Collections.emptyList(), Collections.emptyList()); + Collections.emptyList(), previews, Collections.emptyList(), Collections.emptyList(), null); + } + + @Nullable + public MessageContent getMessageContent() { + return messageContent; } public Recipient getRecipient() { @@ -114,8 +132,6 @@ public boolean isGroup() { return false; } - public boolean isExpirationUpdate() { return false; } - public long getSentTimeMillis() { return sentTimeMillis; } diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java index e93c3c5986..daff026ca4 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java @@ -8,6 +8,7 @@ import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; import org.session.libsession.utilities.recipients.Recipient; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import java.util.Collections; import java.util.List; @@ -22,9 +23,10 @@ public OutgoingSecureMediaMessage(Recipient recipient, String body, long expireStartedAt, @Nullable QuoteModel quote, @NonNull List contacts, - @NonNull List previews) + @NonNull List previews, + @Nullable MessageContent messageContent) { - super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, expireStartedAt, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList()); + super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, expireStartedAt, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList(), messageContent); } public OutgoingSecureMediaMessage(OutgoingMediaMessage base) { diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 26915e8493..865fb4d785 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -419,7 +419,6 @@ object MessageSender { // Result Handling fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) { - val threadId by lazy { requireNotNull(message.threadID) { "threadID for the message is null" } } val storage = MessagingModuleConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey()!! // Ignore future self-sends @@ -427,7 +426,6 @@ object MessageSender { message.id?.let { messageId -> if (openGroupSentTimestamp != -1L && message is VisibleMessage) { storage.addReceivedMessageTimestamp(openGroupSentTimestamp) - storage.updateSentTimestamp(messageId, openGroupSentTimestamp, threadId) message.sentTimestamp = openGroupSentTimestamp } @@ -473,6 +471,9 @@ object MessageSender { // Mark the message as sent. storage.markAsSent(messageId) + // Update the message sent timestamp + storage.updateSentTimestamp(messageId, message.sentTimestamp!!) + // Start the disappearing messages timer if needed SSKEnvironment.shared.messageExpirationManager.onMessageSent(message) } ?: run { diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 5d13006890..8c6eb30b26 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -171,7 +171,10 @@ fun MessageReceiver.cancelTypingIndicatorsIfNeeded(senderPublicKey: String) { } private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimerUpdate) { - SSKEnvironment.shared.messageExpirationManager.insertExpirationTimerMessage(message) + SSKEnvironment.shared.messageExpirationManager.run { + insertExpirationTimerMessage(message) + onMessageReceived(message) + } val isLegacyGroup = message.groupPublicKey != null && message.groupPublicKey?.startsWith(IdPrefix.GROUP.value) == false diff --git a/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt b/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt index 796dd77bed..0fbd0f3151 100644 --- a/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt +++ b/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -2,23 +2,19 @@ package org.session.libsession.messaging.utilities import android.content.Context import com.squareup.phrase.Phrase -import network.loki.messenger.libsession_util.getOrNull import network.loki.messenger.R +import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.calls.CallMessageType.CALL_FIRST_MISSED import org.session.libsession.messaging.calls.CallMessageType.CALL_INCOMING import org.session.libsession.messaging.calls.CallMessageType.CALL_MISSED import org.session.libsession.messaging.calls.CallMessageType.CALL_OUTGOING -import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage.Kind.SCREENSHOT import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ExpirationUtil -import org.session.libsession.utilities.getExpirationTypeDisplayValue -import org.session.libsession.utilities.truncateIdForDisplay -import org.session.libsignal.utilities.Log import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY import org.session.libsession.utilities.StringSubstitutionConstants.DISAPPEARING_MESSAGES_TYPE_KEY import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY @@ -27,6 +23,7 @@ import org.session.libsession.utilities.StringSubstitutionConstants.OTHER_NAME_K import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY import org.session.libsession.utilities.getGroup import org.session.libsignal.utilities.AccountId +import org.session.libsignal.utilities.Log object UpdateMessageBuilder { const val TAG = "UpdateMessageBuilder" @@ -316,15 +313,12 @@ object UpdateMessageBuilder { } } - fun buildExpirationTimerMessage( context: Context, - duration: Long, - isGroup: Boolean, // Note: isGroup should cover both closed groups AND communities - senderId: String? = null, - isOutgoing: Boolean = false, - timestamp: Long, - expireStarted: Long + mode: ExpiryMode, + isGroup: Boolean, // Note: isGroup should cover both closed groups AND communities + senderId: String?, + isOutgoing: Boolean, ): CharSequence { if (!isOutgoing && senderId == null) { Log.w(TAG, "buildExpirationTimerMessage: Cannot build for outgoing message when senderId is null.") @@ -334,7 +328,7 @@ object UpdateMessageBuilder { val senderName = if (isOutgoing) context.getString(R.string.you) else getGroupMemberName(senderId!!) // Case 1.) Disappearing messages have been turned off.. - if (duration <= 0) { + if (mode == ExpiryMode.NONE) { // ..by you.. return if (isOutgoing) { // in a group @@ -356,8 +350,12 @@ object UpdateMessageBuilder { } // Case 2.) Disappearing message settings have been changed but not turned off. - val time = ExpirationUtil.getExpirationDisplayValue(context, duration.toInt()) - val action = context.getExpirationTypeDisplayValue(timestamp >= expireStarted) + val time = ExpirationUtil.getExpirationDisplayValue(context, mode.duration) + val action = if (mode is ExpiryMode.AfterSend) { + context.getString(R.string.disappearingMessagesTypeSent) + } else { + context.getString(R.string.disappearingMessagesTypeRead) + } //..by you.. if (isOutgoing) { @@ -384,6 +382,30 @@ object UpdateMessageBuilder { } } + + @Deprecated("Use the version with ExpiryMode instead. This will be removed in a future release.") + fun buildExpirationTimerMessage( + context: Context, + duration: Long, + isGroup: Boolean, // Note: isGroup should cover both closed groups AND communities + senderId: String? = null, + isOutgoing: Boolean = false, + timestamp: Long, + expireStarted: Long + ): CharSequence { + return buildExpirationTimerMessage( + context, + mode = when { + duration == 0L -> ExpiryMode.NONE + timestamp >= expireStarted -> ExpiryMode.AfterSend(duration) // Not the greatest logic here but keeping it for backwards compatibility, can be removed once migrated over + else -> ExpiryMode.AfterRead(duration) + }, + isGroup = isGroup, + senderId = senderId, + isOutgoing = isOutgoing, + ) + } + fun buildDataExtractionMessage(context: Context, kind: DataExtractionNotificationInfoMessage.Kind, senderId: String? = null): CharSequence { diff --git a/app/src/main/java/org/session/libsession/utilities/ExpirationUtil.kt b/app/src/main/java/org/session/libsession/utilities/ExpirationUtil.kt index 6e1663391a..d1d90560b9 100644 --- a/app/src/main/java/org/session/libsession/utilities/ExpirationUtil.kt +++ b/app/src/main/java/org/session/libsession/utilities/ExpirationUtil.kt @@ -1,14 +1,10 @@ package org.session.libsession.utilities import android.content.Context +import org.session.libsession.LocalisedTimeUtil import java.util.concurrent.TimeUnit import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds -import org.session.libsession.LocalisedTimeUtil -import network.loki.messenger.R -fun Context.getExpirationTypeDisplayValue(sent: Boolean) = if (sent) getString(R.string.disappearingMessagesTypeSent) - else getString(R.string.disappearingMessagesTypeRead) object ExpirationUtil { @@ -16,10 +12,6 @@ object ExpirationUtil { fun getExpirationDisplayValue(context: Context, duration: Duration): String = LocalisedTimeUtil.getDurationWithSingleLargestTimeUnit(context, duration) - @JvmStatic - fun getExpirationDisplayValue(context: Context, expirationTimeSecs: Int) = - LocalisedTimeUtil.getDurationWithSingleLargestTimeUnit(context, expirationTimeSecs.seconds) - fun getExpirationAbbreviatedDisplayValue(expirationTimeSecs: Long): String { return if (expirationTimeSecs < TimeUnit.MINUTES.toSeconds(1)) { expirationTimeSecs.toString() + "s" diff --git a/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt b/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt index 4e92594d3c..b7bc0e542a 100644 --- a/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt +++ b/app/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt @@ -40,13 +40,6 @@ class SSKEnvironment( interface MessageExpirationManagerProtocol { fun insertExpirationTimerMessage(message: ExpirationTimerUpdate) - /** - * Starts the expiration timer for a message, regardless of it has been sent, read or not. - * - * However, the timer will not start if it doesn't have its expiryMode set. - */ - fun startExpiringNow(id: MessageId) - fun onMessageSent(message: Message) fun onMessageReceived(message: Message) } diff --git a/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 3e3cade903..b88154beb9 100644 --- a/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -214,6 +214,8 @@ interface TextSecurePreferences { var migratedToDisablingKDF: Boolean var migratedToMultiPartConfig: Boolean + var migratedDisappearingMessagesToMessageContent: Boolean + var selectedActivityAliasName: String? companion object { @@ -1018,6 +1020,10 @@ class AppTextSecurePreferences @Inject constructor( get() = getBooleanPreference(TextSecurePreferences.MIGRATED_TO_MULTIPART_CONFIG, false) set(value) = setBooleanPreference(TextSecurePreferences.MIGRATED_TO_MULTIPART_CONFIG, value) + override var migratedDisappearingMessagesToMessageContent: Boolean + get() = getBooleanPreference("migrated_disappearing_messages_to_message_content", false) + set(value) = setBooleanPreference("migrated_disappearing_messages_to_message_content", value) + override fun getConfigurationMessageSynced(): Boolean { return getBooleanPreference(TextSecurePreferences.CONFIGURATION_SYNCED, false) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt index ec938d39e6..8fd103003d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt @@ -1,10 +1,7 @@ package org.thoughtcrime.securesms.conversation.disappearingmessages import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch +import androidx.annotation.StringRes import network.loki.messenger.R import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.database.StorageProtocol @@ -12,7 +9,6 @@ import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.sending_receiving.MessageSender -import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.Address import org.session.libsession.utilities.ExpirationUtil @@ -20,13 +16,12 @@ import org.session.libsession.utilities.SSKEnvironment.MessageExpirationManagerP import org.session.libsession.utilities.StringSubstitutionConstants.DISAPPEARING_MESSAGES_TYPE_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.getExpirationTypeDisplayValue +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.AccountId -import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.ui.getSubbedCharSequence import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds class DisappearingMessages @Inject constructor( private val textSecurePreferences: TextSecurePreferences, @@ -55,26 +50,60 @@ class DisappearingMessages @Inject constructor( } } - fun showFollowSettingDialog(context: Context, message: MessageRecord) = context.showSessionDialog { + fun showFollowSettingDialog(context: Context, + threadId: Long, + recipient: Recipient, + content: DisappearingMessageUpdate) = context.showSessionDialog { title(R.string.disappearingMessagesFollowSetting) - text(if (message.expiresIn == 0L) { - context.getText(R.string.disappearingMessagesFollowSettingOff) - } else { - context.getSubbedCharSequence(R.string.disappearingMessagesFollowSettingOn, - TIME_KEY to ExpirationUtil.getExpirationDisplayValue(context, message.expiresIn.milliseconds), - DISAPPEARING_MESSAGES_TYPE_KEY to context.getExpirationTypeDisplayValue(message.isNotDisappearAfterRead)) - }) + + val bodyText: CharSequence + @StringRes + val dangerButtonText: Int + @StringRes + val dangerButtonContentDescription: Int + + when (content.expiryMode) { + ExpiryMode.NONE -> { + bodyText = context.getText(R.string.disappearingMessagesFollowSettingOff) + dangerButtonText = R.string.confirm + dangerButtonContentDescription = R.string.AccessibilityId_confirm + } + is ExpiryMode.AfterSend -> { + bodyText = context.getSubbedCharSequence( + R.string.disappearingMessagesFollowSettingOn, + TIME_KEY to ExpirationUtil.getExpirationDisplayValue( + context, + content.expiryMode.duration + ), + DISAPPEARING_MESSAGES_TYPE_KEY to context.getString(R.string.disappearingMessagesTypeSent) + ) + + dangerButtonText = R.string.set + dangerButtonContentDescription = R.string.AccessibilityId_setButton + } + is ExpiryMode.AfterRead -> { + bodyText = context.getSubbedCharSequence( + R.string.disappearingMessagesFollowSettingOn, + TIME_KEY to ExpirationUtil.getExpirationDisplayValue( + context, + content.expiryMode.duration + ), + DISAPPEARING_MESSAGES_TYPE_KEY to context.getString(R.string.disappearingMessagesTypeRead) + ) + + dangerButtonText = R.string.set + dangerButtonContentDescription = R.string.AccessibilityId_setButton + } + } + + text(bodyText) dangerButton( - text = if (message.expiresIn == 0L) R.string.confirm else R.string.set, - contentDescriptionRes = if (message.expiresIn == 0L) R.string.AccessibilityId_confirm else R.string.AccessibilityId_setButton + text = dangerButtonText, + contentDescriptionRes = dangerButtonContentDescription, ) { - set(message.threadId, message.recipient.address, message.expiryMode, message.recipient.isGroupRecipient) + set(threadId, recipient.address, content.expiryMode, recipient.isGroupRecipient) } cancelButton() } } - -val MessageRecord.expiryMode get() = if (expiresIn <= 0) ExpiryMode.NONE - else if (expireStarted == timestamp) ExpiryMode.AfterSend(expiresIn / 1000) - else ExpiryMode.AfterRead(expiresIn / 1000) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 7f28201f21..4030fe9e29 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -1997,10 +1997,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, message.sentTimestamp = sentTimestamp message.text = text val expiresInMillis = viewModel.expirationConfiguration?.expiryMode?.expiryMillis ?: 0 - val expireStartedAt = if (viewModel.expirationConfiguration?.expiryMode is ExpiryMode.AfterSend) { - message.sentTimestamp - } else 0 - val outgoingTextMessage = OutgoingTextMessage.from(message, recipient, expiresInMillis, expireStartedAt!!) + val outgoingTextMessage = OutgoingTextMessage.from(message, recipient, expiresInMillis, 0) // Clear the input bar binding.inputBar.text = "" diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index d0f2b6d47f..1876bc5a97 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -10,7 +10,6 @@ import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout -import androidx.compose.ui.graphics.Color import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.toBitmap import androidx.core.view.isGone @@ -22,15 +21,14 @@ import network.loki.messenger.databinding.ViewControlMessageBinding import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.ExpirationConfiguration -import org.session.libsession.messaging.utilities.UpdateMessageData import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED import org.session.libsession.utilities.getColorFromAttr import org.thoughtcrime.securesms.conversation.disappearingmessages.DisappearingMessages -import org.thoughtcrime.securesms.conversation.disappearingmessages.expiryMode import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity @@ -84,8 +82,9 @@ class ControlMessageView : LinearLayout { binding.root.contentDescription = null binding.textView.text = messageBody + val messageContent = message.messageContent when { - message.isExpirationTimerUpdate -> { + messageContent is DisappearingMessageUpdate -> { binding.apply { expirationTimerView.isVisible = true @@ -98,12 +97,14 @@ class ControlMessageView : LinearLayout { } followSetting.isVisible = ExpirationConfiguration.isNewConfigEnabled - && !message.isOutgoing - && message.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId)?.expiryMode ?: ExpiryMode.NONE) - && threadRecipient?.isGroupOrCommunityRecipient != true + && !message.isOutgoing + && messageContent.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId)?.expiryMode ?: ExpiryMode.NONE) + && threadRecipient?.isGroupOrCommunityRecipient != true if (followSetting.isVisible) { - binding.controlContentView.setOnClickListener { disappearingMessages.showFollowSettingDialog(context, message) } + binding.controlContentView.setOnClickListener { + disappearingMessages.showFollowSettingDialog(context, threadId = message.threadId, recipient = message.recipient, messageContent) + } } else { binding.controlContentView.setOnClickListener(null) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java index 0f4d7c9f25..cf5d86907d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java @@ -19,10 +19,12 @@ public static class ConversationList extends NoopContentProvider { } public static class Conversation extends NoopContentProvider { - private static final String CONTENT_URI_STRING = "content://network.loki.securesms.database.conversation/"; + public static final Uri CONTENT_URI = Uri.parse("content://network.loki.securesms.database.conversation"); public static Uri getUriForThread(long threadId) { - return Uri.parse(CONTENT_URI_STRING + threadId); + return CONTENT_URI.buildUpon() + .appendPath(String.valueOf(threadId)) + .build(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java index b9f0ea9f1a..eb803a80d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java @@ -47,6 +47,10 @@ public MessagingDatabase(Context context, Provider database public abstract void markAsDeleted(long messageId, boolean isOutgoing, String displayedMessage); + public abstract List getExpiredMessageIDs(long nowMills); + + public abstract long getNextExpiringTimestamp(); + public abstract boolean deleteMessage(long messageId); public abstract boolean deleteMessages(long[] messageId, long threadId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index 6a2e6fbc08..13c3eb380a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -20,6 +20,9 @@ import android.content.ContentValues import android.content.Context import android.database.Cursor import com.annimon.stream.Stream +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import org.apache.commons.lang3.StringUtils import org.json.JSONArray import org.json.JSONException @@ -57,6 +60,8 @@ import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.Quote +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate +import org.thoughtcrime.securesms.database.model.content.MessageContent import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get import org.thoughtcrime.securesms.mms.MmsException import org.thoughtcrime.securesms.mms.SlideDeck @@ -64,9 +69,17 @@ import org.thoughtcrime.securesms.util.asSequence import java.io.Closeable import java.io.IOException import java.util.LinkedList +import javax.inject.Inject import javax.inject.Provider - -class MmsDatabase(context: Context, databaseHelper: Provider) : MessagingDatabase(context, databaseHelper) { +import javax.inject.Singleton + +@Singleton +class MmsDatabase + @Inject constructor( + @ApplicationContext context: Context, + databaseHelper: Provider, + private val json: Json + ) : MessagingDatabase(context, databaseHelper) { private val earlyDeliveryReceiptCache = EarlyReceiptCache() private val earlyReadReceiptCache = EarlyReceiptCache() override fun getTableName() = TABLE_NAME @@ -209,13 +222,17 @@ class MmsDatabase(context: Context, databaseHelper: Provider { + val query = "SELECT " + ID + " FROM " + TABLE_NAME + + " WHERE " + EXPIRES_IN + " > 0 AND " + EXPIRE_STARTED + " > 0 AND " + EXPIRE_STARTED + " + " + EXPIRES_IN + " <= ?" + + return readableDatabase.rawQuery(query, nowMills).use { cursor -> + cursor.asSequence() + .map { it.getLong(0) } + .toList() } + } - val expireNotStartedMessages: Reader - get() { - val where = "$EXPIRES_IN > 0 AND $EXPIRE_STARTED = 0" - return readerFor(rawQuery(where, null))!! + /** + * @return the next expiring timestamp for messages that have started expiring. 0 if no messages are expiring. + */ + override fun getNextExpiringTimestamp(): Long { + val query = + "SELECT MIN(" + EXPIRE_STARTED + " + " + EXPIRES_IN + ") FROM " + TABLE_NAME + + " WHERE " + EXPIRES_IN + " > 0 AND " + EXPIRE_STARTED + " > 0" + + return readableDatabase.rawQuery(query).use { cursor -> + if (cursor.moveToFirst()) { + cursor.getLong(0) + } else { + 0L + } } + } private fun updateMailboxBitmask( id: Long, @@ -490,6 +523,15 @@ class MmsDatabase(context: Context, databaseHelper: Provider(messageContentJson) + }.onFailure { + Log.w(TAG, "Failed to decode message content for message ID $messageId", it) + }.getOrNull() + val message = OutgoingMediaMessage( recipient, body, @@ -503,7 +545,8 @@ class MmsDatabase(context: Context, databaseHelper: Provider { if (threadId < 0 ) throw MmsException("No thread ID supplied!") - if (retrieved.isExpirationUpdate) deleteExpirationTimerMessages(threadId, false.takeUnless { retrieved.groupId != null }) + if (retrieved.messageContent is DisappearingMessageUpdate) deleteExpirationTimerMessages(threadId, false.takeUnless { retrieved.groupId != null }) val contentValues = ContentValues() contentValues.put(DATE_SENT, retrieved.sentTimeMillis) contentValues.put(ADDRESS, retrieved.from.toString()) @@ -640,14 +683,15 @@ class MmsDatabase(context: Context, databaseHelper: Provider { if (threadId < 0 ) throw MmsException("No thread ID supplied!") - if (retrieved.isExpirationUpdate) deleteExpirationTimerMessages(threadId, true.takeUnless { retrieved.isGroup }) + if (retrieved.messageContent is DisappearingMessageUpdate) deleteExpirationTimerMessages(threadId, true.takeUnless { retrieved.isGroup }) val messageId = insertMessageOutbox( retrieved, threadId, @@ -692,9 +736,6 @@ class MmsDatabase(context: Context, databaseHelper: Provider, quoteAttachments: List, sharedContacts: List, @@ -832,6 +872,7 @@ class MmsDatabase(context: Context, databaseHelper: Provider(it) } + }.onFailure { + Log.e(TAG, "Failed to decode message content", it) + }.getOrNull() + return MediaMmsMessageRecord( id, recipient, recipient, addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, threadId, body, slideDeck!!, partCount, box, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, - readReceiptCount, quote, contacts, previews, reactions, hasMention + readReceiptCount, quote, contacts, previews, reactions, hasMention, + messageContent ) } @@ -1468,6 +1519,17 @@ class MmsDatabase(context: Context, databaseHelper: Provider = arrayOf( "$TABLE_NAME.$ID AS $ID", THREAD_ID, + MESSAGE_CONTENT, "$DATE_SENT AS $NORMALIZED_DATE_SENT", "$DATE_RECEIVED AS $NORMALIZED_DATE_RECEIVED", MESSAGE_BOX, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 5e9164508f..35da0238df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -9,6 +9,7 @@ public interface MmsSmsColumns { public static final String THREAD_ID = "thread_id"; public static final String READ = "read"; public static final String BODY = "body"; + public static final String MESSAGE_CONTENT = "message_content"; // This is the address of the message recipient, which may be a single user, a group, or a community! // It is NOT the address of the sender of any given message! @@ -99,6 +100,7 @@ public static class Types { // Group Message Information protected static final long GROUP_UPDATE_BIT = 0x10000; protected static final long GROUP_QUIT_BIT = 0x20000; + @Deprecated(forRemoval = true) protected static final long EXPIRATION_TIMER_UPDATE_BIT = 0x40000; protected static final long GROUP_UPDATE_MESSAGE_BIT = 0x80000; @@ -254,9 +256,6 @@ public static boolean isCallLog(long type) { baseType == MISSED_CALL_TYPE || baseType == FIRST_MISSED_CALL_TYPE; } - public static boolean isExpirationTimerUpdate(long type) { - return (type & EXPIRATION_TIMER_UPDATE_BIT) != 0; - } public static boolean isMediaSavedExtraction(long type) { return (type & MEDIA_SAVED_EXTRACTION_BIT) != 0; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 564ddd075c..6d072eb3a8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -63,7 +63,7 @@ public class MmsSmsDatabase extends Database { public static final String SMS_TRANSPORT = "sms"; private static final String[] PROJECTION = {MmsSmsColumns.ID, MmsSmsColumns.UNIQUE_ROW_ID, - SmsDatabase.BODY, SmsDatabase.TYPE, + SmsDatabase.BODY, SmsDatabase.TYPE, MmsSmsColumns.MESSAGE_CONTENT, MmsSmsColumns.THREAD_ID, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsSmsColumns.NORMALIZED_DATE_SENT, @@ -550,7 +550,9 @@ private Cursor queryTables(String[] projection, String selection, String order, "'" + AttachmentDatabase.STICKER_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_ID + ")) AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS, reactionsColumn, - SmsDatabase.BODY, READ, MmsSmsColumns.THREAD_ID, + SmsDatabase.BODY, + MmsDatabase.MESSAGE_CONTENT, + READ, MmsSmsColumns.THREAD_ID, SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, @@ -579,7 +581,9 @@ private Cursor queryTables(String[] projection, String selection, String order, + " AS " + MmsSmsColumns.UNIQUE_ROW_ID, "NULL AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS, reactionsColumn, - SmsDatabase.BODY, READ, MmsSmsColumns.THREAD_ID, + SmsDatabase.BODY, + MmsSmsColumns.MESSAGE_CONTENT, + READ, MmsSmsColumns.THREAD_ID, SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, @@ -624,6 +628,7 @@ private Cursor queryTables(String[] projection, String selection, String order, mmsColumnsPresent.add(MmsSmsColumns.ID); mmsColumnsPresent.add(READ); mmsColumnsPresent.add(MmsSmsColumns.THREAD_ID); + mmsColumnsPresent.add(MmsSmsColumns.MESSAGE_CONTENT); mmsColumnsPresent.add(MmsSmsColumns.BODY); mmsColumnsPresent.add(MmsSmsColumns.ADDRESS); mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index ebaf61479f..15e97df1be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -24,7 +24,7 @@ import android.content.Context; import android.database.Cursor; import android.text.TextUtils; -import android.util.Pair; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.annimon.stream.Stream; @@ -53,6 +53,7 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import java.io.Closeable; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; @@ -258,12 +259,13 @@ public void markExpireStarted(long id, long startedAtTimestamp) { contentValues.put(EXPIRE_STARTED, startedAtTimestamp); SQLiteDatabase db = getWritableDatabase(); - db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)}); - - long threadId = getThreadIdForMessage(id); - - DatabaseComponent.get(context).threadDatabase().update(threadId, false); - notifyConversationListeners(threadId); + try (final Cursor cursor = db.rawQuery("UPDATE " + TABLE_NAME + " SET " + EXPIRE_STARTED + " = ? " + + "WHERE " + ID + " = ? RETURNING " + THREAD_ID, startedAtTimestamp, id)) { + if (cursor.moveToNext()) { + long threadId = cursor.getLong(0); + DatabaseComponent.get(context).threadDatabase().update(threadId, false); + } + } } public void markAsSentFailed(long id) { @@ -412,34 +414,16 @@ private List setMessagesRead(String where, String[] arguments return results; } - public void updateSentTimestamp(long messageId, long newTimestamp, long threadId) { + public void updateSentTimestamp(long messageId, long newTimestamp) { SQLiteDatabase db = getWritableDatabase(); - db.execSQL("UPDATE " + TABLE_NAME + " SET " + DATE_SENT + " = ? " + - "WHERE " + ID + " = ?", - new String[] {newTimestamp + "", messageId + ""}); - notifyConversationListeners(threadId); - notifyConversationListListeners(); - } - - public Pair updateBundleMessageBody(long messageId, String body) { - long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT; - return updateMessageBodyAndType(messageId, body, Types.TOTAL_MASK, type); - } - - private Pair updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) { - SQLiteDatabase db = getWritableDatabase(); - db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " + - TYPE + " = (" + TYPE + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + ") " + - "WHERE " + ID + " = ?", - new String[] {body, messageId + ""}); - - long threadId = getThreadIdForMessage(messageId); + try(final Cursor cursor = db.rawQuery("UPDATE " + TABLE_NAME + " SET " + DATE_SENT + " = ? " + + "WHERE " + ID + " = ? RETURNING " + THREAD_ID, newTimestamp, messageId)) { + if (cursor.moveToNext()) { + notifyConversationListeners(cursor.getLong(0)); + } + } - DatabaseComponent.get(context).threadDatabase().update(threadId, true); - notifyConversationListeners(threadId); notifyConversationListListeners(); - - return new Pair<>(messageId, threadId); } protected Optional insertMessageInbox(IncomingTextMessage message, long type, long serverTimestamp, boolean runThreadUpdate) { @@ -575,7 +559,7 @@ public long insertMessageOutbox(long threadId, OutgoingTextMessage message, Map earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(date); Map earlyReadReceipts = earlyReadReceiptCache.remove(date); - ContentValues contentValues = new ContentValues(6); + ContentValues contentValues = new ContentValues(); contentValues.put(ADDRESS, address.toString()); contentValues.put(THREAD_ID, threadId); contentValues.put(BODY, message.getMessageBody()); @@ -621,14 +605,36 @@ private Cursor rawQuery(@NonNull String where, @Nullable String[] arguments) { " WHERE " + where + " GROUP BY " + SmsDatabase.TABLE_NAME + "." + SmsDatabase.ID, arguments); } - public Cursor getExpirationStartedMessages() { - String where = EXPIRE_STARTED + " > 0"; - return rawQuery(where, null); + @Override + public List getExpiredMessageIDs(long nowMills) { + String query = "SELECT " + ID + " FROM " + TABLE_NAME + + " WHERE " + EXPIRES_IN + " > 0 AND " + EXPIRE_STARTED + " > 0 AND " + EXPIRE_STARTED + " + " + EXPIRES_IN + " <= ?"; + + try (final Cursor cursor = getReadableDatabase().rawQuery(query, nowMills)) { + List result = new ArrayList<>(cursor.getCount()); + while (cursor.moveToNext()) { + result.add(cursor.getLong(0)); + } + + return result; + } } - public Cursor getExpirationNotStartedMessages() { - String where = EXPIRES_IN + " > 0 AND " + EXPIRE_STARTED + " = 0"; - return rawQuery(where, null); + /** + * @return the next expiring timestamp for messages that have started expiring. 0 if no messages are expiring. + */ + @Override + public long getNextExpiringTimestamp() { + String query = "SELECT MIN(" + EXPIRE_STARTED + " + " + EXPIRES_IN + ") FROM " + TABLE_NAME + + " WHERE " + EXPIRES_IN + " > 0 AND " + EXPIRE_STARTED + " > 0"; + + try (final Cursor cursor = getReadableDatabase().rawQuery(query)) { + if (cursor.moveToFirst()) { + return cursor.getLong(0); + } else { + return 0L; + } + } } @NonNull diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index c3dc4e4c62..736a603fa6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -95,7 +95,6 @@ import java.security.MessageDigest import javax.inject.Inject import javax.inject.Provider import javax.inject.Singleton -import kotlin.collections.set import network.loki.messenger.libsession_util.util.Contact as LibSessionContact import network.loki.messenger.libsession_util.util.GroupMember as LibSessionGroupMember @@ -669,15 +668,12 @@ open class Storage @Inject constructor( override fun updateSentTimestamp( messageId: MessageId, - openGroupSentTimestamp: Long, - threadId: Long + newTimestamp: Long ) { if (messageId.mms) { - val mmsDb = mmsDatabase - mmsDb.updateSentTimestamp(messageId.id, openGroupSentTimestamp, threadId) + mmsDatabase.updateSentTimestamp(messageId.id, newTimestamp) } else { - val smsDb = smsDatabase - smsDb.updateSentTimestamp(messageId.id, openGroupSentTimestamp, threadId) + smsDatabase.updateSentTimestamp(messageId.id, newTimestamp) } } @@ -858,7 +854,20 @@ open class Storage @Inject constructor( val userPublicKey = getUserPublicKey()!! val recipient = Recipient.from(context, fromSerialized(groupID), false) val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: "" - val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, 0, true, null, listOf(), listOf()) + val infoMessage = OutgoingGroupMediaMessage( + recipient, + updateData, + groupID, + null, + sentTimestamp, + 0, + 0, + true, + null, + listOf(), + listOf(), + null + ) val mmsDB = mmsDatabase val mmsSmsDB = mmsSmsDatabase if (mmsSmsDB.getMessageFor(sentTimestamp, userPublicKey) != null) { @@ -1022,7 +1031,8 @@ open class Storage @Inject constructor( true, null, listOf(), - listOf() + listOf(), + null ) val mmsDB = mmsDatabase val mmsSmsDB = mmsSmsDatabase @@ -1501,10 +1511,10 @@ open class Storage @Inject constructor( expireStartedAt, false, false, - false, Optional.absent(), Optional.absent(), Optional.absent(), + null, Optional.absent(), Optional.absent(), Optional.absent(), @@ -1512,10 +1522,6 @@ open class Storage @Inject constructor( ) mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, threadId, runThreadUpdate = true) - .orNull() - ?.let { - messageExpirationManager.startExpiringNow(MessageId(id = it.messageId, mms = true)) - } } /** @@ -1607,12 +1613,12 @@ open class Storage @Inject constructor( -1, 0, 0, - false, true, false, Optional.absent(), Optional.absent(), Optional.absent(), + null, Optional.absent(), Optional.absent(), Optional.absent(), @@ -1639,12 +1645,12 @@ open class Storage @Inject constructor( -1, 0, 0, - false, true, false, Optional.absent(), Optional.absent(), Optional.absent(), + null, Optional.absent(), Optional.absent(), Optional.absent(), @@ -1690,11 +1696,7 @@ open class Storage @Inject constructor( val expiresInMillis = expiryMode.expiryMillis val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 val callMessage = IncomingTextMessage.fromCallInfo(callMessageType, address, Optional.absent(), sentTimestamp, expiresInMillis, expireStartedAt) - smsDatabase.insertCallMessage(callMessage).orNull() - ?.let { - messageExpirationManager.startExpiringNow(MessageId(it.messageId, mms = false)) - } - + smsDatabase.insertCallMessage(callMessage) } override fun conversationHasOutgoing(userPublicKey: String): Boolean { @@ -1952,27 +1954,6 @@ open class Storage @Inject constructor( ) } - override fun getExpiringMessages(messageIds: List): List> { - val expiringMessages = mutableListOf>() - val smsDb = smsDatabase - smsDb.readerFor(smsDb.expirationNotStartedMessages).use { reader -> - while (reader.next != null) { - if (messageIds.isEmpty() || reader.current.id in messageIds) { - expiringMessages.add(reader.current.id to reader.current.expiresIn) - } - } - } - val mmsDb = mmsDatabase - mmsDb.expireNotStartedMessages.use { reader -> - while (reader.next != null) { - if (messageIds.isEmpty() || reader.current.id in messageIds) { - expiringMessages.add(reader.current.id to reader.current.expiresIn) - } - } - } - return expiringMessages - } - override fun updateDisappearingState( messageSender: String, threadID: Long, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index ead7ee356a..c8f44a1b55 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -58,6 +58,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; @@ -65,18 +66,21 @@ import java.io.Closeable; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import javax.inject.Inject; import javax.inject.Provider; +import javax.inject.Singleton; import kotlin.collections.CollectionsKt; +import kotlinx.serialization.json.Json; import network.loki.messenger.libsession_util.util.GroupInfo; +@Singleton public class ThreadDatabase extends Database { public interface ConversationThreadUpdateListener { @@ -102,6 +106,10 @@ public interface ConversationThreadUpdateListener { private static final String ERROR = "error"; public static final String SNIPPET_TYPE = "snippet_type"; public static final String SNIPPET_URI = "snippet_uri"; + /** + * The column that hold a {@link MessageContent}. See {@link MmsDatabase#MESSAGE_CONTENT} for more information + */ + public static final String SNIPPET_CONTENT = "snippet_content"; public static final String ARCHIVED = "archived"; public static final String STATUS = "status"; public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count"; @@ -127,9 +135,11 @@ public interface ConversationThreadUpdateListener { "CREATE INDEX IF NOT EXISTS archived_count_index ON " + TABLE_NAME + " (" + ARCHIVED + ", " + MESSAGE_COUNT + ");", }; + public static final String ADD_SNIPPET_CONTENT_COLUMN = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + SNIPPET_CONTENT + " TEXT DEFAULT NULL;"; + private static final String[] THREAD_PROJECTION = { ID, THREAD_CREATION_DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, UNREAD_MENTION_COUNT, DISTRIBUTION_TYPE, ERROR, SNIPPET_TYPE, - SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT, IS_PINNED + SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT, IS_PINNED, SNIPPET_CONTENT, }; private static final List TYPED_THREAD_PROJECTION = Stream.of(THREAD_PROJECTION) @@ -157,9 +167,33 @@ public static String getUnreadMentionCountCommand() { } private ConversationThreadUpdateListener updateListener; - - public ThreadDatabase(Context context, Provider databaseHelper) { + private final Json json; + + @Inject + public ThreadDatabase( + @dagger.hilt.android.qualifiers.ApplicationContext Context context, + Provider databaseHelper, + TextSecurePreferences prefs, + Json json) { super(context, databaseHelper); + this.json = json; + + if (!prefs.getMigratedDisappearingMessagesToMessageContent()) { + migrateDisappearingMessagesToMessageContent(); + prefs.setMigratedDisappearingMessagesToMessageContent(true); + } + } + + // As we migrate disappearing messages to MessageContent, we need to ensure that + // if they appear in the snippet, they have to be re-generated with the new MessageContent. + private void migrateDisappearingMessagesToMessageContent() { + String sql = "SELECT " + ID + " FROM " + TABLE_NAME + + " WHERE " + SNIPPET_TYPE + " & " + MmsSmsColumns.Types.EXPIRATION_TIMER_UPDATE_BIT + " != 0"; + try (final Cursor cursor = getReadableDatabase().rawQuery(sql)) { + while (cursor.moveToNext()) { + update(cursor.getLong(0), false); + } + } } public void setUpdateListener(ConversationThreadUpdateListener updateListener) { @@ -179,7 +213,7 @@ private long createThreadForRecipient(Address address, boolean group, int distri return db.insert(TABLE_NAME, null, contentValues); } - private void updateThread(long threadId, long count, String body, @Nullable Uri attachment, + private void updateThread(long threadId, long count, String body, @Nullable Uri attachment, @Nullable MessageContent messageContent, long date, int status, int deliveryReceiptCount, long type, boolean unarchive, long expiresIn, int readReceiptCount) { @@ -189,6 +223,7 @@ private void updateThread(long threadId, long count, String body, @Nullable Uri if (!body.isEmpty()) { contentValues.put(SNIPPET, body); } + contentValues.put(SNIPPET_CONTENT, messageContent == null ? null : json.encodeToString(MessageContent.Companion.serializer(), messageContent)); contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString()); contentValues.put(SNIPPET_TYPE, type); contentValues.put(STATUS, status); @@ -207,25 +242,7 @@ public void clearSnippet(long threadId){ ContentValues contentValues = new ContentValues(1); contentValues.put(SNIPPET, ""); - - SQLiteDatabase db = getWritableDatabase(); - db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""}); - notifyConversationListListeners(); - } - - public void updateSnippet(long threadId, String snippet, @Nullable Uri attachment, long date, long type, boolean unarchive) { - ContentValues contentValues = new ContentValues(4); - - contentValues.put(THREAD_CREATION_DATE, date - date % 1000); - if (!snippet.isEmpty()) { - contentValues.put(SNIPPET, snippet); - } - contentValues.put(SNIPPET_TYPE, type); - contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString()); - - if (unarchive) { - contentValues.put(ARCHIVED, 0); - } + contentValues.put(SNIPPET_CONTENT, ""); SQLiteDatabase db = getWritableDatabase(); db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""}); @@ -705,7 +722,7 @@ record = reader.getNext(); } } if (record != null && !record.isDeleted()) { - updateThread(threadId, count, getFormattedBodyFor(record), getAttachmentUriFor(record), + updateThread(threadId, count, getFormattedBodyFor(record), getAttachmentUriFor(record), record.getMessageContent(), record.getTimestamp(), record.getDeliveryStatus(), record.getDeliveryReceiptCount(), record.getType(), unarchive, record.getExpiresIn(), record.getReadReceiptCount()); return false; @@ -881,6 +898,7 @@ public ThreadRecord getCurrent() { Uri snippetUri = getSnippetUri(cursor); boolean pinned = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.IS_PINNED)) != 0; String invitingAdmin = cursor.getString(cursor.getColumnIndexOrThrow(LokiMessageDatabase.invitingSessionId)); + String messageContentJson = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_CONTENT)); if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { readReceiptCount = 0; @@ -910,9 +928,19 @@ public ThreadRecord getCurrent() { groupThreadStatus = GroupThreadStatus.None; } - return new ThreadRecord(body, snippetUri, lastMessage, recipient, date, count, + MessageContent messageContent; + try { + messageContent = (messageContentJson == null || messageContentJson.isEmpty()) ? null : json.decodeFromString( + MessageContent.Companion.serializer(), + messageContentJson + ); + } catch (Exception e) { + Log.e(TAG, "Failed to parse message content for thread: " + threadId, e); + messageContent = null; + } + return new ThreadRecord(body, snippetUri, lastMessage, recipient, date, count, unreadCount, unreadMentionCount, threadId, deliveryReceiptCount, status, type, - distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned, invitingAdmin, groupThreadStatus); + distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned, invitingAdmin, groupThreadStatus, messageContent); } private @Nullable Uri getSnippetUri(Cursor cursor) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index e6feb8794a..378b491057 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -101,9 +101,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV48 = 69; private static final int lokiV49 = 70; private static final int lokiV50 = 71; + private static final int lokiV51 = 72; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV50; + private static final int DATABASE_VERSION = lokiV51; private static final int MIN_DATABASE_VERSION = lokiV7; public static final String DATABASE_NAME = "session.db"; @@ -241,6 +242,8 @@ public void onCreate(SQLiteDatabase db) { db.execSQL(LokiMessageDatabase.getCreateThreadDeleteTrigger()); db.execSQL(SmsDatabase.ADD_IS_GROUP_UPDATE_COLUMN); db.execSQL(MmsDatabase.ADD_IS_GROUP_UPDATE_COLUMN); + db.execSQL(MmsDatabase.ADD_MESSAGE_CONTENT_COLUMN); + db.execSQL(ThreadDatabase.ADD_SNIPPET_CONTENT_COLUMN); } @Override @@ -539,6 +542,12 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { executeStatements(db, ReactionDatabase.CREATE_MESSAGE_ID_MMS_INDEX); } + if (oldVersion < lokiV51) { + db.execSQL(MmsDatabase.ADD_MESSAGE_CONTENT_COLUMN); + db.execSQL(MmsDatabase.MIGRATE_EXPIRY_CONTROL_MESSAGES); + db.execSQL(ThreadDatabase.ADD_SNIPPET_CONTENT_COLUMN); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java index 7eeec84d07..8faaab63a9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -19,10 +19,13 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate; +import org.thoughtcrime.securesms.database.model.content.MessageContent; /** * The base class for all message record models. Encapsulates basic data @@ -43,9 +46,12 @@ public abstract class DisplayRecord { private final int deliveryReceiptCount; private final int readReceiptCount; + @Nullable + private final MessageContent messageContent; + DisplayRecord(String body, Recipient recipient, long dateSent, - long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, - long type, int readReceiptCount) + long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, + long type, int readReceiptCount, @Nullable MessageContent messageContent) { this.threadId = threadId; this.recipient = recipient; @@ -56,6 +62,7 @@ public abstract class DisplayRecord { this.deliveryReceiptCount = deliveryReceiptCount; this.readReceiptCount = readReceiptCount; this.deliveryStatus = deliveryStatus; + this.messageContent = messageContent; } public @NonNull String getBody() { @@ -88,10 +95,14 @@ public boolean isPending() { return isPending; } + @Nullable + public MessageContent getMessageContent() { + return messageContent; + } + public boolean isCallLog() { return SmsDatabase.Types.isCallLog(type); } public boolean isDataExtractionNotification() { return isMediaSavedNotification() || isScreenshotNotification(); } public boolean isDeleted() { return MmsSmsColumns.Types.isDeletedMessage(type); } - public boolean isExpirationTimerUpdate() { return SmsDatabase.Types.isExpirationTimerUpdate(type); } public boolean isFirstMissedCall() { return SmsDatabase.Types.isFirstMissedCall(type); } public boolean isGroupUpdateMessage() { return SmsDatabase.Types.isGroupUpdateMessage(type); } public boolean isIncoming() { return !MmsSmsColumns.Types.isOutgoingMessageType(type); } @@ -112,10 +123,10 @@ public boolean isPending() { public boolean isControlMessage() { return isGroupUpdateMessage() || - isExpirationTimerUpdate() || isDataExtractionNotification() || isMessageRequestResponse() || - isCallLog(); + isCallLog() || + (messageContent instanceof DisappearingMessageUpdate); } public boolean isGroupV2ExpirationTimerUpdate() { return false; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java index fdba0510e3..7fbcf9669e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java @@ -27,6 +27,7 @@ import org.session.libsession.utilities.NetworkFailure; import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.SmsDatabase.Status; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import org.thoughtcrime.securesms.mms.SlideDeck; import java.util.List; @@ -53,12 +54,13 @@ public MediaMmsMessageRecord(long id, Recipient conversationRecipient, long expiresIn, long expireStarted, int readReceiptCount, @Nullable Quote quote, @NonNull List contacts, @NonNull List linkPreviews, - @NonNull List reactions, boolean hasMention) + @NonNull List reactions, boolean hasMention, + @Nullable MessageContent messageContent) { super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures, expiresIn, expireStarted, slideDeck, readReceiptCount, quote, contacts, - linkPreviews, reactions, hasMention); + linkPreviews, reactions, hasMention, messageContent); this.partCount = partCount; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index e5e375dac9..208fdec88f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -37,6 +37,8 @@ import org.session.libsession.utilities.ThemeUtil; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.AccountId; +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import network.loki.messenger.R; @@ -62,10 +64,6 @@ public abstract class MessageRecord extends DisplayRecord { @Nullable private UpdateMessageData groupUpdateMessage; - public final boolean isNotDisappearAfterRead() { - return expireStarted == getTimestamp(); - } - public abstract boolean isMms(); public abstract boolean isMmsNotification(); @@ -74,16 +72,17 @@ public final MessageId getMessageId() { } MessageRecord(long id, String body, Recipient conversationRecipient, - Recipient individualRecipient, - long dateSent, long dateReceived, long threadId, - int deliveryStatus, int deliveryReceiptCount, long type, - List mismatches, - List networkFailures, - long expiresIn, long expireStarted, - int readReceiptCount, List reactions, boolean hasMention) + Recipient individualRecipient, + long dateSent, long dateReceived, long threadId, + int deliveryStatus, int deliveryReceiptCount, long type, + List mismatches, + List networkFailures, + long expiresIn, long expireStarted, + int readReceiptCount, List reactions, boolean hasMention, + @Nullable MessageContent messageContent) { super(body, conversationRecipient, dateSent, dateReceived, - threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount); + threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount, messageContent); this.id = id; this.individualRecipient = individualRecipient; this.mismatches = mismatches; @@ -159,10 +158,10 @@ public CharSequence getDisplayBody(@NonNull Context context) { } return text; - } else if (isExpirationTimerUpdate()) { - int seconds = (int) (getExpiresIn() / 1000); + } else if (getMessageContent() instanceof DisappearingMessageUpdate) { boolean isGroup = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(getThreadId()).isGroupOrCommunityRecipient(); - return new SpannableString(UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, seconds, isGroup, getIndividualRecipient().getAddress().toString(), isOutgoing(), getTimestamp(), expireStarted)); + return UpdateMessageBuilder.INSTANCE + .buildExpirationTimerMessage(context, ((DisappearingMessageUpdate) getMessageContent()).getExpiryMode(), isGroup, getIndividualRecipient().getAddress().toString(), isOutgoing()); } else if (isDataExtractionNotification()) { if (isScreenshotNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT, getIndividualRecipient().getAddress().toString()))); else if (isMediaSavedNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED, getIndividualRecipient().getAddress().toString()))); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java index 320eb4e739..802d16b835 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java @@ -8,6 +8,7 @@ import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.NetworkFailure; import org.session.libsession.utilities.recipients.Recipient; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; @@ -22,15 +23,16 @@ public abstract class MmsMessageRecord extends MessageRecord { private final @NonNull List linkPreviews = new LinkedList<>(); MmsMessageRecord(long id, String body, Recipient conversationRecipient, - Recipient individualRecipient, long dateSent, - long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, - long type, List mismatches, - List networkFailures, long expiresIn, - long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount, - @Nullable Quote quote, @NonNull List contacts, - @NonNull List linkPreviews, List reactions, boolean hasMention) + Recipient individualRecipient, long dateSent, + long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, + long type, List mismatches, + List networkFailures, long expiresIn, + long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount, + @Nullable Quote quote, @NonNull List contacts, + @NonNull List linkPreviews, List reactions, boolean hasMention, + @Nullable MessageContent messageContent) { - super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, expiresIn, expireStarted, readReceiptCount, reactions, hasMention); + super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, expiresIn, expireStarted, readReceiptCount, reactions, hasMention, messageContent); this.slideDeck = slideDeck; this.quote = quote; this.contacts.addAll(contacts); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java index 4a746d23a2..c33c1efb6e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java @@ -47,7 +47,7 @@ public SmsMessageRecord(long id, super(id, body, recipient, individualRecipient, dateSent, dateReceived, threadId, status, deliveryReceiptCount, type, mismatches, new LinkedList<>(), - expiresIn, expireStarted, readReceiptCount, reactions, hasMention); + expiresIn, expireStarted, readReceiptCount, reactions, hasMention, null); } public long getType() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 4a4b8ae462..fd58198d7e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -37,6 +37,8 @@ import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate; +import org.thoughtcrime.securesms.database.model.content.MessageContent; import org.thoughtcrime.securesms.ui.UtilKt; /** @@ -67,11 +69,11 @@ public class ThreadRecord extends DisplayRecord { public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri, @Nullable MessageRecord lastMessage, @NonNull Recipient recipient, long date, long count, int unreadCount, int unreadMentionCount, long threadId, int deliveryReceiptCount, int status, - long snippetType, int distributionType, boolean archived, long expiresIn, + long snippetType, int distributionType, boolean archived, long expiresIn, long lastSeen, int readReceiptCount, boolean pinned, String invitingAdminId, - @NonNull GroupThreadStatus groupThreadStatus) + @NonNull GroupThreadStatus groupThreadStatus, @Nullable MessageContent messageContent) { - super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount); + super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount, messageContent); this.snippetUri = snippetUri; this.lastMessage = lastMessage; this.count = count; @@ -141,7 +143,7 @@ else if (isGroupUpdateMessage()) { return Phrase.from(context, R.string.callsMissedCallFrom) .put(NAME_KEY, getName()) .format().toString(); - } else if (SmsDatabase.Types.isExpirationTimerUpdate(type)) { + } else if (getMessageContent() instanceof DisappearingMessageUpdate) { // Use the same message as we would for displaying on the conversation screen. // lastMessage shouldn't be null here, but we'll check just in case. if (lastMessage != null) { @@ -149,7 +151,8 @@ else if (isGroupUpdateMessage()) { } else { return ""; } - } else if (MmsSmsColumns.Types.isMediaSavedExtraction(type)) { + } + else if (MmsSmsColumns.Types.isMediaSavedExtraction(type)) { return Phrase.from(context, R.string.attachmentsMediaSaved) .put(NAME_KEY, getName()) .format().toString(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/content/DisappearingMessageUpdate.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/content/DisappearingMessageUpdate.kt new file mode 100644 index 0000000000..ab42a31ab2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/content/DisappearingMessageUpdate.kt @@ -0,0 +1,54 @@ +package org.thoughtcrime.securesms.database.model.content + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import network.loki.messenger.libsession_util.util.ExpiryMode +import org.session.libsignal.protos.SignalServiceProtos +import org.thoughtcrime.securesms.util.ProtobufEnumSerializer + + +@Serializable +@SerialName(DisappearingMessageUpdate.TYPE_NAME) +data class DisappearingMessageUpdate( + @SerialName(KEY_EXPIRY_TIME_SECONDS) + val expiryTimeSeconds: Long, + + @SerialName(KEY_EXPIRY_TYPE) + @Serializable(with = ExpirationTypeSerializer::class) + val expiryType: SignalServiceProtos.Content.ExpirationType, +) : MessageContent { + val expiryMode: ExpiryMode + get() = when (expiryType) { + SignalServiceProtos.Content.ExpirationType.DELETE_AFTER_SEND -> ExpiryMode.AfterSend(expiryTimeSeconds) + SignalServiceProtos.Content.ExpirationType.DELETE_AFTER_READ -> ExpiryMode.AfterRead(expiryTimeSeconds) + else -> ExpiryMode.NONE + } + + constructor(mode: ExpiryMode) : this( + expiryTimeSeconds = mode.expirySeconds, + expiryType = when (mode) { + is ExpiryMode.AfterSend -> SignalServiceProtos.Content.ExpirationType.DELETE_AFTER_SEND + is ExpiryMode.AfterRead -> SignalServiceProtos.Content.ExpirationType.DELETE_AFTER_READ + ExpiryMode.NONE -> SignalServiceProtos.Content.ExpirationType.UNKNOWN + } + ) + + class ExpirationTypeSerializer : ProtobufEnumSerializer() { + override fun fromNumber(number: Int): SignalServiceProtos.Content.ExpirationType + = SignalServiceProtos.Content.ExpirationType.forNumber(number) ?: SignalServiceProtos.Content.ExpirationType.UNKNOWN + } + + companion object { + const val TYPE_NAME = "disappearing_message_update" + + const val KEY_EXPIRY_TIME_SECONDS = "expiry_time_seconds" + const val KEY_EXPIRY_TYPE = "expiry_type" + + // These constants map to SignalServiceProtos.Content.ExpirationType but given we want to use + // a constants it's impossible to use the enum directly. Luckily the values aren't supposed + // to change so we can safely use these constants. + const val EXPIRY_MODE_AFTER_SENT = 2 + const val EXPIRY_MODE_AFTER_READ = 1 + const val EXPIRY_MODE_NONE = 0 + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/content/MessageContent.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/content/MessageContent.kt new file mode 100644 index 0000000000..c77ddb647f --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/content/MessageContent.kt @@ -0,0 +1,27 @@ +package org.thoughtcrime.securesms.database.model.content + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonClassDiscriminator +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.modules.subclass + + +@OptIn(ExperimentalSerializationApi::class) +@Serializable +@JsonClassDiscriminator(MessageContent.DISCRIMINATOR) +sealed interface MessageContent { + companion object { + const val DISCRIMINATOR = "type" + + fun serializersModule() = SerializersModule { + polymorphic(MessageContent::class) { + subclass(DisappearingMessageUpdate::class) + defaultDeserializer { + UnknownMessageContent.serializer() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/content/UnknownMessageContent.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/content/UnknownMessageContent.kt new file mode 100644 index 0000000000..cc8abeedcd --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/content/UnknownMessageContent.kt @@ -0,0 +1,10 @@ +package org.thoughtcrime.securesms.database.model.content + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UnknownMessageContent( + @SerialName(MessageContent.DISCRIMINATOR) + val type: String, +) : MessageContent \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt index 5bf323d5f6..7208de9df8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt @@ -8,12 +8,16 @@ import dagger.hilt.EntryPoint import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.plus import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier import org.session.libsession.utilities.AppTextSecurePreferences import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences +import org.thoughtcrime.securesms.database.model.content.MessageContent import org.thoughtcrime.securesms.groups.GroupManagerV2Impl import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier import org.thoughtcrime.securesms.repository.ConversationRepository @@ -26,6 +30,14 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) class AppModule { + @Provides + @Singleton + fun provideJson(): Json { + return Json { + ignoreUnknownKeys = true + serializersModule += MessageContent.serializersModule() + } + } } @Module diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt index ddad988b59..7718a2e159 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt @@ -65,9 +65,6 @@ object DatabaseModule { @Singleton fun provideSmsDatabase(@ApplicationContext context: Context, openHelper: Provider) = SmsDatabase(context, openHelper) - @Provides - @Singleton - fun provideMmsDatabase(@ApplicationContext context: Context, openHelper: Provider) = MmsDatabase(context, openHelper) @Provides @Singleton @@ -78,10 +75,6 @@ object DatabaseModule { @Singleton fun provideMediaDatbase(@ApplicationContext context: Context, openHelper: Provider) = MediaDatabase(context, openHelper) - @Provides - @Singleton - fun provideThread(@ApplicationContext context: Context, openHelper: Provider) = ThreadDatabase(context,openHelper) - @Provides @Singleton fun provideMmsSms(@ApplicationContext context: Context, openHelper: Provider) = MmsSmsDatabase(context, openHelper) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt index a06f7bad67..9539b17ea7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt @@ -15,16 +15,13 @@ import org.session.libsession.messaging.sending_receiving.MessageSender.send import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI.nowWithOffset import org.session.libsession.snode.SnodeClock -import org.session.libsession.snode.utilities.await -import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled import org.session.libsession.utilities.associateByNotNull import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.conversation.disappearingmessages.ExpiryType -import org.thoughtcrime.securesms.database.ExpirationInfo import org.thoughtcrime.securesms.database.MarkedMessageInfo +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.util.SessionMetaProtocol.shouldSendReadReceipt import javax.inject.Inject @@ -60,8 +57,6 @@ class MarkReadReceiver : BroadcastReceiver() { const val THREAD_IDS_EXTRA = "thread_ids" const val NOTIFICATION_ID_EXTRA = "notification_id" - val messageExpirationManager = SSKEnvironment.shared.messageExpirationManager - @JvmStatic fun process( context: Context, @@ -80,14 +75,22 @@ class MarkReadReceiver : BroadcastReceiver() { .asSequence() .filter { it.expiryType == ExpiryType.AFTER_READ } .filter { mmsSmsDatabase.getMessageById(it.expirationInfo.id)?.run { - isExpirationTimerUpdate && threadDb.getRecipientForThreadId(threadId)?.isGroupOrCommunityRecipient == true } == false + (messageContent is DisappearingMessageUpdate) + && threadDb.getRecipientForThreadId(threadId)?.isGroupOrCommunityRecipient == true } == false + } + .forEach { + val db = if (it.expirationInfo.id.mms) { + DatabaseComponent.get(context).mmsDatabase() + } else { + DatabaseComponent.get(context).smsDatabase() + } + + db.markExpireStarted(it.expirationInfo.id.id, nowWithOffset) } - .forEach { messageExpirationManager.startExpiringNow(it.expirationInfo.id) } hashToDisappearAfterReadMessage(context, markedReadMessages)?.let { hashToMessages -> GlobalScope.launch { try { - fetchUpdatedExpiriesAndScheduleDeletion(context, hashToMessages) shortenExpiryOfDisappearingAfterRead(hashToMessages) } catch (e: Exception) { Log.e(TAG, "Failed to fetch updated expiries and schedule deletion", e) @@ -141,37 +144,5 @@ class MarkReadReceiver : BroadcastReceiver() { .let { send(it, address) } } } - - private suspend fun fetchUpdatedExpiriesAndScheduleDeletion( - context: Context, - hashToMessage: Map - ) { - @Suppress("UNCHECKED_CAST") - val expiries = SnodeAPI.getExpiries(hashToMessage.keys.toList(), shared.storage.userAuth!!).await()["expiries"] as Map - hashToMessage.forEach { (hash, info) -> expiries[hash]?.let { scheduleDeletion(context, info.expirationInfo, it - info.expirationInfo.expireStarted) } } - } - - private fun scheduleDeletion( - context: Context, - expirationInfo: ExpirationInfo, - expiresIn: Long = expirationInfo.expiresIn - ) { - if (expiresIn == 0L) return - - val now = nowWithOffset - - val expireStarted = expirationInfo.expireStarted - - if (expirationInfo.isDisappearAfterRead() && expireStarted == 0L || now < expireStarted) { - val db = DatabaseComponent.get(context).run { if (expirationInfo.id.mms) mmsDatabase() else smsDatabase() } - db.markExpireStarted(expirationInfo.id.id, now) - } - - ApplicationContext.getInstance(context).expiringMessageManager.get().scheduleDeletion( - expirationInfo.id, - now, - expiresIn - ) - } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt index 4c262a4dbe..e1ad385bcb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt @@ -43,11 +43,11 @@ import javax.inject.Inject private const val TAG = "PushHandler" class PushReceiver @Inject constructor( - @ApplicationContext private val context: Context, + @param:ApplicationContext private val context: Context, private val configFactory: ConfigFactory, private val groupRevokedMessageHandler: GroupRevokedMessageHandler, + private val json: Json, ) { - private val json = Json { ignoreUnknownKeys = true } /** * Both push services should hit this method once they receive notification data diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt index 4355707a0d..bf8540fbac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt @@ -3,24 +3,21 @@ package org.thoughtcrime.securesms.service import android.content.Context import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.SendChannel -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeoutOrNull import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.signal.IncomingMediaMessage -import org.session.libsession.messaging.messages.signal.OutgoingExpirationUpdateMessage +import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage +import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized +import org.session.libsession.utilities.DistributionTypes import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil.doubleEncodeGroupID import org.session.libsession.utilities.SSKEnvironment.MessageExpirationManagerProtocol @@ -31,70 +28,53 @@ import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.guava.Optional +import org.thoughtcrime.securesms.database.DatabaseContentProviders +import org.thoughtcrime.securesms.database.MessagingDatabase import org.thoughtcrime.securesms.database.MmsDatabase -import org.thoughtcrime.securesms.database.MmsSmsDatabase import org.thoughtcrime.securesms.database.SmsDatabase import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageId -import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate import org.thoughtcrime.securesms.mms.MmsException +import org.thoughtcrime.securesms.util.observeChanges import java.io.IOException -import java.util.PriorityQueue import javax.inject.Inject import javax.inject.Singleton private val TAG = ExpiringMessageManager::class.java.simpleName +/** + * A manager that reactively looking into the [MmsDatabase] and [SmsDatabase] for expired messages, + * and deleting them. This is done by observing the expiration timestamps of messages and scheduling + * the deletion of them when they are expired. + * + * There is no need (and no way) to ask this manager to schedule a deletion of a message, instead, all you + * need to do is set the expiryMills and expiryStarted fields of the message and save to db, + * this manager will take care of the rest. + */ @Singleton class ExpiringMessageManager @Inject constructor( - @ApplicationContext private val context: Context, + @param:ApplicationContext private val context: Context, private val smsDatabase: SmsDatabase, private val mmsDatabase: MmsDatabase, - private val mmsSmsDatabase: MmsSmsDatabase, private val clock: SnodeClock, private val storage: Lazy, private val preferences: TextSecurePreferences, ) : MessageExpirationManagerProtocol { - private val scheduleDeletionChannel: SendChannel - init { - val channel = Channel(capacity = Channel.UNLIMITED) - scheduleDeletionChannel = channel - GlobalScope.launch { - preferences.watchLocalNumber() - .map { it != null } - .distinctUntilChanged() - .collectLatest { loggedIn -> - if (loggedIn) { - try { - process(channel) - } catch (ec: CancellationException) { - throw ec - } - catch (ec: Exception) { - Log.e(TAG, "Error processing expiring messages", ec) - } - } - } + listOf( + launch { processDatabase(smsDatabase) }, + launch { processDatabase(mmsDatabase) } + ).joinAll() } } private fun getDatabase(mms: Boolean) = if (mms) mmsDatabase else smsDatabase - fun scheduleDeletion(id: MessageId, expireStartedAt: Long, expiresInMills: Long) { - check(expireStartedAt > 0 && expiresInMills > 0) { - "Expiration start time and duration must be greater than zero." - } - - getDatabase(id.mms).markExpireStarted(id.id, expireStartedAt) - scheduleDeletionChannel.trySend(ExpiringMessageReference(id, expireStartedAt + expiresInMills)) - } - private fun insertIncomingExpirationTimerMessage( message: ExpirationTimerUpdate, - expireStartedAt: Long ): MessageId? { val senderPublicKey = message.sender val sentTimestamp = message.sentTimestamp @@ -125,12 +105,15 @@ class ExpiringMessageManager @Inject constructor( val threadId = storage.get().getThreadId(recipient) ?: return null val mediaMessage = IncomingMediaMessage( address, sentTimestamp!!, -1, - expiresInMillis, expireStartedAt, true, + expiresInMillis, + 0, // Marking expiryStartedAt as 0 as expiration logic will be universally applied on received messages + // We no longer set this to true anymore as it won't be used in the future, false, false, Optional.absent(), groupInfo, Optional.absent(), + DisappearingMessageUpdate(message.expiryMode), Optional.absent(), Optional.absent(), Optional.absent(), @@ -151,7 +134,6 @@ class ExpiringMessageManager @Inject constructor( private fun insertOutgoingExpirationTimerMessage( message: ExpirationTimerUpdate, - expireStartedAt: Long ): MessageId? { val sentTimestamp = message.sentTimestamp val groupId = message.groupPublicKey @@ -166,13 +148,34 @@ class ExpiringMessageManager @Inject constructor( val recipient = Recipient.from(context, address, false) message.threadID = storage.get().getOrCreateThreadIdFor(address) - val timerUpdateMessage = OutgoingExpirationUpdateMessage( + val content = DisappearingMessageUpdate(message.expiryMode) + val timerUpdateMessage = if (groupId != null) OutgoingGroupMediaMessage( recipient, + "", + groupId, + null, sentTimestamp!!, duration, - expireStartedAt, - groupId + 0, // Marking as 0 as expiration shouldn't start until we send the message + false, + null, + emptyList(), + emptyList(), + content + ) else OutgoingSecureMediaMessage( + recipient, + "", + emptyList(), + sentTimestamp!!, + DistributionTypes.CONVERSATION, + duration, + 0, // Marking as 0 as expiration shouldn't start until we send the message + null, + emptyList(), + emptyList(), + content ) + return mmsDatabase.insertSecureDecryptedMessageOutbox( timerUpdateMessage, message.threadID!!, @@ -189,39 +192,17 @@ class ExpiringMessageManager @Inject constructor( } override fun insertExpirationTimerMessage(message: ExpirationTimerUpdate) { - val expiryMode: ExpiryMode = message.expiryMode - val userPublicKey = preferences.getLocalNumber() val senderPublicKey = message.sender - val sentTimestamp = message.sentTimestamp ?: 0 - val expireStartedAt = if ((expiryMode is ExpiryMode.AfterSend || message.isSenderSelf) && !message.isGroup) sentTimestamp else 0 - // Notify the user - val messageId = if (senderPublicKey == null || userPublicKey == senderPublicKey) { + message.id = if (senderPublicKey == null || userPublicKey == senderPublicKey) { // sender is self or a linked device - insertOutgoingExpirationTimerMessage(message, expireStartedAt) + insertOutgoingExpirationTimerMessage(message) } else { - insertIncomingExpirationTimerMessage(message, expireStartedAt) - } - - if (messageId != null) { - startExpiringNow(messageId) + insertIncomingExpirationTimerMessage(message) } } - override fun startExpiringNow(id: MessageId) { - val message = mmsSmsDatabase.getMessageById(id) ?: run { - Log.w(TAG, "Message with ID $id not found in database, cannot start expiration.") - return - } - - if (message.expiresIn <= 0L) { - Log.w(TAG, "Message with ID $message has no expiration mode set, cannot start expiration.") - return - } - - scheduleDeletion(message.messageId, clock.currentTimeMills(), message.expiresIn) - } override fun onMessageSent(message: Message) { // When a message is sent, we'll schedule deletion immediately if we have an expiry mode, @@ -230,87 +211,67 @@ class ExpiringMessageManager @Inject constructor( // to disappear the message regardlessly for the safety of ourselves. // As for the receiver, they will be able to disappear the message correctly after // they've done reading it. - if (message.expiryMode != ExpiryMode.NONE) { - scheduleMessageDeletion(message) + val messageId = message.id + val sentTimestamp = message.sentTimestamp + if (message.expiryMode != ExpiryMode.NONE && messageId != null && sentTimestamp != null) { + // If the expiryMode is set to AfterRead, the start time must be greater than the sent timestamp. + // This is due to the assumption downstream that `expiryStarted == sentTimestamp` means expiryMode is AfterSend. + val startTime = if (message.expiryMode is ExpiryMode.AfterRead) { + sentTimestamp + 1 // Ensure start time is after sent timestamp + } else { + sentTimestamp + } + + getDatabase(messageId.mms).markExpireStarted(messageId.id, startTime) } } override fun onMessageReceived(message: Message) { // When we receive a message, we'll schedule deletion if it has an expiry mode set to // AfterSend, as the message would be considered sent from the sender's perspective. - if (message.expiryMode is ExpiryMode.AfterSend) { - scheduleMessageDeletion(message) + val messageId = message.id + val sentTimestamp = message.sentTimestamp + if (message.expiryMode is ExpiryMode.AfterSend && messageId != null && sentTimestamp != null) { + getDatabase(messageId.mms).markExpireStarted(messageId.id, sentTimestamp) } } - private fun scheduleMessageDeletion(message: Message) { - if ( - message is ExpirationTimerUpdate && message.isGroup || - message.openGroupServerMessageID != null || // ignore expiration on communities since they do not support disappearing mesasges - message.expiryMode == ExpiryMode.NONE // no expiration mode set - ) return - val id = requireNotNull(message.id) { - "Message ID cannot be null when scheduling deletion." - } + private suspend fun processDatabase(db: MessagingDatabase) { + while (true) { + val expiredMessages = db.getExpiredMessageIDs(clock.currentTimeMills()) + + if (expiredMessages.isNotEmpty()) { + Log.d(TAG, "Deleting ${expiredMessages.size} expired messages from ${db.javaClass.simpleName}") + for (messageId in expiredMessages) { + try { + db.deleteMessage(messageId) + } catch (e: Exception) { + Log.e(TAG, "Failed to delete expired message with ID $messageId", e) + } + } + } - scheduleDeletion( - id = id, - expireStartedAt = clock.currentTimeMills(), // The expiration starts now instead of `message.sentTimestamp`, as that property is not really the time the message is sent but the time the user hits send - expiresInMills = message.expiryMode.expiryMillis - ) - } + val nextExpiration = db.nextExpiringTimestamp + val now = clock.currentTimeMills() - private suspend fun process(scheduleChannel: ReceiveChannel) { - // Populate the expiring message queue with initial data from database to start with. - // This message queue is sorted by the expiration time from closest to furthest - val sortedMessageQueue = smsDatabase.readerFor(smsDatabase.expirationStartedMessages).use { smsReader -> - mmsDatabase.expireStartedMessages.use { mmsReader -> - (generateSequence { smsReader.next } + generateSequence { mmsReader.next }) - .mapTo(PriorityQueue(), ::ExpiringMessageReference) + if (nextExpiration > 0 && nextExpiration <= now) { + continue // Proceed to the next iteration if the next expiration is already or about go to in the past } - } - while (true) { - val millsUntilNextExpiration = sortedMessageQueue.firstOrNull()?.let { it.expiresAtMillis - clock.currentTimeMills() } + val dbChanges = context.contentResolver.observeChanges(DatabaseContentProviders.Conversation.CONTENT_URI, true) - // Wait for the next expiration or a new message to be scheduled - val newScheduledMessage = if (millsUntilNextExpiration != null && millsUntilNextExpiration > 0L) { - // There's something in the queue for later, so we'll wait for that, or a new message to be scheduled - ExpirationListener.setAlarm(context, millsUntilNextExpiration) - withTimeoutOrNull(millsUntilNextExpiration) { - scheduleChannel.receive() + if (nextExpiration > 0) { + val delayMills = nextExpiration - now + Log.d(TAG, "Wait for up to $delayMills ms for next expiration in ${db.javaClass.simpleName}") + withTimeoutOrNull(delayMills) { + dbChanges.first() } - } else if (millsUntilNextExpiration == null) { - // There is nothing in the queue, so we'll just wait for a new message to be expired - scheduleChannel.receive() } else { - // There are some expired messages, so we can process them immediately - null - } - - if (newScheduledMessage != null) { - sortedMessageQueue.add(newScheduledMessage) - } - - // Drain the queue and process expired messages - while (sortedMessageQueue.isNotEmpty() && sortedMessageQueue.peek()!!.expiresAtMillis <= clock.currentTimeMills()) { - val expiredMessage = sortedMessageQueue.remove() - Log.d(TAG, "Processing expired message: ${expiredMessage.id}") - getDatabase(expiredMessage.id.mms).deleteMessage(expiredMessage.id.id) + Log.d(TAG, "No next expiration found, waiting for any change in ${db.javaClass.simpleName}") + // If there are no next expiration, just wait for any change in the database + dbChanges.first() } } } - - private data class ExpiringMessageReference( - val id: MessageId, - val expiresAtMillis: Long - ): Comparable { - constructor(record: MessageRecord): this( - id = record.messageId, - expiresAtMillis = record.expireStarted + record.expiresIn - ) - - override fun compareTo(other: ExpiringMessageReference) = compareValuesBy(this, other, { it.expiresAtMillis }, { it.id.id }, { it.id.mms }) - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenRepository.kt index 2d306f2826..041e64a1cb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/tokenpage/TokenRepository.kt @@ -25,8 +25,9 @@ interface TokenRepository { @Singleton class TokenRepositoryImpl @Inject constructor( - @ApplicationContext val context: Context, - private val storage: StorageProtocol + @param:ApplicationContext val context: Context, + private val storage: StorageProtocol, + private val json: Json, ): TokenRepository { private val TAG = "TokenRepository" @@ -56,8 +57,6 @@ class TokenRepositoryImpl @Inject constructor( ) } - private val json = Json { ignoreUnknownKeys = true } - private suspend inline fun sendOnionRequest( path: String, url: String, body: ByteArray? = null, customCatch: (Exception) -> T? = { e -> defaultErrorHandling(e) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ProtobufEnumSerializer.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ProtobufEnumSerializer.kt new file mode 100644 index 0000000000..57e11842fe --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ProtobufEnumSerializer.kt @@ -0,0 +1,32 @@ +package org.thoughtcrime.securesms.util + +import com.google.protobuf.ProtocolMessageEnum +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +/** + * A base serializer for Protobuf enums that implements the [KSerializer] interface. + * It provides a way to serialize and deserialize Protobuf enum values using their numeric representation. + * + * @param T The type of the Protobuf enum that extends [ProtocolMessageEnum]. + */ +abstract class ProtobufEnumSerializer : KSerializer { + abstract fun fromNumber(number: Int): T + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor( + serialName = javaClass.simpleName, + kind = PrimitiveKind.INT + ) + + override fun serialize(encoder: Encoder, value: T) { + encoder.encodeInt(value.number) + } + + override fun deserialize(decoder: Decoder): T { + return fromNumber(decoder.decodeInt()) + } +} \ No newline at end of file From 7c83261c884b2b9c5fbac5de68a50c5fbd356344 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Tue, 15 Jul 2025 15:02:54 +1000 Subject: [PATCH 51/52] Fix compilation issues --- .../utilities/ProfilePictureUtilities.kt | 14 -------------- .../libsession/utilities/recipients/Recipient.kt | 1 - 2 files changed, 15 deletions(-) diff --git a/app/src/main/java/org/session/libsession/utilities/ProfilePictureUtilities.kt b/app/src/main/java/org/session/libsession/utilities/ProfilePictureUtilities.kt index 2f9cfd28f3..8763a93cfe 100644 --- a/app/src/main/java/org/session/libsession/utilities/ProfilePictureUtilities.kt +++ b/app/src/main/java/org/session/libsession/utilities/ProfilePictureUtilities.kt @@ -2,29 +2,15 @@ package org.session.libsession.utilities import android.content.Context import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import network.loki.messenger.libsession_util.util.Bytes -import network.loki.messenger.libsession_util.util.UserPic import okio.Buffer -import org.session.libsession.avatars.AvatarHelper -import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.file_server.FileServerApi import org.session.libsession.snode.utilities.await -import org.session.libsession.utilities.Address.Companion.fromSerialized -import org.session.libsession.utilities.TextSecurePreferences.Companion.getLastProfilePictureUpload -import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber -import org.session.libsession.utilities.TextSecurePreferences.Companion.getProfileKey -import org.session.libsession.utilities.TextSecurePreferences.Companion.setLastProfilePictureUpload import org.session.libsignal.streams.DigestingRequestBody import org.session.libsignal.streams.ProfileCipherOutputStream import org.session.libsignal.streams.ProfileCipherOutputStreamFactory -import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.ProfileAvatarData import org.session.libsignal.utilities.retryIfNeeded import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream import java.util.Date object ProfilePictureUtilities { diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt index 70c04ba23e..9b1bf14757 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt @@ -78,7 +78,6 @@ data class Recipient( autoDownloadAttachments = true, notifyType = RecipientDatabase.NOTIFY_TYPE_ALL, acceptsCommunityMessageRequests = false, - unblindedRecipientAddress = null, ) } } From a2b1228c51cab5cbe8b0d7dd3a9352c3e7803028 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Tue, 15 Jul 2025 15:41:22 +1000 Subject: [PATCH 52/52] Fix compilation issues --- .../libsession/database/StorageProtocol.kt | 1 - .../utilities/UpdateMessageBuilder.kt | 1 - .../DisappearingMessages.kt | 2 +- .../conversation/v2/ConversationActivityV2.kt | 3 +-- .../v2/messages/ControlMessageView.kt | 3 +-- .../v2/utilities/AttachmentManager.java | 4 ++-- .../securesms/database/Storage.kt | 21 ------------------- 7 files changed, 5 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 23eef06ae5..72302d9419 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -253,7 +253,6 @@ interface StorageProtocol { fun blockedContacts(): List fun getExpirationConfiguration(threadId: Long): ExpiryMode fun setExpirationConfiguration(address: Address, expiryMode: ExpiryMode) - fun getExpiringMessages(messageIds: List = emptyList()): List> // Shared configs fun conversationInConfig(publicKey: String?, groupPublicKey: String?, openGroupId: String?, visibleOnly: Boolean): Boolean diff --git a/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt b/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt index 5c73e45721..a30e51b674 100644 --- a/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt +++ b/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -23,7 +23,6 @@ import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_K import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.OTHER_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY -import org.session.libsession.utilities.getExpirationTypeDisplayValue import org.session.libsession.utilities.getGroup import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Log diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt index 1e90d93fe4..f386f9621a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt @@ -100,7 +100,7 @@ class DisappearingMessages @Inject constructor( text = dangerButtonText, contentDescriptionRes = dangerButtonContentDescription, ) { - set(recipient.address, content.expiryMode, recipient.isGroup) + set(recipient.address, content.expiryMode, recipient.isGroupRecipient) } cancelButton() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 0876fa8b94..d53d2425c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -2185,8 +2185,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, attachmentManager.capturePhoto( this, TAKE_PHOTO, - viewModel.recipient, - viewModel.threadId, + viewModel.address, getMessageBody() ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index fb14fa3331..9b62ae7176 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -27,7 +27,6 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED import org.session.libsession.utilities.getColorFromAttr import org.thoughtcrime.securesms.conversation.disappearingmessages.DisappearingMessages -import org.thoughtcrime.securesms.conversation.disappearingmessages.expiryMode import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate @@ -101,7 +100,7 @@ class ControlMessageView : LinearLayout { followSetting.isVisible = ExpirationConfiguration.isNewConfigEnabled && !message.isOutgoing - && messageContent.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId)?.expiryMode ?: ExpiryMode.NONE) + && messageContent.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId) ?: ExpiryMode.NONE) && threadRecipient?.isGroupOrCommunity != true if (followSetting.isVisible) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java index b78ef05aa2..2562f4f547 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java @@ -319,7 +319,7 @@ public static void selectGif(Activity activity, int requestCode) { return captureUri; } - public void capturePhoto(Activity activity, int requestCode, Address recipient, long threadId, @NonNull String body) { + public void capturePhoto(Activity activity, int requestCode, Address recipient, @NonNull String body) { String cameraPermissionDeniedTxt = Phrase.from(context, R.string.permissionsCameraDenied) .put(APP_NAME_KEY, context.getString(R.string.app_name)) @@ -329,7 +329,7 @@ public void capturePhoto(Activity activity, int requestCode, Address recipient, .request(Manifest.permission.CAMERA) .withPermanentDenialDialog(cameraPermissionDeniedTxt) .onAllGranted(() -> { - Intent captureIntent = MediaSendActivity.buildCameraIntent(activity, recipient, threadId, body); + Intent captureIntent = MediaSendActivity.buildCameraIntent(activity, recipient, body); if (captureIntent.resolveActivity(activity.getPackageManager()) != null) { activity.startActivityForResult(captureIntent, requestCode); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index f4e90c23ec..ff5b769a02 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1688,25 +1688,4 @@ open class Storage @Inject constructor( } } } - - override fun getExpiringMessages(messageIds: List): List> { - val expiringMessages = mutableListOf>() - val smsDb = smsDatabase - smsDb.readerFor(smsDb.expirationNotStartedMessages).use { reader -> - while (reader.next != null) { - if (messageIds.isEmpty() || reader.current.id in messageIds) { - expiringMessages.add(reader.current.id to reader.current.expiresIn) - } - } - } - val mmsDb = mmsDatabase - mmsDb.expireNotStartedMessages.use { reader -> - while (reader.next != null) { - if (messageIds.isEmpty() || reader.current.id in messageIds) { - expiringMessages.add(reader.current.id to reader.current.expiresIn) - } - } - } - return expiringMessages - } }