From 59d906377465a38853782deed90fcf8a4893551e Mon Sep 17 00:00:00 2001 From: Brice-W Date: Fri, 18 Jun 2021 15:08:30 +1000 Subject: [PATCH 01/31] proto update --- libsignal/protobuf/SignalService.proto | 1 + .../libsignal/protos/SignalServiceProtos.java | 128 ++++++++++++++---- 2 files changed, 106 insertions(+), 23 deletions(-) diff --git a/libsignal/protobuf/SignalService.proto b/libsignal/protobuf/SignalService.proto index 7c44fce228..a7a0f6e5f0 100644 --- a/libsignal/protobuf/SignalService.proto +++ b/libsignal/protobuf/SignalService.proto @@ -161,6 +161,7 @@ message ConfigurationMessage { optional KeyPair encryptionKeyPair = 3; repeated bytes members = 4; repeated bytes admins = 5; + optional uint32 expireTimer = 6; } message Contact { diff --git a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java index 8f7921dcb2..004fea73b2 100644 --- a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java +++ b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java @@ -14104,6 +14104,16 @@ public final class SignalServiceProtos { * repeated bytes admins = 5; */ com.google.protobuf.ByteString getAdmins(int index); + + // optional uint32 expireTimer = 6; + /** + * optional uint32 expireTimer = 6; + */ + boolean hasExpireTimer(); + /** + * optional uint32 expireTimer = 6; + */ + int getExpireTimer(); } /** * Protobuf type {@code signalservice.ConfigurationMessage.ClosedGroup} @@ -14195,6 +14205,11 @@ public final class SignalServiceProtos { admins_.add(input.readBytes()); break; } + case 48: { + bitField0_ |= 0x00000008; + expireTimer_ = input.readUInt32(); + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -14368,12 +14383,29 @@ public final class SignalServiceProtos { return admins_.get(index); } + // optional uint32 expireTimer = 6; + public static final int EXPIRETIMER_FIELD_NUMBER = 6; + private int expireTimer_; + /** + * optional uint32 expireTimer = 6; + */ + public boolean hasExpireTimer() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional uint32 expireTimer = 6; + */ + public int getExpireTimer() { + return expireTimer_; + } + private void initFields() { publicKey_ = com.google.protobuf.ByteString.EMPTY; name_ = ""; encryptionKeyPair_ = org.session.libsignal.protos.SignalServiceProtos.KeyPair.getDefaultInstance(); members_ = java.util.Collections.emptyList(); admins_ = java.util.Collections.emptyList(); + expireTimer_ = 0; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -14408,6 +14440,9 @@ public final class SignalServiceProtos { for (int i = 0; i < admins_.size(); i++) { output.writeBytes(5, admins_.get(i)); } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeUInt32(6, expireTimer_); + } getUnknownFields().writeTo(output); } @@ -14447,6 +14482,10 @@ public final class SignalServiceProtos { size += dataSize; size += 1 * getAdminsList().size(); } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(6, expireTimer_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -14578,6 +14617,8 @@ public final class SignalServiceProtos { bitField0_ = (bitField0_ & ~0x00000008); admins_ = java.util.Collections.emptyList(); bitField0_ = (bitField0_ & ~0x00000010); + expireTimer_ = 0; + bitField0_ = (bitField0_ & ~0x00000020); return this; } @@ -14632,6 +14673,10 @@ public final class SignalServiceProtos { bitField0_ = (bitField0_ & ~0x00000010); } result.admins_ = admins_; + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000008; + } + result.expireTimer_ = expireTimer_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -14679,6 +14724,9 @@ public final class SignalServiceProtos { } onChanged(); } + if (other.hasExpireTimer()) { + setExpireTimer(other.getExpireTimer()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -15083,6 +15131,39 @@ public final class SignalServiceProtos { return this; } + // optional uint32 expireTimer = 6; + private int expireTimer_ ; + /** + * optional uint32 expireTimer = 6; + */ + public boolean hasExpireTimer() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional uint32 expireTimer = 6; + */ + public int getExpireTimer() { + return expireTimer_; + } + /** + * optional uint32 expireTimer = 6; + */ + public Builder setExpireTimer(int value) { + bitField0_ |= 0x00000020; + expireTimer_ = value; + onChanged(); + return this; + } + /** + * optional uint32 expireTimer = 6; + */ + public Builder clearExpireTimer() { + bitField0_ = (bitField0_ & ~0x00000020); + expireTimer_ = 0; + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:signalservice.ConfigurationMessage.ClosedGroup) } @@ -21328,35 +21409,36 @@ public final class SignalServiceProtos { "NEW\020\001\022\027\n\023ENCRYPTION_KEY_PAIR\020\003\022\017\n\013NAME_C" + "HANGE\020\004\022\021\n\rMEMBERS_ADDED\020\005\022\023\n\017MEMBERS_RE", "MOVED\020\006\022\017\n\013MEMBER_LEFT\020\007\"$\n\005Flags\022\033\n\027EXP" + - "IRATION_TIMER_UPDATE\020\002\"\316\003\n\024Configuration" + + "IRATION_TIMER_UPDATE\020\002\"\343\003\n\024Configuration" + "Message\022E\n\014closedGroups\030\001 \003(\0132/.signalse" + "rvice.ConfigurationMessage.ClosedGroup\022\022" + "\n\nopenGroups\030\002 \003(\t\022\023\n\013displayName\030\003 \001(\t\022" + "\026\n\016profilePicture\030\004 \001(\t\022\022\n\nprofileKey\030\005 " + "\001(\014\022=\n\010contacts\030\006 \003(\0132+.signalservice.Co" + - "nfigurationMessage.Contact\032\202\001\n\013ClosedGro" + + "nfigurationMessage.Contact\032\227\001\n\013ClosedGro" + "up\022\021\n\tpublicKey\030\001 \001(\014\022\014\n\004name\030\002 \001(\t\0221\n\021e" + "ncryptionKeyPair\030\003 \001(\0132\026.signalservice.K", - "eyPair\022\017\n\007members\030\004 \003(\014\022\016\n\006admins\030\005 \003(\014\032" + - "V\n\007Contact\022\021\n\tpublicKey\030\001 \002(\014\022\014\n\004name\030\002 " + - "\002(\t\022\026\n\016profilePicture\030\003 \001(\t\022\022\n\nprofileKe" + - "y\030\004 \001(\014\"u\n\016ReceiptMessage\0220\n\004type\030\001 \002(\0162" + - "\".signalservice.ReceiptMessage.Type\022\021\n\tt" + - "imestamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010DELIVERY\020\000\022\010\n\004" + - "READ\020\001\"\354\001\n\021AttachmentPointer\022\n\n\002id\030\001 \002(\006" + - "\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004si" + - "ze\030\004 \001(\r\022\021\n\tthumbnail\030\005 \001(\014\022\016\n\006digest\030\006 " + - "\001(\014\022\020\n\010fileName\030\007 \001(\t\022\r\n\005flags\030\010 \001(\r\022\r\n\005", - "width\030\t \001(\r\022\016\n\006height\030\n \001(\r\022\017\n\007caption\030\013" + - " \001(\t\022\013\n\003url\030e \001(\t\"\032\n\005Flags\022\021\n\rVOICE_MESS" + - "AGE\020\001\"\365\001\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022.\n\004ty" + - "pe\030\002 \001(\0162 .signalservice.GroupContext.Ty" + - "pe\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030\004 \003(\t\0220\n\006ava" + - "tar\030\005 \001(\0132 .signalservice.AttachmentPoin" + - "ter\022\016\n\006admins\030\006 \003(\t\"H\n\004Type\022\013\n\007UNKNOWN\020\000" + - "\022\n\n\006UPDATE\020\001\022\013\n\007DELIVER\020\002\022\010\n\004QUIT\020\003\022\020\n\014R" + - "EQUEST_INFO\020\004B3\n\034org.session.libsignal.p" + - "rotosB\023SignalServiceProtos" + "eyPair\022\017\n\007members\030\004 \003(\014\022\016\n\006admins\030\005 \003(\014\022" + + "\023\n\013expireTimer\030\006 \001(\r\032V\n\007Contact\022\021\n\tpubli" + + "cKey\030\001 \002(\014\022\014\n\004name\030\002 \002(\t\022\026\n\016profilePictu" + + "re\030\003 \001(\t\022\022\n\nprofileKey\030\004 \001(\014\"u\n\016ReceiptM" + + "essage\0220\n\004type\030\001 \002(\0162\".signalservice.Rec" + + "eiptMessage.Type\022\021\n\ttimestamp\030\002 \003(\004\"\036\n\004T" + + "ype\022\014\n\010DELIVERY\020\000\022\010\n\004READ\020\001\"\354\001\n\021Attachme" + + "ntPointer\022\n\n\002id\030\001 \002(\006\022\023\n\013contentType\030\002 \001" + + "(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004size\030\004 \001(\r\022\021\n\tthumbna" + + "il\030\005 \001(\014\022\016\n\006digest\030\006 \001(\014\022\020\n\010fileName\030\007 \001", + "(\t\022\r\n\005flags\030\010 \001(\r\022\r\n\005width\030\t \001(\r\022\016\n\006heig" + + "ht\030\n \001(\r\022\017\n\007caption\030\013 \001(\t\022\013\n\003url\030e \001(\t\"\032" + + "\n\005Flags\022\021\n\rVOICE_MESSAGE\020\001\"\365\001\n\014GroupCont" + + "ext\022\n\n\002id\030\001 \001(\014\022.\n\004type\030\002 \001(\0162 .signalse" + + "rvice.GroupContext.Type\022\014\n\004name\030\003 \001(\t\022\017\n" + + "\007members\030\004 \003(\t\0220\n\006avatar\030\005 \001(\0132 .signals" + + "ervice.AttachmentPointer\022\016\n\006admins\030\006 \003(\t" + + "\"H\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020\001\022\013\n\007DEL" + + "IVER\020\002\022\010\n\004QUIT\020\003\022\020\n\014REQUEST_INFO\020\004B3\n\034or" + + "g.session.libsignal.protosB\023SignalServic", + "eProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -21452,7 +21534,7 @@ public final class SignalServiceProtos { internal_static_signalservice_ConfigurationMessage_ClosedGroup_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_ConfigurationMessage_ClosedGroup_descriptor, - new java.lang.String[] { "PublicKey", "Name", "EncryptionKeyPair", "Members", "Admins", }); + new java.lang.String[] { "PublicKey", "Name", "EncryptionKeyPair", "Members", "Admins", "ExpireTimer", }); internal_static_signalservice_ConfigurationMessage_Contact_descriptor = internal_static_signalservice_ConfigurationMessage_descriptor.getNestedTypes().get(1); internal_static_signalservice_ConfigurationMessage_Contact_fieldAccessorTable = new From 35d5def515fbc19c7e6fcdcededd8260caee2583 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Fri, 18 Jun 2021 16:44:58 +1000 Subject: [PATCH 02/31] adding expiration timer to configuration message --- .../messages/control/ConfigurationMessage.kt | 12 ++++++++---- .../sending_receiving/ReceivedMessageHandler.kt | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt index cc49cec65b..eab9a42898 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt @@ -6,6 +6,7 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.ProfileKeyUtil +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 @@ -19,10 +20,10 @@ class ConfigurationMessage(var closedGroups: List, var openGroups: override val isSelfSendValid: Boolean = true - class ClosedGroup(var publicKey: String, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List, var admins: List) { + class ClosedGroup(var publicKey: String, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List, var admins: List, var expireTimer: Int) { val isValid: Boolean get() = members.isNotEmpty() && admins.isNotEmpty() - internal constructor() : this("", "", null, listOf(), listOf()) + internal constructor() : this("", "", null, listOf(), listOf(), 0) override fun toString(): String { return name @@ -39,7 +40,8 @@ class ConfigurationMessage(var closedGroups: List, var openGroups: DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray())) val members = proto.membersList.map { it.toByteArray().toHexString() } val admins = proto.adminsList.map { it.toByteArray().toHexString() } - return ClosedGroup(publicKey, name, encryptionKeyPair, members, admins) + val expireTimer = proto.expireTimer + return ClosedGroup(publicKey, name, encryptionKeyPair, members, admins, expireTimer) } } @@ -53,6 +55,7 @@ class ConfigurationMessage(var closedGroups: List, var openGroups: result.encryptionKeyPair = encryptionKeyPairAsProto.build() result.addAllMembers(members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) result.addAllAdmins(admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) + result.expireTimer = expireTimer return result.build() } } @@ -110,7 +113,8 @@ class ConfigurationMessage(var closedGroups: List, var openGroups: if (!group.members.contains(Address.fromSerialized(storage.getUserPublicKey()!!))) continue val groupPublicKey = GroupUtil.doubleDecodeGroupID(group.encodedId).toHexString() val encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: continue - val closedGroup = ClosedGroup(groupPublicKey, group.title, encryptionKeyPair, group.members.map { it.serialize() }, group.admins.map { it.serialize() }) + val recipient = Recipient.from(context, Address.fromSerialized(group.encodedId), false) + val closedGroup = ClosedGroup(groupPublicKey, group.title, encryptionKeyPair, group.members.map { it.serialize() }, group.admins.map { it.serialize() }, recipient.expireMessages) closedGroups.add(closedGroup) } if (group.isOpenGroup) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 27bd608f3c..279dc60094 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -121,7 +121,7 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) { for (closedGroup in message.closedGroups) { if (allClosedGroupPublicKeys.contains(closedGroup.publicKey)) continue handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, closedGroup.publicKey, closedGroup.name, - closedGroup.encryptionKeyPair!!, closedGroup.members, closedGroup.admins, message.sentTimestamp!!, 0) + closedGroup.encryptionKeyPair!!, closedGroup.members, closedGroup.admins, message.sentTimestamp!!, closedGroup.expireTimer) } val allV2OpenGroups = storage.getAllV2OpenGroups().map { it.value.joinURL } for (openGroup in message.openGroups) { From a71273e813d6fb70a58380ab79fe68c658de55e3 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 21 Jun 2021 14:03:08 +1000 Subject: [PATCH 03/31] refactor --- .../control/ClosedGroupControlMessage.kt | 10 +- .../messages/control/ConfigurationMessage.kt | 8 +- .../ReceivedMessageHandler.kt | 4 +- libsignal/protobuf/SignalService.proto | 4 +- .../libsignal/protos/SignalServiceProtos.java | 216 +++++++++--------- 5 files changed, 121 insertions(+), 121 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt index bd20346f99..7ae5911ef3 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt @@ -34,7 +34,7 @@ class ClosedGroupControlMessage() : ControlMessage() { is Kind.New -> { !kind.publicKey.isEmpty && kind.name.isNotEmpty() && kind.encryptionKeyPair?.publicKey != null && kind.encryptionKeyPair?.privateKey != null && kind.members.isNotEmpty() && kind.admins.isNotEmpty() - && kind.expireTimer >= 0 + && kind.expirationTimer >= 0 } is Kind.EncryptionKeyPair -> true is Kind.NameChange -> kind.name.isNotEmpty() @@ -45,7 +45,7 @@ class ClosedGroupControlMessage() : ControlMessage() { } sealed class Kind { - class New(var publicKey: ByteString, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List, var admins: List, var expireTimer: Int) : Kind() { + class New(var publicKey: ByteString, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List, var admins: List, var expirationTimer: Int) : Kind() { internal constructor() : this(ByteString.EMPTY, "", null, listOf(), listOf(), 0) } /** An encryption key pair encrypted for each member individually. @@ -89,11 +89,11 @@ class ClosedGroupControlMessage() : ControlMessage() { val publicKey = closedGroupControlMessageProto.publicKey ?: return null val name = closedGroupControlMessageProto.name ?: return null val encryptionKeyPairAsProto = closedGroupControlMessageProto.encryptionKeyPair ?: return null - val expireTimer = closedGroupControlMessageProto.expireTimer + val expirationTimer = closedGroupControlMessageProto.expirationTimer try { val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray()), DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray())) - kind = Kind.New(publicKey, name, encryptionKeyPair, closedGroupControlMessageProto.membersList, closedGroupControlMessageProto.adminsList, expireTimer) + kind = Kind.New(publicKey, name, encryptionKeyPair, closedGroupControlMessageProto.membersList, closedGroupControlMessageProto.adminsList, expirationTimer) } catch (e: Exception) { Log.w(TAG, "Couldn't parse key pair from proto: $encryptionKeyPairAsProto.") return null @@ -145,7 +145,7 @@ class ClosedGroupControlMessage() : ControlMessage() { closedGroupControlMessage.encryptionKeyPair = encryptionKeyPair.build() closedGroupControlMessage.addAllMembers(kind.members) closedGroupControlMessage.addAllAdmins(kind.admins) - closedGroupControlMessage.expireTimer = kind.expireTimer + closedGroupControlMessage.expirationTimer = kind.expirationTimer } is Kind.EncryptionKeyPair -> { closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt index eab9a42898..cb355aa15e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt @@ -20,7 +20,7 @@ class ConfigurationMessage(var closedGroups: List, var openGroups: override val isSelfSendValid: Boolean = true - class ClosedGroup(var publicKey: String, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List, var admins: List, var expireTimer: Int) { + class ClosedGroup(var publicKey: String, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List, var admins: List, var expirationTimer: Int) { val isValid: Boolean get() = members.isNotEmpty() && admins.isNotEmpty() internal constructor() : this("", "", null, listOf(), listOf(), 0) @@ -40,8 +40,8 @@ class ConfigurationMessage(var closedGroups: List, var openGroups: DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray())) val members = proto.membersList.map { it.toByteArray().toHexString() } val admins = proto.adminsList.map { it.toByteArray().toHexString() } - val expireTimer = proto.expireTimer - return ClosedGroup(publicKey, name, encryptionKeyPair, members, admins, expireTimer) + val expirationTimer = proto.expirationTimer + return ClosedGroup(publicKey, name, encryptionKeyPair, members, admins, expirationTimer) } } @@ -55,7 +55,7 @@ class ConfigurationMessage(var closedGroups: List, var openGroups: result.encryptionKeyPair = encryptionKeyPairAsProto.build() result.addAllMembers(members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) result.addAllAdmins(admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) - result.expireTimer = expireTimer + result.expirationTimer = expirationTimer return result.build() } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 279dc60094..cfbd5605bd 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -121,7 +121,7 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) { for (closedGroup in message.closedGroups) { if (allClosedGroupPublicKeys.contains(closedGroup.publicKey)) continue handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, closedGroup.publicKey, closedGroup.name, - closedGroup.encryptionKeyPair!!, closedGroup.members, closedGroup.admins, message.sentTimestamp!!, closedGroup.expireTimer) + closedGroup.encryptionKeyPair!!, closedGroup.members, closedGroup.admins, message.sentTimestamp!!, closedGroup.expirationTimer) } val allV2OpenGroups = storage.getAllV2OpenGroups().map { it.value.joinURL } for (openGroup in message.openGroups) { @@ -256,7 +256,7 @@ private fun MessageReceiver.handleNewClosedGroup(message: ClosedGroupControlMess val groupPublicKey = kind.publicKey.toByteArray().toHexString() val members = kind.members.map { it.toByteArray().toHexString() } val admins = kind.admins.map { it.toByteArray().toHexString() } - val expireTimer = kind.expireTimer + val expireTimer = kind.expirationTimer handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, groupPublicKey, kind.name, kind.encryptionKeyPair!!, members, admins, message.sentTimestamp!!, expireTimer) } diff --git a/libsignal/protobuf/SignalService.proto b/libsignal/protobuf/SignalService.proto index a7a0f6e5f0..7ee8dc9946 100644 --- a/libsignal/protobuf/SignalService.proto +++ b/libsignal/protobuf/SignalService.proto @@ -135,7 +135,7 @@ message DataMessage { repeated bytes members = 5; repeated bytes admins = 6; repeated KeyPairWrapper wrappers = 7; - optional uint32 expireTimer = 8; + optional uint32 expirationTimer = 8; } optional string body = 1; @@ -161,7 +161,7 @@ message ConfigurationMessage { optional KeyPair encryptionKeyPair = 3; repeated bytes members = 4; repeated bytes admins = 5; - optional uint32 expireTimer = 6; + optional uint32 expirationTimer = 6; } message Contact { diff --git a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java index 004fea73b2..0ca46d3a9f 100644 --- a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java +++ b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java @@ -9222,15 +9222,15 @@ public final class SignalServiceProtos { org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapperOrBuilder getWrappersOrBuilder( int index); - // optional uint32 expireTimer = 8; + // optional uint32 expirationTimer = 8; /** - * optional uint32 expireTimer = 8; + * optional uint32 expirationTimer = 8; */ - boolean hasExpireTimer(); + boolean hasExpirationTimer(); /** - * optional uint32 expireTimer = 8; + * optional uint32 expirationTimer = 8; */ - int getExpireTimer(); + int getExpirationTimer(); } /** * Protobuf type {@code signalservice.DataMessage.ClosedGroupControlMessage} @@ -9343,7 +9343,7 @@ public final class SignalServiceProtos { } case 64: { bitField0_ |= 0x00000010; - expireTimer_ = input.readUInt32(); + expirationTimer_ = input.readUInt32(); break; } } @@ -10303,20 +10303,20 @@ public final class SignalServiceProtos { return wrappers_.get(index); } - // optional uint32 expireTimer = 8; - public static final int EXPIRETIMER_FIELD_NUMBER = 8; - private int expireTimer_; + // optional uint32 expirationTimer = 8; + public static final int EXPIRATIONTIMER_FIELD_NUMBER = 8; + private int expirationTimer_; /** - * optional uint32 expireTimer = 8; + * optional uint32 expirationTimer = 8; */ - public boolean hasExpireTimer() { + public boolean hasExpirationTimer() { return ((bitField0_ & 0x00000010) == 0x00000010); } /** - * optional uint32 expireTimer = 8; + * optional uint32 expirationTimer = 8; */ - public int getExpireTimer() { - return expireTimer_; + public int getExpirationTimer() { + return expirationTimer_; } private void initFields() { @@ -10327,7 +10327,7 @@ public final class SignalServiceProtos { members_ = java.util.Collections.emptyList(); admins_ = java.util.Collections.emptyList(); wrappers_ = java.util.Collections.emptyList(); - expireTimer_ = 0; + expirationTimer_ = 0; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -10379,7 +10379,7 @@ public final class SignalServiceProtos { output.writeMessage(7, wrappers_.get(i)); } if (((bitField0_ & 0x00000010) == 0x00000010)) { - output.writeUInt32(8, expireTimer_); + output.writeUInt32(8, expirationTimer_); } getUnknownFields().writeTo(output); } @@ -10430,7 +10430,7 @@ public final class SignalServiceProtos { } if (((bitField0_ & 0x00000010) == 0x00000010)) { size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(8, expireTimer_); + .computeUInt32Size(8, expirationTimer_); } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; @@ -10572,7 +10572,7 @@ public final class SignalServiceProtos { } else { wrappersBuilder_.clear(); } - expireTimer_ = 0; + expirationTimer_ = 0; bitField0_ = (bitField0_ & ~0x00000080); return this; } @@ -10644,7 +10644,7 @@ public final class SignalServiceProtos { if (((from_bitField0_ & 0x00000080) == 0x00000080)) { to_bitField0_ |= 0x00000010; } - result.expireTimer_ = expireTimer_; + result.expirationTimer_ = expirationTimer_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -10721,8 +10721,8 @@ public final class SignalServiceProtos { } } } - if (other.hasExpireTimer()) { - setExpireTimer(other.getExpireTimer()); + if (other.hasExpirationTimer()) { + setExpirationTimer(other.getExpirationTimer()); } this.mergeUnknownFields(other.getUnknownFields()); return this; @@ -11430,35 +11430,35 @@ public final class SignalServiceProtos { return wrappersBuilder_; } - // optional uint32 expireTimer = 8; - private int expireTimer_ ; + // optional uint32 expirationTimer = 8; + private int expirationTimer_ ; /** - * optional uint32 expireTimer = 8; + * optional uint32 expirationTimer = 8; */ - public boolean hasExpireTimer() { + public boolean hasExpirationTimer() { return ((bitField0_ & 0x00000080) == 0x00000080); } /** - * optional uint32 expireTimer = 8; + * optional uint32 expirationTimer = 8; */ - public int getExpireTimer() { - return expireTimer_; + public int getExpirationTimer() { + return expirationTimer_; } /** - * optional uint32 expireTimer = 8; + * optional uint32 expirationTimer = 8; */ - public Builder setExpireTimer(int value) { + public Builder setExpirationTimer(int value) { bitField0_ |= 0x00000080; - expireTimer_ = value; + expirationTimer_ = value; onChanged(); return this; } /** - * optional uint32 expireTimer = 8; + * optional uint32 expirationTimer = 8; */ - public Builder clearExpireTimer() { + public Builder clearExpirationTimer() { bitField0_ = (bitField0_ & ~0x00000080); - expireTimer_ = 0; + expirationTimer_ = 0; onChanged(); return this; } @@ -14105,15 +14105,15 @@ public final class SignalServiceProtos { */ com.google.protobuf.ByteString getAdmins(int index); - // optional uint32 expireTimer = 6; + // optional uint32 expirationTimer = 6; /** - * optional uint32 expireTimer = 6; + * optional uint32 expirationTimer = 6; */ - boolean hasExpireTimer(); + boolean hasExpirationTimer(); /** - * optional uint32 expireTimer = 6; + * optional uint32 expirationTimer = 6; */ - int getExpireTimer(); + int getExpirationTimer(); } /** * Protobuf type {@code signalservice.ConfigurationMessage.ClosedGroup} @@ -14207,7 +14207,7 @@ public final class SignalServiceProtos { } case 48: { bitField0_ |= 0x00000008; - expireTimer_ = input.readUInt32(); + expirationTimer_ = input.readUInt32(); break; } } @@ -14383,20 +14383,20 @@ public final class SignalServiceProtos { return admins_.get(index); } - // optional uint32 expireTimer = 6; - public static final int EXPIRETIMER_FIELD_NUMBER = 6; - private int expireTimer_; + // optional uint32 expirationTimer = 6; + public static final int EXPIRATIONTIMER_FIELD_NUMBER = 6; + private int expirationTimer_; /** - * optional uint32 expireTimer = 6; + * optional uint32 expirationTimer = 6; */ - public boolean hasExpireTimer() { + public boolean hasExpirationTimer() { return ((bitField0_ & 0x00000008) == 0x00000008); } /** - * optional uint32 expireTimer = 6; + * optional uint32 expirationTimer = 6; */ - public int getExpireTimer() { - return expireTimer_; + public int getExpirationTimer() { + return expirationTimer_; } private void initFields() { @@ -14405,7 +14405,7 @@ public final class SignalServiceProtos { encryptionKeyPair_ = org.session.libsignal.protos.SignalServiceProtos.KeyPair.getDefaultInstance(); members_ = java.util.Collections.emptyList(); admins_ = java.util.Collections.emptyList(); - expireTimer_ = 0; + expirationTimer_ = 0; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -14441,7 +14441,7 @@ public final class SignalServiceProtos { output.writeBytes(5, admins_.get(i)); } if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeUInt32(6, expireTimer_); + output.writeUInt32(6, expirationTimer_); } getUnknownFields().writeTo(output); } @@ -14484,7 +14484,7 @@ public final class SignalServiceProtos { } if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(6, expireTimer_); + .computeUInt32Size(6, expirationTimer_); } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; @@ -14617,7 +14617,7 @@ public final class SignalServiceProtos { bitField0_ = (bitField0_ & ~0x00000008); admins_ = java.util.Collections.emptyList(); bitField0_ = (bitField0_ & ~0x00000010); - expireTimer_ = 0; + expirationTimer_ = 0; bitField0_ = (bitField0_ & ~0x00000020); return this; } @@ -14676,7 +14676,7 @@ public final class SignalServiceProtos { if (((from_bitField0_ & 0x00000020) == 0x00000020)) { to_bitField0_ |= 0x00000008; } - result.expireTimer_ = expireTimer_; + result.expirationTimer_ = expirationTimer_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -14724,8 +14724,8 @@ public final class SignalServiceProtos { } onChanged(); } - if (other.hasExpireTimer()) { - setExpireTimer(other.getExpireTimer()); + if (other.hasExpirationTimer()) { + setExpirationTimer(other.getExpirationTimer()); } this.mergeUnknownFields(other.getUnknownFields()); return this; @@ -15131,35 +15131,35 @@ public final class SignalServiceProtos { return this; } - // optional uint32 expireTimer = 6; - private int expireTimer_ ; + // optional uint32 expirationTimer = 6; + private int expirationTimer_ ; /** - * optional uint32 expireTimer = 6; + * optional uint32 expirationTimer = 6; */ - public boolean hasExpireTimer() { + public boolean hasExpirationTimer() { return ((bitField0_ & 0x00000020) == 0x00000020); } /** - * optional uint32 expireTimer = 6; + * optional uint32 expirationTimer = 6; */ - public int getExpireTimer() { - return expireTimer_; + public int getExpirationTimer() { + return expirationTimer_; } /** - * optional uint32 expireTimer = 6; + * optional uint32 expirationTimer = 6; */ - public Builder setExpireTimer(int value) { + public Builder setExpirationTimer(int value) { bitField0_ |= 0x00000020; - expireTimer_ = value; + expirationTimer_ = value; onChanged(); return this; } /** - * optional uint32 expireTimer = 6; + * optional uint32 expirationTimer = 6; */ - public Builder clearExpireTimer() { + public Builder clearExpirationTimer() { bitField0_ = (bitField0_ & ~0x00000020); - expireTimer_ = 0; + expirationTimer_ = 0; onChanged(); return this; } @@ -21370,7 +21370,7 @@ public final class SignalServiceProtos { "fication\022<\n\004type\030\001 \002(\0162..signalservice.D", "ataExtractionNotification.Type\022\021\n\ttimest" + "amp\030\002 \001(\004\"\'\n\004Type\022\016\n\nSCREENSHOT\020\001\022\017\n\013MED" + - "IA_SAVED\020\002\"\241\014\n\013DataMessage\022\014\n\004body\030\001 \001(\t" + + "IA_SAVED\020\002\"\245\014\n\013DataMessage\022\014\n\004body\030\001 \001(\t" + "\0225\n\013attachments\030\002 \003(\0132 .signalservice.At" + "tachmentPointer\022*\n\005group\030\003 \001(\0132\033.signals" + "ervice.GroupContext\022\r\n\005flags\030\004 \001(\r\022\023\n\013ex" + @@ -21395,7 +21395,7 @@ public final class SignalServiceProtos { "\n\005image\030\003 \001(\0132 .signalservice.Attachment" + "Pointer\032:\n\013LokiProfile\022\023\n\013displayName\030\001 " + "\001(\t\022\026\n\016profilePicture\030\002 \001(\t\0320\n\023OpenGroup" + - "Invitation\022\013\n\003url\030\001 \002(\t\022\014\n\004name\030\003 \002(\t\032\370\003" + + "Invitation\022\013\n\003url\030\001 \002(\t\022\014\n\004name\030\003 \002(\t\032\374\003" + "\n\031ClosedGroupControlMessage\022G\n\004type\030\001 \002(" + "\01629.signalservice.DataMessage.ClosedGrou", "pControlMessage.Type\022\021\n\tpublicKey\030\002 \001(\014\022" + @@ -21403,42 +21403,42 @@ public final class SignalServiceProtos { "2\026.signalservice.KeyPair\022\017\n\007members\030\005 \003(" + "\014\022\016\n\006admins\030\006 \003(\014\022U\n\010wrappers\030\007 \003(\0132C.si" + "gnalservice.DataMessage.ClosedGroupContr" + - "olMessage.KeyPairWrapper\022\023\n\013expireTimer\030" + - "\010 \001(\r\032=\n\016KeyPairWrapper\022\021\n\tpublicKey\030\001 \002" + - "(\014\022\030\n\020encryptedKeyPair\030\002 \002(\014\"r\n\004Type\022\007\n\003" + - "NEW\020\001\022\027\n\023ENCRYPTION_KEY_PAIR\020\003\022\017\n\013NAME_C" + - "HANGE\020\004\022\021\n\rMEMBERS_ADDED\020\005\022\023\n\017MEMBERS_RE", - "MOVED\020\006\022\017\n\013MEMBER_LEFT\020\007\"$\n\005Flags\022\033\n\027EXP" + - "IRATION_TIMER_UPDATE\020\002\"\343\003\n\024Configuration" + - "Message\022E\n\014closedGroups\030\001 \003(\0132/.signalse" + - "rvice.ConfigurationMessage.ClosedGroup\022\022" + - "\n\nopenGroups\030\002 \003(\t\022\023\n\013displayName\030\003 \001(\t\022" + - "\026\n\016profilePicture\030\004 \001(\t\022\022\n\nprofileKey\030\005 " + - "\001(\014\022=\n\010contacts\030\006 \003(\0132+.signalservice.Co" + - "nfigurationMessage.Contact\032\227\001\n\013ClosedGro" + - "up\022\021\n\tpublicKey\030\001 \001(\014\022\014\n\004name\030\002 \001(\t\0221\n\021e" + - "ncryptionKeyPair\030\003 \001(\0132\026.signalservice.K", - "eyPair\022\017\n\007members\030\004 \003(\014\022\016\n\006admins\030\005 \003(\014\022" + - "\023\n\013expireTimer\030\006 \001(\r\032V\n\007Contact\022\021\n\tpubli" + - "cKey\030\001 \002(\014\022\014\n\004name\030\002 \002(\t\022\026\n\016profilePictu" + - "re\030\003 \001(\t\022\022\n\nprofileKey\030\004 \001(\014\"u\n\016ReceiptM" + - "essage\0220\n\004type\030\001 \002(\0162\".signalservice.Rec" + - "eiptMessage.Type\022\021\n\ttimestamp\030\002 \003(\004\"\036\n\004T" + - "ype\022\014\n\010DELIVERY\020\000\022\010\n\004READ\020\001\"\354\001\n\021Attachme" + - "ntPointer\022\n\n\002id\030\001 \002(\006\022\023\n\013contentType\030\002 \001" + - "(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004size\030\004 \001(\r\022\021\n\tthumbna" + - "il\030\005 \001(\014\022\016\n\006digest\030\006 \001(\014\022\020\n\010fileName\030\007 \001", - "(\t\022\r\n\005flags\030\010 \001(\r\022\r\n\005width\030\t \001(\r\022\016\n\006heig" + - "ht\030\n \001(\r\022\017\n\007caption\030\013 \001(\t\022\013\n\003url\030e \001(\t\"\032" + - "\n\005Flags\022\021\n\rVOICE_MESSAGE\020\001\"\365\001\n\014GroupCont" + - "ext\022\n\n\002id\030\001 \001(\014\022.\n\004type\030\002 \001(\0162 .signalse" + - "rvice.GroupContext.Type\022\014\n\004name\030\003 \001(\t\022\017\n" + - "\007members\030\004 \003(\t\0220\n\006avatar\030\005 \001(\0132 .signals" + - "ervice.AttachmentPointer\022\016\n\006admins\030\006 \003(\t" + - "\"H\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020\001\022\013\n\007DEL" + - "IVER\020\002\022\010\n\004QUIT\020\003\022\020\n\014REQUEST_INFO\020\004B3\n\034or" + - "g.session.libsignal.protosB\023SignalServic", - "eProtos" + "olMessage.KeyPairWrapper\022\027\n\017expirationTi" + + "mer\030\010 \001(\r\032=\n\016KeyPairWrapper\022\021\n\tpublicKey" + + "\030\001 \002(\014\022\030\n\020encryptedKeyPair\030\002 \002(\014\"r\n\004Type" + + "\022\007\n\003NEW\020\001\022\027\n\023ENCRYPTION_KEY_PAIR\020\003\022\017\n\013NA" + + "ME_CHANGE\020\004\022\021\n\rMEMBERS_ADDED\020\005\022\023\n\017MEMBER", + "S_REMOVED\020\006\022\017\n\013MEMBER_LEFT\020\007\"$\n\005Flags\022\033\n" + + "\027EXPIRATION_TIMER_UPDATE\020\002\"\347\003\n\024Configura" + + "tionMessage\022E\n\014closedGroups\030\001 \003(\0132/.sign" + + "alservice.ConfigurationMessage.ClosedGro" + + "up\022\022\n\nopenGroups\030\002 \003(\t\022\023\n\013displayName\030\003 " + + "\001(\t\022\026\n\016profilePicture\030\004 \001(\t\022\022\n\nprofileKe" + + "y\030\005 \001(\014\022=\n\010contacts\030\006 \003(\0132+.signalservic" + + "e.ConfigurationMessage.Contact\032\233\001\n\013Close" + + "dGroup\022\021\n\tpublicKey\030\001 \001(\014\022\014\n\004name\030\002 \001(\t\022" + + "1\n\021encryptionKeyPair\030\003 \001(\0132\026.signalservi", + "ce.KeyPair\022\017\n\007members\030\004 \003(\014\022\016\n\006admins\030\005 " + + "\003(\014\022\027\n\017expirationTimer\030\006 \001(\r\032V\n\007Contact\022" + + "\021\n\tpublicKey\030\001 \002(\014\022\014\n\004name\030\002 \002(\t\022\026\n\016prof" + + "ilePicture\030\003 \001(\t\022\022\n\nprofileKey\030\004 \001(\014\"u\n\016" + + "ReceiptMessage\0220\n\004type\030\001 \002(\0162\".signalser" + + "vice.ReceiptMessage.Type\022\021\n\ttimestamp\030\002 " + + "\003(\004\"\036\n\004Type\022\014\n\010DELIVERY\020\000\022\010\n\004READ\020\001\"\354\001\n\021" + + "AttachmentPointer\022\n\n\002id\030\001 \002(\006\022\023\n\013content" + + "Type\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004size\030\004 \001(\r\022\021\n" + + "\tthumbnail\030\005 \001(\014\022\016\n\006digest\030\006 \001(\014\022\020\n\010file", + "Name\030\007 \001(\t\022\r\n\005flags\030\010 \001(\r\022\r\n\005width\030\t \001(\r" + + "\022\016\n\006height\030\n \001(\r\022\017\n\007caption\030\013 \001(\t\022\013\n\003url" + + "\030e \001(\t\"\032\n\005Flags\022\021\n\rVOICE_MESSAGE\020\001\"\365\001\n\014G" + + "roupContext\022\n\n\002id\030\001 \001(\014\022.\n\004type\030\002 \001(\0162 ." + + "signalservice.GroupContext.Type\022\014\n\004name\030" + + "\003 \001(\t\022\017\n\007members\030\004 \003(\t\0220\n\006avatar\030\005 \001(\0132 " + + ".signalservice.AttachmentPointer\022\016\n\006admi" + + "ns\030\006 \003(\t\"H\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020" + + "\001\022\013\n\007DELIVER\020\002\022\010\n\004QUIT\020\003\022\020\n\014REQUEST_INFO" + + "\020\004B3\n\034org.session.libsignal.protosB\023Sign", + "alServiceProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -21516,7 +21516,7 @@ public final class SignalServiceProtos { internal_static_signalservice_DataMessage_ClosedGroupControlMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor, - new java.lang.String[] { "Type", "PublicKey", "Name", "EncryptionKeyPair", "Members", "Admins", "Wrappers", "ExpireTimer", }); + new java.lang.String[] { "Type", "PublicKey", "Name", "EncryptionKeyPair", "Members", "Admins", "Wrappers", "ExpirationTimer", }); internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_descriptor = internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor.getNestedTypes().get(0); internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_fieldAccessorTable = new @@ -21534,7 +21534,7 @@ public final class SignalServiceProtos { internal_static_signalservice_ConfigurationMessage_ClosedGroup_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_ConfigurationMessage_ClosedGroup_descriptor, - new java.lang.String[] { "PublicKey", "Name", "EncryptionKeyPair", "Members", "Admins", "ExpireTimer", }); + new java.lang.String[] { "PublicKey", "Name", "EncryptionKeyPair", "Members", "Admins", "ExpirationTimer", }); internal_static_signalservice_ConfigurationMessage_Contact_descriptor = internal_static_signalservice_ConfigurationMessage_descriptor.getNestedTypes().get(1); internal_static_signalservice_ConfigurationMessage_Contact_fieldAccessorTable = new From bb7dc318c75cddfb0faddd7feaf2e3885b70571b Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 21 Jun 2021 14:04:00 +1000 Subject: [PATCH 04/31] delete obsolete code --- .../messaging/messages/control/ClosedGroupControlMessage.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt index 7ae5911ef3..146707fe0d 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt @@ -174,9 +174,6 @@ class ClosedGroupControlMessage() : ControlMessage() { // Group context setGroupContext(dataMessageProto) // Expiration timer - // TODO: We * want * expiration timer updates to be explicit. But currently Android will disable the expiration timer for a conversation - // if it receives a message without the current expiration timer value attached to it... - dataMessageProto.expireTimer = Recipient.from(MessagingModuleConfiguration.shared.context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(recipient!!)), false).expireMessages contentProto.dataMessage = dataMessageProto.build() return contentProto.build() } catch (e: Exception) { From 362589f83f0f1bf0ec6dabf65e14d28e705dc242 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 21 Jun 2021 14:06:48 +1000 Subject: [PATCH 05/31] refactor --- .../messaging/messages/control/ClosedGroupControlMessage.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt index 146707fe0d..f4cfd9202c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt @@ -173,7 +173,6 @@ class ClosedGroupControlMessage() : ControlMessage() { dataMessageProto.closedGroupControlMessage = closedGroupControlMessage.build() // Group context setGroupContext(dataMessageProto) - // Expiration timer contentProto.dataMessage = dataMessageProto.build() return contentProto.build() } catch (e: Exception) { From b421cc5a5c7f16802aeddaafc052a6d521898de9 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Fri, 25 Jun 2021 10:59:57 +1000 Subject: [PATCH 06/31] fix #608 --- .../thoughtcrime/securesms/loki/activities/SettingsActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt index ac342a707c..967612a1c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt @@ -281,7 +281,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { private fun sendInvitation() { val intent = Intent() intent.action = Intent.ACTION_SEND - val invitation = "Hey, I've been using Session to chat with complete privacy and security. Come join me! Download it at https://getsession.org/. My Session ID is $hexEncodedPublicKey!" + val invitation = "Hey, I've been using Session to chat with complete privacy and security. Come join me! Download it at https://getsession.org/. My Session ID is $hexEncodedPublicKey" intent.putExtra(Intent.EXTRA_TEXT, invitation) intent.type = "text/plain" startActivity(intent) From b329402faf25b2200c9c5a0735232e17d4c82aed Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 1 Jul 2021 17:06:42 +1000 Subject: [PATCH 07/31] fix: audio waveforms decoded on the attachment download --- app/build.gradle | 4 +-- .../attachments/DatabaseAttachmentProvider.kt | 8 +++++ .../v2/messages/VoiceMessageView.kt | 30 +++++++++------- .../api/PrepareAttachmentAudioExtrasJob.kt | 34 ++----------------- .../securesms/loki/views/WaveformSeekBar.kt | 2 +- build.gradle | 1 + libsession/build.gradle | 4 +++ .../database/MessageDataProvider.kt | 1 + .../messaging/jobs/AttachmentDownloadJob.kt | 16 +++++++++ .../libsession}/utilities/DecodedAudio.kt | 33 +++++++++++++++++- 10 files changed, 84 insertions(+), 49 deletions(-) rename {app/src/main/java/org/thoughtcrime/securesms/loki => libsession/src/main/java/org/session/libsession}/utilities/DecodedAudio.kt (94%) diff --git a/app/build.gradle b/app/build.gradle index cf772c0a75..6554e34fb6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -194,8 +194,8 @@ android { versionCode canonicalVersionCode * postFixSize versionName canonicalVersionName - minSdkVersion 23 - targetSdkVersion 30 + minSdkVersion androidMinimumSdkVersion + targetSdkVersion androidCompileSdkVersion multiDexEnabled = true 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 d017e770f4..18b5ff78f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -97,6 +97,14 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) attachmentDatabase.insertAttachmentsForPlaceholder(messageId, attachmentId, stream) } + override fun updateAudioAttachmentDuration(attachmentId: AttachmentId, durationMs: Long) { + DatabaseFactory.getAttachmentDatabase(context).setAttachmentAudioExtras(DatabaseAttachmentAudioExtras( + attachmentId = attachmentId, + visualSamples = byteArrayOf(), + durationMs = durationMs + )) + } + override fun isOutgoingMessage(timestamp: Long): Boolean { val smsDatabase = DatabaseFactory.getSmsDatabase(context) val mmsDatabase = DatabaseFactory.getMmsDatabase(context) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt index b957b0a166..22e6a33adc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt @@ -10,9 +10,11 @@ import android.widget.RelativeLayout import androidx.core.view.isVisible import kotlinx.android.synthetic.main.view_voice_message.view.* import network.loki.messenger.R +import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.thoughtcrime.securesms.audio.AudioSlidePlayer import org.thoughtcrime.securesms.components.CornerMask import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities +import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.MmsMessageRecord import java.util.concurrent.TimeUnit import kotlin.math.roundToInt @@ -44,27 +46,29 @@ class VoiceMessageView : LinearLayout, AudioSlidePlayer.Listener { val audio = message.slideDeck.audioSlide!! val player = AudioSlidePlayer.createFor(context, audio, this) this.player = player - isPreparing = true - if (!audio.isPendingDownload && !audio.isInProgress) { - player.play(0.0) - } voiceMessageViewLoader.isVisible = audio.isPendingDownload val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing) cornerMask.setTopLeftRadius(cornerRadii[0]) cornerMask.setTopRightRadius(cornerRadii[1]) cornerMask.setBottomRightRadius(cornerRadii[2]) cornerMask.setBottomLeftRadius(cornerRadii[3]) + + // only process audio if downloaded + if (audio.isPendingDownload || audio.isInProgress) return + + (audio.asAttachment() as? DatabaseAttachment)?.let { attachment -> + DatabaseFactory.getAttachmentDatabase(context).getAttachmentAudioExtras(attachment.attachmentId)?.let { audioExtras -> + if (audioExtras.durationMs > 0) { + voiceMessageViewDurationTextView.visibility = View.VISIBLE + voiceMessageViewDurationTextView.text = String.format("%02d:%02d", + TimeUnit.MILLISECONDS.toMinutes(audioExtras.durationMs), + TimeUnit.MILLISECONDS.toSeconds(audioExtras.durationMs)) + } + } + } } - override fun onPlayerStart(player: AudioSlidePlayer) { - if (!isPreparing) { return } - isPreparing = false - duration = player.duration - voiceMessageViewDurationTextView.text = String.format("%01d:%02d", - TimeUnit.MILLISECONDS.toMinutes(duration), - TimeUnit.MILLISECONDS.toSeconds(duration)) - player.stop() - } + override fun onPlayerStart(player: AudioSlidePlayer) {} override fun onPlayerProgress(player: AudioSlidePlayer, progress: Double, unused: Long) { if (progress == 1.0) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PrepareAttachmentAudioExtrasJob.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PrepareAttachmentAudioExtrasJob.kt index 6c1a96d1de..fa92ba3d29 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PrepareAttachmentAudioExtrasJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PrepareAttachmentAudioExtrasJob.kt @@ -9,10 +9,11 @@ import org.session.libsession.messaging.utilities.Data import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachmentAudioExtras +import org.session.libsession.utilities.DecodedAudio +import org.session.libsession.utilities.InputStreamMediaDataSource import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.jobmanager.Job import org.thoughtcrime.securesms.jobs.BaseJob -import org.thoughtcrime.securesms.loki.utilities.DecodedAudio import org.thoughtcrime.securesms.mms.PartAuthority import java.io.InputStream import java.lang.IllegalStateException @@ -133,35 +134,4 @@ class PrepareAttachmentAudioExtrasJob : BaseJob { /** Gets dispatched once the audio extras have been updated. */ data class AudioExtrasUpdatedEvent(val attachmentId: AttachmentId) - - @RequiresApi(Build.VERSION_CODES.M) - private class InputStreamMediaDataSource: MediaDataSource { - - private val data: ByteArray - - constructor(inputStream: InputStream): super() { - this.data = inputStream.readBytes() - } - - override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int { - val length: Int = data.size - if (position >= length) { - return -1 // -1 indicates EOF - } - var actualSize = size - if (position + size > length) { - actualSize -= (position + size - length).toInt() - } - System.arraycopy(data, position.toInt(), buffer, offset, actualSize) - return actualSize - } - - override fun getSize(): Long { - return data.size.toLong() - } - - override fun close() { - // We don't need to close the wrapped stream. - } - } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/WaveformSeekBar.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/WaveformSeekBar.kt index 86028521f1..df74bd9ca0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/WaveformSeekBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/WaveformSeekBar.kt @@ -14,7 +14,7 @@ import android.view.ViewConfiguration import android.view.animation.DecelerateInterpolator import androidx.core.math.MathUtils import network.loki.messenger.R -import org.thoughtcrime.securesms.loki.utilities.byteToNormalizedFloat +import org.session.libsession.utilities.byteToNormalizedFloat import kotlin.math.abs import kotlin.math.max import kotlin.math.min diff --git a/build.gradle b/build.gradle index 3343145c60..884e6ed085 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,7 @@ allprojects { } project.ext { + androidMinimumSdkVersion = 23 androidCompileSdkVersion = 30 } } \ No newline at end of file diff --git a/libsession/build.gradle b/libsession/build.gradle index 6763e0cff5..8bd46532eb 100644 --- a/libsession/build.gradle +++ b/libsession/build.gradle @@ -6,6 +6,10 @@ plugins { android { compileSdkVersion androidCompileSdkVersion + defaultConfig { + minSdkVersion androidMinimumSdkVersion + } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 diff --git a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt index 4cf93a429d..37d6d44e4a 100644 --- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt +++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt @@ -20,6 +20,7 @@ interface MessageDataProvider { fun getSignalAttachmentPointer(attachmentId: Long): SignalServiceAttachmentPointer? fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long) fun insertAttachment(messageId: Long, attachmentId: AttachmentId, stream : InputStream) + fun updateAudioAttachmentDuration(attachmentId: AttachmentId, durationMs: Long) fun isOutgoingMessage(timestamp: Long): Boolean fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) fun handleFailedAttachmentUpload(attachmentId: Long) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt index a5aaeaa429..c2c43b9a31 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt @@ -1,15 +1,22 @@ package org.session.libsession.messaging.jobs +import android.content.ContentResolver +import android.media.MediaDataSource +import android.media.MediaExtractor import okhttp3.HttpUrl import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState import org.session.libsession.messaging.utilities.Data +import org.session.libsession.utilities.DecodedAudio import org.session.libsession.utilities.DownloadUtilities +import org.session.libsession.utilities.FileUtils +import org.session.libsession.utilities.InputStreamMediaDataSource import org.session.libsignal.streams.AttachmentCipherInputStream import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import java.io.File +import java.io.FileDescriptor import java.io.FileInputStream class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) : Job { @@ -67,6 +74,15 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) } FileInputStream(tempFile) } + + if (attachment.contentType.startsWith("audio/")) { + // process the duration + InputStreamMediaDataSource(inputStream).use { mediaDataSource -> + val durationMs = (DecodedAudio.create(mediaDataSource).totalDuration / 1000.0).toLong() + messageDataProvider.updateAudioAttachmentDuration(attachment.attachmentId, durationMs) + } + } + messageDataProvider.insertAttachment(databaseMessageID, attachment.attachmentId, inputStream) tempFile.delete() handleSuccess() diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/DecodedAudio.kt b/libsession/src/main/java/org/session/libsession/utilities/DecodedAudio.kt similarity index 94% rename from app/src/main/java/org/thoughtcrime/securesms/loki/utilities/DecodedAudio.kt rename to libsession/src/main/java/org/session/libsession/utilities/DecodedAudio.kt index 77f7f88983..ef21abe4c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/DecodedAudio.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/DecodedAudio.kt @@ -1,4 +1,4 @@ -package org.thoughtcrime.securesms.loki.utilities +package org.session.libsession.utilities import android.media.AudioFormat import android.media.MediaCodec @@ -11,6 +11,7 @@ import androidx.annotation.RequiresApi import java.io.FileDescriptor import java.io.IOException +import java.io.InputStream import java.nio.ByteBuffer import java.nio.ByteOrder import java.nio.ShortBuffer @@ -365,4 +366,34 @@ inline fun byteToNormalizedFloat(value: Byte): Float { /** Turns a [0..1] float into a signed byte. */ inline fun normalizedFloatToByte(value: Float): Byte { return (255f * value - 128f).roundToInt().toByte() +} + +class InputStreamMediaDataSource: MediaDataSource { + + private val data: ByteArray + + constructor(inputStream: InputStream): super() { + this.data = inputStream.readBytes() + } + + override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int { + val length: Int = data.size + if (position >= length) { + return -1 // -1 indicates EOF + } + var actualSize = size + if (position + size > length) { + actualSize -= (position + size - length).toInt() + } + System.arraycopy(data, position.toInt(), buffer, offset, actualSize) + return actualSize + } + + override fun getSize(): Long { + return data.size.toLong() + } + + override fun close() { + // We don't need to close the wrapped stream. + } } \ No newline at end of file From 340d11377d451cd42d1eb390f229566ec09de5c3 Mon Sep 17 00:00:00 2001 From: jubb Date: Fri, 2 Jul 2021 10:15:56 +1000 Subject: [PATCH 08/31] fix: conversations opened from push now go back to home activity --- app/src/main/AndroidManifest.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 32869dd1f1..a95a9baebe 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -221,7 +221,12 @@ + android:parentActivityName="org.thoughtcrime.securesms.loki.activities.HomeActivity" + android:theme="@style/Theme.Session.DayNight.FlatActionBar"> + + Date: Mon, 5 Jul 2021 09:53:12 +1000 Subject: [PATCH 09/31] fix: failing to serialize from AttachmentUploadJob.kt started from resumed pending jobs --- .../messaging/jobs/AttachmentUploadJob.kt | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt index e4cc76974f..b8e5c9fb7e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt @@ -141,12 +141,12 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess kryo.isRegistrationRequired = false val serializedMessage = ByteArray(4096) val output = Output(serializedMessage) - kryo.writeObject(output, message) + kryo.writeClassAndObject(output, message) output.close() return Data.Builder() .putLong(ATTACHMENT_ID_KEY, attachmentID) .putString(THREAD_ID_KEY, threadID) - .putByteArray(MESSAGE_KEY, serializedMessage) + .putByteArray(MESSAGE_KEY, output.toBytes()) .putString(MESSAGE_SEND_JOB_ID_KEY, messageSendJobID) .build() } @@ -157,18 +157,24 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess class Factory: Job.Factory { - override fun create(data: Data): AttachmentUploadJob { + override fun create(data: Data): AttachmentUploadJob? { val serializedMessage = data.getByteArray(MESSAGE_KEY) val kryo = Kryo() kryo.isRegistrationRequired = false val input = Input(serializedMessage) - val message = kryo.readObject(input, Message::class.java) + val message: Message + try { + message = kryo.readClassAndObject(input) as Message + } catch (e: Exception) { + Log.e("Loki","Couldn't serialize the AttachmentUploadJob", e) + return null + } input.close() return AttachmentUploadJob( - data.getLong(ATTACHMENT_ID_KEY), - data.getString(THREAD_ID_KEY)!!, - message, - data.getString(MESSAGE_SEND_JOB_ID_KEY)!! + data.getLong(ATTACHMENT_ID_KEY), + data.getString(THREAD_ID_KEY)!!, + message, + data.getString(MESSAGE_SEND_JOB_ID_KEY)!! ) } } From d57903aa44fc9102c7d522f9b2d496acb9e543a7 Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 5 Jul 2021 09:59:54 +1000 Subject: [PATCH 10/31] fix: open group poller queues trim thread job only if received messages deleting quotes should now propagate to set quote missing flag in MMS and update the UI accordingly --- .../conversation/v2/messages/QuoteView.kt | 3 +- .../v2/messages/VisibleMessageContentView.kt | 7 ++++- .../securesms/database/MmsDatabase.java | 29 +++++++++++++++++++ .../securesms/database/SmsDatabase.java | 6 ++++ .../pollers/OpenGroupPollerV2.kt | 4 ++- 5 files changed, 45 insertions(+), 4 deletions(-) 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 2ab119950a..b452a58b02 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 @@ -136,8 +136,7 @@ class QuoteView : LinearLayout { accentLineLayoutParams.height = getIntrinsicContentHeight(maxContentWidth) // Match the intrinsic * content * height quoteViewAccentLine.layoutParams = accentLineLayoutParams quoteViewAccentLine.setBackgroundColor(getLineColor(isOutgoingMessage)) - } else { - attachments!! + } else if (attachments != null) { quoteViewAttachmentPreviewImageView.imageTintList = ColorStateList.valueOf(ResourcesCompat.getColor(resources, R.color.white, context.theme)) val backgroundColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.black else R.color.accent val backgroundColor = ResourcesCompat.getColor(resources, backgroundColorID, context.theme) 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 16721b1625..258b93a1ef 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 @@ -86,7 +86,12 @@ class VisibleMessageContentView : LinearLayout { // quote view content area's start margin. This unfortunately has to be calculated manually // here to get the layout right. val maxContentWidth = (maxWidth - 2 * resources.getDimension(R.dimen.medium_spacing) - toPx(16, resources)).roundToInt() - quoteView.bind(quote.author.toString(), quote.text, quote.attachment, thread, + val quoteText = if (quote.isOriginalMissing) { + context.getString(R.string.QuoteView_original_missing) + } else { + quote.text + } + quoteView.bind(quote.author.toString(), quoteText, quote.attachment, thread, message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation, message.threadId, glide) mainContainer.addView(quoteView) val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index ce2ddba24a..abae960330 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -63,6 +63,7 @@ import org.thoughtcrime.securesms.attachments.MmsNotificationAttachment; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; 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.NotificationMmsMessageRecord; import org.thoughtcrime.securesms.database.model.Quote; import org.thoughtcrime.securesms.mms.MmsException; @@ -881,6 +882,20 @@ public class MmsDatabase extends MessagingDatabase { } } + public void deleteQuotedFromMessages(MessageRecord toDeleteRecord) { + String query = THREAD_ID + " = ?"; + Cursor threadMmsCursor = rawQuery(query, new String[]{String.valueOf(toDeleteRecord.getThreadId())}); + Reader reader = readerFor(threadMmsCursor); + MmsMessageRecord messageRecord; + + while ((messageRecord = (MmsMessageRecord) reader.getNext()) != null) { + if (messageRecord.getQuote() != null && toDeleteRecord.getDateSent() == messageRecord.getQuote().getId()) { + setQuoteMissing(messageRecord.getId()); + } + } + reader.close(); + } + public boolean delete(long messageId) { long threadId = getThreadIdForMessage(messageId); AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context); @@ -889,6 +904,12 @@ public class MmsDatabase extends MessagingDatabase { GroupReceiptDatabase groupReceiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context); groupReceiptDatabase.deleteRowsForMessage(messageId); + MessageRecord toDelete; + try (Cursor messageCursor = getMessage(messageId)) { + toDelete = readerFor(messageCursor).getNext(); + } + + deleteQuotedFromMessages(toDelete); SQLiteDatabase database = databaseHelper.getWritableDatabase(); database.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""}); boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false); @@ -1066,6 +1087,14 @@ public class MmsDatabase extends MessagingDatabase { return new OutgoingMessageReader(message, threadId); } + public int setQuoteMissing(long messageId) { + ContentValues contentValues = new ContentValues(); + contentValues.put(QUOTE_MISSING, 1); + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + int rows = database.update(TABLE_NAME, contentValues, ID + " = ?", new String[]{ String.valueOf(messageId) }); + return rows; + } + public static class Status { public static final int DOWNLOAD_INITIALIZED = 1; public static final int DOWNLOAD_NO_CONNECTIVITY = 2; 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 6706f5fe77..f66479c41f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -514,6 +514,12 @@ public class SmsDatabase extends MessagingDatabase { Log.i("MessageDatabase", "Deleting: " + messageId); SQLiteDatabase db = databaseHelper.getWritableDatabase(); long threadId = getThreadIdForMessage(messageId); + try { + SmsMessageRecord toDelete = getMessage(messageId); + DatabaseFactory.getMmsDatabase(context).deleteQuotedFromMessages(toDelete); + } catch (NoSuchMessageException e) { + Log.e(TAG, "Couldn't find message record for messageId "+messageId, e); + } db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""}); boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false); notifyConversationListeners(threadId); diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt index 7cfc44801f..b08fa66f34 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt @@ -94,7 +94,9 @@ class OpenGroupPollerV2(private val server: String, private val executorService: if (actualMax > 0) { storage.setLastMessageServerID(room, server, actualMax) } - JobQueue.shared.add(TrimThreadJob(threadId)) + if (messages.isNotEmpty()) { + JobQueue.shared.add(TrimThreadJob(threadId)) + } } private fun handleDeletedMessages(room: String, openGroupID: String, deletions: List) { From f7bec075038d5a446861dcb53b3494188f17885e Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 5 Jul 2021 10:46:48 +1000 Subject: [PATCH 11/31] fix: raise output max buffer size to Job.MAX_BUFFER_SIZE --- .../session/libsession/messaging/jobs/AttachmentUploadJob.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt index b8e5c9fb7e..9d50e96a98 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt @@ -140,7 +140,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess val kryo = Kryo() kryo.isRegistrationRequired = false val serializedMessage = ByteArray(4096) - val output = Output(serializedMessage) + val output = Output(serializedMessage, Job.MAX_BUFFER_SIZE) kryo.writeClassAndObject(output, message) output.close() return Data.Builder() From f4e5e5e36ad10351c51e14bb0c6513b633faff5a Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 5 Jul 2021 15:00:32 +1000 Subject: [PATCH 12/31] fix: duplicate message send bug on attachments --- .../thoughtcrime/securesms/database/Storage.kt | 3 +-- .../messaging/jobs/AttachmentDownloadJob.kt | 2 +- .../libsession/messaging/jobs/JobQueue.kt | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) 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 15b91048e2..7fe982a9e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -27,7 +27,6 @@ import org.session.libsignal.utilities.KeyHelper 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.MessageRecord import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.loki.api.OpenGroupManager import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase @@ -190,7 +189,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, override fun resumeMessageSendJobIfNeeded(messageSendJobID: String) { val job = DatabaseFactory.getSessionJobDatabase(context).getMessageSendJob(messageSendJobID) ?: return - JobQueue.shared.add(job) + JobQueue.shared.resumePendingSendMessage(job) } override fun isJobCanceled(job: Job): Boolean { diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt index c2c43b9a31..dfc27eb10f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt @@ -92,7 +92,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) } private fun handleSuccess() { - Log.w(AttachmentUploadJob.TAG, "Attachment downloaded successfully.") + Log.w("AttachmentDownloadJob", "Attachment downloaded successfully.") delegate?.handleJobSucceeded(this) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt index e6803e89dc..9ced85b110 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt @@ -23,6 +23,7 @@ class JobQueue : JobDelegate { private val attachmentDispatcher = Executors.newFixedThreadPool(2).asCoroutineDispatcher() private val scope = GlobalScope + SupervisorJob() private val queue = Channel(UNLIMITED) + private val pendingJobIds = mutableSetOf() val timer = Timer() @@ -86,6 +87,19 @@ class JobQueue : JobDelegate { MessagingModuleConfiguration.shared.storage.persistJob(job) } + fun resumePendingSendMessage(job: Job) { + val id = job.id ?: run { + Log.e("Loki", "tried to resume pending send job with no ID") + return + } + if (!pendingJobIds.add(id)) { + Log.e("Loki","tried to re-queue pending/in-progress job") + return + } + queue.offer(job) + Log.d("Loki", "resumed pending send message $id") + } + fun resumePendingJobs() { if (hasResumedPendingJobs) { Log.d("Loki", "resumePendingJobs() should only be called once.") @@ -120,6 +134,7 @@ class JobQueue : JobDelegate { override fun handleJobSucceeded(job: Job) { val jobId = job.id ?: return MessagingModuleConfiguration.shared.storage.markJobAsSucceeded(jobId) + pendingJobIds.remove(jobId) } override fun handleJobFailed(job: Job, error: Exception) { @@ -169,4 +184,7 @@ class JobQueue : JobDelegate { val maxBackoff = (10 * 60).toDouble() // 10 minutes return (1000 * 0.25 * min(maxBackoff, (2.0).pow(job.failureCount))).roundToLong() } + + private fun Job.isSend() = this is MessageSendJob || this is AttachmentUploadJob + } \ No newline at end of file From f66f305d76e28f46f2bff9de5c195d94cb56b6a3 Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 5 Jul 2021 15:48:46 +1000 Subject: [PATCH 13/31] fix: quote model uses own address if outgoing in the send to match the bind --- .../securesms/conversation/v2/ConversationActivityV2.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 cf36023b17..96b3675e1e 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 @@ -885,7 +885,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe message.text = body val quote = quotedMessage?.let { val quotedAttachments = (it as? MmsMessageRecord)?.slideDeck?.asAttachments() ?: listOf() - QuoteModel(it.dateSent, it.individualRecipient.address, it.body, false, quotedAttachments) + val sender = if (it.isOutgoing) fromSerialized(TextSecurePreferences.getLocalNumber(this)!!) else it.individualRecipient.address + QuoteModel(it.dateSent, sender, it.body, false, quotedAttachments) } val outgoingTextMessage = OutgoingMediaMessage.from(message, thread, attachments, quote, linkPreview) // Clear the input bar From 21c05374325ef1aa4125b1b953f129744e8da690 Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 5 Jul 2021 16:52:56 +1000 Subject: [PATCH 14/31] fix: pass original missing to the quote view to prevent image with no content rendering --- .../securesms/conversation/v2/input_bar/InputBar.kt | 2 +- .../securesms/conversation/v2/messages/QuoteView.kt | 5 +++-- .../conversation/v2/messages/VisibleMessageContentView.kt | 3 ++- .../org/thoughtcrime/securesms/database/MmsDatabase.java | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) 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 cfb1e38726..f10ad10e2b 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 @@ -122,7 +122,7 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li val maxContentWidth = (screenWidth - 2 * resources.getDimension(R.dimen.medium_spacing) - toPx(16, resources) - toPx(30, resources)).roundToInt() val sender = if (message.isOutgoing) TextSecurePreferences.getLocalNumber(context)!! else message.individualRecipient.address.serialize() quoteView.bind(sender, message.body, attachments, - thread, true, maxContentWidth, message.isOpenGroupInvitation, message.threadId, glide) + thread, true, maxContentWidth, message.isOpenGroupInvitation, message.threadId, false, glide) // The 6 DP below is the padding the quote view applies to itself, which isn't included in the // intrinsic height calculation. val quoteViewIntrinsicHeight = quoteView.getIntrinsicHeight(maxContentWidth) + toPx(6, resources) 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 b452a58b02..b03b4be278 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 @@ -110,7 +110,8 @@ class QuoteView : LinearLayout { // region Updating fun bind(authorPublicKey: String, body: String?, attachments: SlideDeck?, thread: Recipient, - isOutgoingMessage: Boolean, maxContentWidth: Int, isOpenGroupInvitation: Boolean, threadID: Long, glide: GlideRequests) { + isOutgoingMessage: Boolean, maxContentWidth: Int, isOpenGroupInvitation: Boolean, threadID: Long, + isOriginalMissing: Boolean, glide: GlideRequests) { val contactDB = DatabaseFactory.getSessionContactDatabase(context) // Reduce the max body text view line count to 2 if this is a group thread because // we'll be showing the author text view and we don't want the overall quote view height @@ -128,7 +129,7 @@ class QuoteView : LinearLayout { quoteViewBodyTextView.text = if (isOpenGroupInvitation) resources.getString(R.string.open_group_invitation_view__open_group_invitation) else MentionUtilities.highlightMentions((body ?: "").toSpannable(), threadID, context); quoteViewBodyTextView.setTextColor(getTextColor(isOutgoingMessage)) // Accent line / attachment preview - val hasAttachments = (attachments != null && attachments.asAttachments().isNotEmpty()) + val hasAttachments = (attachments != null && attachments.asAttachments().isNotEmpty()) && !isOriginalMissing quoteViewAccentLine.isVisible = !hasAttachments quoteViewAttachmentPreviewContainer.isVisible = hasAttachments if (!hasAttachments) { 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 258b93a1ef..e2587c9dac 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 @@ -92,7 +92,8 @@ class VisibleMessageContentView : LinearLayout { quote.text } quoteView.bind(quote.author.toString(), quoteText, quote.attachment, thread, - message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation, message.threadId, glide) + message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation, message.threadId, + quote.isOriginalMissing, glide) mainContainer.addView(quoteView) val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery) ViewUtil.setPaddingTop(bodyTextView, 0) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index abae960330..87b59132d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -1090,7 +1090,7 @@ public class MmsDatabase extends MessagingDatabase { public int setQuoteMissing(long messageId) { ContentValues contentValues = new ContentValues(); contentValues.put(QUOTE_MISSING, 1); - SQLiteDatabase database = databaseHelper.getReadableDatabase(); + SQLiteDatabase database = databaseHelper.getWritableDatabase(); int rows = database.update(TABLE_NAME, contentValues, ID + " = ?", new String[]{ String.valueOf(messageId) }); return rows; } From a593ad6a5409b62bb4a22cb589f6004102f1edbc Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 5 Jul 2021 17:18:48 +1000 Subject: [PATCH 15/31] feat: increase build number --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6554e34fb6..756bd715c6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -143,8 +143,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 186 -def canonicalVersionName = "1.11.0" +def canonicalVersionCode = 187 +def canonicalVersionName = "1.11.1" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From 56a1c61f6b21bf4b891793c43af0d20e10d236e9 Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 5 Jul 2021 17:33:03 +1000 Subject: [PATCH 16/31] fix: increase above dev build number --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6357e85967..5782c96e7d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -143,8 +143,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 188 -def canonicalVersionName = "1.11.0" +def canonicalVersionCode = 189 +def canonicalVersionName = "1.11.1" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From 8cc2f78da738cb44dc1f9962d4671330b02c7b61 Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 6 Jul 2021 16:53:44 +1000 Subject: [PATCH 17/31] fix: audio durations set accordingly for send and receive and doesn't break saving / uploading by exhausting the input stream --- .../conversation/v2/ConversationActivityV2.kt | 6 +-- .../v2/messages/VoiceMessageView.kt | 3 +- .../messaging/jobs/AttachmentDownloadJob.kt | 50 +++++++++++-------- .../messaging/jobs/AttachmentUploadJob.kt | 19 ++++++- 4 files changed, 53 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 96b3675e1e..075caf5f6e 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 @@ -1032,10 +1032,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) val future = audioRecorder.stopRecording() stopAudioHandler.removeCallbacks(stopVoiceMessageRecordingTask) - future.addListener(object : ListenableFuture.Listener> { + future.addListener(object : ListenableFuture.Listener> { - override fun onSuccess(result: Pair) { - val audioSlide = AudioSlide(this@ConversationActivityV2, result.first, result.second!!, MediaTypes.AUDIO_AAC, true) + override fun onSuccess(result: Pair) { + val audioSlide = AudioSlide(this@ConversationActivityV2, result.first, result.second, MediaTypes.AUDIO_AAC, true) val slideDeck = SlideDeck() slideDeck.addSlide(audioSlide) sendAttachments(slideDeck.asAttachments(), null) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt index 22e6a33adc..894a078b30 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt @@ -59,8 +59,9 @@ class VoiceMessageView : LinearLayout, AudioSlidePlayer.Listener { (audio.asAttachment() as? DatabaseAttachment)?.let { attachment -> DatabaseFactory.getAttachmentDatabase(context).getAttachmentAudioExtras(attachment.attachmentId)?.let { audioExtras -> if (audioExtras.durationMs > 0) { + duration = audioExtras.durationMs voiceMessageViewDurationTextView.visibility = View.VISIBLE - voiceMessageViewDurationTextView.text = String.format("%02d:%02d", + voiceMessageViewDurationTextView.text = String.format("%01d:%02d", TimeUnit.MILLISECONDS.toMinutes(audioExtras.durationMs), TimeUnit.MILLISECONDS.toSeconds(audioExtras.durationMs)) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt index dfc27eb10f..605052f85c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt @@ -7,17 +7,18 @@ import okhttp3.HttpUrl import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState +import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.utilities.Data +import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.utilities.DecodedAudio import org.session.libsession.utilities.DownloadUtilities -import org.session.libsession.utilities.FileUtils import org.session.libsession.utilities.InputStreamMediaDataSource import org.session.libsignal.streams.AttachmentCipherInputStream import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import java.io.File -import java.io.FileDescriptor import java.io.FileInputStream +import java.io.InputStream class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) : Job { override var delegate: JobDelegate? = null @@ -44,53 +45,62 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) val storage = MessagingModuleConfiguration.shared.storage val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val handleFailure: (java.lang.Exception) -> Unit = { exception -> - if (exception == Error.NoAttachment) { + if (exception == Error.NoAttachment + || (exception is OnionRequestAPI.HTTPRequestFailedAtDestinationException && exception.statusCode == 400)) { messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) this.handlePermanentFailure(exception) } else { this.handleFailure(exception) } } + var tempFile: File? = null try { val attachment = messageDataProvider.getDatabaseAttachment(attachmentID) ?: return handleFailure(Error.NoAttachment) messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID) - val tempFile = createTempFile() + tempFile = createTempFile() val threadID = storage.getThreadIdForMms(databaseMessageID) val openGroupV2 = storage.getV2OpenGroup(threadID) - val inputStream = if (openGroupV2 == null) { + if (openGroupV2 == null) { DownloadUtilities.downloadFile(tempFile, attachment.url) - // Assume we're retrieving an attachment for an open group server if the digest is not set - if (attachment.digest?.size ?: 0 == 0 || attachment.key.isNullOrEmpty()) { - FileInputStream(tempFile) - } else { - AttachmentCipherInputStream.createForAttachment(tempFile, attachment.size, Base64.decode(attachment.key), attachment.digest) - } } else { val url = HttpUrl.parse(attachment.url)!! val fileID = url.pathSegments().last() OpenGroupAPIV2.download(fileID.toLong(), openGroupV2.room, openGroupV2.server).get().let { tempFile.writeBytes(it) } - FileInputStream(tempFile) - } - - if (attachment.contentType.startsWith("audio/")) { - // process the duration - InputStreamMediaDataSource(inputStream).use { mediaDataSource -> - val durationMs = (DecodedAudio.create(mediaDataSource).totalDuration / 1000.0).toLong() - messageDataProvider.updateAudioAttachmentDuration(attachment.attachmentId, durationMs) - } } + val inputStream = getInputStream(tempFile, attachment) messageDataProvider.insertAttachment(databaseMessageID, attachment.attachmentId, inputStream) + if (attachment.contentType.startsWith("audio/")) { + // process the duration + try { + InputStreamMediaDataSource(getInputStream(tempFile, attachment)).use { mediaDataSource -> + val durationMs = (DecodedAudio.create(mediaDataSource).totalDuration / 1000.0).toLong() + messageDataProvider.updateAudioAttachmentDuration(attachment.attachmentId, durationMs) + } + } catch (e: Exception) { + Log.e("Loki", "Couldn't process audio attachment", e) + } + } tempFile.delete() handleSuccess() } catch (e: Exception) { + tempFile?.delete() return handleFailure(e) } } + private fun getInputStream(tempFile: File, attachment: DatabaseAttachment): InputStream { + // Assume we're retrieving an attachment for an open group server if the digest is not set + return if (attachment.digest?.size ?: 0 == 0 || attachment.key.isNullOrEmpty()) { + FileInputStream(tempFile) + } else { + AttachmentCipherInputStream.createForAttachment(tempFile, attachment.size, Base64.decode(attachment.key), attachment.digest) + } + } + private fun handleSuccess() { Log.w("AttachmentDownloadJob", "Attachment downloaded successfully.") delegate?.handleJobSucceeded(this) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt index 9d50e96a98..332d674115 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt @@ -11,6 +11,8 @@ import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.utilities.Data +import org.session.libsession.utilities.DecodedAudio +import org.session.libsession.utilities.InputStreamMediaDataSource import org.session.libsession.utilities.UploadResult import org.session.libsignal.streams.AttachmentCipherOutputStream import org.session.libsignal.messages.SignalServiceAttachmentStream @@ -108,7 +110,22 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) { Log.d(TAG, "Attachment uploaded successfully.") delegate?.handleJobSucceeded(this) - MessagingModuleConfiguration.shared.messageDataProvider.handleSuccessfulAttachmentUpload(attachmentID, attachment, attachmentKey, uploadResult) + val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider + messageDataProvider.handleSuccessfulAttachmentUpload(attachmentID, attachment, attachmentKey, uploadResult) + if (attachment.contentType.startsWith("audio/")) { + // process the duration + try { + val inputStream = messageDataProvider.getAttachmentStream(attachmentID)!!.inputStream!! + InputStreamMediaDataSource(inputStream).use { mediaDataSource -> + val durationMs = (DecodedAudio.create(mediaDataSource).totalDuration / 1000.0).toLong() + messageDataProvider.getDatabaseAttachment(attachmentID)?.attachmentId?.let { attachmentId -> + messageDataProvider.updateAudioAttachmentDuration(attachmentId, durationMs) + } + } + } catch (e: Exception) { + Log.e("Loki", "Couldn't process audio attachment", e) + } + } MessagingModuleConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID) } From b854a5d93624e7890429fdbc6827fac0d3c93776 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 7 Jul 2021 09:41:56 +1000 Subject: [PATCH 18/31] Update build number --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5782c96e7d..053ab72b9a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -143,7 +143,7 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 189 +def canonicalVersionCode = 190 def canonicalVersionName = "1.11.1" def postFixSize = 10 From 9cd99c6c84e850ef097ae6632eb55d047e8c0fab Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 7 Jul 2021 10:55:07 +1000 Subject: [PATCH 19/31] Fix quote view size calculation --- .../conversation/v2/messages/VisibleMessageContentView.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 e2587c9dac..dd111d0a72 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 @@ -82,10 +82,10 @@ class VisibleMessageContentView : LinearLayout { } else if (message is MmsMessageRecord && message.quote != null) { val quote = message.quote!! val quoteView = QuoteView(context, QuoteView.Mode.Regular) - // The max content width is the max message bubble size - 2 times the horizontal padding - the - // quote view content area's start margin. This unfortunately has to be calculated manually + // The max content width is the max message bubble size - 2 times the horizontal padding - 2 + // times the horizontal margin. This unfortunately has to be calculated manually // here to get the layout right. - val maxContentWidth = (maxWidth - 2 * resources.getDimension(R.dimen.medium_spacing) - toPx(16, resources)).roundToInt() + val maxContentWidth = (maxWidth - 2 * resources.getDimension(R.dimen.medium_spacing) - 2 * toPx(16, resources)).roundToInt() val quoteText = if (quote.isOriginalMissing) { context.getString(R.string.QuoteView_original_missing) } else { From 6ce37a6231c7e0ede50dca552cefddfa809c8e7d Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 7 Jul 2021 13:03:48 +1000 Subject: [PATCH 20/31] Remove unused code --- app/src/main/AndroidManifest.xml | 27 - .../securesms/MessageDetailsActivity.java | 476 ---- .../thoughtcrime/securesms/ShareActivity.java | 8 +- .../ConversationSearchBottomBar.java | 2 +- .../conversation/ConversationActivity.java | 2440 ----------------- .../conversation/ConversationAdapter.java | 532 ---- .../conversation/ConversationFragment.java | 1220 --------- .../conversation/ConversationItem.java | 1203 -------- .../ConversationPopupActivity.java | 120 - .../ConversationSearchViewModel.java | 146 - .../conversation/ConversationUpdateItem.java | 198 -- .../conversation/v2/ConversationActivityV2.kt | 2 +- .../activities/CreateClosedGroupActivity.kt | 3 +- .../activities/CreatePrivateChatActivity.kt | 4 +- .../securesms/loki/activities/HomeActivity.kt | 2 +- .../loki/activities/JoinPublicChatActivity.kt | 3 +- .../loki/activities/QRCodeActivity.kt | 4 +- .../notifications/DefaultMessageNotifier.java | 2 +- .../notifications/NotificationItem.java | 2 +- .../notifications/NotificationState.java | 9 +- .../securesms/util/CommunicationActions.java | 7 +- .../res/layout/conversation_item_received.xml | 221 -- .../res/layout/conversation_item_sent.xml | 176 -- .../res/layout/conversation_item_update.xml | 80 - .../res/layout/message_details_activity.xml | 4 - .../conversation/ConversationAdapterTest.java | 41 - 26 files changed, 15 insertions(+), 6917 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/MessageDetailsActivity.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java delete mode 100644 app/src/main/res/layout/conversation_item_received.xml delete mode 100644 app/src/main/res/layout/conversation_item_sent.xml delete mode 100644 app/src/main/res/layout/conversation_item_update.xml delete mode 100644 app/src/test/java/org/thoughtcrime/securesms/conversation/ConversationAdapterTest.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a95a9baebe..bc9e379d11 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -206,18 +206,6 @@ android:resource="@mipmap/ic_launcher" /> - - - - - . - */ -package org.thoughtcrime.securesms; - -import android.annotation.SuppressLint; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.database.Cursor; -import android.graphics.drawable.ColorDrawable; -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ListView; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.loader.app.LoaderManager.LoaderCallbacks; -import androidx.loader.content.Loader; -import org.session.libsession.messaging.messages.visible.LinkPreview; -import org.session.libsession.messaging.messages.visible.OpenGroupInvitation; -import org.session.libsession.messaging.messages.visible.Quote; -import org.session.libsession.messaging.messages.visible.VisibleMessage; -import org.session.libsession.messaging.open_groups.OpenGroupV2; -import org.session.libsession.messaging.sending_receiving.MessageSender; -import org.session.libsession.messaging.utilities.UpdateMessageData; -import org.thoughtcrime.securesms.MessageDetailsRecipientAdapter.RecipientDeliveryStatus; -import org.session.libsession.utilities.MaterialColor; -import org.thoughtcrime.securesms.conversation.ConversationItem; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.GroupReceiptDatabase; -import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo; -import org.thoughtcrime.securesms.database.MmsDatabase; -import org.thoughtcrime.securesms.database.MmsSmsDatabase; -import org.thoughtcrime.securesms.database.SmsDatabase; -import org.thoughtcrime.securesms.database.loaders.MessageDetailsLoader; -import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.database.model.MmsMessageRecord; -import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.RecipientModifiedListener; -import org.thoughtcrime.securesms.util.DateUtils; -import org.session.libsession.utilities.ExpirationUtil; -import org.session.libsession.utilities.Util; -import org.session.libsignal.utilities.guava.Optional; - -import java.lang.ref.WeakReference; -import java.sql.Date; -import java.text.SimpleDateFormat; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; - -import network.loki.messenger.R; - -/** - * @author Jake McGinty - */ -public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity implements LoaderCallbacks, RecipientModifiedListener { - private final static String TAG = MessageDetailsActivity.class.getSimpleName(); - - public final static String MESSAGE_ID_EXTRA = "message_id"; - public final static String THREAD_ID_EXTRA = "thread_id"; - public final static String IS_PUSH_GROUP_EXTRA = "is_push_group"; - public final static String TYPE_EXTRA = "type"; - public final static String ADDRESS_EXTRA = "address"; - - private GlideRequests glideRequests; - private long threadId; - private boolean isPushGroup; - private ConversationItem conversationItem; - private ViewGroup itemParent; - private View metadataContainer; - private View expiresContainer; - private TextView errorText; - private View resendButton; - private TextView sentDate; - private TextView receivedDate; - private TextView expiresInText; - private View receivedContainer; - private TextView transport; - private TextView toFrom; - private View separator; - private ListView recipientsList; - private LayoutInflater inflater; - - private boolean running; - - @Override - public void onCreate(Bundle bundle, boolean ready) { - super.onCreate(bundle, ready); - setContentView(R.layout.message_details_activity); - running = true; - - initializeResources(); - initializeActionBar(); - getSupportLoaderManager().initLoader(0, null, this); - } - - @Override - protected void onResume() { - super.onResume(); - - assert getSupportActionBar() != null; - getSupportActionBar().setTitle("Message Details"); - - ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(threadId); - } - - @Override - protected void onPause() { - super.onPause(); - ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(-1L); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - running = false; - } - - private void initializeActionBar() { - assert getSupportActionBar() != null; - - Recipient recipient = Recipient.from(this, getIntent().getParcelableExtra(ADDRESS_EXTRA), true); - recipient.addListener(this); - } - - private void setActionBarColor(MaterialColor color) { - assert getSupportActionBar() != null; - getSupportActionBar().setBackgroundDrawable(new ColorDrawable(color.toActionBarColor(this))); - } - - @Override - public void onModified(final Recipient recipient) { - Util.runOnMain(() -> setActionBarColor(recipient.getColor())); - } - - private void initializeResources() { - inflater = LayoutInflater.from(this); - View header = inflater.inflate(R.layout.message_details_header, recipientsList, false); - - threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1); - isPushGroup = getIntent().getBooleanExtra(IS_PUSH_GROUP_EXTRA, false); - glideRequests = GlideApp.with(this); - itemParent = header.findViewById(R.id.item_container); - recipientsList = findViewById(R.id.recipients_list); - metadataContainer = header.findViewById(R.id.metadata_container); - errorText = header.findViewById(R.id.error_text); - resendButton = header.findViewById(R.id.resend_button); - sentDate = header.findViewById(R.id.sent_time); - receivedContainer = header.findViewById(R.id.received_container); - receivedDate = header.findViewById(R.id.received_time); - transport = header.findViewById(R.id.transport); - toFrom = header.findViewById(R.id.tofrom); - separator = header.findViewById(R.id.separator); - expiresContainer = header.findViewById(R.id.expires_container); - expiresInText = header.findViewById(R.id.expires_in); - recipientsList.setHeaderDividersEnabled(false); - recipientsList.addHeaderView(header, null, false); - } - - private void updateTransport(MessageRecord messageRecord) { - final String transportText; - if (messageRecord.isOutgoing() && messageRecord.isFailed()) { - transportText = "-"; - } else if (messageRecord.isPending()) { - transportText = getString(R.string.ConversationFragment_pending); - } else if (messageRecord.isMms()) { - transportText = getString(R.string.ConversationFragment_mms); - } else { - transportText = getString(R.string.ConversationFragment_sms); - } - - transport.setText(transportText); - } - - private void updateTime(MessageRecord messageRecord) { - sentDate.setOnLongClickListener(null); - receivedDate.setOnLongClickListener(null); - - if (messageRecord.isPending() || messageRecord.isFailed()) { - sentDate.setText("-"); - receivedContainer.setVisibility(View.GONE); - } else { - Locale dateLocale = Locale.getDefault(); - SimpleDateFormat dateFormatter = DateUtils.getDetailedDateFormatter(this, dateLocale); - sentDate.setText(dateFormatter.format(new Date(messageRecord.getDateSent()))); - sentDate.setOnLongClickListener(v -> { - copyToClipboard(String.valueOf(messageRecord.getDateSent())); - return true; - }); - - if (messageRecord.getDateReceived() != messageRecord.getDateSent() && !messageRecord.isOutgoing()) { - receivedDate.setText(dateFormatter.format(new Date(messageRecord.getDateReceived()))); - receivedDate.setOnLongClickListener(v -> { - copyToClipboard(String.valueOf(messageRecord.getDateReceived())); - return true; - }); - receivedContainer.setVisibility(View.VISIBLE); - } else { - receivedContainer.setVisibility(View.GONE); - } - } - } - - private void updateExpirationTime(final MessageRecord messageRecord) { - if (messageRecord.getExpiresIn() <= 0 || messageRecord.getExpireStarted() <= 0) { - expiresContainer.setVisibility(View.GONE); - return; - } - - expiresContainer.setVisibility(View.VISIBLE); - Util.runOnMain(new Runnable() { - @Override - public void run() { - long elapsed = System.currentTimeMillis() - messageRecord.getExpireStarted(); - long remaining = messageRecord.getExpiresIn() - elapsed; - - String duration = ExpirationUtil.getExpirationDisplayValue(MessageDetailsActivity.this, Math.max((int)(remaining / 1000), 1)); - expiresInText.setText(duration); - - if (running) { - Util.runOnMainDelayed(this, 500); - } - } - }); - } - - private void updateRecipients(MessageRecord messageRecord, Recipient recipient, List recipients) { - final int toFromRes; - if (messageRecord.isOutgoing()) { - toFromRes = R.string.message_details_header__to; - } else { - toFromRes = R.string.message_details_header__from; - } - toFrom.setText(toFromRes); - long threadID = messageRecord.getThreadId(); - OpenGroupV2 openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID); - if (openGroup != null && messageRecord.isOutgoing()) { - toFrom.setVisibility(View.GONE); - separator.setVisibility(View.GONE); - } - conversationItem.bind(messageRecord, Optional.absent(), Optional.absent(), glideRequests, Locale.getDefault(), new HashSet<>(), recipient, null, false); - recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, glideRequests, messageRecord, recipients, isPushGroup)); - } - - private void inflateMessageViewIfAbsent(MessageRecord messageRecord) { - if (conversationItem == null) { - if (messageRecord.isOutgoing()) { - conversationItem = (ConversationItem) inflater.inflate(R.layout.conversation_item_sent, itemParent, false); - } else { - conversationItem = (ConversationItem) inflater.inflate(R.layout.conversation_item_received, itemParent, false); - } - itemParent.addView(conversationItem); - } - } - - private @Nullable MessageRecord getMessageRecord(Context context, Cursor cursor, String type) { - switch (type) { - case MmsSmsDatabase.SMS_TRANSPORT: - SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); - SmsDatabase.Reader reader = smsDatabase.readerFor(cursor); - return reader.getNext(); - case MmsSmsDatabase.MMS_TRANSPORT: - MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); - MmsDatabase.Reader mmsReader = mmsDatabase.readerFor(cursor); - return mmsReader.getNext(); - default: - throw new AssertionError("no valid message type specified"); - } - } - - private void copyToClipboard(@NonNull String text) { - ((ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("text", text)); - } - - @Override - public @NonNull Loader onCreateLoader(int id, Bundle args) { - return new MessageDetailsLoader(this, getIntent().getStringExtra(TYPE_EXTRA), - getIntent().getLongExtra(MESSAGE_ID_EXTRA, -1)); - } - - @Override - public void onLoadFinished(@NonNull Loader loader, Cursor cursor) { - MessageRecord messageRecord = getMessageRecord(this, cursor, getIntent().getStringExtra(TYPE_EXTRA)); - - if (messageRecord == null) { - finish(); - } else { - new MessageRecipientAsyncTask(this, messageRecord).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - - @Override - public void onLoaderReset(@NonNull Loader loader) { - recipientsList.setAdapter(null); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - super.onOptionsItemSelected(item); - - switch (item.getItemId()) { - case android.R.id.home: finish(); return true; - } - - return false; - } - - @SuppressLint("StaticFieldLeak") - private class MessageRecipientAsyncTask extends AsyncTask> { - - private final WeakReference weakContext; - private final MessageRecord messageRecord; - - MessageRecipientAsyncTask(@NonNull Context context, @NonNull MessageRecord messageRecord) { - this.weakContext = new WeakReference<>(context); - this.messageRecord = messageRecord; - } - - protected Context getContext() { - return weakContext.get(); - } - - @Override - public List doInBackground(Void... voids) { - Context context = getContext(); - - if (context == null) { - Log.w(TAG, "associated context is destroyed, finishing early"); - return null; - } - - List recipients = new LinkedList<>(); - - if (!messageRecord.getRecipient().isGroupRecipient()) { - recipients.add(new RecipientDeliveryStatus(messageRecord.getRecipient(), getStatusFor(messageRecord.getDeliveryReceiptCount(), messageRecord.getReadReceiptCount(), messageRecord.isPending()), true, -1)); - } else { - List receiptInfoList = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageRecord.getId()); - - if (receiptInfoList.isEmpty()) { - List group = DatabaseFactory.getGroupDatabase(context).getGroupMembers(messageRecord.getRecipient().getAddress().toGroupString(), false); - - for (Recipient recipient : group) { - recipients.add(new RecipientDeliveryStatus(recipient, RecipientDeliveryStatus.Status.UNKNOWN, false, -1)); - } - } else { - for (GroupReceiptInfo info : receiptInfoList) { - recipients.add(new RecipientDeliveryStatus(Recipient.from(context, info.getAddress(), true), - getStatusFor(info.getStatus(), messageRecord.isPending(), messageRecord.isFailed()), - info.isUnidentified(), - info.getTimestamp())); - } - } - } - - return recipients; - } - - @Override - public void onPostExecute(List recipients) { - if (getContext() == null) { - Log.w(TAG, "AsyncTask finished with a destroyed context, leaving early."); - return; - } - - inflateMessageViewIfAbsent(messageRecord); - updateRecipients(messageRecord, messageRecord.getRecipient(), recipients); - - boolean isGroupNetworkFailure = messageRecord.isFailed() && !messageRecord.getNetworkFailures().isEmpty(); - boolean isIndividualNetworkFailure = messageRecord.isFailed() && !isPushGroup; - - LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(getContext()); - String errorMessage = lokiMessageDatabase.getErrorMessage(messageRecord.id); - if (errorMessage != null) { - errorText.setText(errorMessage); - } - - if (isGroupNetworkFailure || isIndividualNetworkFailure) { - errorText.setVisibility(View.VISIBLE); - resendButton.setVisibility(View.VISIBLE); - resendButton.setOnClickListener(this::onResendClicked); - metadataContainer.setVisibility(View.GONE); - } else if (messageRecord.isFailed()) { - errorText.setVisibility(View.VISIBLE); - resendButton.setVisibility(View.GONE); - resendButton.setOnClickListener(null); - metadataContainer.setVisibility(View.GONE); - } else { - updateTransport(messageRecord); - updateTime(messageRecord); - updateExpirationTime(messageRecord); - errorText.setVisibility(View.GONE); - resendButton.setVisibility(View.GONE); - resendButton.setOnClickListener(null); - metadataContainer.setVisibility(View.VISIBLE); - } - } - - private RecipientDeliveryStatus.Status getStatusFor(int deliveryReceiptCount, int readReceiptCount, boolean pending) { - if (readReceiptCount > 0) return RecipientDeliveryStatus.Status.READ; - else if (deliveryReceiptCount > 0) return RecipientDeliveryStatus.Status.DELIVERED; - else if (!pending) return RecipientDeliveryStatus.Status.SENT; - else return RecipientDeliveryStatus.Status.PENDING; - } - - private RecipientDeliveryStatus.Status getStatusFor(int groupStatus, boolean pending, boolean failed) { - if (groupStatus == GroupReceiptDatabase.STATUS_READ) return RecipientDeliveryStatus.Status.READ; - else if (groupStatus == GroupReceiptDatabase.STATUS_DELIVERED) return RecipientDeliveryStatus.Status.DELIVERED; - else if (groupStatus == GroupReceiptDatabase.STATUS_UNDELIVERED && failed) return RecipientDeliveryStatus.Status.UNKNOWN; - else if (groupStatus == GroupReceiptDatabase.STATUS_UNDELIVERED && !pending) return RecipientDeliveryStatus.Status.SENT; - else if (groupStatus == GroupReceiptDatabase.STATUS_UNDELIVERED) return RecipientDeliveryStatus.Status.PENDING; - else if (groupStatus == GroupReceiptDatabase.STATUS_UNKNOWN) return RecipientDeliveryStatus.Status.UNKNOWN; - throw new AssertionError(); - } - - private void onResendClicked(View v) { - Recipient recipient = messageRecord.getRecipient(); - VisibleMessage message = new VisibleMessage(); - message.setId(messageRecord.getId()); - if (messageRecord.isOpenGroupInvitation()) { - OpenGroupInvitation openGroupInvitation = new OpenGroupInvitation(); - UpdateMessageData updateMessageData = UpdateMessageData.Companion.fromJSON(messageRecord.getBody()); - if (updateMessageData.getKind() instanceof UpdateMessageData.Kind.OpenGroupInvitation) { - UpdateMessageData.Kind.OpenGroupInvitation data = (UpdateMessageData.Kind.OpenGroupInvitation)updateMessageData.getKind(); - openGroupInvitation.setName(data.getGroupName()); - openGroupInvitation.setUrl(data.getGroupUrl()); - } - message.setOpenGroupInvitation(openGroupInvitation); - } else { - message.setText(messageRecord.getBody()); - } - message.setSentTimestamp(messageRecord.getTimestamp()); - if (recipient.isGroupRecipient()) { - message.setGroupPublicKey(recipient.getAddress().toGroupString()); - } else { - message.setRecipient(messageRecord.getRecipient().getAddress().serialize()); - } - message.setThreadID(messageRecord.getThreadId()); - if (messageRecord.isMms()) { - MmsMessageRecord mmsMessageRecord = (MmsMessageRecord) messageRecord; - if (!mmsMessageRecord.getLinkPreviews().isEmpty()) { - message.setLinkPreview(LinkPreview.Companion.from(mmsMessageRecord.getLinkPreviews().get(0))); - } - if (mmsMessageRecord.getQuote() != null) { - message.setQuote(Quote.Companion.from(mmsMessageRecord.getQuote().getQuoteModel())); - } - message.addSignalAttachments(mmsMessageRecord.getSlideDeck().asAttachments()); - } - MessageSender.send(message, recipient.getAddress()); - resendButton.setVisibility(View.GONE); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java index 8f820cba54..ba7e145bf4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java @@ -37,7 +37,7 @@ import androidx.appcompat.widget.Toolbar; import org.session.libsession.utilities.DistributionTypes; import org.thoughtcrime.securesms.components.SearchToolbar; -import org.thoughtcrime.securesms.conversation.ConversationActivity; + import org.session.libsession.utilities.Address; import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -219,7 +219,6 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity final Intent intent = getBaseShareIntent(ConversationActivityV2.class); intent.putExtra(ConversationActivityV2.ADDRESS, address); intent.putExtra(ConversationActivityV2.THREAD_ID, threadId); - intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType); isPassingAlongMedia = true; startActivity(intent); @@ -227,11 +226,6 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity private Intent getBaseShareIntent(final @NonNull Class target) { final Intent intent = new Intent(this, target); - final String textExtra = getIntent().getStringExtra(Intent.EXTRA_TEXT); - final ArrayList mediaExtra = getIntent().getParcelableArrayListExtra(ConversationActivity.MEDIA_EXTRA); - - intent.putExtra(ConversationActivity.TEXT_EXTRA, textExtra); - intent.putExtra(ConversationActivity.MEDIA_EXTRA, mediaExtra); if (resolvedExtra != null) intent.setDataAndType(resolvedExtra, mimeType); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationSearchBottomBar.java b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationSearchBottomBar.java index 603c4869a1..409acd7639 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationSearchBottomBar.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationSearchBottomBar.java @@ -11,7 +11,7 @@ import android.widget.TextView; import network.loki.messenger.R; /** - * Bottom navigation bar shown in the {@link org.thoughtcrime.securesms.conversation.ConversationActivity} + * Bottom navigation bar shown in the ConversationActivity * when the user is searching within a conversation. Shows details about the results and allows the * user to move between them. */ diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java deleted file mode 100644 index d6db6ec16d..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ /dev/null @@ -1,2440 +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.thoughtcrime.securesms.conversation; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.hardware.Camera; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Vibrator; -import android.provider.Browser; -import android.provider.Telephony; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.Pair; -import android.util.TypedValue; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnFocusChangeListener; -import android.view.View.OnKeyListener; -import android.view.WindowManager; -import android.view.inputmethod.EditorInfo; -import android.widget.Button; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.SearchView; -import androidx.appcompat.widget.Toolbar; -import androidx.core.content.pm.ShortcutInfoCompat; -import androidx.core.content.pm.ShortcutManagerCompat; -import androidx.core.graphics.drawable.IconCompat; -import androidx.core.view.MenuItemCompat; -import androidx.lifecycle.ViewModelProviders; -import androidx.loader.app.LoaderManager; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import com.annimon.stream.Stream; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; -import org.session.libsession.messaging.mentions.MentionsManager; -import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate; -import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage; -import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage; -import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; -import org.session.libsession.messaging.messages.visible.OpenGroupInvitation; -import org.session.libsession.messaging.messages.visible.VisibleMessage; -import org.session.libsession.messaging.open_groups.OpenGroupV2; -import org.session.libsession.messaging.sending_receiving.MessageSender; -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.Contact; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.DistributionTypes; -import org.session.libsession.utilities.GroupRecord; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.RecipientFormattingException; -import org.session.libsession.utilities.recipients.RecipientModifiedListener; -import org.session.libsession.utilities.ExpirationUtil; -import org.session.libsession.utilities.GroupUtil; -import org.session.libsession.utilities.MediaTypes; -import org.session.libsession.utilities.ServiceUtil; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.concurrent.AssertedSuccessListener; -import org.session.libsession.utilities.Stub; -import org.session.libsignal.exceptions.InvalidMessageException; -import org.session.libsignal.utilities.guava.Optional; -import org.session.libsession.messaging.mentions.Mention; -import org.session.libsignal.utilities.HexEncodingKt; -import org.session.libsignal.utilities.PublicKeyValidation; -import org.session.libsignal.utilities.ListenableFuture; -import org.session.libsignal.utilities.SettableFuture; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.ExpirationDialog; -import org.thoughtcrime.securesms.MediaOverviewActivity; -import org.thoughtcrime.securesms.MuteDialog; -import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; -import org.thoughtcrime.securesms.ShortcutLauncherActivity; -import org.thoughtcrime.securesms.audio.AudioRecorder; -import org.thoughtcrime.securesms.audio.AudioSlidePlayer; -import org.thoughtcrime.securesms.components.AnimatingToggle; -import org.thoughtcrime.securesms.components.AttachmentTypeSelector; -import org.thoughtcrime.securesms.components.ComposeText; -import org.thoughtcrime.securesms.components.ConversationSearchBottomBar; -import org.thoughtcrime.securesms.components.HidingLinearLayout; -import org.thoughtcrime.securesms.components.InputAwareLayout; -import org.thoughtcrime.securesms.components.InputPanel; -import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener; -import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider; -import org.thoughtcrime.securesms.components.emoji.EmojiStrings; -import org.thoughtcrime.securesms.components.emoji.MediaKeyboard; -import org.thoughtcrime.securesms.contacts.ContactAccessor; -import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData; -import org.thoughtcrime.securesms.contactshare.ContactUtil; -import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.DraftDatabase; -import org.thoughtcrime.securesms.database.DraftDatabase.Draft; -import org.thoughtcrime.securesms.database.DraftDatabase.Drafts; -import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; -import org.thoughtcrime.securesms.database.MmsSmsColumns.Types; -import org.thoughtcrime.securesms.database.ThreadDatabase; -import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.database.model.MmsMessageRecord; -import org.thoughtcrime.securesms.giph.ui.GiphyActivity; -import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; -import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; -import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel; -import org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity; -import org.thoughtcrime.securesms.loki.activities.HomeActivity; -import org.thoughtcrime.securesms.loki.activities.SelectContactsActivity; -import org.thoughtcrime.securesms.loki.api.PublicChatInfoUpdateWorker; -import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol; -import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt; -import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities; -import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities; -import org.thoughtcrime.securesms.loki.views.MentionCandidateSelectionView; -import org.thoughtcrime.securesms.loki.views.ProfilePictureView; -import org.thoughtcrime.securesms.mediasend.Media; -import org.thoughtcrime.securesms.mediasend.MediaSendActivity; -import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager; -import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager.MediaType; -import org.thoughtcrime.securesms.mms.AudioSlide; -import org.thoughtcrime.securesms.mms.GifSlide; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.mms.ImageSlide; -import org.thoughtcrime.securesms.mms.MediaConstraints; -import org.thoughtcrime.securesms.mms.MmsException; -import org.thoughtcrime.securesms.mms.QuoteId; -import org.thoughtcrime.securesms.mms.Slide; -import org.thoughtcrime.securesms.mms.SlideDeck; -import org.thoughtcrime.securesms.mms.TextSlide; -import org.thoughtcrime.securesms.mms.VideoSlide; -import org.thoughtcrime.securesms.notifications.MarkReadReceiver; -import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.providers.BlobProvider; -import org.thoughtcrime.securesms.search.model.MessageResult; -import org.thoughtcrime.securesms.service.ExpiringMessageManager; -import org.thoughtcrime.securesms.util.BitmapUtil; -import org.thoughtcrime.securesms.util.DateUtils; -import org.thoughtcrime.securesms.util.MediaUtil; -import org.thoughtcrime.securesms.util.PushCharacterCalculator; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import kotlin.Unit; -import network.loki.messenger.R; - -/** - * Activity for displaying a message thread, as well as - * composing/sending a new message into that thread. - * - * @author Moxie Marlinspike - * - */ -@SuppressLint("StaticFieldLeak") -public class ConversationActivity extends PassphraseRequiredActionBarActivity - implements ConversationFragment.ConversationFragmentListener, - AttachmentManager.AttachmentListener, - RecipientModifiedListener, - OnKeyboardShownListener, - InputPanel.Listener, - InputPanel.MediaListener, - ComposeText.CursorPositionChangedListener, - ConversationSearchBottomBar.EventListener -{ - private static final String TAG = ConversationActivity.class.getSimpleName(); - - public static final String ADDRESS_EXTRA = "address"; - public static final String THREAD_ID_EXTRA = "thread_id"; - public static final String IS_ARCHIVED_EXTRA = "is_archived"; - public static final String TEXT_EXTRA = "draft_text"; - public static final String MEDIA_EXTRA = "media_list"; - public static final String STICKER_EXTRA = "media_list"; - public static final String DISTRIBUTION_TYPE_EXTRA = "distribution_type"; - public static final String TIMING_EXTRA = "timing"; - public static final String LAST_SEEN_EXTRA = "last_seen"; - public static final String STARTING_POSITION_EXTRA = "starting_position"; - - // private static final int PICK_GALLERY = 1; - private static final int PICK_DOCUMENT = 2; - private static final int PICK_AUDIO = 3; - private static final int PICK_CONTACT = 4; - // private static final int GET_CONTACT_DETAILS = 5; -// private static final int GROUP_EDIT = 6; - private static final int TAKE_PHOTO = 7; - private static final int ADD_CONTACT = 8; - private static final int PICK_LOCATION = 9; - private static final int PICK_GIF = 10; - private static final int SMS_DEFAULT = 11; - private static final int MEDIA_SENDER = 12; - private static final int INVITE_CONTACTS = 124; - - private GlideRequests glideRequests; - protected ComposeText composeText; - private AnimatingToggle buttonToggle; - private ImageButton sendButton; - private ImageButton attachButton; - private ProfilePictureView profilePictureView; - private TextView titleTextView; - private ConversationFragment fragment; - private Button unblockButton; - private Button makeDefaultSmsButton; - private InputAwareLayout container; - private TypingStatusTextWatcher typingTextWatcher; - private MentionTextWatcher mentionTextWatcher; - private ConversationSearchBottomBar searchNav; - private MenuItem searchViewItem; - private ProgressBar messageStatusProgressBar; - private ImageView muteIndicatorImageView; - private TextView subtitleTextView; - private View homeButtonContainer; - - private AttachmentTypeSelector attachmentTypeSelector; - private AttachmentManager attachmentManager; - private AudioRecorder audioRecorder; - private Handler audioHandler; - private Runnable stopRecordingTask; - private Stub emojiDrawerStub; - protected HidingLinearLayout quickAttachmentToggle; - protected HidingLinearLayout inlineAttachmentToggle; - private InputPanel inputPanel; - - private LinkPreviewViewModel linkPreviewViewModel; - private ConversationSearchViewModel searchViewModel; - - private Recipient recipient; - private long threadId; - private int distributionType; - private boolean isDefaultSms = false; - private boolean isSecurityInitialized = false; - private int expandedKeyboardHeight = 0; - private int collapsedKeyboardHeight = Integer.MAX_VALUE; - private int keyboardHeight = 0; - - // Message status bar - private ArrayList broadcastReceivers = new ArrayList<>(); - private String messageStatus = null; - - // Mentions - private View mentionCandidateSelectionViewContainer; - private MentionCandidateSelectionView mentionCandidateSelectionView; - private int currentMentionStartIndex = -1; - private ArrayList mentions = new ArrayList<>(); - private String oldText = ""; - - private final PushCharacterCalculator characterCalculator = new PushCharacterCalculator(); - - @Override - protected void onCreate(Bundle state, boolean ready) { - Log.i(TAG, "onCreate()"); - - setContentView(R.layout.conversation_activity); - - fragment = initFragment(R.id.fragment_content, new ConversationFragment(), Locale.getDefault()); - - registerMessageStatusObserver("calculatingPoW"); - registerMessageStatusObserver("contactingNetwork"); - registerMessageStatusObserver("sendingMessage"); - registerMessageStatusObserver("messageSent"); - registerMessageStatusObserver("messageFailed"); - BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - Toast.makeText(ConversationActivity.this, "Your clock is out of sync with the service node network.", Toast.LENGTH_LONG).show(); - } - }; - broadcastReceivers.add(broadcastReceiver); - LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter("clockOutOfSync")); - - initializeActionBar(); - initializeViews(); - initializeResources(); - initializeLinkPreviewObserver(); - initializeSearchObserver(); - initializeSecurity(false, isDefaultSms).addListener(new AssertedSuccessListener() { - @Override - public void onSuccess(Boolean result) { - initializeDraft().addListener(new AssertedSuccessListener() { - @Override - public void onSuccess(Boolean loadedDraft) { - if (loadedDraft != null && loadedDraft) { - Log.i(TAG, "Finished loading draft"); - Util.runOnMain(() -> { - if (fragment != null && fragment.isResumed()) { - fragment.moveToLastSeen(); - } else { - Log.w(TAG, "Wanted to move to the last seen position, but the fragment was in an invalid state"); - } - }); - } - - if (TextSecurePreferences.isTypingIndicatorsEnabled(ConversationActivity.this)) { - composeText.addTextChangedListener(typingTextWatcher); - } - composeText.setSelection(composeText.length(), composeText.length()); - composeText.addTextChangedListener(mentionTextWatcher); - mentionCandidateSelectionView.setGlide(glideRequests); - mentionCandidateSelectionView.setOnMentionCandidateSelected( mentionCandidate -> { - mentions.add(mentionCandidate); - String oldText = composeText.getText().toString(); - String newText = oldText.substring(0, currentMentionStartIndex) + "@" + mentionCandidate.getDisplayName() + " "; - composeText.setText(newText); - composeText.setSelection(newText.length()); - currentMentionStartIndex = -1; - mentionCandidateSelectionView.hide(); - ConversationActivity.this.oldText = newText; - return Unit.INSTANCE; - }); - } - }); - } - }); - - MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(threadId, this); - - OpenGroupV2 openGroupV2 = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadId); - if (openGroupV2 != null) { - PublicChatInfoUpdateWorker.scheduleInstant(this, openGroupV2.getServer(), openGroupV2.getRoom()); - if (openGroupV2.getRoom().equals("session") || openGroupV2.getRoom().equals("oxen") - || openGroupV2.getRoom().equals("lokinet") || openGroupV2.getRoom().equals("crypto")) { - View openGroupGuidelinesView = findViewById(R.id.open_group_guidelines_view); - openGroupGuidelinesView.setVisibility(View.VISIBLE); - } - } - - View rootView = findViewById(R.id.rootView); - rootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> { - int height = rootView.getRootView().getHeight() - rootView.getHeight(); - int thresholdInDP = 120; - float scale = getResources().getDisplayMetrics().density; - int thresholdInPX = (int)(thresholdInDP * scale); - if (expandedKeyboardHeight == 0 || height > thresholdInPX) { - expandedKeyboardHeight = height; - } - collapsedKeyboardHeight = Math.min(collapsedKeyboardHeight, height); - keyboardHeight = expandedKeyboardHeight - collapsedKeyboardHeight; - - // Use 300dp if the keyboard wasn't opened yet. - if (keyboardHeight == 0) { - keyboardHeight = (int)(300f * getResources().getDisplayMetrics().density); - } - }); - } - - private void registerMessageStatusObserver(String status) { - BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - long timestamp = intent.getLongExtra("long", 0); - handleMessageStatusChanged(status, timestamp); - } - }; - broadcastReceivers.add(broadcastReceiver); - LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter(status)); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - Log.i(TAG, "onNewIntent()"); - - if (isFinishing()) { - Log.w(TAG, "Activity is finishing..."); - return; - } - - if (!org.thoughtcrime.securesms.util.Util.isEmpty(composeText)) { - saveDraft(); - attachmentManager.clear(); - silentlySetComposeText(""); - } - - setIntent(intent); - initializeResources(); - initializeSecurity(false, isDefaultSms).addListener(new AssertedSuccessListener() { - @Override - public void onSuccess(Boolean result) { - initializeDraft(); - } - }); - - if (fragment != null) { - fragment.onNewIntent(); - } - - searchNav.setVisibility(View.GONE); - } - - @Override - protected void onResume() { - super.onResume(); - - EventBus.getDefault().register(this); - initializeEnabledCheck(); - composeText.setTransport(); - - updateTitleTextView(recipient); - updateProfilePicture(); - updateSubtitleTextView(); - updateInputUI(recipient); - - ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(threadId); - markThreadAsRead(); - - inputPanel.setHint(getResources().getString(R.string.ConversationActivity_message)); - - Log.i(TAG, "onResume() Finished: " + (System.currentTimeMillis() - getIntent().getLongExtra(TIMING_EXTRA, 0))); - } - - @Override - protected void onPause() { - super.onPause(); - ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(-1L); - if (isFinishing()) overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right); - inputPanel.onPause(); - - fragment.setLastSeen(System.currentTimeMillis()); - markLastSeen(); - AudioSlidePlayer.stopAll(); - EventBus.getDefault().unregister(this); - } - - @Override - protected void onStop() { - super.onStop(); - EventBus.getDefault().unregister(this); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - Log.i(TAG, "onConfigurationChanged(" + newConfig.orientation + ")"); - super.onConfigurationChanged(newConfig); - composeText.setTransport(); - - if (emojiDrawerStub.resolved() && container.getCurrentInput() == emojiDrawerStub.get()) { - container.hideAttachedInput(true); - } - } - - @Override - protected void onDestroy() { - saveDraft(); - if (recipient != null) recipient.removeListener(this); - for (BroadcastReceiver broadcastReceiver : broadcastReceivers) { - LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver); - } - super.onDestroy(); - } - - @Override - public void onActivityResult(final int reqCode, int resultCode, Intent data) { - Log.i(TAG, "onActivityResult called: " + reqCode + ", " + resultCode + " , " + data); - super.onActivityResult(reqCode, resultCode, data); - - if ((data == null && reqCode != TAKE_PHOTO && reqCode != SMS_DEFAULT) || - (resultCode != RESULT_OK && reqCode != SMS_DEFAULT)) - { - updateLinkPreviewState(); - return; - } - - switch (reqCode) { - case PICK_DOCUMENT: - setMedia(data.getData(), MediaType.DOCUMENT); - break; - case PICK_AUDIO: - setMedia(data.getData(), MediaType.AUDIO); - break; - case TAKE_PHOTO: - if (attachmentManager.getCaptureUri() != null) { - setMedia(attachmentManager.getCaptureUri(), MediaType.IMAGE); - } - break; - case ADD_CONTACT: - recipient = Recipient.from(this, recipient.getAddress(), true); - recipient.addListener(this); - fragment.reloadList(); - break; - /* - case PICK_LOCATION: - SignalPlace place = new SignalPlace(PlacePicker.getPlace(data, this)); - attachmentManager.setLocation(place, getCurrentMediaConstraints()); - break; - */ - case PICK_GIF: - setMedia(data.getData(), - MediaType.GIF, - data.getIntExtra(GiphyActivity.EXTRA_WIDTH, 0), - data.getIntExtra(GiphyActivity.EXTRA_HEIGHT, 0)); - break; - case SMS_DEFAULT: - initializeSecurity(true, isDefaultSms); - break; - case MEDIA_SENDER: - long expiresIn = recipient.getExpireMessages() * 1000L; - int subscriptionId = -1; - boolean initiating = threadId == -1; - String message = data.getStringExtra(MediaSendActivity.EXTRA_MESSAGE); - SlideDeck slideDeck = new SlideDeck(); - - List mediaList = data.getParcelableArrayListExtra(MediaSendActivity.EXTRA_MEDIA); - - for (Media mediaItem : mediaList) { - if (MediaUtil.isVideoType(mediaItem.getMimeType())) { - slideDeck.addSlide(new VideoSlide(this, mediaItem.getUri(), 0, mediaItem.getCaption().orNull())); - } else if (MediaUtil.isGif(mediaItem.getMimeType())) { - slideDeck.addSlide(new GifSlide(this, mediaItem.getUri(), 0, mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.getCaption().orNull())); - } else if (MediaUtil.isImageType(mediaItem.getMimeType())) { - slideDeck.addSlide(new ImageSlide(this, mediaItem.getUri(), 0, mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.getCaption().orNull())); - } else { - Log.w(TAG, "Asked to send an unexpected mimeType: '" + mediaItem.getMimeType() + "'. Skipping."); - } - } - - final Context context = ConversationActivity.this.getApplicationContext(); - - sendMediaMessage(message, - slideDeck, - inputPanel.getQuote().orNull(), - Optional.absent(), - initiating).addListener(new AssertedSuccessListener() { - @Override - public void onSuccess(Void result) { - AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { - Stream.of(slideDeck.getSlides()) - .map(Slide::getUri) - .withoutNulls() - .filter(BlobProvider::isAuthority) - .forEach(uri -> BlobProvider.getInstance().delete(context, uri)); - }); - } - }); - break; - case INVITE_CONTACTS: - if (data.getExtras() == null || !data.hasExtra(SelectContactsActivity.Companion.getSelectedContactsKey())) return; - String[] selectedContacts = data.getExtras().getStringArray(SelectContactsActivity.Companion.getSelectedContactsKey()); - sendOpenGroupInvitations(selectedContacts); - break; - } - } - - @Override - public void startActivity(Intent intent) { - if (intent.getStringExtra(Browser.EXTRA_APPLICATION_ID) != null) { - intent.removeExtra(Browser.EXTRA_APPLICATION_ID); - } - - try { - super.startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.w(TAG, e); - Toast.makeText(this, R.string.ConversationActivity_there_is_no_app_available_to_handle_this_link_on_your_device, Toast.LENGTH_LONG).show(); - } - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - MenuInflater inflater = this.getMenuInflater(); - menu.clear(); - - boolean isOpenGroupOrRSSFeed = recipient.getAddress().isOpenGroup(); - - if (!isOpenGroupOrRSSFeed) { - if (recipient.getExpireMessages() > 0) { - inflater.inflate(R.menu.conversation_expiring_on, menu); - - final MenuItem item = menu.findItem(R.id.menu_expiring_messages); - final View actionView = MenuItemCompat.getActionView(item); - final ImageView iconView = actionView.findViewById(R.id.menu_badge_icon); - final TextView badgeView = actionView.findViewById(R.id.expiration_badge); - - @ColorInt int color = GeneralUtilitiesKt.getColorWithID(getResources(), R.color.text, getTheme()); - iconView.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); - badgeView.setText(ExpirationUtil.getExpirationAbbreviatedDisplayValue(this, recipient.getExpireMessages())); - actionView.setOnClickListener(v -> onOptionsItemSelected(item)); - } else { - inflater.inflate(R.menu.conversation_expiring_off, menu); - } - } - - if (isSingleConversation()) { - if (recipient.isBlocked()) { - inflater.inflate(R.menu.conversation_unblock, menu); - } else { - inflater.inflate(R.menu.conversation_block, menu); - } - inflater.inflate(R.menu.conversation_copy_session_id, menu); - } else if (isGroupConversation() && !isOpenGroupOrRSSFeed) { -// inflater.inflate(R.menu.conversation_group_options, menu); - - if (!isPushGroupConversation()) { - inflater.inflate(R.menu.conversation_mms_group_options, menu); - if (distributionType == DistributionTypes.BROADCAST) { - menu.findItem(R.id.menu_distribution_broadcast).setChecked(true); - } else { - menu.findItem(R.id.menu_distribution_conversation).setChecked(true); - } - } else if (isActiveGroup()) { - inflater.inflate(R.menu.conversation_push_group_options, menu); - } - } else if (isOpenGroupOrRSSFeed) { - inflater.inflate(R.menu.conversation_invite_open_group, menu); - } - - inflater.inflate(R.menu.conversation, menu); - -// if (isSingleConversation()) { -// inflater.inflate(R.menu.conversation_secure, menu); -// } - - if (recipient != null && recipient.isMuted()) inflater.inflate(R.menu.conversation_muted, menu); - else inflater.inflate(R.menu.conversation_unmuted, menu); - - /* - if (isSingleConversation() && getRecipient().getContactUri() == null) { - inflater.inflate(R.menu.conversation_add_to_contacts, menu); - } - if (recipient != null && recipient.isLocalNumber()) { - if (isSecureText) menu.findItem(R.id.menu_call_secure).setVisible(false); - else menu.findItem(R.id.menu_call_insecure).setVisible(false); - MenuItem muteItem = menu.findItem(R.id.menu_mute_notifications); - if (muteItem != null) { - muteItem.setVisible(false); - } - } - */ - - searchViewItem = menu.findItem(R.id.menu_search); - - SearchView searchView = (SearchView)searchViewItem.getActionView(); - SearchView.OnQueryTextListener queryListener = new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String query) { - searchViewModel.onQueryUpdated(query, threadId); - searchNav.showLoading(); - fragment.onSearchQueryUpdated(query); - return true; - } - - @Override - public boolean onQueryTextChange(String query) { - searchViewModel.onQueryUpdated(query, threadId); - searchNav.showLoading(); - fragment.onSearchQueryUpdated(query); - return true; - } - }; - - searchViewItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { - @Override - public boolean onMenuItemActionExpand(MenuItem item) { - searchView.setOnQueryTextListener(queryListener); - searchViewModel.onSearchOpened(); - searchNav.setVisibility(View.VISIBLE); - searchNav.setData(0, 0); - inputPanel.setVisibility(View.GONE); - - for (int i = 0; i < menu.size(); i++) { - if (!menu.getItem(i).equals(searchViewItem)) { - menu.getItem(i).setVisible(false); - } - } - return true; - } - - @Override - public boolean onMenuItemActionCollapse(MenuItem item) { - searchView.setOnQueryTextListener(null); - searchViewModel.onSearchClosed(); - searchNav.setVisibility(View.GONE); - inputPanel.setVisibility(View.VISIBLE); - updateInputUI(recipient); - fragment.onSearchQueryUpdated(null); - invalidateOptionsMenu(); - return true; - } - }); - - super.onPrepareOptionsMenu(menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - super.onOptionsItemSelected(item); - switch (item.getItemId()) { -// case R.id.menu_call_secure: handleDial(getRecipient(), true); return true; -// case R.id.menu_call_insecure: handleDial(getRecipient(), false); return true; - case R.id.menu_unblock: handleUnblock(); return true; - case R.id.menu_block: handleBlock(); return true; - case R.id.menu_copy_session_id: handleCopySessionID(); return true; - case R.id.menu_view_media: handleViewMedia(); return true; - case R.id.menu_add_shortcut: handleAddShortcut(); return true; - case R.id.menu_search: handleSearch(); return true; -// case R.id.menu_add_to_contacts: handleAddToContacts(); return true; -// case R.id.menu_reset_secure_session: handleResetSecureSession(); return true; -// case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true; - case R.id.menu_distribution_broadcast: handleDistributionBroadcastEnabled(item); return true; - case R.id.menu_distribution_conversation: handleDistributionConversationEnabled(item); return true; - case R.id.menu_edit_group: handleEditPushGroup(); return true; - case R.id.menu_leave: handleLeavePushGroup(); return true; - case R.id.menu_mute_notifications: handleMuteNotifications(); return true; - case R.id.menu_unmute_notifications: handleUnmuteNotifications(); return true; -// case R.id.menu_conversation_settings: handleConversationSettings(); return true; - case R.id.menu_expiring_messages_off: - case R.id.menu_expiring_messages: handleSelectMessageExpiration(); return true; - case R.id.menu_invite_to_open_group: handleInviteToOpenGroup(); return true; - case android.R.id.home: handleReturnToConversationList(); return true; - } - - return false; - } - - @Override - public void onBackPressed() { - Log.d(TAG, "onBackPressed()"); - if (container.isInputOpen()) container.hideCurrentInput(composeText); - else super.onBackPressed(); - } - - @Override - public void onKeyboardShown() { - inputPanel.onKeyboardShown(); - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); - } - - //////// Event Handlers - - private void handleReturnToConversationList() { - Intent intent = new Intent(this, HomeActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - finish(); - } - - private void handleSelectMessageExpiration() { - if (isPushGroupConversation() && !isActiveGroup()) { - return; - } - - //noinspection CodeBlock2Expr - ExpirationDialog.show(this, recipient.getExpireMessages(), expirationTime -> { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient, expirationTime); - ExpirationTimerUpdate message = new ExpirationTimerUpdate(expirationTime); - message.setRecipient(recipient.getAddress().serialize()); // we need the recipient in ExpiringMessageManager.insertOutgoingExpirationTimerMessage - message.setSentTimestamp(System.currentTimeMillis()); - ExpiringMessageManager expiringMessageManager = ApplicationContext.getInstance(getApplicationContext()).getExpiringMessageManager(); - expiringMessageManager.setExpirationTimer(message); - MessageSender.send(message, recipient.getAddress()); - - return null; - } - - @Override - protected void onPostExecute(Void result) { - invalidateOptionsMenu(); - if (fragment != null) fragment.setLastSeen(0); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - }); - } - - private void handleMuteNotifications() { - MuteDialog.show(this, until -> { - recipient.setMuted(until); - - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setMuted(recipient, until); - - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - }); - } - - private void handleUnmuteNotifications() { - recipient.setMuted(0); - - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setMuted(recipient, 0); - - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void handleUnblock() { - int titleRes = R.string.ConversationActivity_unblock_this_contact_question; - int bodyRes = R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact; - - new AlertDialog.Builder(this) - .setTitle(titleRes) - .setMessage(bodyRes) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.ConversationActivity_unblock, (dialog, which) -> { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setBlocked(recipient, false); - - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - }).show(); - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - private void handleMakeDefaultSms() { - Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT); - intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, getPackageName()); - startActivityForResult(intent, SMS_DEFAULT); - } - - private void handleBlock() { - int titleRes = R.string.RecipientPreferenceActivity_block_this_contact_question; - int bodyRes = R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact; - - new AlertDialog.Builder(this) - .setTitle(titleRes) - .setMessage(bodyRes) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.RecipientPreferenceActivity_block, (dialog, which) -> { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setBlocked(recipient, true); - - Util.runOnMain(() -> finish()); - - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - }).show(); - } - - private void handleCopySessionID() { - if (recipient.isGroupRecipient()) { return; } - String sessionID = recipient.getAddress().toString(); - ClipboardManager clipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Session ID", sessionID); - clipboard.setPrimaryClip(clip); - Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show(); - } - - private void handleViewMedia() { - Intent intent = new Intent(this, MediaOverviewActivity.class); - intent.putExtra(MediaOverviewActivity.ADDRESS_EXTRA, recipient.getAddress()); - startActivity(intent); - } - - private void handleAddShortcut() { - Log.i(TAG, "Creating home screen shortcut for recipient " + recipient.getAddress()); - - new AsyncTask() { - - @Override - protected IconCompat doInBackground(Void... voids) { - Context context = getApplicationContext(); - IconCompat icon = null; - - if (recipient.getContactPhoto() != null) { - try { - Bitmap bitmap = BitmapFactory.decodeStream(recipient.getContactPhoto().openInputStream(context)); - bitmap = BitmapUtil.createScaledBitmap(bitmap, 300, 300); - icon = IconCompat.createWithAdaptiveBitmap(bitmap); - } catch (IOException e) { - Log.w(TAG, "Failed to decode contact photo during shortcut creation. Falling back to generic icon.", e); - } - } - - if (icon == null) { - icon = IconCompat.createWithResource(context, recipient.isGroupRecipient() ? R.mipmap.ic_group_shortcut - : R.mipmap.ic_person_shortcut); - } - - return icon; - } - - @Override - protected void onPostExecute(IconCompat icon) { - Context context = getApplicationContext(); - String name = Optional.fromNullable(recipient.getName()) - .or(Optional.fromNullable(recipient.getProfileName())) - .or(recipient.toShortString()); - - ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(context, recipient.getAddress().serialize() + '-' + System.currentTimeMillis()) - .setShortLabel(name) - .setIcon(icon) - .setIntent(ShortcutLauncherActivity.createIntent(context, recipient.getAddress())) - .build(); - - if (ShortcutManagerCompat.requestPinShortcut(context, shortcutInfo, null)) { - Toast.makeText(context, getString(R.string.ConversationActivity_added_to_home_screen), Toast.LENGTH_LONG).show(); - } - } - }.execute(); - } - - private void handleSearch() { - searchViewModel.onSearchOpened(); - } - - private void handleLeavePushGroup() { - if (getRecipient() == null) { - Toast.makeText(this, getString(R.string.ConversationActivity_invalid_recipient), - Toast.LENGTH_LONG).show(); - return; - } - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.ConversationActivity_leave_group)); - builder.setIconAttribute(R.attr.dialog_info_icon); - builder.setCancelable(true); - - GroupRecord group = DatabaseFactory.getGroupDatabase(this).getGroup(getRecipient().getAddress().toGroupString()).orNull(); - List
admins = group.getAdmins(); - String userPublicKey = TextSecurePreferences.getLocalNumber(this); - String message = getString(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group); - for (Address admin : admins) { - if (admin.toString().equals(userPublicKey)) { - message = "Because you are the creator of this group it will be deleted for everyone. This cannot be undone."; - } - } - - builder.setMessage(message); - builder.setPositiveButton(R.string.yes, (dialog, which) -> { - Recipient groupRecipient = getRecipient(); - String groupPublicKey; - boolean isClosedGroup; - try { - groupPublicKey = HexEncodingKt.toHexString(GroupUtil.doubleDecodeGroupID(groupRecipient.getAddress().toString())); - isClosedGroup = DatabaseFactory.getLokiAPIDatabase(this).isClosedGroup(groupPublicKey); - } catch (IOException e) { - groupPublicKey = null; - isClosedGroup = false; - } - try { - if (isClosedGroup) { - MessageSender.explicitLeave(groupPublicKey, true); - initializeEnabledCheck(); - } else { - Toast.makeText(this, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show(); - } - } catch (Exception e) { - Toast.makeText(this, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show(); - } - }); - - builder.setNegativeButton(R.string.no, null); - builder.show(); - } - - private void handleEditPushGroup() { - Intent intent = new Intent(this, EditClosedGroupActivity.class); - String groupID = this.recipient.getAddress().toGroupString(); - intent.putExtra(EditClosedGroupActivity.Companion.getGroupIDKey(), groupID); - startActivity(intent); - } - - private void handleInviteToOpenGroup() { - Intent intent = new Intent(this, SelectContactsActivity.class); - startActivityForResult(intent, INVITE_CONTACTS); - } - - private void handleDistributionBroadcastEnabled(MenuItem item) { - distributionType = DistributionTypes.BROADCAST; - item.setChecked(true); - - if (threadId != -1) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getThreadDatabase(ConversationActivity.this) - .setDistributionType(threadId, DistributionTypes.BROADCAST); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - - private void handleDistributionConversationEnabled(MenuItem item) { - distributionType = DistributionTypes.CONVERSATION; - item.setChecked(true); - - if (threadId != -1) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getThreadDatabase(ConversationActivity.this) - .setDistributionType(threadId, DistributionTypes.CONVERSATION); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - - private void handleAddAttachment() { - if (attachmentTypeSelector == null) { - attachmentTypeSelector = new AttachmentTypeSelector( - this, - LoaderManager.getInstance(this), - new AttachmentTypeListener(), - keyboardHeight); - } - attachmentTypeSelector.show(this, attachButton); - } - - private void handleSecurityChange(boolean isSecureText, boolean isDefaultSms) { - Log.i(TAG, "handleSecurityChange(" + isSecureText + ", " + isDefaultSms + ")"); - if (isSecurityInitialized && isSecureText == true && isDefaultSms == this.isDefaultSms) { - return; - } - - this.isDefaultSms = isDefaultSms; - this.isSecurityInitialized = true; - - if (recipient == null || attachmentManager == null) { return; } - - /* Loki - We don't support SMS - if (!isSecureText && !isPushGroupConversation()) sendButton.disableTransport(Type.TEXTSECURE); - if (recipient.isPushGroupRecipient()) sendButton.disableTransport(Type.SMS); - if (!recipient.isPushGroupRecipient() && recipient.isForceSmsSelection()) { - sendButton.setDefaultTransport(Type.SMS); - } else { - if (isSecureText || isPushGroupConversation()) sendButton.setDefaultTransport(Type.TEXTSECURE); - else sendButton.setDefaultTransport(Type.SMS); - } - */ - - supportInvalidateOptionsMenu(); - updateInputUI(recipient); - } - - ///// Initializers - - private ListenableFuture initializeDraft() { - final SettableFuture result = new SettableFuture<>(); - - final String draftText = getIntent().getStringExtra(TEXT_EXTRA); - final Uri draftMedia = getIntent().getData(); - final MediaType draftMediaType = MediaType.from(getIntent().getType()); - final List mediaList = getIntent().getParcelableArrayListExtra(MEDIA_EXTRA); - - if (!Util.isEmpty(mediaList)) { - Intent sendIntent = MediaSendActivity.buildEditorIntent(this, mediaList, recipient, draftText); - startActivityForResult(sendIntent, MEDIA_SENDER); - return new SettableFuture<>(false); - } - - if (draftText != null) { - composeText.setText(""); - composeText.append(draftText); - result.set(true); - } - - if (draftMedia != null && draftMediaType != null) { - return setMedia(draftMedia, draftMediaType); - } - - if (draftText == null && draftMedia == null && draftMediaType == null) { - return initializeDraftFromDatabase(); - } else { - updateToggleButtonState(); - result.set(false); - } - - return result; - } - - private void initializeEnabledCheck() { - boolean enabled = !(isPushGroupConversation() && !isActiveGroup()); - inputPanel.setEnabled(enabled); - sendButton.setEnabled(enabled); - attachButton.setEnabled(enabled); - } - - private ListenableFuture initializeDraftFromDatabase() { - SettableFuture future = new SettableFuture<>(); - - new AsyncTask>() { - @Override - protected List doInBackground(Void... params) { - DraftDatabase draftDatabase = DatabaseFactory.getDraftDatabase(ConversationActivity.this); - List results = draftDatabase.getDrafts(threadId); - - draftDatabase.clearDrafts(threadId); - - return results; - } - - @Override - protected void onPostExecute(List drafts) { - if (drafts.isEmpty()) { - future.set(false); - updateToggleButtonState(); - return; - } - - AtomicInteger draftsRemaining = new AtomicInteger(drafts.size()); - AtomicBoolean success = new AtomicBoolean(false); - ListenableFuture.Listener listener = new AssertedSuccessListener() { - @Override - public void onSuccess(Boolean result) { - success.compareAndSet(false, result); - - if (draftsRemaining.decrementAndGet() <= 0) { - future.set(success.get()); - } - } - }; - - for (Draft draft : drafts) { - switch (draft.getType()) { - case Draft.TEXT: - composeText.setText(draft.getValue()); - listener.onSuccess(true); - break; - case Draft.IMAGE: - setMedia(Uri.parse(draft.getValue()), MediaType.IMAGE).addListener(listener); - break; - case Draft.AUDIO: - setMedia(Uri.parse(draft.getValue()), MediaType.AUDIO).addListener(listener); - break; - case Draft.VIDEO: - setMedia(Uri.parse(draft.getValue()), MediaType.VIDEO).addListener(listener); - break; - case Draft.QUOTE: - SettableFuture quoteResult = new SettableFuture<>(); - new QuoteRestorationTask(draft.getValue(), quoteResult).execute(); - quoteResult.addListener(listener); - break; - } - } - - updateToggleButtonState(); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - - return future; - } - - private ListenableFuture initializeSecurity(final boolean currentSecureText, - final boolean currentIsDefaultSms) - { - final SettableFuture future = new SettableFuture<>(); - - handleSecurityChange(currentSecureText || isPushGroupConversation(), currentIsDefaultSms); - - new AsyncTask() { - @Override - protected boolean[] doInBackground(Recipient... params) { - // Loki - Override the flag below - boolean signalEnabled = true; // TextSecurePreferences.isPushRegistered(context); - - - return new boolean[] { signalEnabled, false}; - } - - @Override - protected void onPostExecute(boolean[] result) { - if (result[0] != currentSecureText || result[1] != currentIsDefaultSms) { - Log.i(TAG, "onPostExecute() handleSecurityChange: " + result[0] + " , " + result[1]); - handleSecurityChange(result[0], result[1]); - } - future.set(true); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient); - - return future; - } - - private void initializeViews() { - profilePictureView = findViewById(R.id.profilePictureView); - titleTextView = findViewById(R.id.titleTextView); - buttonToggle = ViewUtil.findById(this, R.id.button_toggle); - sendButton = ViewUtil.findById(this, R.id.send_button); - attachButton = ViewUtil.findById(this, R.id.attach_button); - composeText = ViewUtil.findById(this, R.id.embedded_text_editor); - emojiDrawerStub = ViewUtil.findStubById(this, R.id.emoji_drawer_stub); - unblockButton = ViewUtil.findById(this, R.id.unblock_button); - makeDefaultSmsButton = ViewUtil.findById(this, R.id.make_default_sms_button); - container = ViewUtil.findById(this, R.id.layout_container); - quickAttachmentToggle = ViewUtil.findById(this, R.id.quick_attachment_toggle); - inlineAttachmentToggle = ViewUtil.findById(this, R.id.inline_attachment_container); - inputPanel = ViewUtil.findById(this, R.id.bottom_panel); - searchNav = ViewUtil.findById(this, R.id.conversation_search_nav); - mentionCandidateSelectionViewContainer = ViewUtil.findById(this, R.id.mentionCandidateSelectionViewContainer); - mentionCandidateSelectionView = ViewUtil.findById(this, R.id.userSelectionView); - messageStatusProgressBar = ViewUtil.findById(this, R.id.messageStatusProgressBar); - muteIndicatorImageView = ViewUtil.findById(this, R.id.muteIndicatorImageView); - subtitleTextView = ViewUtil.findById(this, R.id.subtitleTextView); - homeButtonContainer = ViewUtil.findById(this, R.id.homeButtonContainer); - - ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle); - ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button); - - container.addOnKeyboardShownListener(this); - inputPanel.setListener(this); - inputPanel.setMediaListener(this); - - attachmentTypeSelector = null; - attachmentManager = new AttachmentManager(this, this); - audioRecorder = new AudioRecorder(this); - typingTextWatcher = new TypingStatusTextWatcher(); - mentionTextWatcher = new MentionTextWatcher(); - - SendButtonListener sendButtonListener = new SendButtonListener(); - ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener(); - - composeText.setOnEditorActionListener(sendButtonListener); - composeText.setCursorPositionChangedListener(this); - attachButton.setOnClickListener(new AttachButtonListener()); - attachButton.setOnLongClickListener(new AttachButtonLongClickListener()); - sendButton.setOnClickListener(sendButtonListener); - sendButton.setEnabled(true); - - unblockButton.setOnClickListener(v -> handleUnblock()); - makeDefaultSmsButton.setOnClickListener(v -> handleMakeDefaultSms()); - - composeText.setOnKeyListener(composeKeyPressedListener); - composeText.addTextChangedListener(composeKeyPressedListener); - composeText.setOnEditorActionListener(sendButtonListener); - composeText.setOnClickListener(composeKeyPressedListener); - composeText.setOnFocusChangeListener(composeKeyPressedListener); - - if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA) && Camera.getNumberOfCameras() > 0) { - quickCameraToggle.setVisibility(View.VISIBLE); - quickCameraToggle.setOnClickListener(new QuickCameraToggleListener()); - } else { - quickCameraToggle.setVisibility(View.GONE); - } - - searchNav.setEventListener(this); - - inlineAttachmentButton.setOnClickListener(v -> handleAddAttachment()); - - homeButtonContainer.setOnClickListener(v -> onSupportNavigateUp()); - } - - protected void initializeActionBar() { - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - - ActionBar supportActionBar = getSupportActionBar(); - if (supportActionBar == null) throw new AssertionError(); - -// supportActionBar.setDisplayHomeAsUpEnabled(true); - supportActionBar.setDisplayShowTitleEnabled(false); - } - - private void initializeResources() { - if (recipient != null) recipient.removeListener(this); - - Address address = getIntent().getParcelableExtra(ADDRESS_EXTRA); - if (address == null) { finish(); return; } - recipient = Recipient.from(this, address, true); - threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1); - distributionType = getIntent().getIntExtra(DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT); - glideRequests = GlideApp.with(this); - - recipient.addListener(this); - } - - private void initializeLinkPreviewObserver() { - linkPreviewViewModel = ViewModelProviders.of(this, new LinkPreviewViewModel.Factory(new LinkPreviewRepository(this))).get(LinkPreviewViewModel.class); - - if (!TextSecurePreferences.isLinkPreviewsEnabled(this)) { - linkPreviewViewModel.onUserCancel(); - return; - } - - linkPreviewViewModel.getLinkPreviewState().observe(this, previewState -> { - if (previewState == null) return; - - if (previewState.isLoading()) { - Log.d(TAG, "Loading link preview."); - inputPanel.setLinkPreviewLoading(); - } else { - Log.d(TAG, "Setting link preview: " + previewState.getLinkPreview().isPresent()); - inputPanel.setLinkPreview(glideRequests, previewState.getLinkPreview()); - } - - updateToggleButtonState(); - }); - } - - private void initializeSearchObserver() { - searchViewModel = ViewModelProviders.of(this).get(ConversationSearchViewModel.class); - - searchViewModel.getSearchResults().observe(this, result -> { - if (result == null) return; - - if (!result.getResults().isEmpty()) { - MessageResult messageResult = result.getResults().get(result.getPosition()); - fragment.jumpToMessage(messageResult.messageRecipient.getAddress(), messageResult.receivedTimestampMs, searchViewModel::onMissingResult); - } - - searchNav.setData(result.getPosition(), result.getResults().size()); - }); - } - - @Override - public void onSearchMoveUpPressed() { - searchViewModel.onMoveUp(); - } - - @Override - public void onSearchMoveDownPressed() { - searchViewModel.onMoveDown(); - } - - @Override - public void onModified(final Recipient recipient) { - Log.i(TAG, "onModified(" + recipient.getAddress().serialize() + ")"); - Util.runOnMain(() -> { - Log.i(TAG, "onModifiedRun(): " + recipient.getRegistered()); - updateTitleTextView(recipient); - updateProfilePicture(); - updateSubtitleTextView(); - updateInputUI(recipient); - initializeSecurity(true, isDefaultSms); - - if (searchViewItem == null || !searchViewItem.isActionViewExpanded()) { - invalidateOptionsMenu(); - } - }); - } - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onOpenGroupInfoUpdated(OpenGroupUtilities.GroupInfoUpdatedEvent event) { - OpenGroupV2 openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadId); - if (openGroup != null && - openGroup.getRoom().equals(event.getRoom()) && - openGroup.getServer().equals(event.getUrl())) { - this.updateSubtitleTextView(); - } - } - - //////// Helper Methods - - private void addAttachment(int type) { - linkPreviewViewModel.onUserCancel(); - - Log.i(TAG, "Selected: " + type); - switch (type) { - case AttachmentTypeSelector.ADD_GALLERY: - AttachmentManager.selectGallery(this, MEDIA_SENDER, recipient, composeText.getTextTrimmed()); break; - case AttachmentTypeSelector.ADD_DOCUMENT: - AttachmentManager.selectDocument(this, PICK_DOCUMENT); break; - case AttachmentTypeSelector.ADD_SOUND: - AttachmentManager.selectAudio(this, PICK_AUDIO); break; - case AttachmentTypeSelector.ADD_CONTACT_INFO: - break; - case AttachmentTypeSelector.ADD_LOCATION: - break; - case AttachmentTypeSelector.TAKE_PHOTO: - attachmentManager.capturePhoto(this, TAKE_PHOTO); break; - case AttachmentTypeSelector.ADD_GIF: - boolean hasSeenGIFMetaDataWarning = TextSecurePreferences.hasSeenGIFMetaDataWarning(this); - if (!hasSeenGIFMetaDataWarning) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle("Search GIFs?"); - builder.setMessage("You will not have full metadata protection when sending GIFs."); - builder.setPositiveButton("OK", (dialog, which) -> { - AttachmentManager.selectGif(this, PICK_GIF); - dialog.dismiss(); - }); - builder.setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss()); - builder.create().show(); - TextSecurePreferences.setHasSeenGIFMetaDataWarning(this); - } else { - AttachmentManager.selectGif(this, PICK_GIF); - } - break; - } - } - - private ListenableFuture setMedia(@Nullable Uri uri, @NonNull MediaType mediaType) { - return setMedia(uri, mediaType, 0, 0); - } - - private ListenableFuture setMedia(@Nullable Uri uri, @NonNull MediaType mediaType, int width, int height) { - if (uri == null) { - return new SettableFuture<>(false); - } - - if (MediaType.VCARD.equals(mediaType)) { - return new SettableFuture<>(false); - } else if (MediaType.IMAGE.equals(mediaType) || MediaType.GIF.equals(mediaType) || MediaType.VIDEO.equals(mediaType)) { - Media media = new Media(uri, MediaUtil.getMimeType(this, uri), 0, width, height, 0, Optional.absent(), Optional.absent()); - startActivityForResult(MediaSendActivity.buildEditorIntent(ConversationActivity.this, Collections.singletonList(media), recipient, composeText.getTextTrimmed()), MEDIA_SENDER); - return new SettableFuture<>(false); - } else { - return attachmentManager.setMedia(glideRequests, uri, mediaType, MediaConstraints.getPushMediaConstraints(), width, height); - } - } - - private void addAttachmentContactInfo(Uri contactUri) { - ContactAccessor contactDataList = ContactAccessor.getInstance(); - ContactData contactData = contactDataList.getContactData(this, contactUri); - - if (contactData.numbers.size() == 1) composeText.append(contactData.numbers.get(0).number); - else if (contactData.numbers.size() > 1) selectContactInfo(contactData); - } - - private void selectContactInfo(ContactData contactData) { - final CharSequence[] numbers = new CharSequence[contactData.numbers.size()]; - final CharSequence[] numberItems = new CharSequence[contactData.numbers.size()]; - - for (int i = 0; i < contactData.numbers.size(); i++) { - numbers[i] = contactData.numbers.get(i).number; - numberItems[i] = contactData.numbers.get(i).type + ": " + contactData.numbers.get(i).number; - } - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setIconAttribute(R.attr.conversation_attach_contact_info); - builder.setTitle(R.string.ConversationActivity_select_contact_info); - - builder.setItems(numberItems, (dialog, which) -> composeText.append(numbers[which])); - builder.show(); - } - - private Drafts getDraftsForCurrentState() { - Drafts drafts = new Drafts(); - - if (!org.thoughtcrime.securesms.util.Util.isEmpty(composeText)) { - drafts.add(new Draft(Draft.TEXT, composeText.getTextTrimmed())); - } - - for (Slide slide : attachmentManager.buildSlideDeck().getSlides()) { - if (slide.hasAudio() && slide.getUri() != null) drafts.add(new Draft(Draft.AUDIO, slide.getUri().toString())); - else if (slide.hasVideo() && slide.getUri() != null) drafts.add(new Draft(Draft.VIDEO, slide.getUri().toString())); - else if (slide.hasImage() && slide.getUri() != null) drafts.add(new Draft(Draft.IMAGE, slide.getUri().toString())); - } - - Optional quote = inputPanel.getQuote(); - - if (quote.isPresent()) { - drafts.add(new Draft(Draft.QUOTE, new QuoteId(quote.get().getId(), quote.get().getAuthor()).serialize())); - } - - return drafts; - } - - protected ListenableFuture saveDraft() { - final SettableFuture future = new SettableFuture<>(); - - if (this.recipient == null) { - future.set(threadId); - return future; - } - - final Drafts drafts = getDraftsForCurrentState(); - final long thisThreadId = this.threadId; - final int thisDistributionType = this.distributionType; - - new AsyncTask() { - @Override - protected Long doInBackground(Long... params) { - ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(ConversationActivity.this); - DraftDatabase draftDatabase = DatabaseFactory.getDraftDatabase(ConversationActivity.this); - long threadId = params[0]; - - if (drafts.size() > 0) { - if (threadId == -1) threadId = threadDatabase.getOrCreateThreadIdFor(getRecipient(), thisDistributionType); - - draftDatabase.insertDrafts(threadId, drafts); - threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this), - drafts.getUriSnippet(), - System.currentTimeMillis(), Types.BASE_DRAFT_TYPE, true); - } else if (threadId > 0) { - threadDatabase.update(threadId, false); - } - - return threadId; - } - - @Override - protected void onPostExecute(Long result) { - future.set(result); - } - - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, thisThreadId); - - return future; - } - - private void updateInputUI(Recipient recipient) { - if (recipient.isGroupRecipient() && !isActiveGroup()) { - unblockButton.setVisibility(View.GONE); - inputPanel.setVisibility(View.GONE); - makeDefaultSmsButton.setVisibility(View.GONE); - } else if (recipient.isBlocked()) { - unblockButton.setVisibility(View.VISIBLE); - inputPanel.setVisibility(View.GONE); - makeDefaultSmsButton.setVisibility(View.GONE); - } else { - inputPanel.setVisibility(View.VISIBLE); - unblockButton.setVisibility(View.GONE); - makeDefaultSmsButton.setVisibility(View.GONE); - } - } - - private void initializeMediaKeyboardProviders(@NonNull MediaKeyboard mediaKeyboard) { - boolean isSystemEmojiPreferred = TextSecurePreferences.isSystemEmojiPreferred(this); - if (!isSystemEmojiPreferred) { - mediaKeyboard.setProviders(0, new EmojiKeyboardProvider(this, inputPanel)); - } - } - - - private boolean isSingleConversation() { - return getRecipient() != null && !getRecipient().isGroupRecipient(); - } - - private boolean isActiveGroup() { - if (!isGroupConversation()) return false; - - Optional record = DatabaseFactory.getGroupDatabase(this).getGroup(getRecipient().getAddress().toGroupString()); - return record.isPresent() && record.get().isActive(); - } - - @SuppressWarnings("SimplifiableIfStatement") - private boolean isSelfConversation() { - if (!TextSecurePreferences.isPushRegistered(this)) return false; - if (recipient.isGroupRecipient()) return false; - - return Util.isOwnNumber(this, recipient.getAddress().serialize()); - } - - private boolean isGroupConversation() { - return getRecipient() != null && getRecipient().isGroupRecipient(); - } - - private boolean isPushGroupConversation() { - return getRecipient() != null && getRecipient().isPushGroupRecipient(); - } - - protected Recipient getRecipient() { - return this.recipient; - } - - protected long getThreadId() { - return this.threadId; - } - - private String getMessage() throws InvalidMessageException { - String result = composeText.getTextTrimmed(); - if (result.length() < 1) throw new InvalidMessageException(); - for (Mention mention : mentions) { - try { - int startIndex = result.indexOf("@" + mention.getDisplayName()); - int endIndex = startIndex + mention.getDisplayName().length() + 1; // + 1 to include the @ - result = result.substring(0, startIndex) + "@" + mention.getPublicKey() + result.substring(endIndex); - } catch (Exception exception) { - Log.d("Loki", "Couldn't process mention due to error: " + exception.toString() + "."); - } - } - return result; - } - - private Pair> getSplitMessage(String rawText, int maxPrimaryMessageSize) { - String bodyText = rawText; - Optional textSlide = Optional.absent(); - - if (bodyText.length() > maxPrimaryMessageSize) { - bodyText = rawText.substring(0, maxPrimaryMessageSize); - - byte[] textData = rawText.getBytes(); - String timestamp = new SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.US).format(new Date()); - String filename = String.format("signal-%s.txt", timestamp); - Uri textUri = BlobProvider.getInstance() - .forData(textData) - .withMimeType(MediaTypes.LONG_TEXT) - .withFileName(filename) - .createForSingleSessionInMemory(); - - textSlide = Optional.of(new TextSlide(this, textUri, filename, textData.length)); - } - - return new Pair<>(bodyText, textSlide); - } - - private void markThreadAsRead() { - Recipient recipient = this.recipient; - new AsyncTask() { - @Override - protected Void doInBackground(Long... params) { - Context context = ConversationActivity.this; - List messageIds = DatabaseFactory.getThreadDatabase(context).setRead(params[0], false); - - if (!SessionMetaProtocol.shouldSendReadReceipt(recipient.getAddress())) { - for (MarkedMessageInfo messageInfo : messageIds) { - MarkReadReceiver.scheduleDeletion(context, messageInfo.getExpirationInfo()); - } - } else { - MarkReadReceiver.process(context, messageIds); - } - ApplicationContext.getInstance(context).messageNotifier.updateNotification(context); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadId); - } - - private void markLastSeen() { - new AsyncTask() { - @Override - protected Void doInBackground(Long... params) { - DatabaseFactory.getThreadDatabase(ConversationActivity.this).setLastSeen(params[0]); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadId); - } - - protected void sendComplete(long threadId) { - boolean refreshFragment = (threadId != this.threadId); - this.threadId = threadId; - - if (fragment == null || !fragment.isVisible() || isFinishing()) { - return; - } - - fragment.setLastSeen(0); - - if (refreshFragment) { - fragment.reload(recipient, threadId); - ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(threadId); - } - - fragment.scrollToBottom(); - attachmentManager.cleanup(); - - updateLinkPreviewState(); - } - - private void sendMessage() { - if (inputPanel.isRecordingInLockedMode()) { - inputPanel.releaseRecordingLock(); - return; - } - - try { - Recipient recipient = getRecipient(); - - if (recipient == null) { - throw new RecipientFormattingException("Badly formatted"); - } - - String message = getMessage(); - boolean initiating = threadId == -1; - boolean needsSplit = message.length() > characterCalculator.calculateCharacters(message).maxPrimaryMessageSize; - boolean isMediaMessage = false || -// recipient.isGroupRecipient() || - inputPanel.getQuote().isPresent() || - linkPreviewViewModel.hasLinkPreview() || - LinkPreviewUtil.isValidMediaUrl(message) || // Loki - Send GIFs as media messages - needsSplit; - - if (isMediaMessage) { - sendMediaMessage(initiating); - } else { - sendTextMessage(initiating); - } - } catch (RecipientFormattingException ex) { - Log.w(TAG, ex); - } catch (InvalidMessageException ex) { - Log.w(TAG, ex); - } - - if (messageStatus == null && !isGroupConversation() && !(TextSecurePreferences.getLocalNumber(this).equals(recipient.getAddress().serialize()))) { - messageStatus = "calculatingPoW"; - updateSubtitleTextView(); - updateMessageStatusProgressBar(); - } - } - - private void sendMediaMessage(boolean initiating) - throws InvalidMessageException - { - Log.i(TAG, "Sending media message..."); - sendMediaMessage(getMessage(), attachmentManager.buildSlideDeck(), inputPanel.getQuote().orNull(), linkPreviewViewModel.getActiveLinkPreview(), initiating); - } - - private ListenableFuture sendMediaMessage(String body, - SlideDeck slideDeck, - QuoteModel quote, - Optional linkPreview, - final boolean initiating) - { - - Pair> splitMessage = getSplitMessage(body, characterCalculator.calculateCharacters(body).maxPrimaryMessageSize); - body = splitMessage.first; - - if (splitMessage.second.isPresent()) { - slideDeck.addSlide(splitMessage.second.get()); - } - - List attachments = slideDeck.asAttachments(); - - VisibleMessage message = new VisibleMessage(); - message.setSentTimestamp(System.currentTimeMillis()); - message.setText(body); - OutgoingMediaMessage outgoingMessageCandidate = OutgoingMediaMessage.from(message, recipient, attachments, quote, linkPreview.orNull()); - - final SettableFuture future = new SettableFuture<>(); - final Context context = getApplicationContext(); - - final OutgoingMediaMessage outgoingMessage; - - outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessageCandidate); - ApplicationContext.getInstance(context).getTypingStatusSender().onTypingStopped(threadId); - - inputPanel.clearQuote(); - attachmentManager.clear(); - silentlySetComposeText(""); - - final long id = fragment.stageOutgoingMessage(outgoingMessage); - - if (initiating) { - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); - } - - try { - long allocatedThreadId = getAllocatedThreadId(context); - message.setId(DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMessage, allocatedThreadId, false, ()->fragment.releaseOutgoingMessage(id))); - MessageSender.send(message, recipient.getAddress(), attachments, quote, linkPreview.orNull()); - sendComplete(allocatedThreadId); - } catch (MmsException e) { - Log.w(TAG, e); - sendComplete(threadId); - } - future.set(null); - - return future; - } - - private void sendTextMessage(final boolean initiating) - throws InvalidMessageException - { - final Context context = getApplicationContext(); - final String messageBody = getMessage(); - - VisibleMessage message = new VisibleMessage(); - message.setSentTimestamp(System.currentTimeMillis()); - message.setText(messageBody); - OutgoingTextMessage outgoingTextMessage = OutgoingTextMessage.from(message, recipient); - ApplicationContext.getInstance(context).getTypingStatusSender().onTypingStopped(threadId); - - silentlySetComposeText(""); - final long id = fragment.stageOutgoingMessage(outgoingTextMessage); - - if (initiating) { - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); - } - - long allocatedThreadId = getAllocatedThreadId(context); - message.setId(DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(allocatedThreadId, outgoingTextMessage, false, message.getSentTimestamp(), ()->fragment.releaseOutgoingMessage(id))); - MessageSender.send(message, recipient.getAddress()); - - sendComplete(allocatedThreadId); - } - - private void sendOpenGroupInvitations(String[] contactIDs) { - final Context context = getApplicationContext(); - OpenGroupV2 openGroup = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadId); - for (String contactID : contactIDs) { - Recipient recipient = Recipient.from(context, Address.fromSerialized(contactID), true); - VisibleMessage message = new VisibleMessage(); - message.setSentTimestamp(System.currentTimeMillis()); - OpenGroupInvitation openGroupInvitationMessage = new OpenGroupInvitation(); - openGroupInvitationMessage.setName(openGroup.getName()); - openGroupInvitationMessage.setUrl(openGroup.getJoinURL()); - message.setOpenGroupInvitation(openGroupInvitationMessage); - OutgoingTextMessage outgoingTextMessage = OutgoingTextMessage.fromOpenGroupInvitation(openGroupInvitationMessage, recipient, message.getSentTimestamp()); - DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(-1, outgoingTextMessage, message.getSentTimestamp()); - MessageSender.send(message, recipient.getAddress()); - } - } - - private void updateToggleButtonState() { - if (inputPanel.isRecordingInLockedMode()) { - buttonToggle.display(sendButton); - quickAttachmentToggle.show(); - inlineAttachmentToggle.hide(); - return; - } - - if (composeText.getText().length() == 0) { - buttonToggle.display(attachButton); - quickAttachmentToggle.show(); - inlineAttachmentToggle.hide(); - } else { - buttonToggle.display(sendButton); - quickAttachmentToggle.hide(); - - if (!linkPreviewViewModel.hasLinkPreview()) { - inlineAttachmentToggle.show(); - } else { - inlineAttachmentToggle.hide(); - } - } - } - - private void updateLinkPreviewState() { - if (TextSecurePreferences.isLinkPreviewsEnabled(this)) { - linkPreviewViewModel.onEnabled(); - linkPreviewViewModel.onTextChanged(this, composeText.getTextTrimmed(), composeText.getSelectionStart(), composeText.getSelectionEnd()); - } else { - linkPreviewViewModel.onUserCancel(); - } - } - - @Override - public void onRecorderPermissionRequired() { - Permissions.with(this) - .request(Manifest.permission.RECORD_AUDIO) - .withRationaleDialog(getString(R.string.ConversationActivity_to_send_audio_messages_allow_signal_access_to_your_microphone), R.drawable.ic_baseline_mic_48) - .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_requires_the_microphone_permission_in_order_to_send_audio_messages)) - .execute(); - } - - @Override - public void onRecorderStarted() { - Vibrator vibrator = ServiceUtil.getVibrator(this); - vibrator.vibrate(20); - - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - - audioRecorder.startRecording(); - - audioHandler = new Handler(); - stopRecordingTask = () -> inputPanel.onRecordReleased(); - audioHandler.postDelayed(stopRecordingTask, 60000); - } - - @Override - public void onRecorderLocked() { - updateToggleButtonState(); - } - - @Override - public void onRecorderFinished() { - if (audioHandler != null && stopRecordingTask != null) { - audioHandler.removeCallbacks(stopRecordingTask); - } - updateToggleButtonState(); - Vibrator vibrator = ServiceUtil.getVibrator(this); - vibrator.vibrate(20); - - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - - ListenableFuture> future = audioRecorder.stopRecording(); - future.addListener(new ListenableFuture.Listener>() { - @Override - public void onSuccess(final @NonNull Pair result) { - int subscriptionId = -1; - long expiresIn = recipient.getExpireMessages() * 1000L; - boolean initiating = threadId == -1; - AudioSlide audioSlide = new AudioSlide(ConversationActivity.this, result.first, result.second, MediaTypes.AUDIO_AAC, true); - SlideDeck slideDeck = new SlideDeck(); - slideDeck.addSlide(audioSlide); - - sendMediaMessage("", slideDeck, inputPanel.getQuote().orNull(), Optional.absent(), initiating).addListener(new AssertedSuccessListener() { - @Override - public void onSuccess(Void nothing) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - BlobProvider.getInstance().delete(ConversationActivity.this, result.first); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - }); - } - - @Override - public void onFailure(ExecutionException e) { - Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_unable_to_record_audio, Toast.LENGTH_LONG).show(); - } - }); - } - - @Override - public void onRecorderCanceled() { - if (audioHandler != null && stopRecordingTask != null) { - audioHandler.removeCallbacks(stopRecordingTask); - } - updateToggleButtonState(); - Vibrator vibrator = ServiceUtil.getVibrator(this); - vibrator.vibrate(50); - - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - - ListenableFuture> future = audioRecorder.stopRecording(); - future.addListener(new ListenableFuture.Listener>() { - @Override - public void onSuccess(final Pair result) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - BlobProvider.getInstance().delete(ConversationActivity.this, result.first); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - @Override - public void onFailure(ExecutionException e) {} - }); - } - - @Override - public void onEmojiToggle() { - if (!emojiDrawerStub.resolved()) { - initializeMediaKeyboardProviders(emojiDrawerStub.get()); - - inputPanel.setMediaKeyboard(emojiDrawerStub.get()); - } - - if (container.getCurrentInput() == emojiDrawerStub.get()) { - container.showSoftkey(composeText); - } else { - container.show(composeText, emojiDrawerStub.get()); - } - } - - @Override - public void onLinkPreviewCanceled() { - linkPreviewViewModel.onUserCancel(); - } - - @Override - public void onMediaSelected(@NonNull Uri uri, String contentType) { - if (!TextUtils.isEmpty(contentType) && contentType.trim().equals("image/gif")) { - setMedia(uri, MediaType.GIF); - } else if (MediaUtil.isImageType(contentType)) { - setMedia(uri, MediaType.IMAGE); - } else if (MediaUtil.isVideoType(contentType)) { - setMedia(uri, MediaType.VIDEO); - } else if (MediaUtil.isAudioType(contentType)) { - setMedia(uri, MediaType.AUDIO); - } - } - - @Override - public void onCursorPositionChanged(int start, int end) { - linkPreviewViewModel.onTextChanged(this, composeText.getTextTrimmed(), start, end); - } - - private void silentlySetComposeText(String text) { - typingTextWatcher.setEnabled(false); - composeText.setText(text); - if (text.isEmpty()) { resetMentions(); } - typingTextWatcher.setEnabled(true); - } - - // Listeners - - private class AttachmentTypeListener implements AttachmentTypeSelector.AttachmentClickedListener { - @Override - public void onClick(int type) { - addAttachment(type); - } - - @Override - public void onQuickAttachment(Uri uri, String mimeType, String bucketId, long dateTaken, int width, int height, long size) { - linkPreviewViewModel.onUserCancel(); - Media media = new Media(uri, mimeType, dateTaken, width, height, size, Optional.of(Media.ALL_MEDIA_BUCKET_ID), Optional.absent()); - startActivityForResult(MediaSendActivity.buildEditorIntent(ConversationActivity.this, Collections.singletonList(media), recipient, composeText.getTextTrimmed()), MEDIA_SENDER); - } - } - - private class QuickCameraToggleListener implements OnClickListener { - @Override - public void onClick(View v) { - Permissions.with(ConversationActivity.this) - .request(Manifest.permission.CAMERA) - .withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_baseline_photo_camera_48) - .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video)) - .onAllGranted(() -> { - composeText.clearFocus(); - startActivityForResult(MediaSendActivity.buildCameraIntent(ConversationActivity.this, recipient), MEDIA_SENDER); - overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary); - }) - .onAnyDenied(() -> Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show()) - .execute(); - } - } - - private class SendButtonListener implements OnClickListener, TextView.OnEditorActionListener { - @Override - public void onClick(View v) { - sendMessage(); - } - - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_SEND) { - sendButton.performClick(); - return true; - } - return false; - } - } - - private class AttachButtonListener implements OnClickListener { - @Override - public void onClick(View v) { - handleAddAttachment(); - } - } - - private class AttachButtonLongClickListener implements View.OnLongClickListener { - @Override - public boolean onLongClick(View v) { - return sendButton.performLongClick(); - } - } - - private class ComposeKeyPressedListener implements OnKeyListener, OnClickListener, TextWatcher, OnFocusChangeListener { - - int beforeLength; - - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (keyCode == KeyEvent.KEYCODE_ENTER) { - if (TextSecurePreferences.isEnterSendsEnabled(ConversationActivity.this)) { - sendButton.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)); - sendButton.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER)); - return true; - } - } - } - return false; - } - - @Override - public void onClick(View v) { - container.showSoftkey(composeText); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count,int after) { - beforeLength = composeText.getTextTrimmed().length(); - } - - @Override - public void afterTextChanged(Editable s) { - if (composeText.getTextTrimmed().length() == 0 || beforeLength == 0) { - composeText.postDelayed(ConversationActivity.this::updateToggleButtonState, 50); - } - } - - @Override - public void onTextChanged(CharSequence s, int start, int before,int count) {} - - @Override - public void onFocusChange(View v, boolean hasFocus) {} - } - - private class TypingStatusTextWatcher extends SimpleTextWatcher { - private boolean enabled = true; - - @Override - public void onTextChanged(String text) { - if (enabled && threadId > 0) { - ApplicationContext.getInstance(ConversationActivity.this).getTypingStatusSender().onTypingStarted(threadId); - } - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - } - - private class MentionTextWatcher extends SimpleTextWatcher { - - @Override - public void onTextChanged(String text) { - boolean isBackspace = text.length() < oldText.length(); - if (isBackspace) { - currentMentionStartIndex = -1; - mentionCandidateSelectionView.hide(); - mentionCandidateSelectionViewContainer.setVisibility(View.GONE); - ArrayList mentionsToRemove = new ArrayList<>(); - for (Mention mention : mentions) { - if (!text.contains(mention.getDisplayName())) { - mentionsToRemove.add(mention); - } - } - mentions.removeAll(mentionsToRemove); - } - if (text.length() > 0) { - if (currentMentionStartIndex > text.length()) { - resetMentions(); // Should never occur - } - int lastCharacterIndex = text.length() - 1; - char lastCharacter = text.charAt(lastCharacterIndex); - char secondToLastCharacter = ' '; - if (lastCharacterIndex > 0) { - secondToLastCharacter = text.charAt(lastCharacterIndex - 1); - } - if (lastCharacter == '@' && Character.isWhitespace(secondToLastCharacter)) { - List mentionCandidates = MentionsManager.INSTANCE.getMentionCandidates("", threadId, recipient.isOpenGroupRecipient()); - currentMentionStartIndex = lastCharacterIndex; - mentionCandidateSelectionViewContainer.setVisibility(View.VISIBLE); - mentionCandidateSelectionView.show(mentionCandidates, threadId); - } else if (Character.isWhitespace(lastCharacter)) { - currentMentionStartIndex = -1; - mentionCandidateSelectionView.hide(); - mentionCandidateSelectionViewContainer.setVisibility(View.GONE); - } else { - if (currentMentionStartIndex != -1) { - String query = text.substring(currentMentionStartIndex + 1); // + 1 to get rid of the @ - List mentionCandidates = MentionsManager.INSTANCE.getMentionCandidates(query, threadId, recipient.isOpenGroupRecipient()); - mentionCandidateSelectionViewContainer.setVisibility(View.VISIBLE); - mentionCandidateSelectionView.show(mentionCandidates, threadId); - } - } - } - ConversationActivity.this.oldText = text; - } - } - - private void resetMentions() { - oldText = ""; - currentMentionStartIndex = -1; - mentions.clear(); - } - - @Override - public void setThreadId(long threadId) { - this.threadId = threadId; - } - - @Override - public void handleReplyMessage(MessageRecord messageRecord) { - if (recipient.isGroupRecipient() && !isActiveGroup()) { return; } - - Recipient author; - - if (messageRecord.isOutgoing()) { - author = Recipient.from(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)), true); - } else { - author = messageRecord.getIndividualRecipient(); - } - - if (messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getSharedContacts().isEmpty()) { - Contact contact = ((MmsMessageRecord) messageRecord).getSharedContacts().get(0); - String displayName = ContactUtil.getDisplayName(contact); - String body = getString(R.string.ConversationActivity_quoted_contact_message, EmojiStrings.BUST_IN_SILHOUETTE, displayName); - SlideDeck slideDeck = new SlideDeck(); - - if (contact.getAvatarAttachment() != null) { - slideDeck.addSlide(MediaUtil.getSlideForAttachment(this, contact.getAvatarAttachment())); - } - - inputPanel.setQuote(GlideApp.with(this), - messageRecord.getDateSent(), - author, - body, - slideDeck, - recipient, - threadId); - - } else if (messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getLinkPreviews().isEmpty()) { - LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0); - SlideDeck slideDeck = new SlideDeck(); - - if (linkPreview.getThumbnail().isPresent()) { - slideDeck.addSlide(MediaUtil.getSlideForAttachment(this, linkPreview.getThumbnail().get())); - } - - inputPanel.setQuote(GlideApp.with(this), - messageRecord.getDateSent(), - author, - messageRecord.getBody(), - slideDeck, - recipient, - threadId); - } else { - inputPanel.setQuote(GlideApp.with(this), - messageRecord.getDateSent(), - author, - messageRecord.getBody(), - messageRecord.isMms() ? ((MmsMessageRecord) messageRecord).getSlideDeck() : new SlideDeck(), - recipient, - threadId); - } - } - - @Override - public void onMessageActionToolbarOpened() { - searchViewItem.collapseActionView(); - } - - @Override - public void onForwardClicked() { - inputPanel.clearQuote(); - } - - @Override - public void onAttachmentChanged() { - handleSecurityChange(true, isDefaultSms); - updateToggleButtonState(); - updateLinkPreviewState(); - } - - private class QuoteRestorationTask extends AsyncTask { - - private final String serialized; - private final SettableFuture future; - - QuoteRestorationTask(@NonNull String serialized, @NonNull SettableFuture future) { - this.serialized = serialized; - this.future = future; - } - - @Override - protected MessageRecord doInBackground(Void... voids) { - QuoteId quoteId = QuoteId.deserialize(serialized); - - if (quoteId != null) { - return DatabaseFactory.getMmsSmsDatabase(getApplicationContext()).getMessageFor(quoteId.getId(), quoteId.getAuthor()); - } - - return null; - } - - @Override - protected void onPostExecute(MessageRecord messageRecord) { - if (messageRecord != null) { - handleReplyMessage(messageRecord); - future.set(true); - } else { - Log.e(TAG, "Failed to restore a quote from a draft. No matching message record."); - future.set(false); - } - } - } - - // region Loki - private long getAllocatedThreadId(Context context) { - long allocatedThreadId; - if (threadId == -1) { - allocatedThreadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient); - } else { - allocatedThreadId = threadId; - } - return allocatedThreadId; - } - - private void updateTitleTextView(Recipient recipient) { - String userPublicKey = TextSecurePreferences.getLocalNumber(this); - if (recipient == null) { - titleTextView.setText(R.string.ConversationActivity_compose); - } else if (recipient.getAddress().toString().toLowerCase().equals(userPublicKey)) { - titleTextView.setText(R.string.note_to_self); - } else { - String displayName = recipient.getName(); // Uses the Contact API internally - boolean hasName = (displayName != null); - titleTextView.setText(hasName ? displayName : recipient.getAddress().toString()); - } - } - - private void updateProfilePicture() { - try { - profilePictureView.glide = GlideApp.with(this); - profilePictureView.update(recipient, threadId); - } catch (Exception exception) { - // Do nothing - } - } - - private void updateSubtitleTextView() { - muteIndicatorImageView.setVisibility(View.GONE); - subtitleTextView.setVisibility(View.VISIBLE); - if (recipient.isMuted()) { - muteIndicatorImageView.setVisibility(View.VISIBLE); - subtitleTextView.setText(getString(R.string.ConversationActivity_muted_until_date,DateUtils.getFormattedDateTime(recipient.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault()))); - } else if (recipient.isGroupRecipient() && recipient.getName() != null && !recipient.getName().equals("Session Updates") && !recipient.getName().equals("Loki News")) { - OpenGroupV2 openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadId); - if (openGroup != null) { - Integer userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(openGroup.getRoom(),openGroup.getServer()); - if (userCount == null) { userCount = 0; } - subtitleTextView.setText(getString(R.string.ConversationActivity_member_count,userCount)); - } else if (PublicKeyValidation.isValid(recipient.getAddress().toString())) { - subtitleTextView.setText(recipient.getAddress().toString()); - } else { - subtitleTextView.setVisibility(View.GONE); - } - } else { - subtitleTextView.setVisibility(View.GONE); - } - titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension((subtitleTextView.getVisibility() == View.GONE) ? R.dimen.very_large_font_size : R.dimen.large_font_size)); - } - - private void setMessageStatusProgressAnimatedIfPossible(int progress) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - messageStatusProgressBar.setProgress(progress, true); - } else { - messageStatusProgressBar.setProgress(progress); - } - } - - private void updateMessageStatusProgressBar() { - if (messageStatus != null) { - messageStatusProgressBar.setAlpha(1.0f); - switch (messageStatus) { - case "calculatingPoW": setMessageStatusProgressAnimatedIfPossible(25); break; - case "contactingNetwork": setMessageStatusProgressAnimatedIfPossible(50); break; - case "sendingMessage": setMessageStatusProgressAnimatedIfPossible(75); break; - case "messageSent": - setMessageStatusProgressAnimatedIfPossible(100); - new Handler().postDelayed(() -> messageStatusProgressBar.animate().alpha(0).setDuration(250).start(), 250); - new Handler().postDelayed(() -> messageStatusProgressBar.setProgress(0), 500); - break; - case "messageFailed": - messageStatusProgressBar.animate().alpha(0).setDuration(250).start(); - new Handler().postDelayed(() -> messageStatusProgressBar.setProgress(0), 250); - break; - } - } - } - - private void handleMessageStatusChanged(String newMessageStatus, long timestamp) { - if (timestamp == 0 || (TextSecurePreferences.getLocalNumber(this).equals(recipient.getAddress().serialize())) ) { return; } - updateForNewMessageStatusIfNeeded(newMessageStatus, timestamp); - if (newMessageStatus.equals("messageFailed") || newMessageStatus.equals("messageSent")) { - new Handler().postDelayed(() -> clearMessageStatusIfNeeded(timestamp), 1000); - } - } - - private int precedence(String messageStatus) { - if (messageStatus != null) { - switch (messageStatus) { - case "calculatingPoW": return 0; - case "contactingNetwork": return 1; - case "sendingMessage": return 2; - case "messageSent": return 3; - case "messageFailed": return 4; - default: return -1; - } - } else { - return -1; - } - } - - private void updateForNewMessageStatusIfNeeded(String newMessageStatus, long timestamp) { - if (!DatabaseFactory.getSmsDatabase(this).isOutgoingMessage(timestamp) && !DatabaseFactory.getMmsDatabase(this).isOutgoingMessage(timestamp)) { return; } - if (precedence(newMessageStatus) > precedence(messageStatus)) { - messageStatus = newMessageStatus; - updateSubtitleTextView(); - updateMessageStatusProgressBar(); - } - } - - private void clearMessageStatusIfNeeded(long timestamp) { - if (!DatabaseFactory.getSmsDatabase(this).isOutgoingMessage(timestamp) && !DatabaseFactory.getMmsDatabase(this).isOutgoingMessage(timestamp)) { return; } - messageStatus = null; - updateSubtitleTextView(); - updateMessageStatusProgressBar(); - } - // endregion -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java deleted file mode 100644 index 8c7522a3f0..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java +++ /dev/null @@ -1,532 +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.thoughtcrime.securesms.conversation; - -import android.content.Context; -import android.database.Cursor; -import androidx.annotation.LayoutRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.recyclerview.widget.RecyclerView; - -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.annimon.stream.Stream; - -import org.thoughtcrime.securesms.BindableConversationItem; -import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.FastCursorRecyclerViewAdapter; -import org.thoughtcrime.securesms.database.MmsSmsColumns; -import org.thoughtcrime.securesms.database.MmsSmsDatabase; -import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.database.model.MmsMessageRecord; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.mms.SlideDeck; -import org.session.libsession.utilities.recipients.Recipient; -import org.thoughtcrime.securesms.util.DateUtils; -import org.thoughtcrime.securesms.util.LRUCache; -import org.thoughtcrime.securesms.util.StickyHeaderDecoration; -import org.session.libsignal.utilities.guava.Optional; - -import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; -import org.session.libsession.utilities.Conversions; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.Util; - -import java.lang.ref.SoftReference; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import network.loki.messenger.R; - -/** - * A cursor adapter for a conversation thread. Ultimately - * used by ComposeMessageActivity to display a conversation - * thread in a ListActivity. - * - * @author Moxie Marlinspike - * - */ -public class ConversationAdapter - extends FastCursorRecyclerViewAdapter - implements StickyHeaderDecoration.StickyHeaderAdapter -{ - - private static final int MAX_CACHE_SIZE = 1000; - private static final String TAG = ConversationAdapter.class.getSimpleName(); - private final Map> messageRecordCache = - Collections.synchronizedMap(new LRUCache>(MAX_CACHE_SIZE)); - private final SparseArray positionToCacheRef = new SparseArray<>(); - - private static final int MESSAGE_TYPE_OUTGOING = 0; - private static final int MESSAGE_TYPE_INCOMING = 1; - private static final int MESSAGE_TYPE_UPDATE = 2; - private static final int MESSAGE_TYPE_AUDIO_OUTGOING = 3; - private static final int MESSAGE_TYPE_AUDIO_INCOMING = 4; - private static final int MESSAGE_TYPE_THUMBNAIL_OUTGOING = 5; - private static final int MESSAGE_TYPE_THUMBNAIL_INCOMING = 6; - private static final int MESSAGE_TYPE_DOCUMENT_OUTGOING = 7; - private static final int MESSAGE_TYPE_DOCUMENT_INCOMING = 8; - private static final int MESSAGE_TYPE_INVITATION_OUTGOING = 9; - private static final int MESSAGE_TYPE_INVITATION_INCOMING = 10; - - private final Set batchSelected = Collections.synchronizedSet(new HashSet()); - - private final @Nullable ItemClickListener clickListener; - private final @NonNull - GlideRequests glideRequests; - private final @NonNull Locale locale; - private final @NonNull Recipient recipient; - private final @NonNull MmsSmsDatabase db; - private final @NonNull LayoutInflater inflater; - private final @NonNull Calendar calendar; - private final @NonNull MessageDigest digest; - - private MessageRecord recordToPulseHighlight; - private String searchQuery; - - protected static class ViewHolder extends RecyclerView.ViewHolder { - public ViewHolder(final @NonNull V itemView) { - super(itemView); - } - - @SuppressWarnings("unchecked") - public V getView() { - return (V)itemView; - } - } - - - static class HeaderViewHolder extends RecyclerView.ViewHolder { - TextView textView; - - HeaderViewHolder(View itemView) { - super(itemView); - textView = ViewUtil.findById(itemView, R.id.text); - } - - HeaderViewHolder(TextView textView) { - super(textView); - this.textView = textView; - } - - public void setText(CharSequence text) { - textView.setText(text); - } - } - - - interface ItemClickListener extends BindableConversationItem.EventListener { - void onItemClick(MessageRecord item); - void onItemLongClick(MessageRecord item); - } - - @SuppressWarnings("ConstantConditions") - @VisibleForTesting - ConversationAdapter(Context context, Cursor cursor) { - super(context, cursor); - try { - this.glideRequests = null; - this.locale = null; - this.clickListener = null; - this.recipient = null; - this.inflater = null; - this.db = null; - this.calendar = null; - this.digest = MessageDigest.getInstance("SHA1"); - } catch (NoSuchAlgorithmException nsae) { - throw new AssertionError("SHA1 isn't supported!"); - } - } - - public ConversationAdapter(@NonNull Context context, - @NonNull GlideRequests glideRequests, - @NonNull Locale locale, - @Nullable ItemClickListener clickListener, - @Nullable Cursor cursor, - @NonNull Recipient recipient) - { - super(context, cursor); - - try { - this.glideRequests = glideRequests; - this.locale = locale; - this.clickListener = clickListener; - this.recipient = recipient; - this.inflater = LayoutInflater.from(context); - this.db = DatabaseFactory.getMmsSmsDatabase(context); - this.calendar = Calendar.getInstance(); - this.digest = MessageDigest.getInstance("SHA1"); - - setHasStableIds(true); - } catch (NoSuchAlgorithmException nsae) { - throw new AssertionError("SHA1 isn't supported!"); - } - } - - @Override - public void changeCursor(Cursor cursor) { - messageRecordCache.clear(); - positionToCacheRef.clear(); - super.cleanFastRecords(); - super.changeCursor(cursor); - } - - @Override - protected void onBindItemViewHolder(ViewHolder viewHolder, @NonNull MessageRecord messageRecord) { - int adapterPosition = viewHolder.getAdapterPosition(); - - String prevCachedId = positionToCacheRef.get(adapterPosition + 1,null); - String nextCachedId = positionToCacheRef.get(adapterPosition - 1, null); - - MessageRecord previousRecord = null; - if (adapterPosition < getItemCount() - 1 && !isFooterPosition(adapterPosition + 1)) { - if (prevCachedId != null && messageRecordCache.containsKey(prevCachedId)) { - SoftReference prevSoftRecord = messageRecordCache.get(prevCachedId); - MessageRecord prevCachedRecord = prevSoftRecord.get(); - if (prevCachedRecord != null) { - previousRecord = prevCachedRecord; - } else { - previousRecord = getRecordForPositionOrThrow(adapterPosition + 1); - } - } else { - previousRecord = getRecordForPositionOrThrow(adapterPosition + 1); - } - } - - MessageRecord nextRecord = null; - if (adapterPosition > 0 && !isHeaderPosition(adapterPosition - 1)) { - if (nextCachedId != null && messageRecordCache.containsKey(nextCachedId)) { - SoftReference nextSoftRecord = messageRecordCache.get(nextCachedId); - MessageRecord nextCachedRecord = nextSoftRecord.get(); - if (nextCachedRecord != null) { - nextRecord = nextCachedRecord; - } else { - nextRecord = getRecordForPositionOrThrow(adapterPosition - 1); - } - } else { - nextRecord = getRecordForPositionOrThrow(adapterPosition - 1); - } - } - - viewHolder.getView().bind(messageRecord, - Optional.fromNullable(previousRecord), - Optional.fromNullable(nextRecord), - glideRequests, - locale, - batchSelected, - recipient, - searchQuery, - messageRecord == recordToPulseHighlight); - - if (messageRecord == recordToPulseHighlight) { - recordToPulseHighlight = null; - } - } - - @Override - public ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) { - long start = System.currentTimeMillis(); - final V itemView = ViewUtil.inflate(inflater, parent, getLayoutForViewType(viewType)); - itemView.setOnClickListener(view -> { - if (clickListener != null) { - clickListener.onItemClick(itemView.getMessageRecord()); - } - }); - itemView.setOnLongClickListener(view -> { - if (clickListener != null) { - clickListener.onItemLongClick(itemView.getMessageRecord()); - } - return true; - }); - itemView.setEventListener(clickListener); - Log.d(TAG, "Inflate time: " + (System.currentTimeMillis() - start)); - return new ViewHolder(itemView); - } - - @Override - public void onItemViewRecycled(ViewHolder holder) { - holder.getView().unbind(); - } - - private @LayoutRes int getLayoutForViewType(int viewType) { - switch (viewType) { - case MESSAGE_TYPE_AUDIO_OUTGOING: - case MESSAGE_TYPE_THUMBNAIL_OUTGOING: - case MESSAGE_TYPE_DOCUMENT_OUTGOING: - case MESSAGE_TYPE_INVITATION_OUTGOING: - case MESSAGE_TYPE_OUTGOING: return R.layout.conversation_item_sent; - case MESSAGE_TYPE_AUDIO_INCOMING: - case MESSAGE_TYPE_THUMBNAIL_INCOMING: - case MESSAGE_TYPE_DOCUMENT_INCOMING: - case MESSAGE_TYPE_INVITATION_INCOMING: - case MESSAGE_TYPE_INCOMING: return R.layout.conversation_item_received; - case MESSAGE_TYPE_UPDATE: return R.layout.conversation_item_update; - default: throw new IllegalArgumentException("unsupported item view type given to ConversationAdapter"); - } - } - - @Override - public int getItemViewType(@NonNull MessageRecord messageRecord) { - if (messageRecord.isUpdate()) { - return MESSAGE_TYPE_UPDATE; - } else if (messageRecord.isOpenGroupInvitation()) { - if (messageRecord.isOutgoing()) return MESSAGE_TYPE_INVITATION_OUTGOING; - else return MESSAGE_TYPE_INVITATION_INCOMING; - } else if (hasAudio(messageRecord)) { - if (messageRecord.isOutgoing()) return MESSAGE_TYPE_AUDIO_OUTGOING; - else return MESSAGE_TYPE_AUDIO_INCOMING; - } else if (hasDocument(messageRecord)) { - if (messageRecord.isOutgoing()) return MESSAGE_TYPE_DOCUMENT_OUTGOING; - else return MESSAGE_TYPE_DOCUMENT_INCOMING; - } else if (hasThumbnail(messageRecord)) { - if (messageRecord.isOutgoing()) return MESSAGE_TYPE_THUMBNAIL_OUTGOING; - else return MESSAGE_TYPE_THUMBNAIL_INCOMING; - } else if (messageRecord.isOutgoing()) { - return MESSAGE_TYPE_OUTGOING; - } else { - return MESSAGE_TYPE_INCOMING; - } - } - - @Override - protected boolean isRecordForId(@NonNull MessageRecord record, long id) { - return record.getId() == id; - } - - @Override - public long getItemId(@NonNull Cursor cursor) { - List attachments = DatabaseFactory.getAttachmentDatabase(getContext()).getAttachment(cursor); - List messageAttachments = Stream.of(attachments).filterNot(DatabaseAttachment::isQuote).toList(); - - if (messageAttachments.size() > 0 && messageAttachments.get(0).getFastPreflightId() != null) { - return Long.valueOf(messageAttachments.get(0).getFastPreflightId()); - } - - final String unique = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsColumns.UNIQUE_ROW_ID)); - final byte[] bytes = digest.digest(unique.getBytes()); - return Conversions.byteArrayToLong(bytes); - } - - @Override - protected long getItemId(@NonNull MessageRecord record) { - if (record.isOutgoing() && record.isMms()) { - MmsMessageRecord mmsRecord = (MmsMessageRecord) record; - SlideDeck slideDeck = mmsRecord.getSlideDeck(); - - if (slideDeck.getThumbnailSlide() != null && slideDeck.getThumbnailSlide().getFastPreflightId() != null) { - return Long.valueOf(slideDeck.getThumbnailSlide().getFastPreflightId()); - } - } - - return record.getId(); - } - - @Override - protected MessageRecord getRecordFromCursor(@NonNull Cursor cursor) { - long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.ID)); - String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT)); - - final SoftReference reference = messageRecordCache.get(type + messageId); - if (reference != null) { - final MessageRecord record = reference.get(); - if (record != null) return record; - } - - final MessageRecord messageRecord = db.readerFor(cursor).getCurrent(); - messageRecordCache.put(type + messageId, new SoftReference<>(messageRecord)); - - return messageRecord; - } - - public void close() { - getCursor().close(); - } - - public int findLastSeenPosition(long lastSeen) { - if (lastSeen <= 0) return -1; - if (!isActiveCursor()) return -1; - - int count = getItemCount() - (hasFooterView() ? 1 : 0); - - for (int i=(hasHeaderView() ? 1 : 0);i getSelectedItems() { - return Collections.unmodifiableSet(new HashSet<>(batchSelected)); - } - - public void pulseHighlightItem(int position) { - if (position < getItemCount()) { - recordToPulseHighlight = getRecordForPositionOrThrow(position); - notifyItemChanged(position); - } - } - - public void onSearchQueryUpdated(@Nullable String query) { - this.searchQuery = query; - notifyDataSetChanged(); - } - - private boolean hasAudio(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getAudioSlide() != null; - } - - private boolean hasDocument(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getDocumentSlide() != null; - } - - private boolean hasThumbnail(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide() != null; - } - - @Override - public long getHeaderId(int position) { - if (!isActiveCursor()) return -1; - if (isHeaderPosition(position)) return -1; - if (isFooterPosition(position)) return -1; - if (position >= getItemCount()) return -1; - if (position < 0) return -1; - - MessageRecord record = getRecordForPositionOrThrow(position); - if (record.getRecipient().getAddress().isOpenGroup()) { - calendar.setTime(new Date(record.getDateReceived())); - } else { - calendar.setTime(new Date(record.getDateSent())); - } - return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR)); - } - - public long getReceivedTimestamp(int position) { - if (!isActiveCursor()) return 0; - if (isHeaderPosition(position)) return 0; - if (isFooterPosition(position)) return 0; - if (position >= getItemCount()) return 0; - if (position < 0) return 0; - - MessageRecord messageRecord = getRecordForPositionOrThrow(position); - - if (messageRecord.isOutgoing()) return 0; - else return messageRecord.getDateReceived(); - } - - @Override - public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) { - return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.conversation_item_header, parent, false)); - } - - public HeaderViewHolder onCreateLastSeenViewHolder(ViewGroup parent) { - return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.conversation_item_last_seen, parent, false)); - } - - @Override - public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) { - MessageRecord messageRecord = getRecordForPositionOrThrow(position); - long timestamp = messageRecord.getDateReceived(); - if (recipient.getAddress().isOpenGroup()) { timestamp = messageRecord.getTimestamp(); } - viewHolder.setText(DateUtils.getRelativeDate(getContext(), locale, timestamp)); - } - - public void onBindLastSeenViewHolder(HeaderViewHolder viewHolder, int position) { - viewHolder.setText(getContext().getResources().getQuantityString(R.plurals.ConversationAdapter_n_unread_messages, (position + 1), (position + 1))); - } - - static class LastSeenHeader extends StickyHeaderDecoration { - - private final ConversationAdapter adapter; - private final long lastSeenTimestamp; - - LastSeenHeader(ConversationAdapter adapter, long lastSeenTimestamp) { - super(adapter, false, false); - this.adapter = adapter; - this.lastSeenTimestamp = lastSeenTimestamp; - } - - @Override - protected boolean hasHeader(RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) { - if (!adapter.isActiveCursor()) { - return false; - } - - if (lastSeenTimestamp <= 0) { - return false; - } - - long currentRecordTimestamp = adapter.getReceivedTimestamp(position); - long previousRecordTimestamp = adapter.getReceivedTimestamp(position + 1); - - return currentRecordTimestamp > lastSeenTimestamp && previousRecordTimestamp < lastSeenTimestamp; - } - - @Override - protected int getHeaderTop(RecyclerView parent, View child, View header, int adapterPos, int layoutPos) { - return parent.getLayoutManager().getDecoratedTop(child); - } - - @Override - protected HeaderViewHolder getHeader(RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) { - HeaderViewHolder viewHolder = adapter.onCreateLastSeenViewHolder(parent); - adapter.onBindLastSeenViewHolder(viewHolder, position); - - int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY); - int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED); - - int childWidth = ViewGroup.getChildMeasureSpec(widthSpec, parent.getPaddingLeft() + parent.getPaddingRight(), viewHolder.itemView.getLayoutParams().width); - int childHeight = ViewGroup.getChildMeasureSpec(heightSpec, parent.getPaddingTop() + parent.getPaddingBottom(), viewHolder.itemView.getLayoutParams().height); - - viewHolder.itemView.measure(childWidth, childHeight); - viewHolder.itemView.layout(0, 0, viewHolder.itemView.getMeasuredWidth(), viewHolder.itemView.getMeasuredHeight()); - - return viewHolder; - } - } - -} - diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java deleted file mode 100644 index 74a3b5eddb..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ /dev/null @@ -1,1220 +0,0 @@ -/* - * Copyright (C) 2015 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.conversation; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.ClipData; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.text.ClipboardManager; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.Window; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.TextView; -import android.widget.Toast; -import android.widget.ViewSwitcher; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.view.ActionMode; -import androidx.fragment.app.Fragment; -import androidx.loader.app.LoaderManager; -import androidx.loader.content.Loader; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.OnScrollListener; -import com.annimon.stream.Stream; -import org.session.libsession.messaging.MessagingModuleConfiguration; -import org.session.libsession.messaging.messages.control.DataExtractionNotification; -import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage; -import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; -import org.session.libsession.messaging.messages.visible.Quote; -import org.session.libsession.messaging.messages.visible.VisibleMessage; -import org.session.libsession.messaging.open_groups.OpenGroupAPIV2; -import org.session.libsession.messaging.open_groups.OpenGroupV2; -import org.session.libsession.messaging.sending_receiving.MessageSender; -import org.session.libsession.messaging.sending_receiving.attachments.Attachment; -import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.concurrent.SimpleTask; -import org.session.libsession.utilities.task.ProgressDialogAsyncTask; -import org.session.libsignal.utilities.guava.Optional; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.MessageDetailsActivity; -import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; -import org.thoughtcrime.securesms.ShareActivity; -import org.thoughtcrime.securesms.components.ConversationTypingView; -import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager; -import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder; -import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.MmsSmsDatabase; -import org.thoughtcrime.securesms.database.loaders.ConversationLoader; -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.longmessage.LongMessageActivity; -import org.thoughtcrime.securesms.mediasend.Media; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.mms.PartAuthority; -import org.thoughtcrime.securesms.mms.Slide; -import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.util.CommunicationActions; -import org.thoughtcrime.securesms.util.SaveAttachmentTask; -import org.thoughtcrime.securesms.util.StickyHeaderDecoration; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -import kotlin.Unit; -import network.loki.messenger.R; - -@SuppressLint("StaticFieldLeak") -public class ConversationFragment extends Fragment - implements LoaderManager.LoaderCallbacks -{ - private static final String TAG = ConversationFragment.class.getSimpleName(); - private static final String KEY_LIMIT = "limit"; - - private static final int PARTIAL_CONVERSATION_LIMIT = 500; - private static final int SCROLL_ANIMATION_THRESHOLD = 50; - private static final int CODE_ADD_EDIT_CONTACT = 77; - - private final ActionModeCallback actionModeCallback = new ActionModeCallback(); - private final ItemClickListener selectionClickListener = new ConversationFragmentItemClickListener(); - - private ConversationFragmentListener listener; - - private Recipient recipient; - private long threadId; - private long lastSeen; - private int startingPosition; - private int previousOffset; - private int activeOffset; - private boolean firstLoad; - private long loaderStartTime; - private ActionMode actionMode; - private Locale locale; - private RecyclerView list; - private RecyclerView.ItemDecoration lastSeenDecoration; - private ViewSwitcher topLoadMoreView; - private ViewSwitcher bottomLoadMoreView; - private ConversationTypingView typingView; - private View composeDivider; - private View scrollToBottomButton; - private TextView scrollDateHeader; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA); - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) { - final View view = inflater.inflate(R.layout.conversation_fragment, container, false); - list = ViewUtil.findById(view, android.R.id.list); - composeDivider = ViewUtil.findById(view, R.id.compose_divider); - scrollToBottomButton = ViewUtil.findById(view, R.id.scroll_to_bottom_button); - scrollDateHeader = ViewUtil.findById(view, R.id.scroll_date_header); - - scrollToBottomButton.setOnClickListener(v -> scrollToBottom()); - - final LinearLayoutManager layoutManager = new SmoothScrollingLinearLayoutManager(getActivity(), true); - list.setHasFixedSize(false); - list.setLayoutManager(layoutManager); - list.setItemAnimator(null); - - topLoadMoreView = (ViewSwitcher) inflater.inflate(R.layout.load_more_header, container, false); - bottomLoadMoreView = (ViewSwitcher) inflater.inflate(R.layout.load_more_header, container, false); - initializeLoadMoreView(topLoadMoreView); - initializeLoadMoreView(bottomLoadMoreView); - - typingView = (ConversationTypingView) inflater.inflate(R.layout.conversation_typing_view, container, false); - - return view; - } - - @Override - public void onActivityCreated(Bundle bundle) { - super.onActivityCreated(bundle); - - initializeResources(); - initializeListAdapter(); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - this.listener = (ConversationFragmentListener)activity; - } - - @Override - public void onStart() { - super.onStart(); - initializeTypingObserver(); - } - - @Override - public void onResume() { - super.onResume(); - - if (list.getAdapter() != null) { - list.getAdapter().notifyDataSetChanged(); - } - } - - @Override - public void onStop() { - super.onStop(); - ApplicationContext.getInstance(requireContext()).getTypingStatusRepository().getTypists(threadId).removeObservers(this); - } - - public void onNewIntent() { - if (actionMode != null) { - actionMode.finish(); - } - - initializeResources(); - initializeListAdapter(); - - if (threadId == -1) { - getLoaderManager().restartLoader(0, Bundle.EMPTY, this); - } - } - - public void reloadList() { - getLoaderManager().restartLoader(0, Bundle.EMPTY, this); - } - - public void moveToLastSeen() { - if (lastSeen <= 0) { - Log.i(TAG, "No need to move to last seen."); - return; - } - - if (list == null || getListAdapter() == null) { - Log.w(TAG, "Tried to move to last seen position, but we hadn't initialized the view yet."); - return; - } - - int position = getListAdapter().findLastSeenPosition(lastSeen); - scrollToLastSeenPosition(position); - } - - private void initializeResources() { - this.recipient = Recipient.from(getActivity(), getActivity().getIntent().getParcelableExtra(ConversationActivity.ADDRESS_EXTRA), true); - this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1); - this.lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1); - this.startingPosition = this.getActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1); - this.firstLoad = true; - - OnScrollListener scrollListener = new ConversationScrollListener(getActivity()); - list.addOnScrollListener(scrollListener); - } - - private void initializeListAdapter() { - if (this.recipient != null && this.threadId != -1) { - ConversationAdapter adapter = new ConversationAdapter(getActivity(), GlideApp.with(this), locale, selectionClickListener, null, this.recipient); - list.setAdapter(adapter); - list.addItemDecoration(new StickyHeaderDecoration(adapter, false, false)); - - setLastSeen(lastSeen); - getLoaderManager().restartLoader(0, Bundle.EMPTY, this); - } - } - - private void initializeLoadMoreView(ViewSwitcher loadMoreView) { - loadMoreView.setOnClickListener(v -> { - Bundle args = new Bundle(); - args.putInt(KEY_LIMIT, 0); - getLoaderManager().restartLoader(0, args, ConversationFragment.this); - loadMoreView.showNext(); - loadMoreView.setOnClickListener(null); - }); - } - - private void initializeTypingObserver() { - if (!TextSecurePreferences.isTypingIndicatorsEnabled(requireContext())) { - return; - } - - ApplicationContext.getInstance(requireContext()).getTypingStatusRepository().getTypists(threadId).observe(this, typingState -> { - List recipients; - boolean replacedByIncomingMessage; - - if (typingState != null) { - recipients = typingState.getTypists(); - replacedByIncomingMessage = typingState.isReplacedByIncomingMessage(); - } else { - recipients = Collections.emptyList(); - replacedByIncomingMessage = false; - } - - typingView.setTypists(GlideApp.with(ConversationFragment.this), recipients, recipient.isGroupRecipient()); - - ConversationAdapter adapter = getListAdapter(); - - if (adapter.getHeaderView() != null && adapter.getHeaderView() != typingView) { - Log.i(TAG, "Skipping typing indicator -- the header slot is occupied."); - return; - } - - if (recipients.size() > 0) { - if (adapter.getHeaderView() == null && isAtBottom()) { - list.setVerticalScrollBarEnabled(false); - list.post(() -> getListLayoutManager().smoothScrollToPosition(requireContext(), 0, 250)); - list.postDelayed(() -> list.setVerticalScrollBarEnabled(true), 300); - adapter.setHeaderView(typingView); - adapter.notifyItemInserted(0); - } else { - if (adapter.getHeaderView() == null) { - adapter.setHeaderView(typingView); - adapter.notifyItemInserted(0); - } else { - adapter.setHeaderView(typingView); - adapter.notifyItemChanged(0); - } - } - } else { - if (getListLayoutManager().findFirstCompletelyVisibleItemPosition() == 0 && getListLayoutManager().getItemCount() > 1 && !replacedByIncomingMessage) { - getListLayoutManager().smoothScrollToPosition(requireContext(), 1, 250); - list.setVerticalScrollBarEnabled(false); - list.postDelayed(() -> { - adapter.setHeaderView(null); - adapter.notifyItemRemoved(0); - list.post(() -> list.setVerticalScrollBarEnabled(true)); - }, 200); - } else if (!replacedByIncomingMessage) { - adapter.setHeaderView(null); - adapter.notifyItemRemoved(0); - } else { - adapter.setHeaderView(null); - } - } - }); - } - - private void setCorrectMenuVisibility(Menu menu) { - Set messageRecords = getListAdapter().getSelectedItems(); - boolean actionMessage = false; - boolean hasText = false; - boolean sharedContact = false; - - if (actionMode != null && messageRecords.size() == 0) { - actionMode.finish(); - return; - } - - for (MessageRecord messageRecord : messageRecords) { - if (messageRecord.isCallLog() || messageRecord.isExpirationTimerUpdate()) - { - actionMessage = true; - } - - if (messageRecord.getBody().length() > 0) { - hasText = true; - } - - if (messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getSharedContacts().isEmpty()) { - sharedContact = true; - } - } - - if (messageRecords.size() > 1) { - menu.findItem(R.id.menu_context_details).setVisible(false); - menu.findItem(R.id.menu_context_reply).setVisible(false); - menu.findItem(R.id.menu_context_save_attachment).setVisible(false); - menu.findItem(R.id.menu_context_resend).setVisible(false); - } else { - MessageRecord messageRecord = messageRecords.iterator().next(); - - menu.findItem(R.id.menu_context_details).setVisible(true); - menu.findItem(R.id.menu_context_resend).setVisible(messageRecord.isFailed()); - menu.findItem(R.id.menu_context_save_attachment).setVisible(!actionMessage && - messageRecord.isMms() && - !messageRecord.isMmsNotification() && - ((MediaMmsMessageRecord)messageRecord).containsMediaSlide()); - - menu.findItem(R.id.menu_context_reply).setVisible(!actionMessage && - !messageRecord.isPending() && - !messageRecord.isFailed()); - } - - menu.findItem(R.id.menu_context_copy).setVisible(!actionMessage && hasText); - - boolean isGroupChat = recipient.isGroupRecipient(); - - if (isGroupChat) { - OpenGroupV2 openGroupChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getOpenGroupChat(threadId); - boolean isPublicChat = (openGroupChat != null); - int selectedMessageCount = messageRecords.size(); - boolean areAllSentByUser = true; - Set uniqueUserSet = new HashSet<>(); - for (MessageRecord message : messageRecords) { - if (!message.isOutgoing()) { areAllSentByUser = false; } - uniqueUserSet.add(message.getRecipient().getAddress().toString()); - } - menu.findItem(R.id.menu_context_copy_public_key).setVisible(selectedMessageCount == 1 && !areAllSentByUser); - menu.findItem(R.id.menu_context_reply).setVisible(selectedMessageCount == 1); - String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(requireContext()); - boolean userCanModerate = - (isPublicChat && (OpenGroupAPIV2.isUserModerator(userHexEncodedPublicKey, openGroupChat.getRoom(), openGroupChat.getServer()))); - boolean isDeleteOptionVisible = !isPublicChat || (areAllSentByUser || userCanModerate); - // allow banning if moderating a public chat and only one user's messages are selected - boolean isBanOptionVisible = isPublicChat && userCanModerate && !areAllSentByUser && uniqueUserSet.size() == 1; - menu.findItem(R.id.menu_context_delete_message).setVisible(isDeleteOptionVisible); - menu.findItem(R.id.menu_context_ban_user).setVisible(isBanOptionVisible); - } else { - menu.findItem(R.id.menu_context_copy_public_key).setVisible(false); - menu.findItem(R.id.menu_context_delete_message).setVisible(true); - menu.findItem(R.id.menu_context_ban_user).setVisible(false); - } - } - - private ConversationAdapter getListAdapter() { - return (ConversationAdapter) list.getAdapter(); - } - - private SmoothScrollingLinearLayoutManager getListLayoutManager() { - return (SmoothScrollingLinearLayoutManager) list.getLayoutManager(); - } - - private MessageRecord getSelectedMessageRecord() { - Set messageRecords = getListAdapter().getSelectedItems(); - return messageRecords.iterator().next(); - } - - public void reload(Recipient recipient, long threadId) { - this.recipient = recipient; - - if (this.threadId != threadId) { - this.threadId = threadId; - initializeListAdapter(); - } - } - - public void scrollToBottom() { - if (getListLayoutManager().findFirstVisibleItemPosition() < SCROLL_ANIMATION_THRESHOLD) { - list.smoothScrollToPosition(0); - } else { - list.scrollToPosition(0); - } - } - - public void setLastSeen(long lastSeen) { - this.lastSeen = lastSeen; - if (lastSeenDecoration != null) { - list.removeItemDecoration(lastSeenDecoration); - } - - lastSeenDecoration = new ConversationAdapter.LastSeenHeader(getListAdapter(), lastSeen); - list.addItemDecoration(lastSeenDecoration); - } - - private void handleCopyMessage(final Set messageRecords) { - List messageList = new LinkedList<>(messageRecords); - Collections.sort(messageList, new Comparator() { - @Override - public int compare(MessageRecord lhs, MessageRecord rhs) { - if (lhs.getDateReceived() < rhs.getDateReceived()) return -1; - else if (lhs.getDateReceived() == rhs.getDateReceived()) return 0; - else return 1; - } - }); - - StringBuilder bodyBuilder = new StringBuilder(); - ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); - - for (MessageRecord messageRecord : messageList) { - String body = messageRecord.getDisplayBody(requireContext()).toString(); - if (!TextUtils.isEmpty(body)) { - bodyBuilder.append(body).append('\n'); - } - } - if (bodyBuilder.length() > 0 && bodyBuilder.charAt(bodyBuilder.length() - 1) == '\n') { - bodyBuilder.deleteCharAt(bodyBuilder.length() - 1); - } - - String result = bodyBuilder.toString(); - - if (!TextUtils.isEmpty(result)) - clipboard.setText(result); - } - - private void handleCopyPublicKey(MessageRecord messageRecord) { - String sessionID = messageRecord.getRecipient().getAddress().toString(); - android.content.ClipboardManager clipboard = (android.content.ClipboardManager)requireActivity().getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Session ID", sessionID); - clipboard.setPrimaryClip(clip); - Toast.makeText(getContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show(); - } - - private void handleDeleteMessages(final Set messageRecords) { - int messagesCount = messageRecords.size(); - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - builder.setIconAttribute(R.attr.dialog_alert_icon); - builder.setTitle(getActivity().getResources().getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messagesCount, messagesCount)); - builder.setMessage(getActivity().getResources().getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messagesCount, messagesCount)); - builder.setCancelable(true); - - OpenGroupV2 openGroupChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getOpenGroupChat(threadId); - - builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - new ProgressDialogAsyncTask(getActivity(), - R.string.ConversationFragment_deleting, - R.string.ConversationFragment_deleting_messages) - { - @Override - protected Void doInBackground(MessageRecord... messageRecords) { - if (openGroupChat != null) { - ArrayList serverIDs = new ArrayList<>(); - ArrayList ignoredMessages = new ArrayList<>(); - ArrayList failedMessages = new ArrayList<>(); - boolean isSentByUser = true; - for (MessageRecord messageRecord : messageRecords) { - isSentByUser = isSentByUser && messageRecord.isOutgoing(); - Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id, !messageRecord.isMms()); - if (serverID != null) { - serverIDs.add(serverID); - } else { - ignoredMessages.add(messageRecord.getId()); - } - } - if (openGroupChat != null) { - for (Long serverId : serverIDs) { - OpenGroupAPIV2 - .deleteMessage(serverId, openGroupChat.getRoom(), openGroupChat.getServer()) - .success(l -> { - for (MessageRecord messageRecord : messageRecords) { - Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id, !messageRecord.isMms()); - if (serverID != null && serverID.equals(serverId)) { - MessagingModuleConfiguration.shared.getMessageDataProvider().deleteMessage(messageRecord.id, !messageRecord.isMms()); - break; - } - } - return null; - }).fail(e->{ - Log.e("Loki", "Couldn't delete message due to error",e); - return null; - }); - } - } - } else { - for (MessageRecord messageRecord : messageRecords) { - if (messageRecord.isMms()) { - DatabaseFactory.getMmsDatabase(getActivity()).delete(messageRecord.getId()); - } else { - DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageRecord.getId()); - } - } - } - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, messageRecords.toArray(new MessageRecord[messageRecords.size()])); - } - }); - - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - } - - private void handleBanUser(Set messageRecords) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - String userPublicKey = null; - for (MessageRecord record: messageRecords) { - String currentPublicKey = record.getRecipient().getAddress().toString(); - if (userPublicKey == null) { - userPublicKey = currentPublicKey; - } - } - final String finalPublicKey = userPublicKey; - - builder.setIconAttribute(R.attr.dialog_alert_icon); - builder.setTitle(R.string.ConversationFragment_ban_selected_user); - builder.setCancelable(true); - - final OpenGroupV2 openGroupChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getOpenGroupChat(threadId); - - builder.setPositiveButton(R.string.ban, (dialog, which) -> { - ConversationAdapter chatAdapter = getListAdapter(); - chatAdapter.clearSelection(); - chatAdapter.notifyDataSetChanged(); - new ProgressDialogAsyncTask(getActivity(), - R.string.ConversationFragment_banning, - R.string.ConversationFragment_banning_user) { - @Override - protected Void doInBackground(String... userPublicKeyParam) { - String userPublicKey = userPublicKeyParam[0]; - if (openGroupChat != null) { - OpenGroupAPIV2 - .ban(userPublicKey, openGroupChat.getRoom(), openGroupChat.getServer()) - .success(l -> { - Log.d("Loki", "User banned"); - return Unit.INSTANCE; - }).fail(e -> { - Log.e("Loki", "Failed to ban user",e); - return null; - }); - } else { - Log.d("Loki", "Tried to ban user from a non-public chat"); - } - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, finalPublicKey); - }); - - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - } - - private void handleDisplayDetails(MessageRecord message) { - Intent intent = new Intent(getActivity(), MessageDetailsActivity.class); - intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, message.getId()); - intent.putExtra(MessageDetailsActivity.THREAD_ID_EXTRA, threadId); - intent.putExtra(MessageDetailsActivity.TYPE_EXTRA, message.isMms() ? MmsSmsDatabase.MMS_TRANSPORT : MmsSmsDatabase.SMS_TRANSPORT); - intent.putExtra(MessageDetailsActivity.ADDRESS_EXTRA, recipient.getAddress()); - intent.putExtra(MessageDetailsActivity.IS_PUSH_GROUP_EXTRA, recipient.isGroupRecipient()); - startActivity(intent); - } - - private void handleForwardMessage(MessageRecord message) { - listener.onForwardClicked(); - - SimpleTask.run(getLifecycle(), () -> { - Intent composeIntent = new Intent(getActivity(), ShareActivity.class); - composeIntent.putExtra(Intent.EXTRA_TEXT, message.getDisplayBody(requireContext()).toString()); - - if (message.isMms()) { - MmsMessageRecord mediaMessage = (MmsMessageRecord) message; - boolean isAlbum = mediaMessage.containsMediaSlide() && - mediaMessage.getSlideDeck().getSlides().size() > 1 && - mediaMessage.getSlideDeck().getAudioSlide() == null && - mediaMessage.getSlideDeck().getDocumentSlide() == null; - - if (isAlbum) { - ArrayList mediaList = new ArrayList<>(mediaMessage.getSlideDeck().getSlides().size()); - List attachments = Stream.of(mediaMessage.getSlideDeck().getSlides()) - .filter(s -> s.hasImage() || s.hasVideo()) - .map(Slide::asAttachment) - .toList(); - - for (Attachment attachment : attachments) { - Uri uri = attachment.getDataUri() != null ? attachment.getDataUri() : attachment.getThumbnailUri(); - - if (uri != null) { - mediaList.add(new Media(uri, - attachment.getContentType(), - System.currentTimeMillis(), - attachment.getWidth(), - attachment.getHeight(), - attachment.getSize(), - Optional.absent(), - Optional.fromNullable(attachment.getCaption()))); - } - }; - - if (!mediaList.isEmpty()) { - composeIntent.putExtra(ConversationActivity.MEDIA_EXTRA, mediaList); - } - } else if (mediaMessage.containsMediaSlide()) { - Slide slide = mediaMessage.getSlideDeck().getSlides().get(0); - composeIntent.putExtra(Intent.EXTRA_STREAM, slide.getUri()); - composeIntent.setType(slide.getContentType()); - } - - if (mediaMessage.getSlideDeck().getTextSlide() != null && mediaMessage.getSlideDeck().getTextSlide().getUri() != null) { - try (InputStream stream = PartAuthority.getAttachmentStream(requireContext(), mediaMessage.getSlideDeck().getTextSlide().getUri())) { - String fullBody = Util.readFullyAsString(stream); - composeIntent.putExtra(Intent.EXTRA_TEXT, fullBody); - } catch (IOException e) { - Log.w(TAG, "Failed to read long message text when forwarding."); - } - } - } - - return composeIntent; - }, this::startActivity); - } - - private void handleResendMessage(final MessageRecord message) { - new AsyncTask() { - @Override - protected Void doInBackground(MessageRecord... messageRecords) { - MessageRecord messageRecord = messageRecords[0]; - Recipient recipient = messageRecord.getRecipient(); - VisibleMessage message = new VisibleMessage(); - message.setId(messageRecord.getId()); - message.setText(messageRecord.getBody()); - message.setSentTimestamp(messageRecord.getTimestamp()); - if (recipient.isGroupRecipient()) { - message.setGroupPublicKey(recipient.getAddress().toGroupString()); - } else { - message.setRecipient(messageRecord.getRecipient().getAddress().serialize()); - } - message.setThreadID(messageRecord.getThreadId()); - if (messageRecord.isMms()) { - MmsMessageRecord mmsMessageRecord = (MmsMessageRecord) messageRecord; - if (!mmsMessageRecord.getLinkPreviews().isEmpty()) { - message.setLinkPreview(org.session.libsession.messaging.messages.visible.LinkPreview.Companion.from(mmsMessageRecord.getLinkPreviews().get(0))); - } - if (mmsMessageRecord.getQuote() != null) { - message.setQuote(Quote.Companion.from(mmsMessageRecord.getQuote().getQuoteModel())); - } - message.addSignalAttachments(mmsMessageRecord.getSlideDeck().asAttachments()); - } - MessageSender.send(message, recipient.getAddress()); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message); - } - - private void handleReplyMessage(final MessageRecord message) { - listener.handleReplyMessage(message); - } - - private void handleSaveAttachment(final MediaMmsMessageRecord message) { - SaveAttachmentTask.showWarningDialog(getActivity(), (dialog, which) -> { - Permissions.with(this) - .request(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) - .maxSdkVersion(Build.VERSION_CODES.P) - .withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied)) - .onAnyDenied(() -> Toast.makeText(getContext(), R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()) - .onAllGranted(() -> { - List attachments = - Stream.of(message.getSlideDeck().getSlides()) - .filter(s -> s.getUri() != null && (s.hasImage() || s.hasVideo() || s.hasAudio() || s.hasDocument())) - .map(s -> new SaveAttachmentTask.Attachment(s.getUri(), s.getContentType(), message.getDateReceived(), s.getFileName().orNull())) - .toList(); - if (!Util.isEmpty(attachments)) { - SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity()); - saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachments.toArray(new SaveAttachmentTask.Attachment[0])); - if (!message.isOutgoing()) { - sendMediaSavedNotificationIfNeeded(); - } - return; - } - - Log.w(TAG, "No slide with attachable media found, failing nicely."); - Toast.makeText(getActivity(), - getResources().getQuantityString(R.plurals.ConversationFragment_error_while_saving_attachments_to_sd_card, 1), - Toast.LENGTH_LONG).show(); - }) - .execute(); - }); - } - - private void sendMediaSavedNotificationIfNeeded() { - if (recipient.isGroupRecipient()) return; - DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); - MessageSender.send(message, recipient.getAddress()); - } - - @Override - public @NonNull Loader onCreateLoader(int id, Bundle args) { - Log.i(TAG, "onCreateLoader"); - loaderStartTime = System.currentTimeMillis(); - - int limit = args.getInt(KEY_LIMIT, PARTIAL_CONVERSATION_LIMIT); - int offset = 0; - if (limit != 0 && startingPosition >= limit) { - offset = Math.max(startingPosition - (limit / 2) + 1, 0); - startingPosition -= offset - 1; - } - - return new ConversationLoader(getActivity(), threadId, offset, limit, lastSeen); - } - - @Override - public void onLoadFinished(@NonNull Loader cursorLoader, Cursor cursor) { - long loadTime = System.currentTimeMillis() - loaderStartTime; - int count = cursor.getCount(); - Log.i(TAG, "onLoadFinished - took " + loadTime + " ms to load a cursor of size " + count); - ConversationLoader loader = (ConversationLoader)cursorLoader; - - ConversationAdapter adapter = getListAdapter(); - if (adapter == null) { - return; - } - - if (cursor.getCount() >= PARTIAL_CONVERSATION_LIMIT && loader.hasLimit()) { - adapter.setFooterView(topLoadMoreView); - } else { - adapter.setFooterView(null); - } - - if (lastSeen == -1) { - setLastSeen(loader.getLastSeen()); - } - - if (!loader.hasSent() && !recipient.isSystemContact() && !recipient.isGroupRecipient() && recipient.getRegistered() == Recipient.RegisteredState.REGISTERED) { -// adapter.setHeaderView(unknownSenderView); - } else { - clearHeaderIfNotTyping(adapter); - } - - if (loader.hasOffset()) { - adapter.setHeaderView(bottomLoadMoreView); - } - - if (firstLoad || loader.hasOffset()) { - previousOffset = loader.getOffset(); - } - - activeOffset = loader.getOffset(); - - adapter.changeCursor(cursor); - - int lastSeenPosition = adapter.findLastSeenPosition(lastSeen); - - if (adapter.getHeaderView() == typingView) { - lastSeenPosition = Math.max(lastSeenPosition - 1, 0); - } - - if (firstLoad) { - if (startingPosition >= 0) { - scrollToStartingPosition(startingPosition); - } else { - scrollToLastSeenPosition(lastSeenPosition); - } - firstLoad = false; - } else if (previousOffset > 0) { - int scrollPosition = previousOffset + getListLayoutManager().findFirstVisibleItemPosition(); - scrollPosition = Math.min(scrollPosition, count - 1); - - View firstView = list.getLayoutManager().getChildAt(scrollPosition); - int pixelOffset = (firstView == null) ? 0 : (firstView.getBottom() - list.getPaddingBottom()); - - getListLayoutManager().scrollToPositionWithOffset(scrollPosition, pixelOffset); - previousOffset = 0; - } - - if (lastSeenPosition <= 0) { - setLastSeen(0); - } - } - - private void clearHeaderIfNotTyping(ConversationAdapter adapter) { - if (adapter.getHeaderView() != typingView) { - adapter.setHeaderView(null); - } - } - - @Override - public void onLoaderReset(@NonNull Loader arg0) { - if (list.getAdapter() != null) { - getListAdapter().changeCursor(null); - } - } - - public long stageOutgoingMessage(OutgoingMediaMessage message) { - MessageRecord messageRecord = DatabaseFactory.getMmsDatabase(getContext()).readerFor(message, threadId).getCurrent(); - - if (getListAdapter() != null) { - clearHeaderIfNotTyping(getListAdapter()); - setLastSeen(0); - getListAdapter().addFastRecord(messageRecord); - } - - return messageRecord.getId(); - } - - public long stageOutgoingMessage(OutgoingTextMessage message) { - MessageRecord messageRecord = DatabaseFactory.getSmsDatabase(getContext()).readerFor(message, threadId).getCurrent(); - - if (getListAdapter() != null) { - clearHeaderIfNotTyping(getListAdapter()); - setLastSeen(0); - getListAdapter().addFastRecord(messageRecord); - } - - return messageRecord.getId(); - } - - public void releaseOutgoingMessage(long id) { - if (getListAdapter() != null) { - getListAdapter().releaseFastRecord(id); - } - } - - private void scrollToStartingPosition(final int startingPosition) { - list.post(() -> { - list.getLayoutManager().scrollToPosition(startingPosition); - getListAdapter().pulseHighlightItem(startingPosition); - }); - } - - private void scrollToLastSeenPosition(final int lastSeenPosition) { - if (lastSeenPosition > 0) { - list.post(() -> getListLayoutManager().scrollToPositionWithOffset(lastSeenPosition, list.getHeight())); - } - } - - private boolean isAtBottom() { - if (list.getChildCount() == 0) return true; - - int firstVisiblePosition = getListLayoutManager().findFirstVisibleItemPosition(); - - if (getListAdapter().getHeaderView() == typingView) { - RecyclerView.ViewHolder item1 = list.findViewHolderForAdapterPosition(1); - return firstVisiblePosition <= 1 && item1 != null && item1.itemView.getBottom() <= list.getHeight(); - } - - return firstVisiblePosition == 0 && list.getChildAt(0).getBottom() <= list.getHeight(); - } - - public void onSearchQueryUpdated(@Nullable String query) { - if (getListAdapter() != null) { - getListAdapter().onSearchQueryUpdated(query); - } - } - - public void jumpToMessage(@NonNull Address author, long timestamp, @Nullable Runnable onMessageNotFound) { - SimpleTask.run(getLifecycle(), () -> { - return DatabaseFactory.getMmsSmsDatabase(getContext()) - .getMessagePositionInConversation(threadId, timestamp, author); - }, p -> moveToMessagePosition(p, onMessageNotFound)); - } - - private void moveToMessagePosition(int position, @Nullable Runnable onMessageNotFound) { - Log.d(TAG, "Moving to message position: " + position + " activeOffset: " + activeOffset + " cursorCount: " + getListAdapter().getCursorCount()); - - if (position >= activeOffset && position >= 0 && position < getListAdapter().getCursorCount()) { - int offset = activeOffset > 0 ? activeOffset - 1 : 0; - list.scrollToPosition(position - offset); - getListAdapter().pulseHighlightItem(position - offset); - } else if (position < 0) { - Log.w(TAG, "Tried to navigate to message, but it wasn't found."); - if (onMessageNotFound != null) { - onMessageNotFound.run(); - } - } else { - Log.i(TAG, "Message was outside of the loaded range. Need to restart the loader."); - - firstLoad = true; - startingPosition = position; - getLoaderManager().restartLoader(0, Bundle.EMPTY, ConversationFragment.this); - } - } - - public interface ConversationFragmentListener { - void setThreadId(long threadId); - void handleReplyMessage(MessageRecord messageRecord); - void onMessageActionToolbarOpened(); - void onForwardClicked(); - } - - private class ConversationScrollListener extends OnScrollListener { - - private final Animation scrollButtonInAnimation; - private final Animation scrollButtonOutAnimation; - private final ConversationDateHeader conversationDateHeader; - - private boolean wasAtBottom = true; - private boolean wasAtZoomScrollHeight = false; - private long lastPositionId = -1; - - ConversationScrollListener(@NonNull Context context) { - this.scrollButtonInAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_in); - this.scrollButtonOutAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_out); - this.conversationDateHeader = new ConversationDateHeader(context, scrollDateHeader); - - this.scrollButtonInAnimation.setDuration(100); - this.scrollButtonOutAnimation.setDuration(50); - } - - @Override - public void onScrolled(@NonNull final RecyclerView rv, final int dx, final int dy) { - boolean currentlyAtBottom = isAtBottom(); - boolean currentlyAtZoomScrollHeight = isAtZoomScrollHeight(); - int positionId = getHeaderPositionId(); - - if (currentlyAtBottom && !wasAtBottom) { - ViewUtil.fadeOut(composeDivider, 50, View.INVISIBLE); - ViewUtil.animateOut(scrollToBottomButton, scrollButtonOutAnimation, View.INVISIBLE); - } else if (!currentlyAtBottom && wasAtBottom) { - ViewUtil.fadeIn(composeDivider, 500); - } - - if (currentlyAtZoomScrollHeight && !wasAtZoomScrollHeight) { - ViewUtil.animateIn(scrollToBottomButton, scrollButtonInAnimation); - } - - if (positionId != lastPositionId) { - bindScrollHeader(conversationDateHeader, positionId); - } - - wasAtBottom = currentlyAtBottom; - wasAtZoomScrollHeight = currentlyAtZoomScrollHeight; - lastPositionId = positionId; - } - - @Override - public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { - if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { - conversationDateHeader.show(); - } else if (newState == RecyclerView.SCROLL_STATE_IDLE) { - conversationDateHeader.hide(); - } - } - - private boolean isAtZoomScrollHeight() { - return getListLayoutManager().findFirstCompletelyVisibleItemPosition() > 4; - } - - private int getHeaderPositionId() { - return getListLayoutManager().findLastVisibleItemPosition(); - } - - private void bindScrollHeader(HeaderViewHolder headerViewHolder, int positionId) { - if (((ConversationAdapter)list.getAdapter()).getHeaderId(positionId) != -1) { - ((ConversationAdapter) list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId); - } - } - } - - private class ConversationFragmentItemClickListener implements ItemClickListener { - - @Override - public void onItemClick(MessageRecord messageRecord) { - if (messageRecord.isUpdate()) return; - if (actionMode != null) { - ((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord); - list.getAdapter().notifyDataSetChanged(); - - if (getListAdapter().getSelectedItems().size() == 0) { - actionMode.finish(); - } else { - setCorrectMenuVisibility(actionMode.getMenu()); - actionMode.setTitle(String.valueOf(getListAdapter().getSelectedItems().size())); - } - } - } - - @Override - public void onItemLongClick(MessageRecord messageRecord) { - if (messageRecord.isUpdate()) return; - if (actionMode == null) { - ((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord); - list.getAdapter().notifyDataSetChanged(); - - actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback); - - View titleTextView = (getActivity().findViewById(R.id.action_bar_title)); - if (titleTextView != null) { - titleTextView.setBackgroundColor(getResources().getColor(R.color.transparent)); - ViewParent titleTextViewContainerView = titleTextView.getParent(); - if (titleTextViewContainerView != null) { - ((View)titleTextViewContainerView).setBackgroundColor(getResources().getColor(R.color.transparent)); - } - } - } - } - - @Override - public void onQuoteClicked(MmsMessageRecord messageRecord) { - if (messageRecord.getQuote() == null) { - Log.w(TAG, "Received a 'quote clicked' event, but there's no quote..."); - return; - } - - if (messageRecord.getQuote().isOriginalMissing()) { - Log.i(TAG, "Clicked on a quote whose original message we never had."); - Toast.makeText(getContext(), R.string.ConversationFragment_quoted_message_not_found, Toast.LENGTH_SHORT).show(); - return; - } - - SimpleTask.run(getLifecycle(), () -> { - return DatabaseFactory.getMmsSmsDatabase(getContext()) - .getQuotedMessagePosition(threadId, - messageRecord.getQuote().getId(), - messageRecord.getQuote().getAuthor()); - }, p -> moveToMessagePosition(p, () -> { - Toast.makeText(getContext(), R.string.ConversationFragment_quoted_message_no_longer_available, Toast.LENGTH_SHORT).show(); - })); - } - - @Override - public void onLinkPreviewClicked(@NonNull LinkPreview linkPreview) { - if (getContext() != null && getActivity() != null) { - CommunicationActions.openBrowserLink(getActivity(), linkPreview.getUrl()); - } - } - - @Override - public void onMoreTextClicked(@NonNull Address conversationAddress, long messageId, boolean isMms) { - if (getContext() != null && getActivity() != null) { - startActivity(LongMessageActivity.getIntent(getContext(), conversationAddress, messageId, isMms)); - } - } - } - - private class ActionModeCallback implements ActionMode.Callback { - - private int statusBarColor; - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - MenuInflater inflater = mode.getMenuInflater(); - inflater.inflate(R.menu.conversation_context, menu); - - mode.setTitle("1"); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - Window window = getActivity().getWindow(); - statusBarColor = window.getStatusBarColor(); - } - - setCorrectMenuVisibility(menu); - listener.onMessageActionToolbarOpened(); - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { - return false; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - ((ConversationAdapter)list.getAdapter()).clearSelection(); - list.getAdapter().notifyDataSetChanged(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - getActivity().getWindow().setStatusBarColor(statusBarColor); - } - - actionMode = null; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - switch(item.getItemId()) { - case R.id.menu_context_copy: - handleCopyMessage(getListAdapter().getSelectedItems()); - actionMode.finish(); - return true; - case R.id.menu_context_copy_public_key: - handleCopyPublicKey((MessageRecord) getListAdapter().getSelectedItems().toArray()[0]); - actionMode.finish(); - return true; - case R.id.menu_context_delete_message: - handleDeleteMessages(getListAdapter().getSelectedItems()); - actionMode.finish(); - return true; - case R.id.menu_context_ban_user: - handleBanUser(getListAdapter().getSelectedItems()); - return true; - case R.id.menu_context_details: - handleDisplayDetails(getSelectedMessageRecord()); - actionMode.finish(); - return true; -// case R.id.menu_context_forward: -// handleForwardMessage(getSelectedMessageRecord()); -// actionMode.finish(); -// return true; - case R.id.menu_context_resend: - handleResendMessage(getSelectedMessageRecord()); - actionMode.finish(); - return true; - case R.id.menu_context_save_attachment: - handleSaveAttachment((MediaMmsMessageRecord)getSelectedMessageRecord()); - actionMode.finish(); - return true; - case R.id.menu_context_reply: - handleReplyMessage(getSelectedMessageRecord()); - actionMode.finish(); - return true; - } - - return false; - } - } - - private static class ConversationDateHeader extends HeaderViewHolder { - - private final Animation animateIn; - private final Animation animateOut; - - private boolean pendingHide = false; - - private ConversationDateHeader(Context context, TextView textView) { - super(textView); - this.animateIn = AnimationUtils.loadAnimation(context, R.anim.slide_from_top); - this.animateOut = AnimationUtils.loadAnimation(context, R.anim.slide_to_top); - - this.animateIn.setDuration(100); - this.animateOut.setDuration(100); - } - - public void show() { - if (pendingHide) { - pendingHide = false; - } else { - ViewUtil.animateIn(textView, animateIn); - } - } - - public void hide() { - pendingHide = true; - - textView.postDelayed(new Runnable() { - @Override - public void run() { - if (pendingHide) { - pendingHide = false; - ViewUtil.animateOut(textView, animateOut, View.GONE); - } - } - }, 400); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java deleted file mode 100644 index aa60603074..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ /dev/null @@ -1,1203 +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.thoughtcrime.securesms.conversation; - -import android.annotation.SuppressLint; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.Typeface; -import android.net.Uri; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.style.BackgroundColorSpan; -import android.text.style.CharacterStyle; -import android.text.style.ClickableSpan; -import android.text.style.ForegroundColorSpan; -import android.text.style.URLSpan; -import android.text.util.Linkify; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.DimenRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.annimon.stream.Stream; - -import org.session.libsession.messaging.contacts.Contact; -import org.session.libsession.messaging.jobs.AttachmentDownloadJob; -import org.session.libsession.messaging.jobs.JobQueue; -import org.session.libsession.messaging.open_groups.OpenGroupAPIV2; -import org.session.libsession.messaging.open_groups.OpenGroupV2; -import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress; -import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; -import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; -import org.session.libsession.messaging.utilities.UpdateMessageData; -import org.session.libsession.utilities.Stub; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.ThemeUtil; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.RecipientModifiedListener; -import org.session.libsignal.utilities.Log; -import org.session.libsignal.utilities.guava.Optional; -import org.thoughtcrime.securesms.BindableConversationItem; -import org.thoughtcrime.securesms.MediaPreviewActivity; -import org.thoughtcrime.securesms.MessageDetailsActivity; -import org.thoughtcrime.securesms.components.ConversationItemAlertView; -import org.thoughtcrime.securesms.components.ConversationItemFooter; -import org.thoughtcrime.securesms.components.ConversationItemThumbnail; -import org.thoughtcrime.securesms.components.DocumentView; -import org.thoughtcrime.securesms.components.LinkPreviewView; -import org.thoughtcrime.securesms.components.QuoteView; -import org.thoughtcrime.securesms.components.StickerView; -import org.thoughtcrime.securesms.components.emoji.EmojiTextView; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.MmsSmsDatabase; -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.Quote; -import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; -import org.thoughtcrime.securesms.loki.utilities.MentionUtilities; -import org.thoughtcrime.securesms.loki.views.MessageAudioView; -import org.thoughtcrime.securesms.loki.views.OpenGroupInvitationView; -import org.thoughtcrime.securesms.loki.views.ProfilePictureView; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.mms.ImageSlide; -import org.thoughtcrime.securesms.mms.PartAuthority; -import org.thoughtcrime.securesms.mms.Slide; -import org.thoughtcrime.securesms.mms.SlideClickListener; -import org.thoughtcrime.securesms.mms.SlidesClickedListener; -import org.thoughtcrime.securesms.mms.TextSlide; -import org.thoughtcrime.securesms.util.DateUtils; -import org.thoughtcrime.securesms.util.LongClickCopySpan; -import org.thoughtcrime.securesms.util.LongClickMovementMethod; -import org.thoughtcrime.securesms.util.SearchUtil; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -import network.loki.messenger.R; - -/** - * A view that displays an individual conversation item within a conversation - * thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter. - * - * @author Moxie Marlinspike - * - */ - -public class ConversationItem extends LinearLayout - implements RecipientModifiedListener, BindableConversationItem -{ - private static final String TAG = ConversationItem.class.getSimpleName(); - - private static final int MAX_MEASURE_CALLS = 3; - private static final int MAX_BODY_DISPLAY_LENGTH = 1000; - - private MessageRecord messageRecord; - private Locale locale; - private boolean groupThread; - private Recipient recipient; - private GlideRequests glideRequests; - - protected ViewGroup bodyBubble; - private QuoteView quoteView; - private EmojiTextView bodyText; - private ConversationItemFooter footer; - private ConversationItemFooter stickerFooter; - private TextView groupSender; - private TextView groupSenderProfileName; - private View groupSenderHolder; - private ProfilePictureView profilePictureView; - private ImageView moderatorIconImageView; - private ViewGroup contactPhotoHolder; - private ConversationItemAlertView alertView; - private ViewGroup container; - - private @NonNull Set batchSelected = new HashSet<>(); - private Recipient conversationRecipient; - private Stub mediaThumbnailStub; - private Stub audioViewStub; - private Stub documentViewStub; - private Stub linkPreviewStub; - private Stub stickerStub; - private Stub openGroupInvitationViewStub; - private @Nullable EventListener eventListener; - - private int defaultBubbleColor; - private int measureCalls; - - private final PassthroughClickListener passthroughClickListener = new PassthroughClickListener(); - private final AttachmentDownloadClickListener downloadClickListener = new AttachmentDownloadClickListener(); - private final SlideClickPassthroughListener singleDownloadClickListener = new SlideClickPassthroughListener(downloadClickListener); - private final LinkPreviewClickListener linkPreviewClickListener = new LinkPreviewClickListener(); - - private final Context context; - - public ConversationItem(Context context) { - this(context, null); - } - - public ConversationItem(Context context, AttributeSet attrs) { - super(context, attrs); - this.context = context; - } - - @Override - public void setOnClickListener(OnClickListener l) { - super.setOnClickListener(new ClickListener(l)); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - initializeAttributes(); - - this.bodyText = findViewById(R.id.conversation_item_body); - this.footer = findViewById(R.id.conversation_item_footer); - this.stickerFooter = findViewById(R.id.conversation_item_sticker_footer); - this.groupSender = findViewById(R.id.group_message_sender); - this.groupSenderProfileName = findViewById(R.id.group_message_sender_profile); - this.alertView = findViewById(R.id.indicators_parent); - this.profilePictureView = findViewById(R.id.profilePictureView); - this.moderatorIconImageView = findViewById(R.id.moderator_icon_image_view); - this.contactPhotoHolder = findViewById(R.id.contact_photo_container); - this.bodyBubble = findViewById(R.id.body_bubble); - this.mediaThumbnailStub = new Stub<>(findViewById(R.id.image_view_stub)); - this.audioViewStub = new Stub<>(findViewById(R.id.audio_view_stub)); - this.documentViewStub = new Stub<>(findViewById(R.id.document_view_stub)); - this.linkPreviewStub = new Stub<>(findViewById(R.id.link_preview_stub)); - this.stickerStub = new Stub<>(findViewById(R.id.sticker_view_stub)); - this.openGroupInvitationViewStub = new Stub<>(findViewById(R.id.open_group_invitation_stub)); - this.groupSenderHolder = findViewById(R.id.group_sender_holder); - this.quoteView = findViewById(R.id.quote_view); - this.container = findViewById(R.id.container); - - setOnClickListener(new ClickListener(null)); - - bodyText.setOnLongClickListener(passthroughClickListener); - bodyText.setOnClickListener(passthroughClickListener); - - bodyText.setMovementMethod(LongClickMovementMethod.getInstance(getContext())); - } - - @Override - public void bind(@NonNull MessageRecord messageRecord, - @NonNull Optional previousMessageRecord, - @NonNull Optional nextMessageRecord, - @NonNull GlideRequests glideRequests, - @NonNull Locale locale, - @NonNull Set batchSelected, - @NonNull Recipient conversationRecipient, - @Nullable String searchQuery, - boolean pulseHighlight) - { - this.messageRecord = messageRecord; - this.locale = locale; - this.glideRequests = glideRequests; - this.batchSelected = batchSelected; - this.conversationRecipient = conversationRecipient; - this.groupThread = conversationRecipient.isGroupRecipient(); - this.recipient = messageRecord.getIndividualRecipient(); - - this.recipient.addListener(this); - this.conversationRecipient.addListener(this); - - setGutterSizes(messageRecord, groupThread); - setMessageShape(messageRecord, previousMessageRecord, nextMessageRecord, groupThread); - setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, conversationRecipient, groupThread); - setInteractionState(messageRecord, pulseHighlight); - setBodyText(messageRecord, searchQuery, groupThread); - setBubbleState(messageRecord); - setStatusIcons(messageRecord); - setContactPhoto(recipient); - setGroupMessageStatus(messageRecord, recipient); - setGroupAuthorColor(messageRecord); - setAuthor(messageRecord, previousMessageRecord, nextMessageRecord, groupThread); - setQuote(messageRecord, previousMessageRecord, nextMessageRecord, groupThread); - setMessageSpacing(context, messageRecord, previousMessageRecord, nextMessageRecord, groupThread); - setFooter(messageRecord, nextMessageRecord, locale, groupThread); - adjustMarginsIfNeeded(messageRecord); - } - - @Override - public void setEventListener(@Nullable EventListener eventListener) { - this.eventListener = eventListener; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (isInEditMode()) { - return; - } - - boolean needsMeasure = false; - - if (hasQuote(messageRecord)) { - int quoteWidth = quoteView.getMeasuredWidth(); - int availableWidth = getAvailableMessageBubbleWidth(quoteView); - - if (quoteWidth != availableWidth) { - quoteView.getLayoutParams().width = availableWidth; - needsMeasure = true; - } - } - - if (hasThumbnail(messageRecord) && messageRecord.getDisplayBody(context).length() > 0) { - ViewUtil.updateLayoutParams(bodyText, getAvailableMessageBubbleWidth(bodyText), ViewGroup.LayoutParams.WRAP_CONTENT); - } - - ConversationItemFooter activeFooter = getActiveFooter(messageRecord); - int availableWidth = getAvailableMessageBubbleWidth(footer); - - if (activeFooter.getVisibility() != GONE && activeFooter.getMeasuredWidth() != availableWidth) { - activeFooter.getLayoutParams().width = availableWidth; - needsMeasure = true; - } - - if (needsMeasure) { - if (measureCalls < MAX_MEASURE_CALLS) { - measureCalls++; - measure(widthMeasureSpec, heightMeasureSpec); - } else { - Log.w(TAG, "Hit measure() cap of " + MAX_MEASURE_CALLS); - } - } else { - measureCalls = 0; - } - } - - private int getAvailableMessageBubbleWidth(@NonNull View forView) { - int availableWidth; - if (hasAudio(messageRecord)) { - availableWidth = audioViewStub.get().getMeasuredWidth() + ViewUtil.getLeftMargin(audioViewStub.get()) + ViewUtil.getRightMargin(audioViewStub.get()); - } else if (hasThumbnail(messageRecord) || hasBigImageLinkPreview(messageRecord)) { - availableWidth = mediaThumbnailStub.get().getMeasuredWidth(); - } else { - availableWidth = bodyBubble.getMeasuredWidth() - bodyBubble.getPaddingLeft() - bodyBubble.getPaddingRight(); - } - - availableWidth -= ViewUtil.getLeftMargin(forView) + ViewUtil.getRightMargin(forView); - - return availableWidth; - } - - private void initializeAttributes() { - final int[] attributes = new int[] {R.attr.conversation_item_bubble_background}; - final TypedArray attrs = context.obtainStyledAttributes(attributes); - - defaultBubbleColor = attrs.getColor(0, Color.WHITE); - attrs.recycle(); - } - - @Override - public void unbind() { - if (recipient != null) { - recipient.removeListener(this); - } - if (profilePictureView != null) { - profilePictureView.recycle(); - } - } - - public MessageRecord getMessageRecord() { - return messageRecord; - } - - /// MessageRecord Attribute Parsers - - private void setBubbleState(MessageRecord messageRecord) { - int bubbleColor = ThemeUtil.getThemedColor(getContext(), messageRecord.isOutgoing() ? - R.attr.message_sent_background_color : - R.attr.message_received_background_color); - bodyBubble.getBackground().setColorFilter(bubbleColor, PorterDuff.Mode.MULTIPLY); - - if (audioViewStub.resolved()) { - setAudioViewTint(messageRecord, this.conversationRecipient); - } - } - - private void setAudioViewTint(MessageRecord messageRecord, Recipient recipient) { -// audioViewStub.get().setTint(Color.WHITE, getResources().getColor(R.color.action_bar_background)); - } - - private void setInteractionState(MessageRecord messageRecord, boolean pulseHighlight) { - if (batchSelected.contains(messageRecord)) { - setBackgroundResource(R.drawable.conversation_item_background); - setSelected(true); - } else if (pulseHighlight) { - setBackgroundResource(R.drawable.conversation_item_background_animated); - setSelected(true); - postDelayed(() -> setSelected(false), 500); - } else { - setSelected(false); - } - - if (mediaThumbnailStub.resolved()) { - mediaThumbnailStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty()); - mediaThumbnailStub.get().setClickable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty()); - mediaThumbnailStub.get().setLongClickable(batchSelected.isEmpty()); - } - - if (audioViewStub.resolved()) { - audioViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty()); - audioViewStub.get().setClickable(batchSelected.isEmpty()); - audioViewStub.get().setEnabled(batchSelected.isEmpty()); - } - - if (documentViewStub.resolved()) { - documentViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty()); - documentViewStub.get().setClickable(batchSelected.isEmpty()); - } - } - - private boolean isCaptionlessMms(MessageRecord messageRecord) { - return TextUtils.isEmpty(messageRecord.getDisplayBody(getContext())) && messageRecord.isMms() && ((MmsMessageRecord) messageRecord).getSlideDeck().getTextSlide() == null; - } - - private boolean hasAudio(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getAudioSlide() != null; - } - - private boolean hasThumbnail(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide() != null; - } - - private boolean hasOnlyThumbnail(MessageRecord messageRecord) { - return hasThumbnail(messageRecord) && - !hasAudio(messageRecord) && - !hasDocument(messageRecord); - } - - private boolean hasOnlyDocument(MessageRecord messageRecord) { - return messageRecord.getBody().length() == 0 && - !hasThumbnail(messageRecord) && - !hasAudio(messageRecord) && - hasDocument(messageRecord) && - !hasQuote(messageRecord); - } - - private boolean hasOnlyText(MessageRecord messageRecord) { - return messageRecord.getBody().length() != 0 && - !hasThumbnail(messageRecord) && - !hasAudio(messageRecord) && - !hasDocument(messageRecord) && - !hasQuote(messageRecord); - } - - private boolean hasDocument(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getDocumentSlide() != null; - } - - private boolean hasExtraText(MessageRecord messageRecord) { - boolean hasTextSlide = messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getTextSlide() != null; - boolean hasOverflowText = messageRecord.getBody().length() > MAX_BODY_DISPLAY_LENGTH; - - return hasTextSlide || hasOverflowText; - } - - private boolean hasQuote(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getQuote() != null; - } - - private boolean hasLinkPreview(MessageRecord messageRecord) { - return messageRecord.isMms() && !((MmsMessageRecord)messageRecord).getLinkPreviews().isEmpty(); - } - - private boolean hasBigImageLinkPreview(MessageRecord messageRecord) { - if (!hasLinkPreview(messageRecord)) return false; - - LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0); - int minWidth = getResources().getDimensionPixelSize(R.dimen.media_bubble_min_width); - - return linkPreview.getThumbnail().isPresent() && - linkPreview.getThumbnail().get().getWidth() >= minWidth; - } - - private void setBodyText(MessageRecord messageRecord, @Nullable String searchQuery, boolean isGroupThread) { - bodyText.setClickable(false); - bodyText.setFocusable(false); - bodyText.setTextSize(TypedValue.COMPLEX_UNIT_SP, TextSecurePreferences.getMessageBodyTextSize(context)); - if (isCaptionlessMms(messageRecord)) { - bodyText.setVisibility(View.GONE); - } else { - Spannable text = MentionUtilities.highlightMentions(linkifyMessageBody(messageRecord.getDisplayBody(context), batchSelected.isEmpty()), messageRecord.isOutgoing(), messageRecord.getThreadId(), context); - text = SearchUtil.getHighlightedSpan(locale, () -> new BackgroundColorSpan(Color.WHITE), text, searchQuery); - text = SearchUtil.getHighlightedSpan(locale, () -> new ForegroundColorSpan(Color.BLACK), text, searchQuery); - - if (hasExtraText(messageRecord)) { - bodyText.setOverflowText(getLongMessageSpan(messageRecord)); - } else { - bodyText.setOverflowText(null); - } - - if (!messageRecord.isOpenGroupInvitation()) - bodyText.setText(text); - - bodyText.setVisibility(View.VISIBLE); - } - } - - private void adjustMarginsIfNeeded(MessageRecord messageRecord) { - LinearLayout.LayoutParams bodyTextLayoutParams = (LinearLayout.LayoutParams)bodyText.getLayoutParams(); - bodyTextLayoutParams.topMargin = 0; - if (hasOnlyThumbnail(messageRecord) || hasLinkPreview(messageRecord)) { - int topPadding = 0; - if (groupSenderHolder.getVisibility() == VISIBLE) { - topPadding = (int)getResources().getDimension(R.dimen.medium_spacing); - } - int bottomPadding = 0; - if (messageRecord.getBody().length() > 0) { - bodyTextLayoutParams.topMargin = (int)getResources().getDimension(R.dimen.medium_spacing); - bottomPadding = (int)getResources().getDimension(R.dimen.medium_spacing); - } - bodyBubble.setPadding(0, topPadding, 0, bottomPadding); - } else { - bodyBubble.setPadding(0, (int)getResources().getDimension(R.dimen.medium_spacing), 0, (int)getResources().getDimension(R.dimen.medium_spacing)); - } - bodyText.setLayoutParams(bodyTextLayoutParams); - LinearLayout.LayoutParams senderHolderLayoutParams = (LinearLayout.LayoutParams)groupSenderHolder.getLayoutParams(); - if (groupSenderHolder.getVisibility() == VISIBLE && hasOnlyText(messageRecord)) { - senderHolderLayoutParams.bottomMargin = (int)(getResources().getDisplayMetrics().density * 4); - } else { - senderHolderLayoutParams.bottomMargin = (int)getResources().getDimension(R.dimen.medium_spacing); - } - groupSenderHolder.setLayoutParams(senderHolderLayoutParams); - if (documentViewStub.resolved()) { - LinearLayout.LayoutParams documentViewLayoutParams = (LinearLayout.LayoutParams)documentViewStub.get().getLayoutParams(); - int bottomMargin = 0; - if (hasOnlyDocument(messageRecord)) { - if (footer.getVisibility() == VISIBLE) { - bottomMargin = (int)(4 * getResources().getDisplayMetrics().density); - } else { - bottomMargin = (int)(-4 * getResources().getDisplayMetrics().density); - } - } else { - bottomMargin = (int)(4 * getResources().getDisplayMetrics().density); - } - documentViewLayoutParams.bottomMargin = bottomMargin; - documentViewStub.get().setLayoutParams(documentViewLayoutParams); - } - } - - private void setMediaAttributes(@NonNull MessageRecord messageRecord, - @NonNull Optional previousRecord, - @NonNull Optional nextRecord, - @NonNull Recipient conversationRecipient, - boolean isGroupThread) - { - boolean showControls = !messageRecord.isFailed(); - - if (hasLinkPreview(messageRecord)) { - linkPreviewStub.get().setVisibility(View.VISIBLE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - if (openGroupInvitationViewStub.resolved()) openGroupInvitationViewStub.get().setVisibility(View.GONE); - - //noinspection ConstantConditions - LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0); - - if (hasBigImageLinkPreview(messageRecord)) { - mediaThumbnailStub.get().setVisibility(VISIBLE); - mediaThumbnailStub.get().setImageResource(glideRequests, Collections.singletonList(new ImageSlide(context, linkPreview.getThumbnail().get())), showControls, false); - mediaThumbnailStub.get().setThumbnailClickListener(new LinkPreviewThumbnailClickListener()); - mediaThumbnailStub.get().setDownloadClickListener(downloadClickListener); - mediaThumbnailStub.get().setOnLongClickListener(passthroughClickListener); - - linkPreviewStub.get().setLinkPreview(glideRequests, linkPreview, false, false); - - setThumbnailCorners(messageRecord, previousRecord, nextRecord, isGroupThread); - setLinkPreviewCorners(messageRecord, previousRecord, nextRecord, isGroupThread, true); - - ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - } else { - linkPreviewStub.get().setLinkPreview(glideRequests, linkPreview, true, false); - linkPreviewStub.get().setDownloadClickedListener(downloadClickListener); - setLinkPreviewCorners(messageRecord, previousRecord, nextRecord, isGroupThread, false); - ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - } - - linkPreviewStub.get().setOnClickListener(linkPreviewClickListener); - linkPreviewStub.get().setOnLongClickListener(passthroughClickListener); - - footer.setVisibility(VISIBLE); - } else if (hasAudio(messageRecord)) { - audioViewStub.get().setVisibility(View.VISIBLE); - if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - if (openGroupInvitationViewStub.resolved()) openGroupInvitationViewStub.get().setVisibility(View.GONE); - - //noinspection ConstantConditions - audioViewStub.get().setAudio(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getAudioSlide(), showControls); - audioViewStub.get().setDownloadClickListener(singleDownloadClickListener); - audioViewStub.get().setOnLongClickListener(passthroughClickListener); - - ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - footer.setVisibility(VISIBLE); - } else if (hasDocument(messageRecord)) { - documentViewStub.get().setVisibility(View.VISIBLE); - if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - if (openGroupInvitationViewStub.resolved()) openGroupInvitationViewStub.get().setVisibility(View.GONE); - - //noinspection ConstantConditions - documentViewStub.get().setDocument(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getDocumentSlide(), showControls); - documentViewStub.get().setDocumentClickListener(new ThumbnailClickListener()); - documentViewStub.get().setDownloadClickListener(singleDownloadClickListener); - documentViewStub.get().setOnLongClickListener(passthroughClickListener); - - ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - footer.setVisibility(VISIBLE); - } else if (hasThumbnail(messageRecord)) { - mediaThumbnailStub.get().setVisibility(View.VISIBLE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - if (openGroupInvitationViewStub.resolved()) openGroupInvitationViewStub.get().setVisibility(View.GONE); - - //noinspection ConstantConditions - List thumbnailSlides = ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlides(); - mediaThumbnailStub.get().setImageResource(glideRequests, - thumbnailSlides, - showControls, - false); - mediaThumbnailStub.get().setThumbnailClickListener(new ThumbnailClickListener()); - mediaThumbnailStub.get().setDownloadClickListener(downloadClickListener); - mediaThumbnailStub.get().setOnLongClickListener(passthroughClickListener); - mediaThumbnailStub.get().setOnClickListener(passthroughClickListener); - mediaThumbnailStub.get().showShade(TextUtils.isEmpty(messageRecord.getDisplayBody(getContext())) && !hasExtraText(messageRecord)); - mediaThumbnailStub.get().setConversationColor(messageRecord.isOutgoing() ? defaultBubbleColor - : messageRecord.getRecipient().getColor().toConversationColor(context)); - mediaThumbnailStub.get().setBorderless(false); - - setThumbnailCorners(messageRecord, previousRecord, nextRecord, isGroupThread); - - ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - footer.setVisibility(VISIBLE); - } else if (messageRecord.isOpenGroupInvitation()) { - openGroupInvitationViewStub.get().setVisibility(View.VISIBLE); - if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - - UpdateMessageData updateMessageData = UpdateMessageData.Companion.fromJSON(messageRecord.getBody()); - String name = null, url = null; - if (updateMessageData.getKind() instanceof UpdateMessageData.Kind.OpenGroupInvitation) { - UpdateMessageData.Kind.OpenGroupInvitation data = (UpdateMessageData.Kind.OpenGroupInvitation)updateMessageData.getKind(); - name = data.getGroupName(); - url = data.getGroupUrl(); - } - - openGroupInvitationViewStub.get().setOpenGroup(name, url, messageRecord.isOutgoing()); - openGroupInvitationViewStub.get().setOnLongClickListener(passthroughClickListener); - - bodyText.setVisibility(View.GONE); - - ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - } else { - if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - - ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - footer.setVisibility(VISIBLE); - } - } - - private void setThumbnailCorners(@NonNull MessageRecord current, - @NonNull Optional previous, - @NonNull Optional next, - boolean isGroupThread) - { - int defaultRadius = readDimen(R.dimen.message_corner_radius); - int collapseRadius = readDimen(R.dimen.message_corner_collapse_radius); - - int topLeft = defaultRadius; - int topRight = defaultRadius; - int bottomLeft = defaultRadius; - int bottomRight = defaultRadius; - - if (isSingularMessage(current, previous, next, isGroupThread)) { - topLeft = defaultRadius; - topRight = defaultRadius; - bottomLeft = defaultRadius; - bottomRight = defaultRadius; - } else if (isStartOfMessageCluster(current, previous, isGroupThread)) { - if (current.isOutgoing()) { - bottomRight = collapseRadius; - } else { - bottomLeft = collapseRadius; - } - } else if (isEndOfMessageCluster(current, next, isGroupThread)) { - if (current.isOutgoing()) { - topRight = collapseRadius; - } else { - topLeft = collapseRadius; - } - } else { - if (current.isOutgoing()) { - topRight = collapseRadius; - bottomRight = collapseRadius; - } else { - topLeft = collapseRadius; - bottomLeft = collapseRadius; - } - } - - if (!TextUtils.isEmpty(current.getDisplayBody(getContext()))) { - bottomLeft = 0; - bottomRight = 0; - } - - if (isStartOfMessageCluster(current, previous, isGroupThread) && !current.isOutgoing() && isGroupThread) { - topLeft = 0; - topRight = 0; - } - - if (hasQuote(messageRecord)) { - topLeft = 0; - topRight = 0; - } - - if (hasLinkPreview(messageRecord) || hasExtraText(messageRecord)) { - bottomLeft = 0; - bottomRight = 0; - } - - mediaThumbnailStub.get().setCorners(topLeft, topRight, bottomRight, bottomLeft); - } - private void setLinkPreviewCorners(@NonNull MessageRecord current, @NonNull Optional previous, @NonNull Optional next, boolean isGroupThread, boolean bigImage) { - int defaultRadius = readDimen(R.dimen.message_corner_radius); - int collapseRadius = readDimen(R.dimen.message_corner_collapse_radius); - - if (bigImage) { - linkPreviewStub.get().setCorners(0, 0); - } else if (isStartOfMessageCluster(current, previous, isGroupThread) && !current.isOutgoing() && isGroupThread) { - linkPreviewStub.get().setCorners(0, 0); - } else if (isSingularMessage(current, previous, next, isGroupThread) || isStartOfMessageCluster(current, previous, isGroupThread)) { - linkPreviewStub.get().setCorners(defaultRadius, defaultRadius); - } else if (current.isOutgoing()) { - linkPreviewStub.get().setCorners(defaultRadius, collapseRadius); - } else { - linkPreviewStub.get().setCorners(collapseRadius, defaultRadius); - } - } - - private void setContactPhoto(@NonNull Recipient recipient) { - if (messageRecord == null) { return; } // TODO: Figure out how this happens - LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)bodyBubble.getLayoutParams(); - int groupThreadMargin = (int)((12 * getResources().getDisplayMetrics().density) + getResources().getDimension(R.dimen.small_profile_picture_size)); - int defaultMargin = 0; - long threadID = messageRecord.getThreadId(); - Recipient r = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadID); - String threadName = r != null ? r.getName() : ""; - boolean isRSSFeed = threadName != null && (threadName.equals("Loki News") || threadName.equals("Session Updates")); - layoutParams.setMarginStart((groupThread && !isRSSFeed) ? groupThreadMargin : defaultMargin); - bodyBubble.setLayoutParams(layoutParams); - if (profilePictureView == null) { return; } - String publicKey = recipient.getAddress().toString(); - profilePictureView.setPublicKey(publicKey); - String displayName = recipient.getName(); - profilePictureView.setDisplayName(displayName); - profilePictureView.setAdditionalPublicKey(null); - profilePictureView.setGlide(glideRequests); - profilePictureView.update(); - } - - private SpannableString linkifyMessageBody(SpannableString messageBody, boolean shouldLinkifyAllLinks) { - int linkPattern = Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS; - boolean hasLinks = Linkify.addLinks(messageBody, shouldLinkifyAllLinks ? linkPattern : 0); - - if (hasLinks) { - Stream.of(messageBody.getSpans(0, messageBody.length(), URLSpan.class)) - .filterNot(url -> LinkPreviewUtil.isLegalUrl(url.getURL())) - .forEach(messageBody::removeSpan); - - URLSpan[] urlSpans = messageBody.getSpans(0, messageBody.length(), URLSpan.class); - - for (URLSpan urlSpan : urlSpans) { - int start = messageBody.getSpanStart(urlSpan); - int end = messageBody.getSpanEnd(urlSpan); - messageBody.setSpan(new LongClickCopySpan(urlSpan.getURL()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - return messageBody; - } - - private void setStatusIcons(MessageRecord messageRecord) { - bodyText.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - - if (messageRecord.isFailed()) { - alertView.setFailed(); - } else { - alertView.setNone(); - } - } - - private void setQuote(@NonNull MessageRecord current, @NonNull Optional previous, @NonNull Optional next, boolean isGroupThread) { - if (current.isMms() && !current.isMmsNotification() && ((MediaMmsMessageRecord)current).getQuote() != null) { - Quote quote = ((MediaMmsMessageRecord)current).getQuote(); - //noinspection ConstantConditions - String quoteBody = MentionUtilities.highlightMentions(quote.getText(), current.getThreadId(), context); - quoteView.setQuote(glideRequests, quote.getId(), Recipient.from(context, quote.getAuthor(), true), quoteBody, quote.isOriginalMissing(), quote.getAttachment(), conversationRecipient); - quoteView.setVisibility(View.VISIBLE); - quoteView.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT; - - quoteView.setOnClickListener(view -> { - if (eventListener != null && batchSelected.isEmpty()) { - eventListener.onQuoteClicked((MmsMessageRecord) current); - } else { - passthroughClickListener.onClick(view); - } - }); - - quoteView.setOnLongClickListener(passthroughClickListener); - - if (isStartOfMessageCluster(current, previous, isGroupThread)) { - if (current.isOutgoing()) { - quoteView.setTopCornerSizes(true, true); - } else if (isGroupThread) { - quoteView.setTopCornerSizes(false, false); - } else { - quoteView.setTopCornerSizes(true, true); - } - } else if (!isSingularMessage(current, previous, next, isGroupThread)) { - if (current.isOutgoing()) { - quoteView.setTopCornerSizes(true, false); - } else { - quoteView.setTopCornerSizes(false, true); - } - } - - if (mediaThumbnailStub.resolved()) { - ViewUtil.setTopMargin(mediaThumbnailStub.get(), readDimen(R.dimen.message_bubble_top_padding)); - } - } else { - quoteView.dismiss(); - - if (mediaThumbnailStub.resolved()) { - ViewUtil.setTopMargin(mediaThumbnailStub.get(), 0); - } - } - } - - private void setGutterSizes(@NonNull MessageRecord current, boolean isGroupThread) { - if (isGroupThread && current.isOutgoing()) { - ViewUtil.setLeftMargin(container, readDimen(R.dimen.conversation_group_left_gutter)); - } else if (current.isOutgoing()) { - ViewUtil.setLeftMargin(container, readDimen(R.dimen.conversation_individual_left_gutter)); - } - } - - private void setFooter(@NonNull MessageRecord current, @NonNull Optional next, @NonNull Locale locale, boolean isGroupThread) { - ViewUtil.updateLayoutParams(footer, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - - footer.setVisibility(GONE); - stickerFooter.setVisibility(GONE); - if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().getFooter().setVisibility(GONE); - - boolean differentTimestamps = next.isPresent() && !DateUtils.isSameExtendedRelativeTimestamp(context, locale, next.get().getTimestamp(), current.getTimestamp()); - - if (current.getExpiresIn() > 0 || current.isPending() || - current.isFailed() || differentTimestamps || isEndOfMessageCluster(current, next, isGroupThread)) - { - ConversationItemFooter activeFooter = getActiveFooter(current); - activeFooter.setVisibility(VISIBLE); - activeFooter.setMessageRecord(current, locale); - } - } - - private ConversationItemFooter getActiveFooter(@NonNull MessageRecord messageRecord) { - if (hasOnlyThumbnail(messageRecord) && TextUtils.isEmpty(messageRecord.getDisplayBody(getContext()))) { - return mediaThumbnailStub.get().getFooter(); - } else { - return footer; - } - } - - private int readDimen(@DimenRes int dimenId) { - return context.getResources().getDimensionPixelOffset(dimenId); - } - - private boolean shouldInterceptClicks(MessageRecord messageRecord) { - return batchSelected.isEmpty() && (messageRecord.isFailed() && !messageRecord.isMmsNotification()); - } - - @SuppressLint("SetTextI18n") - private void setGroupMessageStatus(MessageRecord messageRecord, Recipient recipient) { - if (groupThread && !messageRecord.isOutgoing()) { - String sessionID = recipient.getAddress().serialize(); - Contact contact = DatabaseFactory.getSessionContactDatabase(context).getContactWithSessionID(sessionID); - String displayName; - if (contact != null) { - Contact.ContactContext context = (this.conversationRecipient.isOpenGroupRecipient()) ? Contact.ContactContext.OPEN_GROUP : Contact.ContactContext.REGULAR; - displayName = contact.displayName(context); - } else { - displayName = sessionID; - } - - this.groupSender.setText(displayName); - - if (recipient.getName() == null && !TextUtils.isEmpty(recipient.getProfileName())) { - this.groupSenderProfileName.setText("~" + recipient.getProfileName()); - this.groupSenderProfileName.setVisibility(View.VISIBLE); - } else { - this.groupSenderProfileName.setText(null); - this.groupSenderProfileName.setVisibility(View.GONE); - } - } - } - - private void setGroupAuthorColor(@NonNull MessageRecord messageRecord) { - groupSender.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_received_text_primary_color)); - groupSenderProfileName.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_received_text_primary_color)); - } - - private void setAuthor(@NonNull MessageRecord current, @NonNull Optional previous, @NonNull Optional next, boolean isGroupThread) { - Recipient recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(current.getThreadId()); - String threadName = null; - if (recipient != null) { - threadName = recipient.getName(); - } - boolean isRSSFeed = threadName != null && (threadName.equals("Loki News") || threadName.equals("Session Updates")); - if (isGroupThread && !isRSSFeed && !current.isOutgoing()) { - contactPhotoHolder.setVisibility(VISIBLE); - - if (!previous.isPresent() || previous.get().isUpdate() || !current.getRecipient().getAddress().equals(previous.get().getRecipient().getAddress()) || - !DateUtils.isSameDay(previous.get().getTimestamp(), current.getTimestamp())) - { - groupSenderHolder.setVisibility(VISIBLE); - } else { - groupSenderHolder.setVisibility(GONE); - } - - if (!previous.isPresent() || previous.get().isUpdate() || !current.getRecipient().getAddress().equals(previous.get().getRecipient().getAddress())) { - profilePictureView.setVisibility(VISIBLE); - int visibility = View.GONE; - - OpenGroupV2 openGroupV2 = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(messageRecord.getThreadId()); - if (openGroupV2 != null) { - boolean isModerator = OpenGroupAPIV2.isUserModerator(current.getRecipient().getAddress().toString(), openGroupV2.getRoom(), openGroupV2.getServer()); - visibility = isModerator ? View.VISIBLE : View.GONE; - } - - moderatorIconImageView.setVisibility(visibility); - } else { - profilePictureView.setVisibility(GONE); - moderatorIconImageView.setVisibility(GONE); - - } - } else { - groupSenderHolder.setVisibility(GONE); - - if (contactPhotoHolder != null) { - contactPhotoHolder.setVisibility(GONE); - moderatorIconImageView.setVisibility(GONE); - } - } - } - - private void setMessageShape(@NonNull MessageRecord current, @NonNull Optional previous, @NonNull Optional next, boolean isGroupThread) { - int background; - if (isSingularMessage(current, previous, next, isGroupThread)) { - background = current.isOutgoing() ? R.drawable.message_bubble_background_sent_alone : R.drawable.message_bubble_background_received_alone; - } else if (isStartOfMessageCluster(current, previous, isGroupThread)) { - background = current.isOutgoing() ? R.drawable.message_bubble_background_sent_start : R.drawable.message_bubble_background_received_start; - } else if (isEndOfMessageCluster(current, next, isGroupThread)) { - background = current.isOutgoing() ? R.drawable.message_bubble_background_sent_end : R.drawable.message_bubble_background_received_end; - } else { - background = current.isOutgoing() ? R.drawable.message_bubble_background_sent_middle : R.drawable.message_bubble_background_received_middle; - } - - bodyBubble.setBackgroundResource(background); - } - - private boolean isStartOfMessageCluster(@NonNull MessageRecord current, @NonNull Optional previous, boolean isGroupThread) { - if (isGroupThread) { - return !previous.isPresent() || previous.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), previous.get().getTimestamp()) || - !current.getRecipient().getAddress().equals(previous.get().getRecipient().getAddress()); - } else { - return !previous.isPresent() || previous.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), previous.get().getTimestamp()) || - current.isOutgoing() != previous.get().isOutgoing(); - } - } - - private boolean isEndOfMessageCluster(@NonNull MessageRecord current, @NonNull Optional next, boolean isGroupThread) { - if (isGroupThread) { - return !next.isPresent() || next.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), next.get().getTimestamp()) || - !current.getRecipient().getAddress().equals(next.get().getRecipient().getAddress()); - } else { - return !next.isPresent() || next.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), next.get().getTimestamp()) || - current.isOutgoing() != next.get().isOutgoing(); - } - } - - private boolean isSingularMessage(@NonNull MessageRecord current, @NonNull Optional previous, @NonNull Optional next, boolean isGroupThread) { - return isStartOfMessageCluster(current, previous, isGroupThread) && isEndOfMessageCluster(current, next, isGroupThread); - } - - private void setMessageSpacing(@NonNull Context context, @NonNull MessageRecord current, @NonNull Optional previous, @NonNull Optional next, boolean isGroupThread) { - int spacingTop = readDimen(context, R.dimen.conversation_vertical_message_spacing_collapse); - int spacingBottom = spacingTop; - - if (isStartOfMessageCluster(current, previous, isGroupThread)) { - spacingTop = readDimen(context, R.dimen.conversation_vertical_message_spacing_default); - } - - if (isEndOfMessageCluster(current, next, isGroupThread)) { - spacingBottom = readDimen(context, R.dimen.conversation_vertical_message_spacing_default); - } - - ViewUtil.setPaddingTop(this, spacingTop); - ViewUtil.setPaddingBottom(this, spacingBottom); - } - - private int readDimen(@NonNull Context context, @DimenRes int dimenId) { - return context.getResources().getDimensionPixelOffset(dimenId); - } - - /// Event handlers - - private Spannable getLongMessageSpan(@NonNull MessageRecord messageRecord) { - String message; - Runnable action; - - if (messageRecord.isMms()) { - TextSlide slide = ((MmsMessageRecord) messageRecord).getSlideDeck().getTextSlide(); - - if (slide != null && slide.asAttachment().getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE) { - message = getResources().getString(R.string.ConversationItem_read_more); - action = () -> eventListener.onMoreTextClicked(conversationRecipient.getAddress(), messageRecord.getId(), messageRecord.isMms()); - } else if (slide != null && slide.asAttachment().getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED) { - message = getResources().getString(R.string.ConversationItem_pending); - action = () -> {}; - } else if (slide != null) { - message = getResources().getString(R.string.ConversationItem_download_more); - action = () -> singleDownloadClickListener.onClick(bodyText, slide); - } else { - message = getResources().getString(R.string.ConversationItem_read_more); - action = () -> eventListener.onMoreTextClicked(conversationRecipient.getAddress(), messageRecord.getId(), messageRecord.isMms()); - } - } else { - message = getResources().getString(R.string.ConversationItem_read_more); - action = () -> eventListener.onMoreTextClicked(conversationRecipient.getAddress(), messageRecord.getId(), messageRecord.isMms()); - } - - SpannableStringBuilder span = new SpannableStringBuilder(message); - CharacterStyle style = new ClickableSpan() { - @Override - public void onClick(@NonNull View widget) { - if (eventListener != null && batchSelected.isEmpty()) { - action.run(); - } - } - - @Override - public void updateDrawState(@NonNull TextPaint ds) { - ds.setTypeface(Typeface.DEFAULT_BOLD); - } - }; - span.setSpan(style, 0, span.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - return span; - } - - @Override - public void onModified(final Recipient modified) { - Util.runOnMain(() -> { - setBubbleState(messageRecord); - setContactPhoto(recipient); - setGroupMessageStatus(messageRecord, recipient); - setAudioViewTint(messageRecord, conversationRecipient); - }); - } - - private class LinkPreviewClickListener implements View.OnClickListener { - @Override - public void onClick(View view) { - if (eventListener != null && batchSelected.isEmpty() && messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getLinkPreviews().isEmpty()) { - eventListener.onLinkPreviewClicked(((MmsMessageRecord) messageRecord).getLinkPreviews().get(0)); - } else { - passthroughClickListener.onClick(view); - } - } - } - - private class LinkPreviewThumbnailClickListener implements SlideClickListener { - public void onClick(final View v, final Slide slide) { - if (eventListener != null && batchSelected.isEmpty() && messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getLinkPreviews().isEmpty()) { - eventListener.onLinkPreviewClicked(((MmsMessageRecord) messageRecord).getLinkPreviews().get(0)); - } else { - performClick(); - } - } - } - - private class AttachmentDownloadClickListener implements SlidesClickedListener { - @Override - public void onClick(View v, final List slides) { - Log.i(TAG, "onClick() for attachment download"); - Log.i(TAG, "Scheduling push attachment downloads for " + slides.size() + " items"); - - for (Slide slide : slides) { - JobQueue.getShared().add( - new AttachmentDownloadJob(messageRecord.getId(), - ((DatabaseAttachment)slide.asAttachment()).getAttachmentId().getRowId()) - ); - } - } - } - - private class SlideClickPassthroughListener implements SlideClickListener { - - private final SlidesClickedListener original; - - private SlideClickPassthroughListener(@NonNull SlidesClickedListener original) { - this.original = original; - } - - @Override - public void onClick(View v, Slide slide) { - original.onClick(v, Collections.singletonList(slide)); - } - } - - private class StickerClickListener implements SlideClickListener { - @Override - public void onClick(View v, Slide slide) { - if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) { - performClick(); - } - } - } - - private class ThumbnailClickListener implements SlideClickListener { - public void onClick(final View v, final Slide slide) { - if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) { - performClick(); - } else if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) { - Intent intent = new Intent(context, MediaPreviewActivity.class); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.setDataAndType(slide.getUri(), slide.getContentType()); - intent.putExtra(MediaPreviewActivity.ADDRESS_EXTRA, conversationRecipient.getAddress()); - intent.putExtra(MediaPreviewActivity.OUTGOING_EXTRA, messageRecord.isOutgoing()); - intent.putExtra(MediaPreviewActivity.DATE_EXTRA, messageRecord.getTimestamp()); - intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, slide.asAttachment().getSize()); - intent.putExtra(MediaPreviewActivity.CAPTION_EXTRA, slide.getCaption().orNull()); - intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, false); - - context.startActivity(intent); - } else if (slide.getUri() != null) { - Log.i(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType()); - Uri publicUri = PartAuthority.getAttachmentPublicUri(slide.getUri()); - Log.i(TAG, "Public URI: " + publicUri); - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.setDataAndType(PartAuthority.getAttachmentPublicUri(slide.getUri()), slide.getContentType()); - try { - context.startActivity(intent); - } catch (ActivityNotFoundException anfe) { - Log.w(TAG, "No activity existed to view the media."); - Toast.makeText(context, R.string.ConversationItem_unable_to_open_media, Toast.LENGTH_LONG).show(); - } - } - } - } - - private class PassthroughClickListener implements View.OnLongClickListener, View.OnClickListener { - - @Override - public boolean onLongClick(View v) { - if (bodyText.hasSelection()) { - return false; - } - performLongClick(); - return true; - } - - @Override - public void onClick(View v) { - performClick(); - } - } - - private class ClickListener implements View.OnClickListener { - private OnClickListener parent; - - ClickListener(@Nullable OnClickListener parent) { - this.parent = parent; - } - - public void onClick(View v) { - if (!shouldInterceptClicks(messageRecord) && parent != null) { - parent.onClick(v); - } else if (messageRecord.isFailed()) { - Intent intent = new Intent(context, MessageDetailsActivity.class); - intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, messageRecord.getId()); - intent.putExtra(MessageDetailsActivity.THREAD_ID_EXTRA, messageRecord.getThreadId()); - intent.putExtra(MessageDetailsActivity.TYPE_EXTRA, messageRecord.isMms() ? MmsSmsDatabase.MMS_TRANSPORT : MmsSmsDatabase.SMS_TRANSPORT); - intent.putExtra(MessageDetailsActivity.IS_PUSH_GROUP_EXTRA, groupThread); - intent.putExtra(MessageDetailsActivity.ADDRESS_EXTRA, conversationRecipient.getAddress()); - context.startActivity(intent); - } - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java deleted file mode 100644 index ece1bb784d..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.thoughtcrime.securesms.conversation; - -import android.content.Intent; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import android.os.Bundle; -import androidx.core.app.ActivityOptionsCompat; -import android.view.Display; -import android.view.Gravity; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.WindowManager; - -import org.session.libsignal.utilities.Log; -import org.session.libsignal.utilities.ListenableFuture; -import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; - -import java.util.concurrent.ExecutionException; - -import network.loki.messenger.R; - -public class ConversationPopupActivity extends ConversationActivity { - - private static final String TAG = ConversationPopupActivity.class.getSimpleName(); - - @Override - protected void onPreCreate() { - super.onPreCreate(); - overridePendingTransition(R.anim.slide_from_top, R.anim.slide_to_top); - } - - @Override - protected void onCreate(Bundle bundle, boolean ready) { - getWindow().setFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND, - WindowManager.LayoutParams.FLAG_DIM_BEHIND); - - WindowManager.LayoutParams params = getWindow().getAttributes(); - params.alpha = 1.0f; - params.dimAmount = 0.1f; - params.gravity = Gravity.TOP; - getWindow().setAttributes(params); - - Display display = getWindowManager().getDefaultDisplay(); - int width = display.getWidth(); - int height = display.getHeight(); - - if (height > width) getWindow().setLayout((int) (width * .85), (int) (height * .5)); - else getWindow().setLayout((int) (width * .7), (int) (height * .75)); - - super.onCreate(bundle, ready); - } - - @Override - protected void onResume() { - super.onResume(); - composeText.requestFocus(); - quickAttachmentToggle.disable(); - } - - @Override - protected void onPause() { - super.onPause(); - if (isFinishing()) overridePendingTransition(R.anim.slide_from_top, R.anim.slide_to_top); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - MenuInflater inflater = this.getMenuInflater(); - menu.clear(); - - inflater.inflate(R.menu.conversation_popup, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_expand: - saveDraft().addListener(new ListenableFuture.Listener() { - @Override - public void onSuccess(Long result) { - ActivityOptionsCompat transition = ActivityOptionsCompat.makeScaleUpAnimation(getWindow().getDecorView(), 0, 0, getWindow().getAttributes().width, getWindow().getAttributes().height); - Intent intent = new Intent(ConversationPopupActivity.this, ConversationActivityV2.class); - intent.putExtra(ConversationActivityV2.ADDRESS, getRecipient().getAddress()); - intent.putExtra(ConversationActivityV2.THREAD_ID, result); - - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { - startActivity(intent, transition.toBundle()); - } else { - startActivity(intent); - overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right); - } - - finish(); - } - - @Override - public void onFailure(ExecutionException e) { - Log.w(TAG, e); - } - }); - return true; - } - - return false; - } - - @Override - protected void initializeActionBar() { - super.initializeActionBar(); - getSupportActionBar().setDisplayHomeAsUpEnabled(false); - } - - @Override - protected void sendComplete(long threadId) { - super.sendComplete(threadId); - finish(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java deleted file mode 100644 index d24539982d..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java +++ /dev/null @@ -1,146 +0,0 @@ -package org.thoughtcrime.securesms.conversation; - -import android.app.Application; -import androidx.lifecycle.AndroidViewModel; -import androidx.lifecycle.LiveData; -import android.content.Context; -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.contacts.ContactAccessor; -import org.thoughtcrime.securesms.database.CursorList; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.search.SearchRepository; -import org.thoughtcrime.securesms.search.model.MessageResult; -import org.thoughtcrime.securesms.util.CloseableLiveData; -import org.session.libsession.utilities.Debouncer; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.concurrent.SignalExecutors; - -import java.io.Closeable; -import java.util.List; - -public class ConversationSearchViewModel extends AndroidViewModel { - - private final SearchRepository searchRepository; - private final CloseableLiveData result; - private final Debouncer debouncer; - - private boolean firstSearch; - private boolean searchOpen; - private String activeQuery; - private long activeThreadId; - - public ConversationSearchViewModel(@NonNull Application application) { - super(application); - Context context = application.getApplicationContext(); - result = new CloseableLiveData<>(); - debouncer = new Debouncer(500); - searchRepository = new SearchRepository(context, - DatabaseFactory.getSearchDatabase(context), - DatabaseFactory.getThreadDatabase(context), - ContactAccessor.getInstance(), - SignalExecutors.SERIAL); - } - - LiveData getSearchResults() { - return result; - } - - void onQueryUpdated(@NonNull String query, long threadId) { - if (firstSearch && query.length() < 2) { - result.postValue(new SearchResult(CursorList.emptyList(), 0)); - return; - } - - if (query.equals(activeQuery)) { - return; - } - - updateQuery(query, threadId); - } - - void onMissingResult() { - if (activeQuery != null) { - updateQuery(activeQuery, activeThreadId); - } - } - - void onMoveUp() { - debouncer.clear(); - - CursorList messages = (CursorList) result.getValue().getResults(); - int position = Math.min(result.getValue().getPosition() + 1, messages.size() - 1); - - result.setValue(new SearchResult(messages, position), false); - } - - void onMoveDown() { - debouncer.clear(); - - CursorList messages = (CursorList) result.getValue().getResults(); - int position = Math.max(result.getValue().getPosition() - 1, 0); - - result.setValue(new SearchResult(messages, position), false); - } - - - void onSearchOpened() { - searchOpen = true; - firstSearch = true; - } - - void onSearchClosed() { - searchOpen = false; - debouncer.clear(); - result.close(); - } - - @Override - protected void onCleared() { - super.onCleared(); - result.close(); - } - - private void updateQuery(@NonNull String query, long threadId) { - activeQuery = query; - activeThreadId = threadId; - - debouncer.publish(() -> { - firstSearch = false; - - searchRepository.query(query, threadId, messages -> { - Util.runOnMain(() -> { - if (searchOpen && query.equals(activeQuery)) { - result.setValue(new SearchResult(messages, 0)); - } else { - messages.close(); - } - }); - }); - }); - } - - static class SearchResult implements Closeable { - - private final CursorList results; - private final int position; - - SearchResult(CursorList results, int position) { - this.results = results; - this.position = position; - } - - public List getResults() { - return results; - } - - public int getPosition() { - return position; - } - - @Override - public void close() { - results.close(); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java deleted file mode 100644 index 6dfd2fa887..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ /dev/null @@ -1,198 +0,0 @@ -package org.thoughtcrime.securesms.conversation; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage; -import org.session.libsession.utilities.ExpirationUtil; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.RecipientModifiedListener; -import org.session.libsignal.utilities.guava.Optional; -import org.thoughtcrime.securesms.BindableConversationItem; -import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.util.DateUtils; - -import java.util.Locale; -import java.util.Set; - -import network.loki.messenger.R; - -//TODO Remove this class. -public class ConversationUpdateItem extends LinearLayout - implements RecipientModifiedListener, BindableConversationItem -{ - private static final String TAG = ConversationUpdateItem.class.getSimpleName(); - - private Set batchSelected; - - private ImageView icon; - private TextView title; - private TextView body; - private TextView date; - private Recipient sender; - private MessageRecord messageRecord; - private Locale locale; - - public ConversationUpdateItem(Context context) { - super(context); - } - - public ConversationUpdateItem(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public void onFinishInflate() { - super.onFinishInflate(); - - this.icon = findViewById(R.id.conversation_update_icon); - this.title = findViewById(R.id.conversation_update_title); - this.body = findViewById(R.id.conversation_update_body); - this.date = findViewById(R.id.conversation_update_date); - - this.setOnClickListener(new InternalClickListener(null)); - } - - @Override - public void bind(@NonNull MessageRecord messageRecord, - @NonNull Optional previousMessageRecord, - @NonNull Optional nextMessageRecord, - @NonNull GlideRequests glideRequests, - @NonNull Locale locale, - @NonNull Set batchSelected, - @NonNull Recipient conversationRecipient, - @Nullable String searchQuery, - boolean pulseUpdate) - { - this.batchSelected = batchSelected; - - bind(messageRecord, locale); - } - - @Override - public void setEventListener(@Nullable EventListener listener) { - // No events to report yet - } - - @Override - public MessageRecord getMessageRecord() { - return messageRecord; - } - - private void bind(@NonNull MessageRecord messageRecord, @NonNull Locale locale) { - this.messageRecord = messageRecord; - this.sender = messageRecord.getIndividualRecipient(); - this.locale = locale; - - this.sender.addListener(this); - - if (messageRecord.isCallLog()) setCallRecord(messageRecord); - else if (messageRecord.isExpirationTimerUpdate()) setTimerRecord(messageRecord); - else if (messageRecord.isScreenshotNotification()) setDataExtractionRecord(messageRecord, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT); - else if (messageRecord.isMediaSavedNotification()) setDataExtractionRecord(messageRecord, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED); - else throw new AssertionError("Neither group nor log nor joined."); - - if (batchSelected.contains(messageRecord)) setSelected(true); - else setSelected(false); - } - - private void setCallRecord(MessageRecord messageRecord) { - if (messageRecord.isIncomingCall()) icon.setImageResource(R.drawable.ic_call_received_grey600_24dp); - else if (messageRecord.isOutgoingCall()) icon.setImageResource(R.drawable.ic_call_made_grey600_24dp); - else icon.setImageResource(R.drawable.ic_call_missed_grey600_24dp); - - body.setText(messageRecord.getDisplayBody(getContext())); - date.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), locale, messageRecord.getDateReceived())); - - title.setVisibility(GONE); - body.setVisibility(VISIBLE); - date.setVisibility(View.VISIBLE); - } - - private void setTimerRecord(final MessageRecord messageRecord) { - @ColorInt int color = GeneralUtilitiesKt.getColorWithID(getResources(), R.color.text, getContext().getTheme()); - if (messageRecord.getExpiresIn() > 0) { - icon.setImageResource(R.drawable.ic_timer); - icon.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); - } else { - icon.setImageResource(R.drawable.ic_timer_disabled); - icon.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); - } - - title.setText(ExpirationUtil.getExpirationDisplayValue(getContext(), (int)(messageRecord.getExpiresIn() / 1000))); - body.setText(messageRecord.getDisplayBody(getContext())); - - title.setVisibility(VISIBLE); - body.setVisibility(VISIBLE); - date.setVisibility(GONE); - } - - private void setDataExtractionRecord(final MessageRecord messageRecord, DataExtractionNotificationInfoMessage.Kind kind) { - @ColorInt int color = GeneralUtilitiesKt.getColorWithID(getResources(), R.color.text, getContext().getTheme()); - if (kind == DataExtractionNotificationInfoMessage.Kind.SCREENSHOT) { - icon.setImageResource(R.drawable.quick_camera_dark); - } else if (kind == DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED) { - icon.setImageResource(R.drawable.ic_file_download_white_36dp); - } - icon.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); - - body.setText(messageRecord.getDisplayBody(getContext())); - - title.setVisibility(VISIBLE); - body.setVisibility(VISIBLE); - date.setVisibility(GONE); - } - - private void setTextMessageRecord(MessageRecord messageRecord) { - body.setText(messageRecord.getDisplayBody(getContext())); - - icon.setVisibility(GONE); - title.setVisibility(GONE); - body.setVisibility(VISIBLE); - date.setVisibility(GONE); - } - - @Override - public void onModified(Recipient recipient) { - Util.runOnMain(() -> bind(messageRecord, locale)); - } - - @Override - public void setOnClickListener(View.OnClickListener l) { - super.setOnClickListener(new InternalClickListener(l)); - } - - @Override - public void unbind() { - if (sender != null) { - sender.removeListener(this); - } - } - - private class InternalClickListener implements View.OnClickListener { - - @Nullable private final View.OnClickListener parent; - - InternalClickListener(@Nullable View.OnClickListener parent) { - this.parent = parent; - } - - @Override - public void onClick(View v) { - - } - } -} 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 075caf5f6e..eeb9deacab 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 @@ -75,7 +75,7 @@ import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.audio.AudioRecorder import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher -import org.thoughtcrime.securesms.conversation.ConversationActivity + import org.thoughtcrime.securesms.conversation.v2.dialogs.* import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt index df4f3ce614..9b40454a2b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt @@ -17,7 +17,7 @@ import nl.komponents.kovenant.ui.successUi import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.groupSizeLimit import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.conversation.ConversationActivity + import org.session.libsession.utilities.Address import org.session.libsession.utilities.DistributionTypes import org.thoughtcrime.securesms.database.DatabaseFactory @@ -138,7 +138,6 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) { val intent = Intent(context, ConversationActivityV2::class.java) intent.putExtra(ConversationActivityV2.THREAD_ID, threadId) - intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT) intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) context.startActivity(intent) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt index 7d429bf223..6eedd97013 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt @@ -29,7 +29,7 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.PublicKeyValidation import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.conversation.ConversationActivity + import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment @@ -114,11 +114,9 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC val recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false) val intent = Intent(this, ConversationActivityV2::class.java) intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) - intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA)) intent.setDataAndType(getIntent().data, getIntent().type) val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient) intent.putExtra(ConversationActivityV2.THREAD_ID, existingThread) - intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT) startActivity(intent) finish() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index 474d8036de..58eda3cc91 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -37,7 +37,7 @@ import org.session.libsignal.utilities.toHexString import org.session.libsignal.utilities.ThreadUtils import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.conversation.ConversationActivity + import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.ThreadRecord diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt index dc038cc0cb..4ce8bbde04 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt @@ -32,7 +32,7 @@ import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.PublicKeyValidation import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.conversation.ConversationActivity + import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.loki.api.OpenGroupManager @@ -130,7 +130,6 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) { val intent = Intent(context, ConversationActivityV2::class.java) intent.putExtra(ConversationActivityV2.THREAD_ID, threadId) - intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT) intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) context.startActivity(intent) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/QRCodeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/QRCodeActivity.kt index 96d175ef99..2f5638d952 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/QRCodeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/QRCodeActivity.kt @@ -14,7 +14,7 @@ import kotlinx.android.synthetic.main.activity_qr_code.* import kotlinx.android.synthetic.main.fragment_view_my_qr_code.* import network.loki.messenger.R import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.conversation.ConversationActivity + import org.session.libsession.utilities.Address import org.session.libsession.utilities.DistributionTypes import org.thoughtcrime.securesms.database.DatabaseFactory @@ -56,11 +56,9 @@ class QRCodeActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperF val recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false) val intent = Intent(this, ConversationActivityV2::class.java) intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) - intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA)) intent.setDataAndType(getIntent().data, getIntent().type) val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient) intent.putExtra(ConversationActivityV2.THREAD_ID, existingThread) - intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT) startActivity(intent) finish() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java index a0c1046e07..c02663843e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -48,7 +48,7 @@ import org.session.libsignal.utilities.Util; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.contactshare.ContactUtil; -import org.thoughtcrime.securesms.conversation.ConversationActivity; + import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; 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 c2041a84fa..991989e8da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java @@ -8,7 +8,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.TaskStackBuilder; -import org.thoughtcrime.securesms.conversation.ConversationActivity; + import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import org.thoughtcrime.securesms.mms.SlideDeck; import org.session.libsession.utilities.recipients.Recipient; 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 9330ec9e37..fe934e229f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java @@ -7,11 +7,10 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.thoughtcrime.securesms.conversation.ConversationActivity; -import org.thoughtcrime.securesms.conversation.ConversationPopupActivity; import org.session.libsignal.utilities.Log; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.Recipient.*; +import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import java.util.LinkedHashSet; import java.util.LinkedList; @@ -167,9 +166,9 @@ public class NotificationState { 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, ConversationPopupActivity.class); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress()); - intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, (long)threads.toArray()[0]); + Intent intent = new Intent(context, ConversationActivityV2.class); + intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress()); + intent.putExtra(ConversationActivityV2.THREAD_ID, (long)threads.toArray()[0]); intent.setData((Uri.parse("custom://"+System.currentTimeMillis()))); return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java index e8dd7cd589..9bb71ad5cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -11,7 +11,7 @@ import androidx.core.app.TaskStackBuilder; import android.text.TextUtils; import android.widget.Toast; -import org.thoughtcrime.securesms.conversation.ConversationActivity; + import network.loki.messenger.R; import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; @@ -36,11 +36,6 @@ public class CommunicationActions { Intent intent = new Intent(context, ConversationActivityV2.class); intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress()); intent.putExtra(ConversationActivityV2.THREAD_ID, threadId); - intent.putExtra(ConversationActivity.TIMING_EXTRA, System.currentTimeMillis()); - - if (!TextUtils.isEmpty(text)) { - intent.putExtra(ConversationActivity.TEXT_EXTRA, text); - } if (backStack != null) { backStack.addNextIntent(intent); diff --git a/app/src/main/res/layout/conversation_item_received.xml b/app/src/main/res/layout/conversation_item_received.xml deleted file mode 100644 index ade3ea2c78..0000000000 --- a/app/src/main/res/layout/conversation_item_received.xml +++ /dev/null @@ -1,221 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/conversation_item_sent.xml b/app/src/main/res/layout/conversation_item_sent.xml deleted file mode 100644 index b6a7ff291a..0000000000 --- a/app/src/main/res/layout/conversation_item_sent.xml +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/conversation_item_update.xml b/app/src/main/res/layout/conversation_item_update.xml deleted file mode 100644 index 69a49226e3..0000000000 --- a/app/src/main/res/layout/conversation_item_update.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/message_details_activity.xml b/app/src/main/res/layout/message_details_activity.xml index 88cfb1061f..aa4bac9814 100644 --- a/app/src/main/res/layout/message_details_activity.xml +++ b/app/src/main/res/layout/message_details_activity.xml @@ -6,9 +6,5 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/ConversationAdapterTest.java b/app/src/test/java/org/thoughtcrime/securesms/conversation/ConversationAdapterTest.java deleted file mode 100644 index 898ae811c6..0000000000 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/ConversationAdapterTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.thoughtcrime.securesms.conversation; - -import android.database.Cursor; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.thoughtcrime.securesms.BaseUnitTest; -import org.thoughtcrime.securesms.conversation.ConversationAdapter; - -import static org.junit.Assert.*; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.powermock.api.mockito.PowerMockito.mock; -import static org.powermock.api.mockito.PowerMockito.when; - -public class ConversationAdapterTest extends BaseUnitTest { - private Cursor cursor = mock(Cursor.class); - private ConversationAdapter adapter; - - @Override - @Before - public void setUp() throws Exception { - super.setUp(); - adapter = new ConversationAdapter(context, cursor); - when(cursor.getColumnIndexOrThrow(anyString())).thenReturn(0); - } - - @Test - @Ignore("TODO: Fix test") - public void testGetItemIdEquals() throws Exception { - when(cursor.getString(anyInt())).thenReturn(null).thenReturn("SMS::1::1"); - long firstId = adapter.getItemId(cursor); - when(cursor.getString(anyInt())).thenReturn(null).thenReturn("MMS::1::1"); - long secondId = adapter.getItemId(cursor); - assertNotEquals(firstId, secondId); - when(cursor.getString(anyInt())).thenReturn(null).thenReturn("MMS::2::1"); - long thirdId = adapter.getItemId(cursor); - assertNotEquals(secondId, thirdId); - } -} \ No newline at end of file From 09502ee9abfc283f36717ca1bcd7bea573806cd3 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 7 Jul 2021 14:00:18 +1000 Subject: [PATCH 21/31] Remove unused resources --- .../securesms/components/ComposeText.java | 296 ++++++------ .../ConversationSearchBottomBar.java | 88 ---- .../components/ConversationTypingView.java | 61 --- .../securesms/components/InputPanel.java | 437 +----------------- .../components/MicrophoneRecorderView.java | 272 ----------- .../loki/activities/LinkDeviceActivity.kt | 1 - .../mediasend/MediaSendFragment.java | 2 +- .../org/thoughtcrime/securesms/util/Util.java | 1 - .../res/drawable-hdpi/ic_add_white_24dp.png | Bin 129 -> 0 bytes .../ic_call_made_grey600_24dp.png | Bin 284 -> 0 bytes .../ic_call_missed_grey600_24dp.png | Bin 311 -> 0 bytes .../ic_call_received_grey600_24dp.png | Bin 309 -> 0 bytes .../res/drawable-hdpi/ic_check_white_24dp.png | Bin 561 -> 0 bytes .../ic_favorite_grey600_24dp.png | Bin 483 -> 0 bytes .../drawable-hdpi/ic_group_grey600_24dp.png | Bin 297 -> 0 bytes .../main/res/drawable-hdpi/ic_menu_login.png | Bin 1259 -> 0 bytes .../drawable-hdpi/ic_refresh_white_24dp.png | Bin 571 -> 0 bytes .../drawable-hdpi/ic_security_white_24dp.png | Bin 428 -> 0 bytes .../res/drawable-hdpi/ic_timer_disabled.png | Bin 1140 -> 0 bytes .../res/drawable-hdpi/quick_camera_dark.png | Bin 457 -> 0 bytes .../res/drawable-mdpi/ic_add_white_24dp.png | Bin 92 -> 0 bytes .../ic_call_made_grey600_24dp.png | Bin 227 -> 0 bytes .../ic_call_missed_grey600_24dp.png | Bin 249 -> 0 bytes .../ic_call_received_grey600_24dp.png | Bin 230 -> 0 bytes .../res/drawable-mdpi/ic_check_white_24dp.png | Bin 351 -> 0 bytes .../ic_favorite_grey600_24dp.png | Bin 344 -> 0 bytes .../drawable-mdpi/ic_group_grey600_24dp.png | Bin 211 -> 0 bytes .../main/res/drawable-mdpi/ic_menu_login.png | Bin 777 -> 0 bytes .../drawable-mdpi/ic_refresh_white_24dp.png | Bin 488 -> 0 bytes .../drawable-mdpi/ic_security_white_24dp.png | Bin 288 -> 0 bytes .../res/drawable-mdpi/ic_timer_disabled.png | Bin 746 -> 0 bytes .../res/drawable-mdpi/quick_camera_dark.png | Bin 326 -> 0 bytes .../res/drawable-xhdpi/ic_add_white_24dp.png | Bin 97 -> 0 bytes .../ic_call_made_grey600_24dp.png | Bin 308 -> 0 bytes .../ic_call_missed_grey600_24dp.png | Bin 359 -> 0 bytes .../ic_call_received_grey600_24dp.png | Bin 322 -> 0 bytes .../drawable-xhdpi/ic_check_white_24dp.png | Bin 576 -> 0 bytes .../ic_favorite_grey600_24dp.png | Bin 575 -> 0 bytes .../drawable-xhdpi/ic_group_grey600_24dp.png | Bin 364 -> 0 bytes .../main/res/drawable-xhdpi/ic_menu_login.png | Bin 1740 -> 0 bytes .../drawable-xhdpi/ic_refresh_white_24dp.png | Bin 872 -> 0 bytes .../drawable-xhdpi/ic_security_white_24dp.png | Bin 507 -> 0 bytes .../res/drawable-xhdpi/ic_timer_disabled.png | Bin 1602 -> 0 bytes .../res/drawable-xhdpi/quick_camera_dark.png | Bin 534 -> 0 bytes .../res/drawable-xxhdpi/ic_add_white_24dp.png | Bin 97 -> 0 bytes .../ic_call_made_grey600_24dp.png | Bin 386 -> 0 bytes .../ic_call_missed_grey600_24dp.png | Bin 449 -> 0 bytes .../ic_call_received_grey600_24dp.png | Bin 384 -> 0 bytes .../drawable-xxhdpi/ic_check_white_24dp.png | Bin 990 -> 0 bytes .../ic_favorite_grey600_24dp.png | Bin 834 -> 0 bytes .../drawable-xxhdpi/ic_group_grey600_24dp.png | Bin 474 -> 0 bytes .../drawable-xxhdpi/ic_refresh_white_24dp.png | Bin 875 -> 0 bytes .../ic_security_white_24dp.png | Bin 702 -> 0 bytes .../res/drawable-xxhdpi/ic_timer_disabled.png | Bin 2493 -> 0 bytes .../res/drawable-xxhdpi/quick_camera_dark.png | Bin 781 -> 0 bytes .../drawable-xxxhdpi/ic_check_white_24dp.png | Bin 946 -> 0 bytes .../ic_favorite_grey600_24dp.png | Bin 1043 -> 0 bytes .../ic_security_white_24dp.png | Bin 913 -> 0 bytes .../drawable-xxxhdpi/ic_timer_disabled.png | Bin 3436 -> 0 bytes .../drawable/compose_divider_background.xml | 8 - .../res/drawable/contact_photo_background.xml | 10 - .../conversation_back_button_background.xml | 15 - .../conversation_home_touch_highlight.xml | 15 - .../drawable/conversation_item_background.xml | 5 - .../conversation_item_background_animated.xml | 5 - app/src/main/res/drawable/error_round.xml | 16 - .../ic_baseline_arrow_back_compact_24.xml | 10 - .../main/res/drawable/ic_baseline_key_24.xml | 10 - .../res/drawable/ic_baseline_refresh_24.xml | 10 - app/src/main/res/drawable/ic_circle_plus.xml | 21 - app/src/main/res/drawable/ic_shield.xml | 9 - app/src/main/res/drawable/info_round.xml | 16 - .../drawable/message_bubble_background.xml | 12 - .../profile_picture_view_large_foreground.xml | 9 - ...profile_picture_view_medium_foreground.xml | 9 - ...ile_picture_view_rss_medium_background.xml | 9 - .../profile_picture_view_small_foreground.xml | 11 - .../prominent_dialog_button_background.xml | 11 - .../scroll_to_bottom_button_background.xml | 8 - .../main/res/drawable/session_logo_white.xml | 9 - .../res/layout/activity_backup_restore.xml | 125 ----- .../main/res/layout/conversation_activity.xml | 235 ---------- ...sation_activity_attachment_editor_stub.xml | 56 --- ...conversation_activity_emojidrawer_stub.xml | 7 - .../main/res/layout/conversation_fragment.xml | 60 --- .../res/layout/conversation_input_panel.xml | 208 --------- .../res/layout/conversation_item_header.xml | 22 - .../layout/conversation_item_last_seen.xml | 29 -- .../conversation_item_received_audio.xml | 13 - .../conversation_item_received_document.xml | 13 - ...onversation_item_received_link_preview.xml | 11 - ...on_item_received_open_group_invitation.xml | 10 - .../conversation_item_received_sticker.xml | 8 - .../conversation_item_received_thumbnail.xml | 19 - .../layout/conversation_item_sent_audio.xml | 11 - .../conversation_item_sent_document.xml | 13 - .../conversation_item_sent_link_preview.xml | 11 - ...sation_item_sent_open_group_invitation.xml | 10 - .../layout/conversation_item_sent_sticker.xml | 8 - .../conversation_item_sent_thumbnail.xml | 21 - .../res/layout/conversation_search_nav.xml | 68 --- .../res/layout/conversation_typing_view.xml | 34 -- app/src/main/res/layout/load_more_header.xml | 47 -- .../layout/longmessage_bubble_received.xml | 41 -- .../res/layout/longmessage_bubble_sent.xml | 41 -- .../res/layout/message_details_activity.xml | 10 - .../res/layout/message_details_header.xml | 162 ------- .../res/layout/microphone_recorder_view.xml | 65 --- app/src/main/res/layout/recording_layout.xml | 104 ----- .../main/res/layout/view_input_bar_button.xml | 19 - .../view_mention_candidate_selection.xml | 7 - app/src/main/res/menu/conversation.xml | 19 - app/src/main/res/menu/conversation_block.xml | 8 - .../main/res/menu/conversation_context.xml | 52 --- .../res/menu/conversation_copy_session_id.xml | 8 - .../res/menu/conversation_expiring_off.xml | 9 - .../res/menu/conversation_expiring_on.xml | 10 - .../menu/conversation_invite_open_group.xml | 8 - .../menu/conversation_mms_group_options.xml | 25 - app/src/main/res/menu/conversation_muted.xml | 5 - app/src/main/res/menu/conversation_popup.xml | 9 - .../menu/conversation_push_group_options.xml | 12 - .../main/res/menu/conversation_unblock.xml | 8 - .../main/res/menu/conversation_unmuted.xml | 7 - .../main/res/values-notnight-v21/colors.xml | 1 - app/src/main/res/values/colors.xml | 5 - app/src/main/res/values/dimens.xml | 9 - app/src/main/res/values/styles.xml | 5 - app/src/main/res/values/themes.xml | 5 - .../src/main/res/drawable/ic_person_large.xml | 4 - libsession/src/main/res/values/arrays.xml | 55 +-- libsession/src/main/res/values/colors.xml | 36 -- .../src/main/res/values/core_colors.xml | 2 - libsession/src/main/res/values/dimens.xml | 43 -- .../res/values/google-playstore-strings.xml | 18 +- .../src/main/res/values/material_colors.xml | 245 ---------- .../src/main/res/values/text_styles.xml | 20 - 137 files changed, 163 insertions(+), 3339 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/ConversationSearchBottomBar.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/ConversationTypingView.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/MicrophoneRecorderView.java delete mode 100644 app/src/main/res/drawable-hdpi/ic_add_white_24dp.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_call_made_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_call_missed_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_call_received_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_check_white_24dp.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_favorite_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_group_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_menu_login.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_security_white_24dp.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_timer_disabled.png delete mode 100644 app/src/main/res/drawable-hdpi/quick_camera_dark.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_add_white_24dp.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_call_made_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_call_missed_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_call_received_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_check_white_24dp.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_favorite_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_group_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_menu_login.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_security_white_24dp.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_timer_disabled.png delete mode 100644 app/src/main/res/drawable-mdpi/quick_camera_dark.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_call_made_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_call_missed_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_call_received_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_check_white_24dp.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_favorite_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_group_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_menu_login.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_security_white_24dp.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_timer_disabled.png delete mode 100644 app/src/main/res/drawable-xhdpi/quick_camera_dark.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_call_made_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_call_missed_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_call_received_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_check_white_24dp.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_favorite_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_group_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_security_white_24dp.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_timer_disabled.png delete mode 100644 app/src/main/res/drawable-xxhdpi/quick_camera_dark.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/ic_check_white_24dp.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/ic_favorite_grey600_24dp.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/ic_security_white_24dp.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/ic_timer_disabled.png delete mode 100644 app/src/main/res/drawable/compose_divider_background.xml delete mode 100644 app/src/main/res/drawable/contact_photo_background.xml delete mode 100644 app/src/main/res/drawable/conversation_back_button_background.xml delete mode 100644 app/src/main/res/drawable/conversation_home_touch_highlight.xml delete mode 100644 app/src/main/res/drawable/conversation_item_background.xml delete mode 100644 app/src/main/res/drawable/conversation_item_background_animated.xml delete mode 100644 app/src/main/res/drawable/error_round.xml delete mode 100644 app/src/main/res/drawable/ic_baseline_arrow_back_compact_24.xml delete mode 100644 app/src/main/res/drawable/ic_baseline_key_24.xml delete mode 100644 app/src/main/res/drawable/ic_baseline_refresh_24.xml delete mode 100644 app/src/main/res/drawable/ic_circle_plus.xml delete mode 100644 app/src/main/res/drawable/ic_shield.xml delete mode 100644 app/src/main/res/drawable/info_round.xml delete mode 100644 app/src/main/res/drawable/message_bubble_background.xml delete mode 100644 app/src/main/res/drawable/profile_picture_view_large_foreground.xml delete mode 100644 app/src/main/res/drawable/profile_picture_view_medium_foreground.xml delete mode 100644 app/src/main/res/drawable/profile_picture_view_rss_medium_background.xml delete mode 100644 app/src/main/res/drawable/profile_picture_view_small_foreground.xml delete mode 100644 app/src/main/res/drawable/prominent_dialog_button_background.xml delete mode 100644 app/src/main/res/drawable/scroll_to_bottom_button_background.xml delete mode 100644 app/src/main/res/drawable/session_logo_white.xml delete mode 100644 app/src/main/res/layout/activity_backup_restore.xml delete mode 100644 app/src/main/res/layout/conversation_activity.xml delete mode 100644 app/src/main/res/layout/conversation_activity_attachment_editor_stub.xml delete mode 100644 app/src/main/res/layout/conversation_activity_emojidrawer_stub.xml delete mode 100644 app/src/main/res/layout/conversation_fragment.xml delete mode 100644 app/src/main/res/layout/conversation_input_panel.xml delete mode 100644 app/src/main/res/layout/conversation_item_header.xml delete mode 100644 app/src/main/res/layout/conversation_item_last_seen.xml delete mode 100644 app/src/main/res/layout/conversation_item_received_audio.xml delete mode 100644 app/src/main/res/layout/conversation_item_received_document.xml delete mode 100644 app/src/main/res/layout/conversation_item_received_link_preview.xml delete mode 100644 app/src/main/res/layout/conversation_item_received_open_group_invitation.xml delete mode 100644 app/src/main/res/layout/conversation_item_received_sticker.xml delete mode 100644 app/src/main/res/layout/conversation_item_received_thumbnail.xml delete mode 100644 app/src/main/res/layout/conversation_item_sent_audio.xml delete mode 100644 app/src/main/res/layout/conversation_item_sent_document.xml delete mode 100644 app/src/main/res/layout/conversation_item_sent_link_preview.xml delete mode 100644 app/src/main/res/layout/conversation_item_sent_open_group_invitation.xml delete mode 100644 app/src/main/res/layout/conversation_item_sent_sticker.xml delete mode 100644 app/src/main/res/layout/conversation_item_sent_thumbnail.xml delete mode 100644 app/src/main/res/layout/conversation_search_nav.xml delete mode 100644 app/src/main/res/layout/conversation_typing_view.xml delete mode 100644 app/src/main/res/layout/load_more_header.xml delete mode 100644 app/src/main/res/layout/longmessage_bubble_received.xml delete mode 100644 app/src/main/res/layout/longmessage_bubble_sent.xml delete mode 100644 app/src/main/res/layout/message_details_activity.xml delete mode 100644 app/src/main/res/layout/message_details_header.xml delete mode 100644 app/src/main/res/layout/microphone_recorder_view.xml delete mode 100644 app/src/main/res/layout/recording_layout.xml delete mode 100644 app/src/main/res/layout/view_input_bar_button.xml delete mode 100644 app/src/main/res/layout/view_mention_candidate_selection.xml delete mode 100644 app/src/main/res/menu/conversation.xml delete mode 100644 app/src/main/res/menu/conversation_block.xml delete mode 100644 app/src/main/res/menu/conversation_context.xml delete mode 100644 app/src/main/res/menu/conversation_copy_session_id.xml delete mode 100644 app/src/main/res/menu/conversation_expiring_off.xml delete mode 100644 app/src/main/res/menu/conversation_expiring_on.xml delete mode 100644 app/src/main/res/menu/conversation_invite_open_group.xml delete mode 100644 app/src/main/res/menu/conversation_mms_group_options.xml delete mode 100644 app/src/main/res/menu/conversation_muted.xml delete mode 100644 app/src/main/res/menu/conversation_popup.xml delete mode 100644 app/src/main/res/menu/conversation_push_group_options.xml delete mode 100644 app/src/main/res/menu/conversation_unblock.xml delete mode 100644 app/src/main/res/menu/conversation_unmuted.xml delete mode 100644 libsession/src/main/res/drawable/ic_person_large.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java b/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java index 012086dc94..42825360c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java @@ -28,166 +28,166 @@ import org.session.libsession.utilities.TextSecurePreferences; public class ComposeText extends EmojiEditText { - private CharSequence hint; - private SpannableString subHint; + private CharSequence hint; + private SpannableString subHint; - @Nullable private InputPanel.MediaListener mediaListener; - @Nullable private CursorPositionChangedListener cursorPositionChangedListener; + @Nullable private InputPanel.MediaListener mediaListener; + @Nullable private CursorPositionChangedListener cursorPositionChangedListener; - public ComposeText(Context context) { - super(context); - initialize(); - } - - public ComposeText(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); - } - - public ComposeText(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initialize(); - } - - public String getTextTrimmed(){ - return getText().toString().trim(); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - - if (!TextUtils.isEmpty(hint)) { - if (!TextUtils.isEmpty(subHint)) { - setHint(new SpannableStringBuilder().append(ellipsizeToWidth(hint)) - .append("\n") - .append(ellipsizeToWidth(subHint))); - } else { - setHint(ellipsizeToWidth(hint)); - } - } - } - - @Override - protected void onSelectionChanged(int selStart, int selEnd) { - super.onSelectionChanged(selStart, selEnd); - - if (cursorPositionChangedListener != null) { - cursorPositionChangedListener.onCursorPositionChanged(selStart, selEnd); - } - } - - private CharSequence ellipsizeToWidth(CharSequence text) { - return TextUtils.ellipsize(text, - getPaint(), - getWidth() - getPaddingLeft() - getPaddingRight(), - TruncateAt.END); - } - - public void setHint(@NonNull String hint, @Nullable CharSequence subHint) { - this.hint = hint; - - if (subHint != null) { - this.subHint = new SpannableString(subHint); - this.subHint.setSpan(new RelativeSizeSpan(0.5f), 0, subHint.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); - } else { - this.subHint = null; + public ComposeText(Context context) { + super(context); + initialize(); } - if (this.subHint != null) { - super.setHint(new SpannableStringBuilder().append(ellipsizeToWidth(this.hint)) - .append("\n") - .append(ellipsizeToWidth(this.subHint))); - } else { - super.setHint(ellipsizeToWidth(this.hint)); - } - } - - public void setCursorPositionChangedListener(@Nullable CursorPositionChangedListener listener) { - this.cursorPositionChangedListener = listener; - } - - public void setTransport() { - final boolean useSystemEmoji = TextSecurePreferences.isSystemEmojiPreferred(getContext()); - final boolean isIncognito = TextSecurePreferences.isIncognitoKeyboardEnabled(getContext()); - - int imeOptions = (getImeOptions() & ~EditorInfo.IME_MASK_ACTION) | EditorInfo.IME_ACTION_SEND; - int inputType = getInputType(); - - setImeActionLabel(null, 0); - - if (useSystemEmoji) { - inputType = (inputType & ~InputType.TYPE_MASK_VARIATION) | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE; + public ComposeText(Context context, AttributeSet attrs) { + super(context, attrs); + initialize(); } - setInputType(inputType); - if (isIncognito) { - setImeOptions(imeOptions | 16777216); - } else { - setImeOptions(imeOptions); - } - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo editorInfo) { - InputConnection inputConnection = super.onCreateInputConnection(editorInfo); - - if(TextSecurePreferences.isEnterSendsEnabled(getContext())) { - editorInfo.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION; + public ComposeText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(); } - if (Build.VERSION.SDK_INT < 21) return inputConnection; - if (mediaListener == null) return inputConnection; - if (inputConnection == null) return null; - - EditorInfoCompat.setContentMimeTypes(editorInfo, new String[] {"image/jpeg", "image/png", "image/gif"}); - return InputConnectionCompat.createWrapper(inputConnection, editorInfo, new CommitContentListener(mediaListener)); - } - - public void setMediaListener(@Nullable InputPanel.MediaListener mediaListener) { - this.mediaListener = mediaListener; - } - - private void initialize() { - if (TextSecurePreferences.isIncognitoKeyboardEnabled(getContext())) { - setImeOptions(getImeOptions() | 16777216); - } - } - - @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR2) - private static class CommitContentListener implements InputConnectionCompat.OnCommitContentListener { - - private static final String TAG = CommitContentListener.class.getSimpleName(); - - private final InputPanel.MediaListener mediaListener; - - private CommitContentListener(@NonNull InputPanel.MediaListener mediaListener) { - this.mediaListener = mediaListener; + public String getTextTrimmed(){ + return getText().toString().trim(); } @Override - public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts) { - if (BuildCompat.isAtLeastNMR1() && (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { - try { - inputContentInfo.requestPermission(); - } catch (Exception e) { - Log.w(TAG, e); - return false; + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (!TextUtils.isEmpty(hint)) { + if (!TextUtils.isEmpty(subHint)) { + setHint(new SpannableStringBuilder().append(ellipsizeToWidth(hint)) + .append("\n") + .append(ellipsizeToWidth(subHint))); + } else { + setHint(ellipsizeToWidth(hint)); + } } - } - - if (inputContentInfo.getDescription().getMimeTypeCount() > 0) { - mediaListener.onMediaSelected(inputContentInfo.getContentUri(), - inputContentInfo.getDescription().getMimeType(0)); - - return true; - } - - return false; } - } - public interface CursorPositionChangedListener { - void onCursorPositionChanged(int start, int end); - } + @Override + protected void onSelectionChanged(int selStart, int selEnd) { + super.onSelectionChanged(selStart, selEnd); + + if (cursorPositionChangedListener != null) { + cursorPositionChangedListener.onCursorPositionChanged(selStart, selEnd); + } + } + + private CharSequence ellipsizeToWidth(CharSequence text) { + return TextUtils.ellipsize(text, + getPaint(), + getWidth() - getPaddingLeft() - getPaddingRight(), + TruncateAt.END); + } + + public void setHint(@NonNull String hint, @Nullable CharSequence subHint) { + this.hint = hint; + + if (subHint != null) { + this.subHint = new SpannableString(subHint); + this.subHint.setSpan(new RelativeSizeSpan(0.5f), 0, subHint.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); + } else { + this.subHint = null; + } + + if (this.subHint != null) { + super.setHint(new SpannableStringBuilder().append(ellipsizeToWidth(this.hint)) + .append("\n") + .append(ellipsizeToWidth(this.subHint))); + } else { + super.setHint(ellipsizeToWidth(this.hint)); + } + } + + public void setCursorPositionChangedListener(@Nullable CursorPositionChangedListener listener) { + this.cursorPositionChangedListener = listener; + } + + public void setTransport() { + final boolean useSystemEmoji = TextSecurePreferences.isSystemEmojiPreferred(getContext()); + final boolean isIncognito = TextSecurePreferences.isIncognitoKeyboardEnabled(getContext()); + + int imeOptions = (getImeOptions() & ~EditorInfo.IME_MASK_ACTION) | EditorInfo.IME_ACTION_SEND; + int inputType = getInputType(); + + setImeActionLabel(null, 0); + + if (useSystemEmoji) { + inputType = (inputType & ~InputType.TYPE_MASK_VARIATION) | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE; + } + + setInputType(inputType); + if (isIncognito) { + setImeOptions(imeOptions | 16777216); + } else { + setImeOptions(imeOptions); + } + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo editorInfo) { + InputConnection inputConnection = super.onCreateInputConnection(editorInfo); + + if(TextSecurePreferences.isEnterSendsEnabled(getContext())) { + editorInfo.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION; + } + + if (Build.VERSION.SDK_INT < 21) return inputConnection; + if (mediaListener == null) return inputConnection; + if (inputConnection == null) return null; + + EditorInfoCompat.setContentMimeTypes(editorInfo, new String[] {"image/jpeg", "image/png", "image/gif"}); + return InputConnectionCompat.createWrapper(inputConnection, editorInfo, new CommitContentListener(mediaListener)); + } + + public void setMediaListener(@Nullable InputPanel.MediaListener mediaListener) { + this.mediaListener = mediaListener; + } + + private void initialize() { + if (TextSecurePreferences.isIncognitoKeyboardEnabled(getContext())) { + setImeOptions(getImeOptions() | 16777216); + } + } + + @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR2) + private static class CommitContentListener implements InputConnectionCompat.OnCommitContentListener { + + private static final String TAG = CommitContentListener.class.getSimpleName(); + + private final InputPanel.MediaListener mediaListener; + + private CommitContentListener(@NonNull InputPanel.MediaListener mediaListener) { + this.mediaListener = mediaListener; + } + + @Override + public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts) { + if (BuildCompat.isAtLeastNMR1() && (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { + try { + inputContentInfo.requestPermission(); + } catch (Exception e) { + Log.w(TAG, e); + return false; + } + } + + if (inputContentInfo.getDescription().getMimeTypeCount() > 0) { + mediaListener.onMediaSelected(inputContentInfo.getContentUri(), + inputContentInfo.getDescription().getMimeType(0)); + + return true; + } + + return false; + } + } + + public interface CursorPositionChangedListener { + void onCursorPositionChanged(int start, int end); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationSearchBottomBar.java b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationSearchBottomBar.java deleted file mode 100644 index 409acd7639..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationSearchBottomBar.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.ConstraintLayout; -import android.util.AttributeSet; -import android.view.View; -import android.widget.TextView; - -import network.loki.messenger.R; - -/** - * Bottom navigation bar shown in the ConversationActivity - * when the user is searching within a conversation. Shows details about the results and allows the - * user to move between them. - */ -public class ConversationSearchBottomBar extends ConstraintLayout { - - private View searchDown; - private View searchUp; - private TextView searchPositionText; - private View progressWheel; - - private EventListener eventListener; - - - public ConversationSearchBottomBar(Context context) { - super(context); - } - - public ConversationSearchBottomBar(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - this.searchUp = findViewById(R.id.conversation_search_up); - this.searchDown = findViewById(R.id.conversation_search_down); - this.searchPositionText = findViewById(R.id.conversation_search_position); - this.progressWheel = findViewById(R.id.conversation_search_progress_wheel); - } - - public void setData(int position, int count) { - progressWheel.setVisibility(GONE); - - searchUp.setOnClickListener(v -> { - if (eventListener != null) { - eventListener.onSearchMoveUpPressed(); - } - }); - - searchDown.setOnClickListener(v -> { - if (eventListener != null) { - eventListener.onSearchMoveDownPressed(); - } - }); - - if (count > 0) { - searchPositionText.setText(getResources().getString(R.string.ConversationActivity_search_position, position + 1, count)); - } else { - searchPositionText.setText(R.string.ConversationActivity_no_results); - } - - setViewEnabled(searchUp, position < (count - 1)); - setViewEnabled(searchDown, position > 0); - } - - public void showLoading() { - progressWheel.setVisibility(VISIBLE); - } - - private void setViewEnabled(@NonNull View view, boolean enabled) { - view.setEnabled(enabled); - view.setAlpha(enabled ? 1f : 0.25f); - } - - public void setEventListener(@Nullable EventListener eventListener) { - this.eventListener = eventListener; - } - - public interface EventListener { - void onSearchMoveUpPressed(); - void onSearchMoveDownPressed(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationTypingView.java b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationTypingView.java deleted file mode 100644 index ddc782628a..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationTypingView.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import android.graphics.PorterDuff; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; - -import org.thoughtcrime.securesms.conversation.v2.components.TypingIndicatorView; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.ThemeUtil; - - -import java.util.List; - -import network.loki.messenger.R; - -public class ConversationTypingView extends LinearLayout { - - private AvatarImageView avatar; - private View bubble; - private TypingIndicatorView indicator; - - public ConversationTypingView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - avatar = findViewById(R.id.typing_avatar); - bubble = findViewById(R.id.typing_bubble); - indicator = findViewById(R.id.typing_indicator); - } - - public void setTypists(@NonNull GlideRequests glideRequests, @NonNull List typists, boolean isGroupThread) { - if (typists.isEmpty()) { - indicator.stopAnimation(); - return; - } - - Recipient typist = typists.get(0); - - bubble.getBackground().setColorFilter( - ThemeUtil.getThemedColor(getContext(), R.attr.message_received_background_color), - PorterDuff.Mode.MULTIPLY); - - if (isGroupThread) { - avatar.setAvatar(glideRequests, typist, false); - avatar.setVisibility(VISIBLE); - } else { - avatar.setVisibility(GONE); - } - - indicator.startAnimation(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java b/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java index 9b05c269a4..e81757026c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java @@ -4,443 +4,26 @@ import android.annotation.TargetApi; import android.content.Context; import android.net.Uri; import android.os.Build; -import androidx.annotation.DimenRes; -import androidx.annotation.MainThread; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.view.ViewCompat; -import android.text.format.DateUtils; import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.View; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.Interpolator; -import android.view.animation.TranslateAnimation; import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; -import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider; -import org.thoughtcrime.securesms.components.emoji.EmojiToggle; -import org.thoughtcrime.securesms.components.emoji.MediaKeyboard; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.loki.utilities.MentionUtilities; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.mms.SlideDeck; +public class InputPanel extends LinearLayout { -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.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.concurrent.AssertedSuccessListener; -import org.session.libsignal.utilities.ListenableFuture; -import org.session.libsignal.utilities.SettableFuture; -import org.session.libsignal.utilities.guava.Optional; - -import java.util.concurrent.TimeUnit; - -import network.loki.messenger.R; - -public class InputPanel extends LinearLayout - implements MicrophoneRecorderView.Listener, - KeyboardAwareLinearLayout.OnKeyboardShownListener, - EmojiKeyboardProvider.EmojiEventListener -{ - - private static final String TAG = InputPanel.class.getSimpleName(); - - private static final int FADE_TIME = 150; - - private QuoteView quoteView; - private LinkPreviewView linkPreview; - private EmojiToggle mediaKeyboard; - public ComposeText composeText; - private View quickCameraToggle; - private View quickAudioToggle; - private View buttonToggle; - private View recordingContainer; - private View recordLockCancel; - - private MicrophoneRecorderView microphoneRecorderView; - private SlideToCancel slideToCancel; - private RecordTime recordTime; - - private @Nullable Listener listener; - private boolean emojiVisible; - - public InputPanel(Context context) { - super(context); - } - - public InputPanel(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public InputPanel(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Override - public void onFinishInflate() { - super.onFinishInflate(); - - View quoteDismiss = findViewById(R.id.quote_dismiss); - - this.quoteView = findViewById(R.id.quote_view); - this.linkPreview = findViewById(R.id.link_preview); - this.mediaKeyboard = findViewById(R.id.emoji_toggle); - this.composeText = findViewById(R.id.embedded_text_editor); - this.quickCameraToggle = findViewById(R.id.quick_camera_toggle); - this.quickAudioToggle = findViewById(R.id.quick_audio_toggle); - this.buttonToggle = findViewById(R.id.button_toggle); - this.recordingContainer = findViewById(R.id.recording_container); - this.recordLockCancel = findViewById(R.id.record_cancel); - View slideToCancelView = findViewById(R.id.slide_to_cancel); - this.slideToCancel = new SlideToCancel(slideToCancelView); - this.microphoneRecorderView = findViewById(R.id.recorder_view); - this.microphoneRecorderView.setListener(this); - this.recordTime = new RecordTime(findViewById(R.id.record_time), - findViewById(R.id.microphone), - TimeUnit.HOURS.toSeconds(1), - () -> microphoneRecorderView.cancelAction()); - - this.recordLockCancel.setOnClickListener(v -> microphoneRecorderView.cancelAction()); - - if (TextSecurePreferences.isSystemEmojiPreferred(getContext())) { - mediaKeyboard.setVisibility(View.GONE); - emojiVisible = false; - } else { - mediaKeyboard.setVisibility(View.VISIBLE); - emojiVisible = true; + public InputPanel(Context context) { + super(context); } - quoteDismiss.setOnClickListener(v -> clearQuote()); - - linkPreview.setCloseClickedListener(() -> { - if (listener != null) { - listener.onLinkPreviewCanceled(); - } - }); - } - - public void setListener(final @NonNull Listener listener) { - this.listener = listener; - - mediaKeyboard.setOnClickListener(v -> listener.onEmojiToggle()); - } - - public void setMediaListener(@NonNull MediaListener listener) { - composeText.setMediaListener(listener); - } - - public void setQuote(@NonNull GlideRequests glideRequests, long id, @NonNull Recipient author, @NonNull String body, @NonNull SlideDeck attachments, @NonNull Recipient conversationRecipient, long threadID) { - this.quoteView.setQuote(glideRequests, id, author, MentionUtilities.highlightMentions(body, threadID, getContext()), false, attachments, conversationRecipient); - this.quoteView.setVisibility(View.VISIBLE); - - if (this.linkPreview.getVisibility() == View.VISIBLE) { - int cornerRadius = readDimen(R.dimen.message_corner_collapse_radius); - this.linkPreview.setCorners(cornerRadius, cornerRadius); - } - } - - public void clearQuote() { - this.quoteView.dismiss(); - - if (this.linkPreview.getVisibility() == View.VISIBLE) { - int cornerRadius = readDimen(R.dimen.message_corner_radius); - this.linkPreview.setCorners(cornerRadius, cornerRadius); - } - } - - public Optional getQuote() { - if (quoteView.getQuoteId() > 0 && quoteView.getVisibility() == View.VISIBLE) { - return Optional.of(new QuoteModel(quoteView.getQuoteId(), quoteView.getAuthor().getAddress(), quoteView.getBody(), false, quoteView.getAttachments())); - } else { - return Optional.absent(); - } - } - - public void setLinkPreviewLoading() { - this.linkPreview.setVisibility(View.VISIBLE); - this.linkPreview.setLoading(); - } - - public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull Optional preview) { - if (preview.isPresent()) { - this.linkPreview.setVisibility(View.VISIBLE); - this.linkPreview.setLinkPreview(glideRequests, preview.get(), true); - } else { - this.linkPreview.setVisibility(View.GONE); + public InputPanel(Context context, AttributeSet attrs) { + super(context, attrs); } - int largeCornerRadius = (int)(16 * getResources().getDisplayMetrics().density); - int cornerRadius = quoteView.getVisibility() == VISIBLE ? readDimen(R.dimen.message_corner_collapse_radius) : largeCornerRadius; - - this.linkPreview.setCorners(cornerRadius, cornerRadius); - } - - public void setMediaKeyboard(@NonNull MediaKeyboard mediaKeyboard) { - this.mediaKeyboard.attach(mediaKeyboard); - } - - @Override - public void onRecordPermissionRequired() { - if (listener != null) listener.onRecorderPermissionRequired(); - } - - @Override - public void onRecordPressed() { - if (listener != null) listener.onRecorderStarted(); - recordTime.display(); - slideToCancel.display(); - - if (emojiVisible) ViewUtil.fadeOut(mediaKeyboard, FADE_TIME, View.INVISIBLE); - ViewUtil.fadeOut(composeText, FADE_TIME, View.INVISIBLE); - ViewUtil.fadeOut(quickCameraToggle, FADE_TIME, View.INVISIBLE); - ViewUtil.fadeOut(quickAudioToggle, FADE_TIME, View.INVISIBLE); - buttonToggle.animate().alpha(0).setDuration(FADE_TIME).start(); - } - - @Override - public void onRecordReleased() { - long elapsedTime = onRecordHideEvent(); - - if (listener != null) { - Log.d(TAG, "Elapsed time: " + elapsedTime); - if (elapsedTime > 1000) { - listener.onRecorderFinished(); - } else { - Toast.makeText(getContext(), R.string.InputPanel_tap_and_hold_to_record_a_voice_message_release_to_send, Toast.LENGTH_LONG).show(); - listener.onRecorderCanceled(); - } - } - } - - @Override - public void onRecordMoved(float offsetX, float absoluteX) { - slideToCancel.moveTo(offsetX); - - int direction = ViewCompat.getLayoutDirection(this); - float position = absoluteX / recordingContainer.getWidth(); - - if (direction == ViewCompat.LAYOUT_DIRECTION_LTR && position <= 0.5 || - direction == ViewCompat.LAYOUT_DIRECTION_RTL && position >= 0.6) - { - this.microphoneRecorderView.cancelAction(); - } - } - - @Override - public void onRecordCanceled() { - onRecordHideEvent(); - if (listener != null) listener.onRecorderCanceled(); - } - - @Override - public void onRecordLocked() { - slideToCancel.hide(); - recordLockCancel.setVisibility(View.VISIBLE); - buttonToggle.animate().alpha(1).setDuration(FADE_TIME).start(); - if (listener != null) listener.onRecorderLocked(); - } - - public void onPause() { - this.microphoneRecorderView.cancelAction(); - } - - public void setEnabled(boolean enabled) { - composeText.setEnabled(enabled); - mediaKeyboard.setEnabled(enabled); - quickAudioToggle.setEnabled(enabled); - quickCameraToggle.setEnabled(enabled); - } - - public void setHint(@NonNull String hint) { - composeText.setHint(hint, null); - } - - private long onRecordHideEvent() { - recordLockCancel.setVisibility(View.GONE); - - ListenableFuture future = slideToCancel.hide(); - long elapsedTime = recordTime.hide(); - - future.addListener(new AssertedSuccessListener() { - @Override - public void onSuccess(Void result) { - if (emojiVisible) ViewUtil.fadeIn(mediaKeyboard, FADE_TIME); - ViewUtil.fadeIn(composeText, FADE_TIME); - ViewUtil.fadeIn(quickCameraToggle, FADE_TIME); - ViewUtil.fadeIn(quickAudioToggle, FADE_TIME); - buttonToggle.animate().alpha(1).setDuration(FADE_TIME).start(); - } - }); - - return elapsedTime; - } - - @Override - public void onKeyboardShown() { - mediaKeyboard.setToMedia(); - } - - @Override - public void onKeyEvent(KeyEvent keyEvent) { - composeText.dispatchKeyEvent(keyEvent); - } - - @Override - public void onEmojiSelected(String emoji) { - composeText.insertEmoji(emoji); - } - - private int readDimen(@DimenRes int dimenRes) { - return getResources().getDimensionPixelSize(dimenRes); - } - - public boolean isRecordingInLockedMode() { - return microphoneRecorderView.isRecordingLocked(); - } - - public void releaseRecordingLock() { - microphoneRecorderView.unlockAction(); - } - - public interface Listener { - void onRecorderStarted(); - void onRecorderLocked(); - void onRecorderFinished(); - void onRecorderCanceled(); - void onRecorderPermissionRequired(); - void onEmojiToggle(); - void onLinkPreviewCanceled(); - } - - private static class SlideToCancel { - - private final View slideToCancelView; - - SlideToCancel(View slideToCancelView) { - this.slideToCancelView = slideToCancelView; + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public InputPanel(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); } - public void display() { - ViewUtil.fadeIn(this.slideToCancelView, FADE_TIME); + public interface MediaListener { + void onMediaSelected(@NonNull Uri uri, String contentType); } - - public ListenableFuture hide() { - final SettableFuture future = new SettableFuture<>(); - - AnimationSet animation = new AnimationSet(true); - animation.addAnimation(new TranslateAnimation(Animation.ABSOLUTE, slideToCancelView.getTranslationX(), - Animation.ABSOLUTE, 0, - Animation.RELATIVE_TO_SELF, 0, - Animation.RELATIVE_TO_SELF, 0)); - animation.addAnimation(new AlphaAnimation(1, 0)); - - animation.setDuration(MicrophoneRecorderView.ANIMATION_DURATION); - animation.setFillBefore(true); - animation.setFillAfter(false); - - slideToCancelView.postDelayed(() -> future.set(null), MicrophoneRecorderView.ANIMATION_DURATION); - slideToCancelView.setVisibility(View.GONE); - slideToCancelView.startAnimation(animation); - - return future; - } - - void moveTo(float offset) { - Animation animation = new TranslateAnimation(Animation.ABSOLUTE, offset, - Animation.ABSOLUTE, offset, - Animation.RELATIVE_TO_SELF, 0, - Animation.RELATIVE_TO_SELF, 0); - - animation.setDuration(0); - animation.setFillAfter(true); - animation.setFillBefore(true); - - slideToCancelView.startAnimation(animation); - } - } - - private static class RecordTime implements Runnable { - - private final @NonNull TextView recordTimeView; - private final @NonNull View microphone; - private final @NonNull Runnable onLimitHit; - private final long limitSeconds; - private long startTime; - - private RecordTime(@NonNull TextView recordTimeView, @NonNull View microphone, long limitSeconds, @NonNull Runnable onLimitHit) { - this.recordTimeView = recordTimeView; - this.microphone = microphone; - this.limitSeconds = limitSeconds; - this.onLimitHit = onLimitHit; - } - - @MainThread - public void display() { - this.startTime = System.currentTimeMillis(); - this.recordTimeView.setText(DateUtils.formatElapsedTime(0)); - ViewUtil.fadeIn(this.recordTimeView, FADE_TIME); - Util.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1)); - microphone.setVisibility(View.VISIBLE); - microphone.startAnimation(pulseAnimation()); - } - - @MainThread - public long hide() { - long elapsedTime = System.currentTimeMillis() - startTime; - this.startTime = 0; - ViewUtil.fadeOut(this.recordTimeView, FADE_TIME, View.INVISIBLE); - microphone.clearAnimation(); - ViewUtil.fadeOut(this.microphone, FADE_TIME, View.INVISIBLE); - return elapsedTime; - } - - @Override - @MainThread - public void run() { - long localStartTime = startTime; - if (localStartTime > 0) { - long elapsedTime = System.currentTimeMillis() - localStartTime; - long elapsedSeconds = TimeUnit.MILLISECONDS.toSeconds(elapsedTime); - if (elapsedSeconds >= limitSeconds) { - onLimitHit.run(); - } else { - recordTimeView.setText(DateUtils.formatElapsedTime(elapsedSeconds)); - Util.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1)); - } - } - } - - private static Animation pulseAnimation() { - AlphaAnimation animation = new AlphaAnimation(0, 1); - - animation.setInterpolator(pulseInterpolator()); - animation.setRepeatCount(Animation.INFINITE); - animation.setDuration(1000); - - return animation; - } - - private static Interpolator pulseInterpolator() { - return input -> { - input *= 5; - if (input > 1) { - input = 4 - input; - } - return Math.max(0, Math.min(1, input)); - }; - } - } - - public interface MediaListener { - void onMediaSelected(@NonNull Uri uri, String contentType); - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/MicrophoneRecorderView.java b/app/src/main/java/org/thoughtcrime/securesms/components/MicrophoneRecorderView.java deleted file mode 100644 index 26b3d4f62f..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/MicrophoneRecorderView.java +++ /dev/null @@ -1,272 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.Manifest; -import android.content.Context; -import android.graphics.PorterDuff; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.view.ViewCompat; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.AnticipateOvershootInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.LinearInterpolator; -import android.view.animation.OvershootInterpolator; -import android.view.animation.ScaleAnimation; -import android.view.animation.TranslateAnimation; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import org.thoughtcrime.securesms.permissions.Permissions; - -import org.session.libsession.utilities.ViewUtil; - -import network.loki.messenger.R; - -public final class MicrophoneRecorderView extends FrameLayout implements View.OnTouchListener { - - enum State { - NOT_RUNNING, - RUNNING_HELD, - RUNNING_LOCKED - } - - public static final int ANIMATION_DURATION = 200; - - private FloatingRecordButton floatingRecordButton; - private LockDropTarget lockDropTarget; - private @Nullable Listener listener; - private @NonNull State state = State.NOT_RUNNING; - - public MicrophoneRecorderView(Context context) { - super(context); - } - - public MicrophoneRecorderView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public void onFinishInflate() { - super.onFinishInflate(); - - floatingRecordButton = new FloatingRecordButton(getContext(), findViewById(R.id.quick_audio_fab)); - lockDropTarget = new LockDropTarget (getContext(), findViewById(R.id.lock_drop_target)); - - View recordButton = ViewUtil.findById(this, R.id.quick_audio_toggle); - recordButton.setOnTouchListener(this); - } - - public void cancelAction() { - if (state != State.NOT_RUNNING) { - state = State.NOT_RUNNING; - hideUi(); - - if (listener != null) listener.onRecordCanceled(); - } - } - - public boolean isRecordingLocked() { - return state == State.RUNNING_LOCKED; - } - - private void lockAction() { - if (state == State.RUNNING_HELD) { - state = State.RUNNING_LOCKED; - hideUi(); - - if (listener != null) listener.onRecordLocked(); - } - } - - public void unlockAction() { - if (state == State.RUNNING_LOCKED) { - state = State.NOT_RUNNING; - hideUi(); - - if (listener != null) listener.onRecordReleased(); - } - } - - private void hideUi() { - floatingRecordButton.hide(); - lockDropTarget.hide(); - } - - @Override - public boolean onTouch(View v, final MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - if (!Permissions.hasAll(getContext(), Manifest.permission.RECORD_AUDIO)) { - if (listener != null) listener.onRecordPermissionRequired(); - } else { - state = State.RUNNING_HELD; - floatingRecordButton.display(event.getX(), event.getY()); - lockDropTarget.display(); - if (listener != null) listener.onRecordPressed(); - } - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - if (this.state == State.RUNNING_HELD) { - state = State.NOT_RUNNING; - hideUi(); - if (listener != null) listener.onRecordReleased(); - } - break; - case MotionEvent.ACTION_MOVE: - if (this.state == State.RUNNING_HELD) { - this.floatingRecordButton.moveTo(event.getX(), event.getY()); - if (listener != null) listener.onRecordMoved(floatingRecordButton.lastOffsetX, event.getRawX()); - - int dimensionPixelSize = getResources().getDimensionPixelSize(R.dimen.recording_voice_lock_target); - if (floatingRecordButton.lastOffsetY <= dimensionPixelSize) { - lockAction(); - } - } - break; - } - - return false; - } - - public void setListener(@Nullable Listener listener) { - this.listener = listener; - } - - public interface Listener { - void onRecordPressed(); - void onRecordReleased(); - void onRecordCanceled(); - void onRecordLocked(); - void onRecordMoved(float offsetX, float absoluteX); - void onRecordPermissionRequired(); - } - - private static class FloatingRecordButton { - - private final ImageView recordButtonFab; - - private float startPositionX; - private float startPositionY; - private float lastOffsetX; - private float lastOffsetY; - - FloatingRecordButton(Context context, ImageView recordButtonFab) { - this.recordButtonFab = recordButtonFab; - this.recordButtonFab.getBackground().setColorFilter(context.getResources() - .getColor(R.color.destructive), - PorterDuff.Mode.SRC_IN); - } - - void display(float x, float y) { - this.startPositionX = x; - this.startPositionY = y; - - recordButtonFab.setVisibility(View.VISIBLE); - - AnimationSet animation = new AnimationSet(true); - animation.addAnimation(new TranslateAnimation(Animation.ABSOLUTE, 0, - Animation.ABSOLUTE, 0, - Animation.ABSOLUTE, 0, - Animation.ABSOLUTE, 0)); - - animation.addAnimation(new ScaleAnimation(.5f, 1f, .5f, 1f, - Animation.RELATIVE_TO_SELF, .5f, - Animation.RELATIVE_TO_SELF, .5f)); - - animation.setDuration(ANIMATION_DURATION); - animation.setInterpolator(new OvershootInterpolator()); - - recordButtonFab.startAnimation(animation); - } - - void moveTo(float x, float y) { - lastOffsetX = getXOffset(x); - lastOffsetY = getYOffset(y); - - if (Math.abs(lastOffsetX) > Math.abs(lastOffsetY)) { - lastOffsetY = 0; - } else { - lastOffsetX = 0; - } - - recordButtonFab.setTranslationX(lastOffsetX); - recordButtonFab.setTranslationY(lastOffsetY); - } - - void hide() { - recordButtonFab.setTranslationX(0); - recordButtonFab.setTranslationY(0); - if (recordButtonFab.getVisibility() != VISIBLE) return; - - AnimationSet animation = new AnimationSet(false); - Animation scaleAnimation = new ScaleAnimation(1, 0.5f, 1, 0.5f, - Animation.RELATIVE_TO_SELF, 0.5f, - Animation.RELATIVE_TO_SELF, 0.5f); - - Animation translateAnimation = new TranslateAnimation(Animation.ABSOLUTE, lastOffsetX, - Animation.ABSOLUTE, 0, - Animation.ABSOLUTE, lastOffsetY, - Animation.ABSOLUTE, 0); - - scaleAnimation.setInterpolator(new AnticipateOvershootInterpolator(1.5f)); - translateAnimation.setInterpolator(new DecelerateInterpolator()); - animation.addAnimation(scaleAnimation); - animation.addAnimation(translateAnimation); - animation.setDuration(ANIMATION_DURATION); - animation.setInterpolator(new AnticipateOvershootInterpolator(1.5f)); - - recordButtonFab.setVisibility(View.GONE); - recordButtonFab.clearAnimation(); - recordButtonFab.startAnimation(animation); - } - - private float getXOffset(float x) { - return ViewCompat.getLayoutDirection(recordButtonFab) == ViewCompat.LAYOUT_DIRECTION_LTR ? - -Math.max(0, this.startPositionX - x) : Math.max(0, x - this.startPositionX); - } - - private float getYOffset(float y) { - return Math.min(0, y - this.startPositionY); - } - } - - private static class LockDropTarget { - - private final View lockDropTarget; - private final int dropTargetPosition; - - LockDropTarget(Context context, View lockDropTarget) { - this.lockDropTarget = lockDropTarget; - this.dropTargetPosition = context.getResources().getDimensionPixelSize(R.dimen.recording_voice_lock_target); - } - - void display() { - lockDropTarget.setScaleX(1); - lockDropTarget.setScaleY(1); - lockDropTarget.setAlpha(0); - lockDropTarget.setTranslationY(0); - lockDropTarget.setVisibility(VISIBLE); - lockDropTarget.animate() - .setStartDelay(ANIMATION_DURATION * 2) - .setDuration(ANIMATION_DURATION) - .setInterpolator(new DecelerateInterpolator()) - .translationY(dropTargetPosition) - .alpha(1) - .start(); - } - - void hide() { - lockDropTarget.animate() - .setStartDelay(0) - .setDuration(ANIMATION_DURATION) - .setInterpolator(new LinearInterpolator()) - .scaleX(0).scaleY(0) - .start(); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt index ec2a76e760..8cfa9d9e3e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt @@ -14,7 +14,6 @@ import androidx.fragment.app.FragmentPagerAdapter import androidx.lifecycle.lifecycleScope import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.activity_link_device.* -import kotlinx.android.synthetic.main.conversation_activity.* import kotlinx.android.synthetic.main.fragment_recovery_phrase.* import kotlinx.coroutines.Job import kotlinx.coroutines.delay diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java index 0df65497c8..ce1ccb2bbe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java @@ -82,7 +82,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl private InputAwareLayout hud; private View captionAndRail; private ImageButton sendButton; - private ComposeText composeText; + private ComposeText composeText; private ViewGroup composeContainer; private EmojiEditText captionText; private EmojiToggle emojiToggle; diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/Util.java b/app/src/main/java/org/thoughtcrime/securesms/util/Util.java index 2384fe482a..41b18f0247 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/Util.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/Util.java @@ -19,7 +19,6 @@ package org.thoughtcrime.securesms.util; import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.Context; -import android.content.pm.PackageManager; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.text.TextUtils; diff --git a/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png deleted file mode 100644 index b48ba111a34a3b99d1a428c287649cf9e1fe9979..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K;ho_5UNCjiE#Ki=WKk6*9|I8T{ zG9P7QSzgM9!R!UYDvdOuqg&=jTyUsv*7z>|k0wldT1B8K;Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZK3>dAqwX{BQ3+vmeOgEbxddW?|k0wldT1B8K;Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZK3>dAqwX{BQ3+vmeOgEbxddW?)>`X-8t7bI9O+q6K``*XH+@C1doogvQh z!Oq$XWtO#xE%ASRctOmvHl-s=rFvLdU2U&6t0g;&xk~U~y25=(QvTO6qxkOo(U)JB z2h{97@ilwZ$qy1YH4lAxGEt#$nf{FyW92{WC5A6}ZBD;kb)`Wv8R&EdPgg&ebxsLQ E04<1b3jhEB diff --git a/app/src/main/res/drawable-hdpi/ic_call_received_grey600_24dp.png b/app/src/main/res/drawable-hdpi/ic_call_received_grey600_24dp.png deleted file mode 100644 index 5c9a88d1267de47760941376195f1d2de85458e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 309 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K;Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZK3>dAqwX{BQ3+vmeOgEbxddW?*;UV`o`9x4VcVr-8Ag+7@fuc(p{0xMzV^o|7aba_Ck&s}+=tp5YCh(G7s{0mkEF3W0xp(oow#MV$G75fW1fkk98ysZ>(pKFbJ9g~5o4iNVl9p+H8X z(G4toZnOoYW0 zZh%D!%DDu+<_cF`EEaD;Nu5rI;1n)QVFQUjH4h^sCi1oY1{*@Jx#Xed^Z6Y(6~w3O zy2@Y2%PUN1-wObh%-KP%R$Hw(Z-&U|^y%*d&F=6jK-# z26chEF3}FA)oSHOdy}x3wv6wbL4h%jn@Vrm5Z@V6u=mU)ahOf<_4|Fi=nwFoDJK$B zOi7@crV;L?CzCW4X}S{G2eJOOA269R{RO=PB~;9nh)E0b00000NkvXXu0mjfM@s_j diff --git a/app/src/main/res/drawable-hdpi/ic_favorite_grey600_24dp.png b/app/src/main/res/drawable-hdpi/ic_favorite_grey600_24dp.png deleted file mode 100644 index 2bdad7d3f145aa089789dc4d791daa5eed260408..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 483 zcmV<90UZ8`P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00CP`L_t(Y$K}?|D@0)w$MJhFGb^@ST+`Ev!(svYM^3oKiB`u$qh{H8mx9nVZc}WIQ&Ed1jvb_FTK^EYIn)JLh){ zjQ=U4ouC8@WLRef8&m37q*-Hw4D%$sp+OG#K-BA&Wh@K~I#{LD(0ON{0XJw4r(yKB zj_D!EsiIS1Rt4F-DuxAu0!5cjmC0r(#+A#kcU{scLrYqWK(Hu7N7~S#3_WN=_cBz| zhCXHJO&hApP*EEy$k3iPv@1h1+R(HNndG&YlhzfP;wv;$Bc)o}tdi&#?g<|r= z-0o37<-kmdq}PQUqY{XDV#M#%rg-sXoWDEdb^Oyll!F~KMQom3nMWqV#L+l+N~X+c z*jyXtriHm?NGBlsxR980dUcbwn_QjA(G?}K<75#Cvc#iizr_yQKY1JRi7=affOh+j Zz5$6Lf)5LHQ^f!P002ovPDHLkV1kPj$MXOH diff --git a/app/src/main/res/drawable-hdpi/ic_group_grey600_24dp.png b/app/src/main/res/drawable-hdpi/ic_group_grey600_24dp.png deleted file mode 100644 index 816fe2053f973cb1cf46355e0812b983cd9e23ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 297 zcmV+^0oMMBP)b;z07Xj?N9Yk!s?woMv>P0wBfZ1B0o(zpTUmn)reZDT>`pDm+_4!0qs@!Z2y`Q=;aCaXa00000NkvXXu0mjfo%eq5 diff --git a/app/src/main/res/drawable-hdpi/ic_menu_login.png b/app/src/main/res/drawable-hdpi/ic_menu_login.png deleted file mode 100644 index 163f0aa854b985a331bdc54557d4ee34849992bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1259 zcmVJNR9M5^m|JWVRT#(rXJ&W0&2}rK7ZRXI08?lwVk;17}0f|8)n1~7w_@)nNKw`Hu z^Z78lxTV$ZjTg@Mw3E#F&H11If6li9Jg7s!fJpfz@;mw6>Ap5E8TrMCpa6i%V$8(T zSb;{Y!eTsOO<8FHfRP)*6c9jYG8(YOf1oz<`ibV)#^{pcQ;uLSnv4b0gMi2$VF_SD zp|udL)klx^f3M=2l~;f0FVxmp=nyspD}gM9@(ZA(7_Sx{?&-U&nOw<|$svbWvUG{n zRlWEmb_O2L3SkPMJcN~#y3Za!B+WdqB~yDuuH zIN9FB(z%OZVsglqtb3x^6{z+eDShh*z*mEJKPIL)F*)j@I!)gyR!phok|SBKjAG-| z;!IW{fJcLPbM;A9wq*KCZ-}Bpv|@itbV%_%t6=$)F6+XgOaKOe8ng3)W*H||hx_nO zaL)Mgg^ywTamh)+5@i#s_C9Qy70PV0m%n)2)ht=Yb$tZSC9W=QIL4YK-?N7Al>Z=R zCmt;wf#wCV<$cLCL@aH+D1sLNfCZK<83AyK<==W<1ka?q0s;6YVfW|6o*Ak46j}=F zipmP+V@Ll8iI^N>wJC~KaU=a5c*OVKC6%=0plXSU1K436?1;Jd_=t&Vh*)*>W5a$c zeL$tvc}1~wk|L-}>LYnsv2tlt0Co1`t*r&=1Ev}KE-0oXz#*0+gO=)}-KWs>p)c(e zgvzn~G^MKTke$GdTI#hESdl@JTP-y0ImOEU?AQ zLI|L^0bWK%dhJXP4PoZsB z^v(d2D{ZpLC70Z!WhLhW5varV1(7pkYwt`;6tVhM-MAaYoDiW3@e20UM|%6eQaAKX z{ky)bKBaB3lCD`@*K}$avqBUK;3Z#R1{Py8+JcA2^*j{zAHYtm_1B}ZDtt}lj7Wy3 zE@(WA8q{JgDvKsGS-`|ntgq_M2V&Hd$}ol_+=Q>PMR={QkthLpH%~0Z`s(m?#nO?< zlP-J2$o9-bG}VNE(6{t&AN;BB3!=CT>jH;Lj#vj59vjp=icr2>dT^frN{d8|H5W_k z!nZXia{n))xHRhqImKrJz@qt)!2zb^x$PmU!&o!5Fn5FrpuRlZ9NQLs`{d7wx9kux zomc3<^jrbn?IAob)}j%6zD&GC9Af#S3Up#-x=q;_V~zogo91snpXf*&VtGYbov6$Y zK)?XZ3bdV3OmXSZ5V5?h3i1X31TdojZ_Mtwu2?zBQA}5L9wqV+!BR9;^|bai$C_g; zvBs#`j>)-i^05DQMJ1vEYtV>Btifu`%kxJ8FSqB+bLKhotoe|k0wldT1B8JTOS+@4BLidG0>c;6;(>goByV>Y zhX3vTXZ8bmoCO|{#S9GGK$YCWj105pNH8!k=6bp~hIkx*J7upIbD%)0zf02MgNNJ% zHhX$iyEgasD|jf_C*-g6-SxyZhh_3LwU!RX`v-qC&lQqp4P2eEY5vi5ifX&d!!LI{ z37J0kX8yS|cV#md)vF4LWL{Wyn4N!4(2-wa`mK!-AukQr=!rgxm*6<|q~fsu`V^H@ z0vwMRq#0&QRZeo5spi<&*1c(^b-RK&-?F2xJaQ)Ab}W8&)53SI;cLnH&+TMZyxKMM zYv*)lOC?AC4eql)9h%|b%8+Gu@zMv@U(Iu>PqrVP;gHKPNh-XAq2u4PyDK-Yu5OyA z;_3akIcn09k_Q}Rj20i+O;tT_9{9AgG3UIOSnPqcvoF+pdG~D2@LI)CaB@z*TaPpY z-`Tsz@6O-$vRCPtCBu0)`$;a>VwJ4fI5C-6YO+fBAn}8q+78XVk5+sONUl)m_IRvrD9}u(=3HJkR6@sJ@n^;)cg!GDH znM#W&R@X?d@DAP0BiIb&yWQJw3qkOma^NYZn3=i17=3+nv#P#5Z^J&=w_&~=v|wl? z!pP7<9<*rN1i*KZiEWF`puSy^1NU?t&u%)kbRv;A)}8p%Ns~-1nAMnw!j2WutIEh~ z3Jn2=9y=RIEyS=2+R+K1Jrs@q(7yp~wt_ZO=s^usNb3mPPocXiGZlXd?xfI-Oz17R zo?&};u_W!7(x W*Q%Px(E=fc|R9FeEm`iL;Q51$<^{Dr|T92qiLSjG=nvjZMP!S>+2q7h@m{d_Fj68#g zfkYS?=&T|ph&Lg@ph#3AB03RT474O3MSXwWeY4KK=bWB<-IaXyUTd$l&bnvsv-hr4 zuL4O|S62tAK0zD2f}0|hO1-BotnjcDl*MIb{11f5J&emC6&Ig)j70O`F+7K&s6fV| z3Sy{Rn)$afD!xZM!F}O0*avNk3dFA%#=(5p4p*QPto%kf0mV^yLhS?$gPY(>D~3U# zK_Nzb$H0DQ2P+RzhKGcNn{YmBqv?M)`oZw1Xe+T};f~3nZWD}(j`BuuUI%ZW7B+#C zZ*V4{xZ~p7PgZ{>z7NP1na@B%j1D~21~%r zv8LuQ3=TM_vMcd!uxdk6Xg(A-O&LGMhW96H4H z0qsvtngZci_qY+S)ihODXY}5%1@y($*s)yt#Fl}Z8M<97Gw~=_Wl4+uqGZzBI}hVC z0d&xZ!fqH~?YZ=cJpxYdcgLcY!mqSd?G!i&`kJhKMcE6vZE7(Ul1uCNCrGqumgtw( zNK4BN7mr>J7ug3Qd5 zE+J#YOmj(SKgsaL7zXwb-#v`~RM2Ve%S#NQZ#gP2{zFWXZz`zG7?K}$|9;&vVpW7M zDt2mA5!Q~Mwht$rDyYS2v=gm!l0GrIH|+#Xn-xu~?molf@-RN#1huN5C&rNXjgB0; z`{>%Dg>K~oinf-}V+MPbaek_TP8-ACxL=cGj9fF|uF2`T+B&ihWNfqHZQALa3evsD z$+aX8P2xksHPGS`o3@dDxy^w-2o6BC3K|bibO+9P9ogizPp)Fv2Re4F=vYz#;n!gX zt{d+YHfbUHP%*oO0b`7U1qSr9iF3O#ur`IN&=6i(>^ zV5J!)I7LxOaMTIx@3z`Yw0BI7iu3LGmV%anf6hI?Hp2Ih)d?5?y8Gy!ZAH^~8?-PN z!B`j!{a^wtfE{oRet;Fl>V}&49|qc^=72qbQS*`)MCE;W2i_iuieM#N0v#_&MQ=n4 zcLgNc*Ah9|>PW1GxC&;$OwgL@5B;DWTHrO@fvdUxm)>8AO2gN~fYWmT0000004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00BWsL_t(Y$K}>LZo)tmh2hf}QYB>~9zjBECIv;7fRMMs zLm-5Af$$DgQJP!WbLRga+kIH^o=XoqK15F0*t+&;v`3v*g|s zy5o&gdF9pwx*;GdFO>73G9EFe+2B!1LLRp9l2B|7#YyNdDFns%jOj6BZ4X7bIQzOd zbXeIy?>wf8Tw!4fMZZi7kC2clO@yctnL#c>JoCNZ+0jObt_c+5AX*0uz?KHWNgaC* z<$B+uf mO=~G=WkJm z?djqeVsScIBUORdh$oo$|3-&bSq=^xFRfmZ(V(&4WewYmqQFNc3R8>)7Bfa#3Q7e` z6~5B2ZcYQ6&(jLigy25)Oo4?mk<5XoCYruccC*~XHuuurmZ|*xI$R9hzkJRf%~<*w PXbpp>tDnm{r-UW|CZ9vU diff --git a/app/src/main/res/drawable-mdpi/ic_call_missed_grey600_24dp.png b/app/src/main/res/drawable-mdpi/ic_call_missed_grey600_24dp.png deleted file mode 100644 index 609ef526176a54e7b4a4ae9b955e1e06c440a493..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 249 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1g=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWO=~G=WkKR z=;`7ZVsZNI<&(Sz6nI<$%@b#N)CR`|v2cE!z^uViJ>fxL%@cdhjT|YtDb1`C&VGqJ z(3&jAqLMO)$7RKBmy`Ra+IO7T!E0E_e?@TmcL%M?EQca)DNdX33nm`nZ;bU*{BwrQ ld~Jt~ygl#JpZ^?+KQnP2_cfXO^a{u?44$rjF6*2UngBuDPecF! diff --git a/app/src/main/res/drawable-mdpi/ic_call_received_grey600_24dp.png b/app/src/main/res/drawable-mdpi/ic_call_received_grey600_24dp.png deleted file mode 100644 index 685982d8e2043c04a67f82cfb13aaa16aeafb327..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1g=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWO=~G=WkJm z=jq}YVsZNJ<=wmu20Sf^`i;{Q_DS=%bxf)BaZLT$Ii>Bzt^eBn4)@*uv-C)(%NwY% zgp2R_dEg?m+WE%G*$NZ1`W+{@E1apF@I=m|n7ivvlhXDDCu|pN7kg6^xRqVS#plds Ts}t=&n;1M@{an^LB{Ts5U4BV; diff --git a/app/src/main/res/drawable-mdpi/ic_check_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_check_white_24dp.png deleted file mode 100644 index 6a93d691945abe4c1a5bffa713df9fdf4d0c021c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 351 zcmV-l0igbgP)YO!~# zeVsG%_dMh0Fm}f>cSWN72)q$mTj;u_FWj?H@`#hAPCf<&3`7rg1;OECqWtn zb|TD9oZu9-G)=FTWqBKh;X!|wQYvj?!Jm&lIe^wk)ph-dn?KvOy~oXg6Wr)x9Zl0T z6+*tcu3JsQFx2p;f5YkZkqMlhOupga+>GN`Il;|{(zpy0(#{EPnJ6^bBK!m3*UoU$ xg#IOvs4CC%TW@glPPC650`JnDoD6=E)-OH9%}`b#x4r-X002ovPDHLkV1o43o#+4n diff --git a/app/src/main/res/drawable-mdpi/ic_favorite_grey600_24dp.png b/app/src/main/res/drawable-mdpi/ic_favorite_grey600_24dp.png deleted file mode 100644 index 63f13a33b4002215b26ca0cfd30d8f89b71f14fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 344 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1g=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWO=~G=WkKx z!PCVt#Nu?aMydj@(S^X~u$PN2O<(%oD}IWe-c{yqi79%mT{=2f9j*%m{o4G+Ugn?X zSS(dh@}3rIpiRrKVGk1&mIIy%(9-IA{EDpVGE}$*m7E3ll{H zrKTyHSO_gWC}$~WCJ?eu%=FQb#>58ypO=FSzbr|N`X##jlm1e*8$$D?`+oe3d)1@8 zwTelYVOf!R&B@yl_bYf ikCTg<94s7K85nMfG4g-Wd3qPji7#oylSOb?IUH+`Hv`iBv`o=JYM!bdhgFPg_*uGTv9f+rmiLKrzhNT za{jTT>z>y32bT|-{#tOyX}*}=wf**)**2eF&XS(j)%tb+;Vxd;bh$~wvZ|*o^tZYf zyh)L9+Zp_D$=z&m3n3#*kKNbwKAe)XSvogcoa1ND@B94APD=9&|4^QGwettil?kdg0008eNkl z&1(};6vfY*CP8hqwh=W+MY>RsZmj5{D1IUot+s_21$8M11&xZI#f1ipiK3vG+&gc^ zjS8+5@edF`z=eo^fD2s|T#1N9X*2V#i%CPOWSZ!GFspZd@4Pc}?*M<7s;z%0wXi|f z)h0wmg6$C`kj64Z>uy?;LjfI03?)u%yE;t#pXvu6tF#lodbDSe|9}b zwC_y1kMv*xL)#)i3oedFFm}8=) z`#O<%z``C&@T8{>vcT?;9HPrleR8Gp!`*NRT>v&+xhJ+q!or4yzPf8KqBAVY#Mbpci$mj39*TSYI7xqc?;oIl~U^N3Q z+mZJiArr{#MtR_1tQ77bmO4G@3KveBagI@*C3o-9t%Iy z$O4IG=UWQEsx5_Ij?6i`{w45R>dqk*lEGpHV9!KZY@fXP{SABU*AOpt&)HA}usMoD z6BXYP5;EkGA)#3Z9t*#=!x0J?jA8Hg(sSp#E81w$Q|=S}mI8Toendn;0oH09SaV^l zbmaC}Y277c61i78khg%S&z?o-MIX|L14#9peQgWRP?e`!Kmck^+o}NZ#tz<3m0Cdr zPOO?A3qUG$j@`%nfrO_U5iQP23y7^;up8Oax5V~nP8O|>+o%Pg|5)YA{mELKFLieg zi`sI5p3b8fAG+pgVUI-bFSX-gHqhm}^1dS#zasW<0m-%imIAPF>B$$4Xy2Cm`)`Cd zcry>_WN!4v$o0{isa!~dhgCd|UZl~BC2dRVF8;IBpFjWK9T~5k2CWQ&00000NkvXX Hu0mjfWb$l@ diff --git a/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png deleted file mode 100644 index 48e37b75c402e0b36e6bd8096a863750808a3027..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 488 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1mUKs7M+U~W1%@xC#RK_FN#5=* z4FB8v&+G^CI14-?fvUKLL70(Y)*PUIMk!Ah#}JFtS0~x}9S#s^InJTJZT1%##>poF zFI^~N*UEaAXf;d2!^7+(qs9X+j)R&%G94bW2y&Gr*X@!^&*df5lF`xOi}} zU%PP815TZ$SrSajhg2tbT(8u=yx-Sxn-Bkyi%Dk6l0gf?7aqTVTIa&qpO+_mQ<*!R zb#iR(>!PEU+m48QxF(;MYU|ahY(Ib}_(3`Tt@D2Ayh&YeY#(Vo9o1a#1RfVlXl=G}SdQ)HO5+F*LF= z1|oA^0}CqygGkfQT__rI^HVa@DnS}dfU0$kj6#fnsttikbq&mcT(FN`3Qp#N3mm6la}KEbFt(9` za6c2A<4#C{x?(EWH8!%9gK(Au+XiS0Ik4n51)7GqQJN0IifaHndf>{kNJ09McK`zi mB0c1Px%q)9|UR7ee#md{HSQ547D7=kXcB0TS2Rq zwXMyAYE>EiB8du${sM!bADaidkP1sm=!fO>`OdrNHgCE!?{(nAJ@=gNId|rI=RTv- ze_9;JUn%(rui+_7Mo~1&*tpSTJ(Qt4TSaUc()N?dRp`uWgL8xf@C9V|vm#>cup5rR z6xf2F@rSbdnmK|)@Ec^mszbc5MwFSL*$NMxR*Rj?NJ=GwJr=|tgyV1)WYf@O`Oru{ zf^%RGH|TW)`#>vS1HX^>El>g3s4pmR(Ah&R;CCfboH1Ae*&>{Q79|-w;X1sBRRz7R zm)=Bw7g^bYIx*UBdp#v9;9nDeFAUdPKzVfCAXCpS1tv7ZZo+=ho|`?x>20KOT0_kX z*Wi>Exa%g#6XG|4w(Wz9>&-4hN4iOokMOgwZM#@ zjBDW$+|~kmBH4{lv!WO^z3^Pc&ZZgt-G#qF3v7fRpbukHQNz3(b-KSQV0H`VZ!acg zm${eMq+XnQ-CeqiY}<4?$C5f6-8u6h8?Vbf{L9+{3TkQ|mXw|5#ZcjZu1%^(oQ~Is*N$6V|{An1i?Q81!$; c3}ev#0M6gJuw3A#O#lD@07*qoM6N<$g8VC7hX4Qo diff --git a/app/src/main/res/drawable-mdpi/quick_camera_dark.png b/app/src/main/res/drawable-mdpi/quick_camera_dark.png deleted file mode 100644 index 4dbf6c77bce2898fe96ca0c423836a5c297478a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 326 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1g=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWO=~G=WkLc z&(p;*#NzbP%h6uUh7xQS?Cp3rb8&j??uZLhI4aBW&OxFuaH&YSq6pWzMQ@dC_K6&4 zoV!Hh$5YJ@a|Mbx3vYJZZ&r`LJoP|x;=Q}|?C)fz7HxXvxRdX=+?8kS8_X_eoXO2P zuy}%xugXI0#;pa9dEcZaTJxo?J6C$cQ|$2qL)jdqZ1033$r;|?=5G!^StOIwl2g7e zo?Cxu^4k`b(~~~L6c3aRC*el28Q-Sxey;J17Hm-`3VuX#lsR6lWG Q9ndEXp00i_>zopr0HvXL*#H0l diff --git a/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png deleted file mode 100644 index 67bb598e52a36b6caba846efe733501479965a41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 97 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZA`BpB)|k7xlYrjj7PU0;YOOAhVxZ1nUHa>8-=jwAuF8py2H)j9AuhuwW+4NnP zEgm#5|7TjW)0BM`ydVB`!zxama!ws^jlV9*vJYn26u57zwQT;if4&t&6lD0p@|^E}I`6Eq^P|I~hGF3(gnr^DmfVFo6r3H^+kPE-1s z4U>+&nyPQ{>fEAd>{tCdK4&iI4S1q@z})cClhgz16Ei=3I#?-u*Yzoe&q;p|^eTg=tDnm{r-UW|gcyc( diff --git a/app/src/main/res/drawable-xhdpi/ic_call_received_grey600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_call_received_grey600_24dp.png deleted file mode 100644 index 91b5587a8adb5f24313e960030c30fe39da921a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 322 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tg=CK)Uj~LMH3o);76yi2K%s^g z3=E|}g|8AA7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^+;1OBOz`!jG!i)^F=12eq zcX_%vhD02Gd)=3d$&jb*VR_2CXNp;@dz+tsWOuY#+_NJ<;K)q1pH|!(Z+@?Rp|#|; z;rkNaj05(JovPApI~$I4vWkCF4`i=+#(3_@uLIWh6Lwr^&6y|Jabh^jn z@zY4*nfHOPv;NEl;h$9*O!FC+?0L(cY`jljY5~(*hD-AxAR)q(hcYegxM-9W6@NmQEe&=tpqz8z>afMQ{`p1*=PFQ`(QwT~}xSETuGQ z65lsH%MsGpR%qV9{U98d6#6`O$K9J~nu>~wii!%saL#o|KH(W-3-a7a;K*ZJE|)dS zvLXjrh;&TTWZ2g0bshwPJsb|N;a)T;*YP{3uy$U4N~ zVe~_|-05`cqJ(YRCt`VV>hLcD-V;|inM__=t=1kcA)Ezj@VbC!(#S91;ociO+Z8(# zY51o!c=#=+3eOe1S~i=(KW+^k50C#ARx$X8t-@pTTVrh^kr}+$c9~%qTD4l;!FJv4 zIO~yTSV(od-D5b--=VEwAD$VjNJ#KHfZlHv9*4PDENZ=8?+D;sIL@&o`Re!k2dhYA z1}_Sxh^*aimyjCZeUyM;_xmr2OyR{0=wb#en1QQsPCtWYCP^eNr_AAH3GpWyz+NUu ze8ByESwg@w0My_&cbOkj^Y;gxuOb0ElqtdAk~v{x5*JAlgTbJJ6IQF$7%BKA5^Y%m zg5A&2XmmIpk8iL;vbIT)@FCcp33#7uVU{L=GZ7%Y@6)a-V-n|*1bz543H*AH3-}}n z$8pYtL_XkS-!G$TwbSXGHk-{-Zs3=^FVOq+vBn!4&n3Kyii(O#?(+lG+Ay1H)vTKU O0000J%=P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00Fm2L_t(o!|j*7OG053#;^26Lm3gJnUxR(MSnz8W6%=R z&=gG#682kypsBGy2%Mz7A%cjMq-bmif`*2|$h6I*NkzR)`So7!z25seC1*Y7`N4ZH z=RA*s(%Mqmfd2|`6Cgs8JVjDO*u^9Cc!4Ni6v>n1njJ>W0h|QMq1IJ_Fs^dXD5n&J zo-D`o8v*8s3xj`^W*G&A6*3Kb-k8+`%<A^hE+Htm{dSi1nf%%?1_LGseoy%RdFv({#JVzElC5+H(J-1QsmDY--AhhY|Cfx>Rq%A z+kkbWn;fzwe`0*}yScM0e?ylU+cLX9$5jT zldvEkqC;lFad228e}F@Ngz}di_I9f>+8TLsthO=)F0#;$v28$I{Q}S_1JczfnQs69 N002ovPDHLkV1g9>>@xrW diff --git a/app/src/main/res/drawable-xhdpi/ic_group_grey600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_group_grey600_24dp.png deleted file mode 100644 index 1865da69420df32b20fc33add3ebc145b1e6189c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 364 zcmV-y0h9iTP)4l$|K>(F3`$z{mWgkNUl^7{2s zFS(|cyME;GI2(-e3lj0E+pCcN_JV=6;KJ9d(6$!r`g#>+ zxh;$jsOsJu8r)2b$1M1J->*2J&uf+|n$&b3m?EgN!7*J%Bz$B^xTDJv>(na~u*18{ z7a(Grb5e{wym7`>{&8WGPrENbm3^Ku9(kh4jLaRnyud0k#y|8~k;CqP@&b!Iit^0^ z^Umi)U=Ks)wupkf!OgUfXlgZ3_#@jjo0000< KMNUMnLSTZ#6{vgw diff --git a/app/src/main/res/drawable-xhdpi/ic_menu_login.png b/app/src/main/res/drawable-xhdpi/ic_menu_login.png deleted file mode 100644 index 17c2e5c39bb4ea098a5c0ccc5cf6d745d5a18358..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1740 zcmV;-1~d7IP)$1k6)-=4Le{zR6_%_5^03PP!5Hf_4+5{xX6KUP0B6N znqD2fg`LED#ce;j;|V~25FFB!=s@K*FsTZFQ8OXhuS#VOEa?4S zc3V#seuisLZ5%8cJ%=cWKrH+wue3syW_f5FFf6CehxUG?xU8qj`jDmv>fj*MKxe7_ zCUW|PK;9NVmf$FB6k>vhKt9tiVk$ESe#H)AKZWoKf^OI!yGHxCuso(1Ozp^}%nt_+ z={C+Cgu`*^Apy{=hZd9QRzhq=C8KOj+$#5DL#nDG%QAr!zj;BwID?>r-STMgE--Ll zWO}~BPPYp24$c{5Ds{;mO9~ykPz%O|$1h^Hd;G=0WqH8>l<9<^?H3I2PMYPW?5=ZU zQoL)Jt&YubzmSl~?n^&$2`hmrgXqB9hTxDcH8>|w0L!4-IP63-q+$u$V|K@YI;V@~eP=&u;Nj03AzTxI!v3|&HQ@Br39%W)r)-RiF33-~9j zNXi`RLg)af3syl>`&}1!Q%k~9bj)G>bTK-Of_DK0kPbB^Bhx|XQ|;%peflj$>K=m@ zVQtU=bcs-&ZTahN8qQaoxM&!vg{48)LW2s5`QC5II_L}rfOtb*GywaA_z;#uJ_k&F zqW^lY+%E#84-s@R+x2V+i?Kchs@7SqW5>Vnxo;72l7!70;%eho1Q!7w2r)cg0G($g z|Mh-u05?#?B#yF=2~GW<3$mD;Az zlsG>(khjHgoM(l4wUM$A90c+apTL(u>oGeiNb_WYmZGaT&k_;~^dl~XDTlUxJMD`n z=cs?P+cV0-6`W5T5dsTzlf`Fp70^~`n~(!Fptg?QD zrm%Z0pxY2Cp0Z4+z9xTwHV^F@yo!}s z!5SVG=tqn?i#3vlyd&|%&fyN*S#(}LFP}z7?8cGDPCysbLS4Rf!cUhN78cyaM2LyY zU^m0tPz6oU0Y`{*LNj5e47LbannGyIw_b@93)3K8X9S&YiLhFDMR-lf6eMmR+bEax|ShyBJ7Va0UE*7?0X-51_67>!d zXGB;CTssY#VgkYPJ9eKT{TD36bH&iS)>35OZ7Z_BDgNNqLq~}P#-Q2;hoGKp<}2a7 z&t>n3h!zX1{)q)?Nf{cWq2~+ISx$N_SKchJ`p6t^&P-V#^nQ&!MSC_2ja(t9BXzt> zPSY2uVW+nHoctZSfWN`N%&H3;El?Aa7P(VqI_(S4ta?{{Oxw-()cWkSXjGU*Fp*7Q z2(9b=hX&R_As9I$R8{)`pxe8?vB(3US*c3mVzZh0!>61+02HY|3IIHJD#A*rufK;}+|T)?P{(8n&+9h0Mc6>B*GBi!?}51T iWWD5pfb?+OGyVZkMzqL`gIA^i0000`p diff --git a/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png deleted file mode 100644 index 7e5c6ef194b3bb4a114430631ac02ae59345f792..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 872 zcmV-u1DE`XP)QqUqIAva~KHZ3HApueDPo0-yVBQ;`Bi}Vk!RD)_& zt}-oaPVY6Yl?{0NHR<1gpd2}Bm!rK3t4yCYrBOZ7m_`F@In1!G1QNcs*CyihF+qlW zWSAs{6E`t_5DDge$XAFMJ$%Irt8^1xkDerewS28;W`I?SA;VLm-$jyn9|jR|mT4pM zg(0qUhB`t7iEx@OMp!cQlRWr2v`tQPmi%ivY0PCrMT|?#6mH1sXo)dka@Mu^gTqX?mDB0T3l~Oj zVGt6X?yVilubgbmh(uxZ76z^2zRSGk8GT~2dza4*=6*IvjyoLmjAT&fi)MQxToxbQ zQ0jh5pa5mLpXE~69e(;N23wCjgJo&K9q!l~gS|0rF=c*`Z@IAk_W6ZI5#^%EJ&d#4hrARK9a$EO4AVsi zyVypEdOGN4m_;+6rrnpk*~K%fhu0JP805UK!H7zhPEio`uF)}(cGCDIiEigDbFfds)^NIif03~!qSaf7zbY(hi yZ)9m^c>ppnF*GePFfB4NR5CI;Gch_aFfA}QIxsNZ;$7?j0000VR(!H diff --git a/app/src/main/res/drawable-xhdpi/ic_security_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_security_white_24dp.png deleted file mode 100644 index 7e306c303cb42512db0e5354ea0fd3ec38fecffb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 507 zcmV{_!j` z(65rV@qh=MM2Lnc{VF;T-}p_AA+fQ@0*E#)O97&dFAWf6S-=Wd0V}{;2=GG(zFGh~ z`e6F310P6Frw?J8>A({w;1s4uI&i}YxQgkz4qR{oo?$wt14kWz3O=T~4hU~@0~ay9 zMv{PTPN2XP)1_qK2!1YL1Cz($WI(u=1K7nB(*Q{WS_GMa%a{V1>40!IBe2E}rrU@O zP~%$$V1KlpFI4RSVbdBY@szOhh%F!ttbn~dByg;HXZ-~002ovPDHLkV1k+$*vtR` diff --git a/app/src/main/res/drawable-xhdpi/ic_timer_disabled.png b/app/src/main/res/drawable-xhdpi/ic_timer_disabled.png deleted file mode 100644 index 45165abecc22bb5491b60183b6f994a6e058753a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1602 zcmV-I2EF--P)Px){7FPXRA>dwnq6!aMHI)q5D=loCI||sxuOXWiM%Kw5VdL42v#G(ic!Fi2T>9H zYK$7=M?_+@sPPHkj6Nb5Uu?wGG)jUdjkbvajHrF^i;9XURkT=t|8n=<*>lg{?)_Nf zNq*g#Gv}O{xw|`aW+8V<#xgoOdXz2>!yy=gFQ5lL$mMd!xcc1JCZ0dey zi7^v)nb@k1z%40;TyBD+UflJd+pIwMLmOCGTkc((hQRlL`U*u0!ODseG3bPao)P*c zxHxK~Y=5b5&`pcl>%9`|Iv9pf^d8P}W7Dv_VEH%Km!QQpEGTK82Y*5+TPpcf+eYAP z!QKgNd=q1_P)vY62t~8K8{)=+Cv96p2g-Wc^MOg-MdEEVeW?0yEHH|8$0$+_P-u%e z6e{Q^f+iU*u{%85dUV9!38DN>uO_&#RDpb(7z{{Y=Pjb0uSF4VA zv%rq+iC8=gPJ>^elIWS=0!=aJLIwQ-=!Sln;MvrwBc4{jP>xV3y{A-e3R@{FC($P5 ziI!9?t?t^7{u*elb+Dcv@uq^k3wIT5X?zDld78=+@HVo@PPx(2Vvvjr^!0euapBrM z)+H4^V|KZkuSWFR$ku_@c~5kgUWd6Ek6&X^?i7lW$!0Zlgj&jdb};Cs!LyK*7wM5x zVm8J4HnL9q&mfczO`t2(P<|z;Q`+*N6)8!+reSrGf4zEQKW94LrqyDx3B1qLi&f6B zgP`oN6$Xk8r9z&fCfEx$Zpo_l(thK*KzCrjnbl>+$gSd|9ee}W)gcO}1Z+E;;RdKd zNvtcP;Dzf!P2i|e&Z;IhzAf+~D0lbd7nBdcq-x@Nw#3k06Ut#tz$Rt!ZQ6_E=!i2P zw2>vr0UEA$TvMy?Z#Kb)HGwDvh}zCd9nmo)$O_P4lSF&Y*fQ<7S5)CQ*#sTY1hg%O z;^sURBb!EoO@ar(#*X^Y@GAx_Gtp|8s)lr2X6g|zZiN=KVh|>%D7o9HfusB%DXl5J*LA;O}*|ad&dB@ z5~u?P!HV__O>hU~ZDUYZ0yz@YKhYg%g-{Lz1`RLLkv^j+F;n{CiP9B)x^&Oj==B)w z2FYqoKxY9ZMxmV3@R6HfTXlt3FKNxO51htKe=L3*aM+FZ~DuuN~cRfsdmm~C87R%y@%VVxgxjWbj-h9=kwHakEy zCi*J)D4be043v+InUwzgL^1P&O4&eA(67Eb?J5nWtn?+CRWV-_EnvTU_MmfLqL_0= z75Z*-#)PUU@vqmYHx-*oh4_c0j0Wq4We7?~lBAD@@*fwaJ>V;E$?TNn4O}|4k-3YNl{n6u$6+ZlEJa2ryDC^x}T&!KV?E5}fDLL-ie zN@eZ^@9dx-@aFQ7RiPn(ma?S8O9y)WdqUM`2z<_lr$8}0(UN}Ectb5;h8zz9>6Eq| zu5_9s)u$ACp&#_g<$E|@Kxs+7z$IK)z#>SJ4{5lA6#QV4K}~ulqU=zosH{YHT6%%FoVXyin+MCuGMv!+4S;zVaUxL*%%L$tY2Of7mr-v_SDd=!UmL3~RJ4em-~a#s07*qoM6N<$f;B+$ AJOBUy diff --git a/app/src/main/res/drawable-xhdpi/quick_camera_dark.png b/app/src/main/res/drawable-xhdpi/quick_camera_dark.png deleted file mode 100644 index 753805275baa08d95ede5eddb9f5bbf40a23395b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 534 zcmV+x0_pvUP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00E9kL_t(o!|j*NN&- zyFjRwSJ1{y7a@w05dK_G2zmh_Dy5V?Ejog-#QC9u|4dsxr9TI7ilCQO_HZI99(?xAl0u-Ny-YiJbE+wbM~jWhS_`+&KDaC)2NkH4`;z!88T YKN)pv1mZE@z5oCK07*qoM6N<$f-VW$-~a#s diff --git a/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png deleted file mode 100644 index 0fdced8fce76a0f9a527388935adecebf63d0dae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 97 zcmeAS@N?(olHy`uVBq!ia0vp^9w5vJBp7O^^}Pa8OeH~n!3+##lh0ZJc~YJ(jv*C{ s$r5`OFVdQ&MBb@07vc`*Z=?k diff --git a/app/src/main/res/drawable-xxhdpi/ic_call_made_grey600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_call_made_grey600_24dp.png deleted file mode 100644 index 66a9ff46e885ca02f6b1d0a90d8e09be872de8d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 386 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g$s z6#V7s;uw-~@9j-PzC#8)tO4bk?b;zp2~IN2(T?nS?A$4?Dt*Eq$|wHY-!Om5jb__9 zsj-qAN*xMKU}95cUGQWe`&Xb@(bP=a1w!f^AbKJ+`&OthG9kTW4^4X7R|RT*Da9i zQ(n>knC;b5i^gA;daoroj`J}Uu2GOU&c;-vqoA>zkEu9I!NQ%LwZJ5R!<|`o)A3@H z3sXFlXD$A}Ft=)p;OUj6H~sWFS)N>ZenCgw@{xs&zo3hNBa7gd{|m&+!u#G Ph6019tDnm{r-UW|oVI(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g&? zz`$tf>EalYaqsO7PrfDx0oDuW+4#~Q9eSt{cYLXIsdB~6dl|}xe!V5UU7h>4IXrL8 zbNZ_l_G7IUhf;?^6Pzf&;JK^q!#^(RHHCjf16pm>IA$#Myw6rTXB*SAjALwfEo3tr zt9&jfFJCJ=?QvtAW%~)vsDMw2S8qy282;yt^tdT=b6Tb+Q{NY!Ob?}V1}_?39^PtI zx9VZ>oWy|EwT&vzc$&GUWKC+C%-l3-$)hKYK}*#BbvD_ni>s)vwEU^SdGf%-h9Xb3 zKAtJgMny{&h`f}j-v8j}#O8YwWh|ruK7Q7pE+Py9kd=40Sun5elF{r5}E*960e2; diff --git a/app/src/main/res/drawable-xxhdpi/ic_call_received_grey600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_call_received_grey600_24dp.png deleted file mode 100644 index f4be04c671a959c500991442c88f966ff99d66fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g$s z6#U`o;uw-~@9j-Rp+gJ;%m+fI?tj=|*ZN)|FrlGt9h(cQ!5N0~>03lQ_nlm0F+Ht6 zb*bSAg=QY36AM(Eqhtyl_KO5`8()*y@Y8J;pGgrz!U3`EJ01PcdE9zvrT$_rGlP1` z=SDkA@e+w=Osy7G0TULpwOU36G%aRXYGD;HX))7MOR0dy#SBv|_B(#R9&Ovst5v*1 zfvKHyQ9*@*P&@0QLJI|@cE&|TuM~`yb1f>)Qea%pG^yZ|f}lG0q{1QvMRn#$MUNB= z)ww1WCoPydPnh{@l^+j_tvbI@;U)!1b#|d5E&DI-+a1{?SMo diff --git a/app/src/main/res/drawable-xxhdpi/ic_check_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_check_white_24dp.png deleted file mode 100644 index 2a7c32de61f8b28d694e1cb1c8f8ee7b6f178574..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 990 zcmV<410np0P)z=aW_HZym6&gBkVE;C75ZErhLvcO>!wPC!J1nGd4Ch?w_8XK6f0)Ad`eSsZ@$*&f3~q z&iDQA(ENJ6zMRkJ4KT>GEX&k+%;CYWudk1SFZ-_R`W+CO)Mzx8ve|5Kt)VSetE;Q` zz>jZNS6AqZ3ypzMo-pY}E|(k9YTypRYXAEB8vWpzBjmN)ZEjMnR(pom+O}u6WX|sN^fWgonM|5GVKoHtf9ZwQ!m2w~!(hZ7VHJ*58sgt2tirLv0eLSrSV8I<*m=HuutIaBkLGP}XJU<2 zu=6dIN~7g+`AI&X=cl1#{{pLQtgwO(@VhfVKaV@+J!n4<4h|+9$Ki=}Q`{7+GVuG` zvBAo=ZJs#`3kyl;+a{*`<>e*z^Q75qj*Ake^|vO4U{#F`R>2rEH#cWP-##(Md}x^R z9g+UW^?Lm=;wFmWH^pj4{H_~|*$R(~i;HMZ6|)A+kyG9iweTN}Mq@%`Qs20RV6`P- z#qRtfFnB6iC=^T>Z{G9UDhl7SNg}9|!^6V>x1LLYSYxFGf25N65B2{toB5*sBaf7=bU}XYUxR%Ui*-bCRhOlm@V3ax_p3>;Q{;AY^f zB@wtWvHEV|QGCa=sRSysTNG|gIqFpjl*c5%?R5lhOzQ%>gj)h|TN1c2HEXmju!qcU zuO!@<)&=%1xV@D2Zl0FUt-9c5iP??g)^4|%mRR-5BrM4Wnbc~vwu6_Kw0+Ao#H2(f zlQAomN_l2x=7F|Y^(xsa^(9hB_0Xcf0gy-}5{X12kw_#GiF6D44PiGWSrCEbA^-pY M07*qoM6N<$f=A-eU;qFB diff --git a/app/src/main/res/drawable-xxhdpi/ic_favorite_grey600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_favorite_grey600_24dp.png deleted file mode 100644 index 11f108dad156b8acd2cac550efe453c7b0c02f25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 834 zcmV-I1HJr-P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00O#6L_t(&-tF2wXcJ)^$ML%+)})HfAf#?0I*Oo!IEcZB zgD4_)6hy?g#qEpGE|pf=q2O8v6$A&-67W4~d;tf?S_i>F4UK}YgEpp#v^f`B%*%6k z&)swPbU2>*|L`G~Joo(nzZe)RYZ(zJ0!5$*wBkWAGK^55M1=-5W+^htUgEY@tJudF zPbgEPL4^_phS@`xKTwK6K4NuTpSeW3V^trcd=XTY86f2iWOAAxf^C;pl_3&MRY``K zcc}W#NlaN#in|WmF0D6gU9Q^3TbETidSyU;%(&dx!s0gvF)%nxRif%u=Lg>T(|Iki zv4zC~M>xuYWYsk5+<;8i59|L`qwb+ z3yTYOpqTI&3~tRfpWe-&Oz0d7i``<-h!)UAF{q#g^jHikX#u?#gDP4;b7Ih<7SN&? zG^YjhQw%C=0hPp{q83nI47#iZG%f}m(gHdl2CbzQI%l39ySM$#&_OrsuOAt`uhw>u z$;(hVFIq3L?N`|Sp@On*x{iCHadPh3piPdwrfGOp zdiyk=0>0X>mz{p>;!j|lIy=40hmHpZI_7m?6-*a0a#cC!ig;M%X2uvNr<8Mt znBSpig1j=$LsqL83nzK1gfrDLkMuF8UZa3iY`%#39h~?0Y_szuRZsF0Jn+o9@8)62 zM+@ED_QbhGcTibJoa@q@Yfe9Of8k4)WbZa^e+;TfgG`AzBL*QPXEpwiW zV!3cr=>(Ji!ls1(-PDBJF(#Ohdqo7D+ne%da`exJ2o!-Lkb!~m7b5uxE2rfLmjD0& M07*qoM6N<$f-&=Ui2wiq diff --git a/app/src/main/res/drawable-xxhdpi/ic_group_grey600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_group_grey600_24dp.png deleted file mode 100644 index 2aa030e4cf116e408cfb86867e7740adeca10a5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 474 zcmV<00VV#4P)pcGlmtu1`<5s zzDR{Bg|B!WUmuY&LsDP=-O1xx;?xm#l8ko!GUS9uY>cJ^+#e~3nM&6)|VI*`+As(m?$%;@RhHW zYpOqCNa5>Yo^5V;@wsM;q}VqM2C4KMQ^$YUk;t5 z+29Hn$QA3P=!p15{v7E`V1f)KoFFAKjDOLTq!#c)kR*ruW==^o^(_beXc%B0r_3Jx z4XpgI?>}+hx877k{Mvc90!p2mSa#hG7_nVHm%`2Ymi$AGqb= Q$N&HU07*qoM6N<$g2KAn+5i9m diff --git a/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png deleted file mode 100644 index 72128fe69086bd90233bd2f16ca25cd52f423326..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 875 zcmV-x1C;!UP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00QGlL_t(&-tE~>NRwe4!0}CTo{HE;x%AM%xu62Y4l4=5 zL)5J>sA)kGC5fenLhzE>F|ZsdL3A_fP}i9R5)?rwRM%VObYIzLi4$aA zihqXFZWXpZqu8**g`zoKtYlnHjrybG@8@_{Ip!(53P$agM%M4?utis$x-&H*Dp zk8kE-)f!b=q{JT%hI7$0*Sxmp~@O40KeOUgyh7>7%-wczKq z66Ym5OSRWg$yI5pEZi39rJ}T(FFuBgtmZFq1JB0n26^b_)jEaWHD1u2f6Y^>?Wt{? z4uW0Wf`YiI z1yL{#LUF95wDo}?CDfvAO_Thc%J~v$J-LrBpx^(_4Zo1n<(?b+4~rr}KY8->lOSe$ zY(JolL1tKOhC$kz2TC)_0`)C0N;-T{FFDGrRhb;U;eb{rMv>W6nM{l#^Qp3%80jsN z)l}I`jLb_PF(j+0QcaBJySthy#l+~Q!_`zNCPw*96=|kQF)_+nP^9^!Mn3vGBkTOP zn575lfBEo1c}~j2G#ezpbMry+2N5U&MW6^2fg(@@`o};kp@B*|(0pj1q7L-M2(+v| z(kC70Jtk@4+mI%7peJ5I8KlQL&@Hc^OGsHAXuvBdhjc~<>hTDQ^A)LE2THQ!4RjFc z2MO(E!?-ulHKb?y+mAs{pg2=V7xkb$oztP$w0nbB%%8 zS#}N@Lt13B4kYKkW6%LANOv2*^xQ?+Dd+{#3TZt^&O?WwQv@3`yB6Nbq9afTMWml} zm|mP;aRN&4l3+vTH~E_=H~`%y*d!ZmRttSpEJ5c9R_3tn@@3cxbebx`uG?>el8hUJ z2B;A1ISCi*sjW;KfZ|-GwgT<0_GMknH3-_mQ)>IhK9?KC9_IBRPVkx9igdePaUP+l z0`2Az_04g}#}aryvws0~@qiNbO|#e6RyxHS*6J*;sPbp8+3s_1zkw|Js@x>$|7F8* zrqER2SXfUjS{Y`U^;+Tvt>HdK*}*+ZNYXFvkZR6zofJcSB-kvMWr)KB$!Q_O2p^av kONN&4A2j(q5lC@<1G;jC0F4+6x&QzG07*qoM6N<$g6>#9O#lD@ diff --git a/app/src/main/res/drawable-xxhdpi/ic_timer_disabled.png b/app/src/main/res/drawable-xxhdpi/ic_timer_disabled.png deleted file mode 100644 index fabf9ffdbaf1468db36d38c12669a7bafa699c5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2493 zcmV;u2}1UXP)Px;cS%G+RCod9oN0^{MHI(bVHH6GM3IOZ*L%G&fZ&n1Vk90Y9w?yTtW%wzhSov=P?AYFG?Q;A@x#U)I&t{mpj!6gp@#M9Ci%PJ!#7 z_jcDyG&-(>Fe%DE>`jByw~3A-(XMbU?2t5J>Q>T0m>1;>_C`Ucq_H!rM^Zb&82A|^ zS0)Wc-J~7V9+-c=1Laa19YrF?!F(tcb=0m&I*EGKc2JPVr|cY-B!^V$5PWBt0Dpr^ z?oSd3N1dTJ91bUfE}Ms82CRchWIe_=L0ytu64xQ%elQ23WCMl!Cl2DMh`&zI1W&-P z;494-HaJ|SuOiT3SnZkFr1UfR&m-~c;cWQGGnZEEj?XHecE1D|4x2m^l9|SnBQs43 z=fu#DHWT7RbEu|F^1<;{V2w9XiA~KUA%|0vxDZxGB}19UvB8e&)v0i*Yrh37`N1kc ze_nOb{c97e7rYUXi;6Tpp5^b=(-Nm0K@-8!UrA@x&9Dco0hcUBxszv@RLqE{Jfo%z z8Y-G93PuI588CL;;UQ2*ofZUP{}!<1w!kj0Es2~7YpsOPFN2=m`C$AsOoF|ADq&Y& zYR9o=)Zc(hG(hfIYM;kGG5f;`S3(8dWVZ41B#ioj9{wbM!X2=4rH!D@5U}LgO8uo_ zPcnTW>P#9J8wUrPsfJ}Kiea&pLk85*a)e9t0~{MN&*hM~y48ui8huar2r23(@YgDydQ_RoXGE<;22 z3i|zm`n9$tw;kXYBlj`Id@*>(Fq&92D(qml6Wk8!JRi~0!g$yv?5h@z$?IvO5pTBV zN1?_7&23y#d{PfL2IJl^5%l=rBg-%v1zTkn9nL>ECZBVSVqv$Gb+-m+0ZldI+8En+soCw1LJe-Ll8ShM2m#%rw`~+L4x3I>*>&T|Cza(5N%W zdUzXYREkyOvjKDm^OYqS=C*DfZUC{ZtLRmAkY=Gg)4|dr%Bpl;J;Q}WunB|-;sw@OVt2tK9lNl z1aZ27p4T>kkF;WRBX}*VRMSVq*xg8Ig*s@RQJBfVNvuYgY{Yb_7oarfW7Tv_P#6Dt z+2E43>L6mx4g&VfgYIdO0exI`}!V^FK9 zFwouXXK=~%6mrki1&ufqrnnko=;ou3{(c&RTaC(6UzTeNQwm1jL*N@Vz*FAD?jUD) z3c7%G7U@=bfU{zaE6H06Vz7%mp#}<@Y=CouM1<}`P4R;o~_X3~K4b^_x z6~9X;HVdVC`%AFoo}%$KwnKx)S&ee&%fD9)+KaKCd#uGP-5yU4Y`2vyIS-9!6GdCd zjM#Qzu3M!gdUiV{)=7;RkZT`M&X&xeAozY#6Xt%gsbka`J_k#*mYx1z%*19^nE^TK zYS5Q&mZ)Y=pPNoDIoM51rO)EsEL);$Ed`q0=IwzffT? zM9oSyR$c4wF&|5Ex&f?ll%n>I>zhh8I2#@lOaWhn9;pPZ!U;L_hSwlUG|nXcIK!D9 z4lf7mi9bk2%bi+q&s~VC#k8xT6{2Jlh4wAHn#^%L8Z^s{laDaS?JF{3oeIHs z1E*Hg2eEYt&;)t{_7NqmFJn8#I*mm5p? zhkgB7XhRA5L%b7UNAQ(R7~T!{^R8{PZ>3c2=qQ~PT1%-l(JP|EQ*CTgXC%@Iu7{`@ zE5*6UGNNcjBJri1v>#&YkyJOh6?~TzFilMs;OPpeOoQ;-N>r(d zX82P<19v6kpm)17aatOk#Cvi)6!bnyb0fdE2pAf&0{vuo8{T8^U1Qj#qRza7!dcQ` zx;yE!l>mKKr@znFr6F{8)V38CgZ_&^ZN&nZL#MQ{%|-qNEtbi4BF2ES00000NkvXX Hu0mjfZQ7z% diff --git a/app/src/main/res/drawable-xxhdpi/quick_camera_dark.png b/app/src/main/res/drawable-xxhdpi/quick_camera_dark.png deleted file mode 100644 index c1a3549bfc0d4b26e9beaf97a65220052ffab653..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 781 zcmV+o1M>WdP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00M`hft2b> zSEV3Gx9zH6OyWYbk*SEEAe(#yA&V{){SZE&J`nl|suHxJLh2*!zpKVO(V4k1xp&g` zo>}HBewq6?=iaffdh4+a$bbyU!eSnonAZh*k6irXoet0&8py^quT_Iy@q>1v&P$b` z7u0AszVTcY=qbnCC%*Ee6Oc`gP0mpr7wj;L69r1Ki_X|56$Y|3a{%_RgF!jsh(a)E zGZxTxFz7rMP(2tFR~V2s&?T#6N#kN;ezXZ^vN*k1}OE#{G2(i)q!M6<>ZoA362?US)wk zUrt3O=*%~jpYi3+tV2e8)4Qe)1N)9)M6fM=4aG^+%^WxKWIrcpqovvECnhF zL;Qb$dTz*WCiS=!XjJv2o)Hd|)kw~fuVkE(*CLoP=D!3<;Ub>Z*N>0U>*sgMhaqat8UXvO%k~bc@B7OROX2TDrzhs$1A> zOOyJLF^ZHZQDjtDS#o8yD^32Cbd&#qRjC;|;dM00000 LNkvXXu0mjf*Bf09 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_check_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_check_white_24dp.png deleted file mode 100644 index d601ca823c6915740a511649f5f6843bf5cd9526..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 946 zcmV;j15NyiP)4D_~(7jH6<4N(z9Z-wo! zi$gt%M`7T8WNjC}-})9~s9Un;hb+$n;Z4$n?tMPV`@Zeo#grDE0V^{bxe zElc@SwujB@4we#@KR!Ngt*)+K5&0iCHa4X2nzB77`%iKwq$mHw&d!d?cct8uvLX9t z2_aqi4evKiUkDH7xZ`?)<2ZE83CrK}{?G|wTl$0tlarI@GyoYL^iP{_l2>pW&5%QKY^_es=O-BR_ljP2?jV`A1H^nJwnXVw=fJ$WK$g z<(n-g6w(eMxANr=)_=X(I$^0=txm~uP1jY1k&^Z}e@vvl$&-9Xg-b58U(UriKOp~xROm}*wwNp} zvz}-O`^Z1qSH9!4r@Qp&3}LCrb!~P6v&Hs&l}#jsJj?IX`7YDb)7JX>`dz8eC)aiT z^1Uu0Ecrs{xnmBDc2xdSZa!q002~v*y>^@9y1Ux!bg6jkcS3Hj{6Gjg>8K!0&{?U} zyw=XljZ8EI7 zLa>50Aq>b*xKHQ|T^VX77{crUeF9g0!hOOC z{aJs65Rjix#~daE33>QmG8p>-9O2|4pB} zBin<>PdS8gxqP}-tKF7z82Kp^e7+9P+sP(`g8s$;`2YX_0000000000002jcU#U~L UWj~2`3;+NC07*qoM6N<$f`1gjw*UYD diff --git a/app/src/main/res/drawable-xxxhdpi/ic_favorite_grey600_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_favorite_grey600_24dp.png deleted file mode 100644 index e8ee60e7e68b23c2b9e68740ce2dafc7f6f004a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1043 zcmV+u1nm2XP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00WCjL_t(|+U=W7NEJ~O$KNx3eLxsl=Bf{kHbS(C#2^q7 zwMArDnn7kkWiDF%GBk`bkw9&cE!r2jX_fL!G2B!{fkYJK2S!vzqFGw$<2|QEA85Uq zx$}JohOrh0gb4XNLIFSk5C8-K0YCr{00aQ;0c220J5ffMB1VE4M(CxL zGBOP1S;ryT=wp-_62zEdm?&)=V69UC5i02+fua%#9#F+f-8`!}!GK1dI8iD{vlk$Z zTHdNIxi}_hV6|$VbQ+ly%JYUAQtblB=Y`Osf8ry@2>+KQOf{bj<#|q?b$}G=H6FKm zbd$LxPZsx0WQkKpiWPteohFw374O*jH%}fPOy{{n#4JFBsOe=D6uwbJh!FeuZYfW< z7J}Bx(`jj01%-JEDI#V$&u#S=Ngbfx^0EpFv&`C(2crP_raoQgBF-)&0BHs{PB&sO z`MHoBpjJBh3JRx10V0B@uG=w|?1GUDpi)Zt3JT?706o$Ibcq0DNVfkh-HX=f1C&ZD zUqNBNK0v$F0Im7}QK29P5yz&6pJ`hI_G zq3Qr7QUa8S1B7`Zo&0A>O7wV&ghhM@&?GB}Aqk98m z9`yq-m{!N+w-}Xg&b1h(#S!^ejmtM{y9{&1cKMBN`$p~E zk?OoKzMiv9eyvx|-IF$)&Izi#v>umRyd%wX&};8>u?0)tEPG`zfA8kADfv@sHZu3! z>)RPOEdRaOecvtneYU~w=4&>|Hb0QXps@U>te0^BBAo#t`F$Gxc8@1GR&ZBWekaRi z9zj{gElv41^xWv)&(MUqtSbManfs;}LqIi2t{4eU`Du!&>>u0C9Ho96!zx%*44Y<; zALqz+{63$@+3wdtxNL?QVvCYC}3+H>*gM8q5z{NZxT&8llsArT7 zHW;NIVd@FYycDzS@o#&~l1dIB%^W9qJ7JD=`~ewOIp?y@StS!!z{SL0C#_qdb>bD$ zwiWs&UOg>aVRGW-3~hz!i9ayC6(%Qsn;Du-Z-uFe|B4an5D3#-p?~7bEc4)hpEA7_ zx+nfEx)9Ryr*$jzO?-}Nnh^*EfQGHmGx1;Y-!w0-c z=anO&1$p9=2aqQ|fdJtE4&VR|-~bNb01n^)4&VR| z-~bT;DzO4o)B%6T3UI0pD8>pr4n-w~qF@DAiQ)B)>O0goZ~nL6Nx zRlrNgy{!(Iu?lz}x#!gZ16Bds$UUMCXtM~o9l0tgb$}pm4e%6lIYbb^k~P35$h{W~ z@QfuuBS*+h1Os$i0*v@Jpd%PS@Pie=8giS6pnx|l0B)m%+-t!B_gVnFinn`$0|c89 z18(Lw0cp-60qa-0D}8C)&$5+#u?TV99rE_9RJsk^xC}SyOoa!(+Vy2Zz(4^BuGWZ<8lnz#pkHDMrXXvafkVd z>$b&U91o%!$g;;({mc~g@xAD7|5}fDmrRm=)j-h1FbnLk n!vaGzAPx?CP_p=RCodHoDI+wRT;;7Q2_}HL^KfzM2U|=z|0Z|!3w0*#MG=%9Foj%aI9&f zv>L6bX>=M*(x4p7NK=6+OSBQ9R1(CDZ&6Srz61nRJ_PLT_kX$fws+Y*AA5K2-S^Tn z^M7~uoaghLySwK+=j_|?u4Yb+jg7kqVIO!4=zKeP8LTN_702fq8X9(U%pEo4&LdBU z=m0hTQ5LZS)Gr3}!IR)oI%Frun$zLZ0pW(;Pr+O;6U?GBHia4ff9==;x&uHZv>)LI z!7xXY&6AA)j|87g+k14X0n?%tD7qRPnYN!b(?>83;PYS=5DiMx8T3_p2Q)bq`m>tr z(V#_|zE)iq0Sp7rgCIT2MQZOJbY;;2MSqubGPLRqSE)gqZU?3oqlh?ORjG}lJi4>& zfTHX7aMGb@GtFaazr>H1cY+g(Wf8}Xl=Wcrp|N{KguT`%I2N=oK321!Zpv8wwX*;Y6*7;H`$$<#=tRVu{i?qCW?@*a5&IX&WP zM>sGN>;_3mfuB~p2o74thi>3kz;{mZ7BZi%iT@bB6eRiOAXY|?)Fgliqj;q|LbGBY z(mQ2zgu3jCFsR4QJ3d}oc!y`_t-oe`)x>d|#phtPI^hz3OBG09P>-LLK`&y?7sZqZ zi#6fN)$|{KP6U4pI%zrA)8I1|G^xKR1~tUH0qg)_(jD?m&;n@GV515C9%L_p(_aCu z?}k0_4$R)GkOho(1b2htV`(SnrI!=u1A&cRhar>MX2@OdOZ*xR-n0c!sNcl#ByS@{ z0fak%KE-RFBXa$~{h(J^j0R-y&=Qs%W1!{yMZRO+|a6}sMpy#_q#fIY(Y1qn2 z2Yz<}8w~yjtIqfG1Ial->A?9C&d>Ey7Qu{;qruvM-ni3I5#qTi!s-6N<(;5MmfL#` z#!BeUcA=q0r_u0j7cC(k-Cm=C_nHDSZNZO$Ix~)Ri<{OIj4@TK z5+7&91rR8Ij`R5CO)6&xn0-$*$@*B5fRQ@|%rqFBmPBtgW$^udz$I-l#^@JHE(foI zq;wyGosC9fDv=uo^r8>be<=7!j96}p@$m~`$0M%SQD)vi216U*a!W<%Zz|F_|0du) z2|?xva4pc#H%Rlj@P5oyyLa%>bSD?%xzg$voJ)(`6&znd_F}8C2nOWZ15E;h`Ij$6 zu;!`=fAu)Ejf$2SokqbBkS5JTSwExEm`db&gU1Ta{F~#uV_0)jjE}!5_-i_hh;y)6o3v`S%Y^Xr)e6R}KVqnWf6+Y@mqxY7HW<@%S#PL8mZ4>E5Y2%5LSIYY9 z!2BYL@D3U6xEK#o%S}a?Nu>xMX8;quO{KQbloN$tHc1ugGzISg{_MaT$MOOd=apNp zpDSYfa9rSl@^ObmOy!>f3{j`cgF-Nj0L!8n9m^htuXj*qy_^%*6P7lZR~9SG#{ko; zRQkSCCd370+VOk?*bb7AMmo2Fc46Jk`I>laDGH62-dl>1i_ZZYbt?&kP7pi}XdS^e z42!d|g$g;HO4>W*Uezy&qjOxKf$|GWj#2otKtK$O4wPm*75!+qC zLm(-=j9{X)cSK{PUW9MwS^|4U9iU93i4vugeWJnmIY4uOq%;>neG8T=!l%rICGG0zb4OHV$Z3oP;zBzZ)FqYdb4`e9hDZKr{2C zX>aAFCIIiWcM^Y1GWehrjfpm?18fteHYOuk)!|o9Fx%w4rEUfF!?W_wMKAWXj$xuV z)B&~)vo#%Q;NM8F(iDZIPHA?Hx~fjtRhGd-`_%!qG(@Yadmb`~kERbnOaT__^fe~|d+z)8SMN;}Ff*<#_>&uS~E#aDIp?(RQEs9UD)K|Tl+4S>Z2!<~OYZEx2 zAC)@r_rR8lNua(YV%tQp#iavs^9U-1RXCv&9^|wqxC!hk6#E=5fmttjrM6T zv{fCj(ZoSr?q--lf9Zt1;0Kgxr1M*#ETj)@Bm<gD9m;y7@ zY4PYDP|^1Z$nOLu>a7mY(xGkI-y<)L>*EJ;wFQ%aZLR}LBq>YqE#N&DLPkG=>w!`u zQ6$}Z0yXXLv1KB!_X#|2yacN8_{^wQa4cI5-)ED=g`Dz1QCoKAmGr@I7ckM|CFW*) zLV&}-VpE`&I<1}9p6ihIX&g+2pe|bTs zH7L(AQ!pEt=*f)QqNZvIxEFZL6u6~M|7zz`6$gQmuUQPKx_8Bfa;kvX^cc1+&#DdL z=nqt!mN-0wj|F?;v=_?nNNk`P2oj?fP`&|dy9CFaR@~7ZEiNZxtpPHIT9hBSL zP@*a4Xn2Gl?u-4Qlmb&|?@8aYuj8|Hesd|Q!vN~cS=A*&)z%G!4b|dtC zP(J(&w-F4QsH_Aw8Wom39Ik1UlfiUb;~45+rS5WLTfv}P&PK{KwlgZK<4R&52%fby zlA(TfZ1W(bOr9-4C*9Lyl^g~Y#NPrW?SzTOCPMK!z~!Tg2}VVATuJ*(1W8D_AbW}% zre8W}-w)5=I&SC4(1>{yaQT5oHKE)46G`pUbXir>#3T7MJTk=m780xG2T4ambG#zM z&_%$7^mecN`2jI@0_$BG1RkxkYzy!zmF>TtfY9WC64U8RRMu^(g4j+4FPa)4P&Y}x zbgq?S`#DhKC(9xrs3g_Xz=hNa+9Fa{L`-J^ZEbX+M_?+;19AO9kZ2L7sUZ>8t+xrd zkQOV3)JqQ#%Y{HQSr=LXQ?hKRt-(+{2<&n30v_dz#@ck|y`C7x1J|smc0go17{S0u zun)MB{_TKmk7Vxp0iSip&vFeCK0V`yS0xY(X<=CgT}h*c%9rh`5FLIh3os7s0IsCF z-JIV^+jk6(2ClbeF%a#|IW=ux($Xj2z+B)%Z@~PCw0!W=gW(H+?}S8;uiHSLzGhUz zYpaRt`wiA6(ggGjuSQFoSq=b~*>aP|fjjGMouQp;eL%%~ z_0gdPCjHWH1`9ya1RA1E1CO6@Ozq3jiUSPaS~xe7vR{KVejz*1qknO;46Fh#f=xhM z=7oJg|I0|w-P09xD;#w$=ox+J|LLUAla|AO72hB%@RX}QJDs50`~gr8q`UHmz@ERa zep8z*;N15|l0?@=pFTHi3xzI~EdIiv_R=q@kgTccwvz2R>Lo&!QbSVJNhr8Hpd%`ZC_FV O0000 - - - diff --git a/app/src/main/res/drawable/contact_photo_background.xml b/app/src/main/res/drawable/contact_photo_background.xml deleted file mode 100644 index ff1153bf2a..0000000000 --- a/app/src/main/res/drawable/contact_photo_background.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/conversation_back_button_background.xml b/app/src/main/res/drawable/conversation_back_button_background.xml deleted file mode 100644 index f259e83e74..0000000000 --- a/app/src/main/res/drawable/conversation_back_button_background.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/conversation_home_touch_highlight.xml b/app/src/main/res/drawable/conversation_home_touch_highlight.xml deleted file mode 100644 index 5fbf5ba7d0..0000000000 --- a/app/src/main/res/drawable/conversation_home_touch_highlight.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/conversation_item_background.xml b/app/src/main/res/drawable/conversation_item_background.xml deleted file mode 100644 index dfae25e4f8..0000000000 --- a/app/src/main/res/drawable/conversation_item_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/conversation_item_background_animated.xml b/app/src/main/res/drawable/conversation_item_background_animated.xml deleted file mode 100644 index 4206e6a57f..0000000000 --- a/app/src/main/res/drawable/conversation_item_background_animated.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/error_round.xml b/app/src/main/res/drawable/error_round.xml deleted file mode 100644 index 56cc75291a..0000000000 --- a/app/src/main/res/drawable/error_round.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_arrow_back_compact_24.xml b/app/src/main/res/drawable/ic_baseline_arrow_back_compact_24.xml deleted file mode 100644 index f632160152..0000000000 --- a/app/src/main/res/drawable/ic_baseline_arrow_back_compact_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_key_24.xml b/app/src/main/res/drawable/ic_baseline_key_24.xml deleted file mode 100644 index 2316df207c..0000000000 --- a/app/src/main/res/drawable/ic_baseline_key_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_refresh_24.xml b/app/src/main/res/drawable/ic_baseline_refresh_24.xml deleted file mode 100644 index f2be45bab5..0000000000 --- a/app/src/main/res/drawable/ic_baseline_refresh_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_circle_plus.xml b/app/src/main/res/drawable/ic_circle_plus.xml deleted file mode 100644 index 5e243e5296..0000000000 --- a/app/src/main/res/drawable/ic_circle_plus.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/ic_shield.xml b/app/src/main/res/drawable/ic_shield.xml deleted file mode 100644 index 67a9845daa..0000000000 --- a/app/src/main/res/drawable/ic_shield.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/info_round.xml b/app/src/main/res/drawable/info_round.xml deleted file mode 100644 index 668eaf3bee..0000000000 --- a/app/src/main/res/drawable/info_round.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/message_bubble_background.xml b/app/src/main/res/drawable/message_bubble_background.xml deleted file mode 100644 index 9c4347ae6e..0000000000 --- a/app/src/main/res/drawable/message_bubble_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/profile_picture_view_large_foreground.xml b/app/src/main/res/drawable/profile_picture_view_large_foreground.xml deleted file mode 100644 index e73b68d168..0000000000 --- a/app/src/main/res/drawable/profile_picture_view_large_foreground.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/profile_picture_view_medium_foreground.xml b/app/src/main/res/drawable/profile_picture_view_medium_foreground.xml deleted file mode 100644 index 808be8d0e4..0000000000 --- a/app/src/main/res/drawable/profile_picture_view_medium_foreground.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/profile_picture_view_rss_medium_background.xml b/app/src/main/res/drawable/profile_picture_view_rss_medium_background.xml deleted file mode 100644 index a12f21fce5..0000000000 --- a/app/src/main/res/drawable/profile_picture_view_rss_medium_background.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/profile_picture_view_small_foreground.xml b/app/src/main/res/drawable/profile_picture_view_small_foreground.xml deleted file mode 100644 index 6a46584bf8..0000000000 --- a/app/src/main/res/drawable/profile_picture_view_small_foreground.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/prominent_dialog_button_background.xml b/app/src/main/res/drawable/prominent_dialog_button_background.xml deleted file mode 100644 index de3409d482..0000000000 --- a/app/src/main/res/drawable/prominent_dialog_button_background.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/scroll_to_bottom_button_background.xml b/app/src/main/res/drawable/scroll_to_bottom_button_background.xml deleted file mode 100644 index 387c75be90..0000000000 --- a/app/src/main/res/drawable/scroll_to_bottom_button_background.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/session_logo_white.xml b/app/src/main/res/drawable/session_logo_white.xml deleted file mode 100644 index 9927e8822f..0000000000 --- a/app/src/main/res/drawable/session_logo_white.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/activity_backup_restore.xml b/app/src/main/res/layout/activity_backup_restore.xml deleted file mode 100644 index af86967c77..0000000000 --- a/app/src/main/res/layout/activity_backup_restore.xml +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - - - - - - - - - -